Compare commits

...

454 Commits

Author SHA1 Message Date
Marc Brugger
5acd3beff5 fix: correct division by 0 issue in web percentage (#661) 2025-09-15 21:00:21 +02:00
bakito
e10830434b chore: remove openapi patch for fixed upstream / move utils to cmd 2025-09-15 18:47:07 +02:00
bakito-renovate[bot]
c7a1d5dcef chore(deps): update dependency adguardteam/adguardhome to v0.107.66 (#660)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-09-15 17:56:45 +02:00
imgbot[bot]
7b4c342631 chore: [ImgBot] Optimize images (#659)
*Total -- 17.52kb -> 4.59kb (73.81%)

/media/adguardhome-sync.svg -- 8.76kb -> 2.29kb (73.81%)
/internal/sync/static/logo.svg -- 8.76kb -> 2.29kb (73.81%)

Signed-off-by: ImgBotApp <ImgBotHelp@gmail.com>
Co-authored-by: ImgBotApp <ImgBotHelp@gmail.com>
2025-09-15 17:46:26 +02:00
Marc Brugger
700dbd2d63 feat: offline js and css (#658) 2025-09-15 17:37:06 +02:00
Marc Brugger
f5c9e822a7 fix: disable tls feature by default (#657) 2025-09-15 16:26:19 +02:00
bakito
fb5afb2505 fix: correct typo in index page 2025-09-15 08:10:43 +02:00
Marc Brugger
2882785b9b chore: move pkg to internal (#655) 2025-09-15 08:07:27 +02:00
Marc Brugger
7d9edf4fa0 feat: remove support for deprecated env vars (#654) 2025-09-15 07:59:29 +02:00
apo-mak
bc70795769 fix: Fix typo in RUN_ON_START description (#652) 2025-09-15 07:46:46 +02:00
bakito
ea92d376c7 chore: actions/checkout@v5 2025-09-15 07:21:03 +02:00
bakito-renovate[bot]
16e97cd9b2 chore(deps): update module github.com/go-faker/faker/v4 to v4.6.2 (#653)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-09-15 06:54:50 +02:00
Marc Brugger
20fe83745f feat: sync tls config (#651)
* feat: implement tls config sync

* feat: implement workaround for #633

* extend feature test

* add tests
2025-09-14 18:25:36 +02:00
Marc Brugger
961cf4dbb5 feat: implement workaround for #633 (#650) 2025-09-14 18:03:32 +02:00
bakito-renovate[bot]
81d1bbf27b chore(deps): update module k8s.io/apimachinery to v0.34.1 (#647)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-09-11 07:31:17 +02:00
Marc Brugger
0fe6bb3a0b chore: update github actions (#646) 2025-09-08 08:07:23 +02:00
bakito-renovate[bot]
847a38fe1c chore(deps): update module golang.org/x/mod to v0.28.0 (#645)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-09-08 07:02:36 +02:00
bakito-renovate[bot]
1db6a5a4e0 chore(deps): update module github.com/onsi/ginkgo/v2 to v2.25.3 (#640)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-09-05 17:51:15 +02:00
bakito-renovate[bot]
6388eec95f chore(deps): update module github.com/spf13/cobra to v1.10.1 (#644)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-09-05 17:09:59 +02:00
bakito
31ac480737 update tools 2025-09-05 17:09:23 +02:00
bakito-renovate[bot]
6877b58b02 chore(deps): update module github.com/prometheus/client_golang to v1.23.2 (#641)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-09-05 17:07:36 +02:00
bakito-renovate[bot]
d212045058 chore(deps): update module github.com/bakito/semver to v1.1.7 (#639)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-09-05 17:07:24 +02:00
bakito-renovate[bot]
83f34fb377 chore(deps): update module k8s.io/apimachinery to v0.34.0 (#637)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-09-01 07:24:25 +02:00
bakito-renovate[bot]
f1a943491a chore(deps): update module github.com/kubernetes-sigs/controller-tools to v0.19.0 (#636)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-09-01 06:40:47 +02:00
bakito-renovate[bot]
75d9724db6 chore(deps): update onsi (#635)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-09-01 06:39:59 +02:00
bakito-renovate[bot]
531d63d04f chore(deps): update onsi (#632)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-08-25 07:42:02 +02:00
bakito-renovate[bot]
84999be6fc chore(deps): update module go.uber.org/mock to v0.6.0 (#631)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-08-25 07:24:49 +02:00
bakito-renovate[bot]
190ccdad8a chore(deps): update module github.com/uber-go/mock to v0.6.0 (#630)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-08-25 07:24:30 +02:00
bakito-renovate[bot]
49054242f8 chore(deps): update module github.com/segmentio/golines to v0.13.0 (#629)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-08-25 07:24:20 +02:00
bakito-renovate[bot]
930684ffc2 chore(deps): update k8s.io/utils digest to 0af2bda (#627)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-08-25 07:23:58 +02:00
bakito-renovate[bot]
3fac1d2ced chore(deps): update dependency adguardteam/adguardhome to v0.107.65 (#628)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-08-25 07:23:48 +02:00
bakito-renovate[bot]
c85579ce4a chore(deps): update golang docker tag to v1.25 (#625)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-08-18 06:46:46 +02:00
bakito-renovate[bot]
d0dd73220d chore(deps): update module github.com/bakito/semver to v1.1.5 (#623)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-08-18 06:25:31 +02:00
bakito-renovate[bot]
80f4436d98 chore(deps): update module k8s.io/apimachinery to v0.33.4 (#624)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-08-18 06:25:19 +02:00
bakito-renovate[bot]
fecc60cfca chore(deps): update module github.com/golangci/golangci-lint/v2 to v2.4.0 (#626)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-08-18 06:25:04 +02:00
bakito-renovate[bot]
b3d052db80 chore(deps): update module golang.org/x/mod to v0.27.0 (#622)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-08-11 06:43:10 +02:00
bakito-renovate[bot]
9cd88a0b47 chore(deps): update module github.com/golangci/golangci-lint/v2 to v2.3.1 (#618)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-08-04 07:41:18 +02:00
bakito-renovate[bot]
56eb522ea5 chore(deps): update module github.com/goreleaser/goreleaser/v2 to v2.11.2 (#619)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-08-04 07:41:08 +02:00
bakito-renovate[bot]
e6feaddc15 chore(deps): update module github.com/prometheus/client_golang to v1.23.0 (#620)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-08-04 07:40:56 +02:00
bakito-renovate[bot]
70ae93f73a chore(deps): update dependency adguardteam/adguardhome to v0.107.64 (#617)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-08-04 07:40:46 +02:00
bakito-renovate[bot]
c3d6e696a1 chore(deps): update module github.com/goreleaser/goreleaser/v2 to v2.11.1 (#616)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-07-28 07:46:26 +02:00
bakito-renovate[bot]
89f2a1eb82 chore(deps): update module github.com/golangci/golangci-lint/v2 to v2.3.0 (#614)
* chore(deps): update module github.com/golangci/golangci-lint/v2 to v2.3.0

* fix lint

---------

Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
Co-authored-by: bakito <github@bakito.ch>
2025-07-24 21:11:18 +02:00
bakito-renovate[bot]
307b050579 chore(deps): update module github.com/onsi/gomega to v1.38.0 (#615)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-07-24 20:47:07 +02:00
bakito-renovate[bot]
caddf2a029 chore(deps): update module github.com/oapi-codegen/runtime to v1.1.2 (#610)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-07-21 07:17:34 +02:00
bakito-renovate[bot]
20ff0192d5 chore(deps): update module k8s.io/apimachinery to v0.33.3 (#611)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-07-21 07:17:18 +02:00
bakito-renovate[bot]
7d8cfbaeed chore(deps): update module github.com/oapi-codegen/oapi-codegen/v2 to v2.5.0 (#612)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-07-21 07:17:04 +02:00
Marc Brugger
b07101c97a fix: Correct version check and refactor healthz check (#609) 2025-07-16 17:06:17 +02:00
Marc Brugger
447bfb24a9 test: extend versions check #607 (#608) 2025-07-16 07:55:00 +02:00
Marc Brugger
4fff8f4302 test: run goreleaser with --parallelism 2 (#606) 2025-07-14 20:48:51 +02:00
Michael Stegeman
04bd12d7dd feat: Add OpenBSD support to releaser. (#605)
* Add OpenBSD support to releaser.

* Skip upx on OpenBSD.
2025-07-14 20:01:18 +02:00
bakito-renovate[bot]
4e33a88163 chore(deps): update module golang.org/x/mod to v0.26.0 (#604)
* chore(deps): update module golang.org/x/mod to v0.26.0

* go 1.24.5

---------

Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
Co-authored-by: bakito <github@bakito.ch>
2025-07-13 14:30:32 +02:00
bakito
4a38df1b59 chore: update goreleaser 2025-07-13 09:18:00 +02:00
Marc Brugger
9ec5c0412e add new logo (#603) 2025-07-13 08:23:12 +02:00
ElevenNotes
01676e12f3 feat: add unauthorized health check based on status (if all success = http OK) (#601)
* feat: add unauthorized health check based on status (if all success = http OK)

* feat: ignore v or other non-numerical version affixes and suffixes

* fix: syntax

* fix: add head not just get
2025-07-10 22:33:46 +02:00
Marc Brugger
78f7d08398 test: add rewrites for e2e test (#600) 2025-07-05 09:25:56 +02:00
bakito-renovate[bot]
11d4d14f4d chore(deps): update module github.com/golangci/golangci-lint/v2 to v2.2.1 (#598)
* chore(deps): update module github.com/golangci/golangci-lint/v2 to v2.2.1

* ignore revive var-naming

---------

Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
Co-authored-by: bakito <github@bakito.ch>
2025-06-30 20:36:54 +02:00
bakito-renovate[bot]
ff91f4873a chore(deps): update dependency adguardteam/adguardhome to v0.107.63 (#597)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-06-27 16:43:31 +02:00
bakito-renovate[bot]
778bf7b516 chore(deps): update module k8s.io/apimachinery to v0.33.2 (#596)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-06-23 06:53:46 +02:00
bakito
cce24c1223 docs: add icon to readme 2025-06-19 09:28:38 +02:00
Ted Jangius
b4fabf687b Add logo (#595) 2025-06-18 21:57:16 +02:00
Joseph Williams
74ae6a9096 Docs: Clarify initial setup instructions for origin and replicas (#593)
* Disambiguate 'Prerequisites' section in README.md

* Correct  part of speech error in 'Prerequisite' section in README.md
2025-06-12 23:48:16 +02:00
bakito-renovate[bot]
92129ab069 chore(deps): update k8s.io/utils digest to 4c0f3b2 (#590)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-06-09 07:59:24 +02:00
bakito-renovate[bot]
d7f50086c3 chore(deps): update module github.com/goreleaser/goreleaser/v2 to v2.10.2 (#591)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-06-09 07:59:11 +02:00
bakito-renovate[bot]
50f883337a chore(deps): update module golang.org/x/mod to v0.25.0 (#592)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-06-09 07:58:52 +02:00
bakito-renovate[bot]
5a35f7c9d1 chore(deps): update dependency adguardteam/adguardhome to v0.107.62 (#589)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-06-01 13:06:59 +02:00
bakito-renovate[bot]
1b1a712a65 chore(deps): update module github.com/santhosh-tekuri/jsonschema/v6 to v6.0.2 (#588)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-05-24 11:02:28 +02:00
bakito-renovate[bot]
724a20e47e chore(deps): update module github.com/gin-gonic/gin to v1.10.1 (#587)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-05-24 11:02:17 +02:00
Marc Brugger
64c922a1d8 feat: support request headers (#586) 2025-05-18 16:59:29 +02:00
bakito-renovate[bot]
905c783ca9 chore(deps): update module k8s.io/apimachinery to v0.33.1 (#585)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-05-18 10:10:30 +02:00
bakito-renovate[bot]
4eac62e6ce chore(deps): update module sigs.k8s.io/controller-tools/cmd/controller-gen to v0.18.0 (#583)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-05-12 07:18:01 +02:00
bakito
418409ac47 chore: re-generate mock and makefie deps 2025-05-05 20:16:27 +02:00
bakito-renovate[bot]
4895bfdc55 chore(deps): update module github.com/golangci/golangci-lint/v2/cmd/golangci-lint to v2.1.6 (#582)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-05-05 07:26:23 +02:00
bakito-renovate[bot]
976902f861 chore(deps): update github.com/google/pprof digest to c008609 (#580)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-05-03 09:05:28 +02:00
bakito-renovate[bot]
b06a1cfec0 chore(deps): update k8s.io/utils digest to 0f33e8f (#581)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-05-03 09:05:13 +02:00
bakito-renovate[bot]
449fce9d6a chore(deps): update module github.com/goreleaser/goreleaser/v2 to v2.9.0 (#578)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-05-02 21:16:37 +02:00
bakito-renovate[bot]
6b45caadfb chore(deps): update uber to v0.5.2 (#577)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-04-30 00:04:50 +02:00
bakito-renovate[bot]
06b49cb810 chore(deps): update module github.com/go-faker/faker/v4 to v4.6.1 (#576)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-04-27 08:28:35 +02:00
bakito
ae58133a0d chore: regenerate model 2025-04-26 15:23:37 +02:00
Marc Brugger
2b0bfb126e test: correct env var for test (#574) 2025-04-26 09:25:33 +02:00
Marc Brugger
97841e3f32 Correct env variable handling to not use unrelated variables (#571) 2025-04-26 09:01:30 +02:00
bakito-renovate[bot]
2fc2baf6f8 chore(deps): update module github.com/golangci/golangci-lint/v2/cmd/golangci-lint to v2.1.5 (#568)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-04-25 07:54:58 +02:00
bakito-renovate[bot]
06355a71da chore(deps): update k8s to v0.33.0 (#567)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-04-24 18:58:59 +02:00
Marc Brugger
9782c430af feat: implement sync metrics (#562) 2025-04-23 23:59:27 +02:00
bakito-renovate[bot]
cafdf14008 chore(deps): update indirect go dependencies (#563)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-04-23 07:25:59 +02:00
bakito-renovate[bot]
e7569de5c6 chore(deps): update dependency adguardteam/adguardhome to v0.107.61 (#566)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-04-23 07:20:21 +02:00
Sy
2b69fe15ed docs: Add Home Assistant paragraph to README (#564)
Adding guidance to help Home Assistant AdGuard Home Add-on users enable syncing.
2025-04-20 17:41:17 +02:00
Marc Brugger
bb1f2d02c5 fix: ignore empty config file in config validation (#561) 2025-04-16 12:51:27 +02:00
bakito-renovate[bot]
3bd093ceb9 chore(deps): update indirect go dependencies (#548)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-04-15 19:33:48 +02:00
bakito-renovate[bot]
05c0eab4a8 chore(deps): update module github.com/golangci/golangci-lint/v2/cmd/golangci-lint to v2.1.2 (#558)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-04-15 19:33:38 +02:00
bakito-renovate[bot]
57745f2953 chore(deps): update module github.com/prometheus/client_golang to v1.22.0 (#553)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-04-15 18:19:04 +02:00
bakito-renovate[bot]
9f5ac14aa1 chore(deps): update module github.com/golangci/golangci-lint/v2/cmd/golangci-lint to v2.1.1 (#554)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-04-15 18:18:48 +02:00
bakito-renovate[bot]
8f2aa58fe8 chore(deps): update module mvdan.cc/gofumpt to v0.8.0 (#555)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-04-15 18:18:31 +02:00
bakito-renovate[bot]
01e23d3b69 chore(deps): update dependency adguardteam/adguardhome to v0.107.60 (#557)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-04-15 18:18:18 +02:00
Marc Brugger
c2da171c85 Update bug_report.yaml 2025-04-14 21:03:57 +02:00
bakito-renovate[bot]
b44dd16ad8 chore(deps): update module github.com/onsi/ginkgo/v2 to v2.23.4 (#549)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-04-09 04:00:36 +00:00
bakito-renovate[bot]
ba00016be8 chore(deps): update uber to v0.5.1 (#552)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-04-08 09:21:20 +00:00
Marc Brugger
e301f483c6 docs: add env variables to readme (#547) 2025-04-05 21:44:46 +02:00
bakito-renovate[bot]
ebf25a8ddf chore(deps): update indirect go dependencies (#546)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-04-05 21:39:59 +02:00
bakito-renovate[bot]
56d6644194 chore(deps): update module github.com/goreleaser/goreleaser/v2 to v2.8.2 (#544)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-04-05 11:22:15 +01:00
bakito-renovate[bot]
51401dac81 chore(deps): update module github.com/onsi/gomega to v1.37.0 (#545)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-04-04 04:00:44 +00:00
bakito-renovate[bot]
18b07898ea chore(deps): update indirect go dependencies (#543)
Co-authored-by: bakito-renovate[bot] <205501741+bakito-renovate[bot]@users.noreply.github.com>
2025-03-30 13:26:05 +02:00
Marc Brugger
223739f4a7 chore(deps): update module github.com/golangci/golangci-lint/v2/cmd/golangci-lint to v2.0.2 (#539)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-03-25 22:48:39 +01:00
Marc Brugger
02ff6a11f0 feat: implement stricter lint rules git golanci-lint v2 (#538)
* feat: implement stricter lint rules git golanci-lint v2

* fix lint issues

* fix lint issues
2025-03-25 21:30:22 +01:00
Marc Brugger
2bff464f65 Correct metrics indent 2025-03-23 15:24:30 +01:00
Marc Brugger
8fe0fe27ea add unraid disclaimer (#535) 2025-03-23 15:07:31 +01:00
Marc Brugger
5c00caa296 chore(deps): update dependency adguardteam/adguardhome to v0.107.59 (#527)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-03-22 18:16:35 +01:00
Marc Brugger
344337a3d5 chore(deps): update k8s.io/utils digest to 1f6e0b7 (#530)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-03-22 18:16:25 +01:00
Marc Brugger
e1287564c3 chore(deps): update indirect go dependencies (#533)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-03-22 18:16:11 +01:00
Marc Brugger
8f61de83bf chore(deps): update onsi (#528)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-03-22 09:08:24 +01:00
bakito
f3810cf241 test: add test layout 2025-03-18 20:37:28 +01:00
Marc Brugger
3d385f409e chore(deps): update module github.com/golangci/golangci-lint/cmd/golangci-lint to v1.64.8 (#526)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-03-18 06:38:12 +01:00
Marc Brugger
19bdd47b3c chore: update depenencies (#525) 2025-03-16 11:29:37 +01:00
Marc Brugger
32c728ccf8 fix: handle small passwords in mask function #522 (#523) 2025-03-13 19:10:37 +01:00
dependabot[bot]
327d67c9ef build(deps): bump golang.org/x/net from 0.35.0 to 0.36.0 (#519)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.35.0 to 0.36.0.
- [Commits](https://github.com/golang/net/compare/v0.35.0...v0.36.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-13 07:07:32 +01:00
Marc Brugger
edd7f82351 chore(deps): update k8s to v0.32.3 (#520)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-03-13 07:07:21 +01:00
Marc Brugger
012d43b950 chore(deps): update module github.com/goreleaser/goreleaser/v2 to v2.8.0 (#521)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-03-13 07:07:06 +01:00
Marc Brugger
e2298f6f1e chore(deps): update module github.com/golangci/golangci-lint/cmd/golangci-lint to v1.64.7 (#518)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-03-12 22:45:48 +01:00
Marc Brugger
18350f94a6 test: add metrics test (#517) 2025-03-09 17:38:08 +01:00
bakito
1a9d5df5cb test: use files instead of const for test expects 2025-03-09 17:09:30 +01:00
bakito
8ef1eb8c2a feat: print runtime info in config only mode 2025-03-09 10:53:38 +01:00
Marc Brugger
7c2018acbc test: add print config tests (#508) 2025-03-06 07:28:25 +01:00
Marc Brugger
2079f6a3eb chore(deps): update module github.com/onsi/ginkgo/v2 to v2.23.0 (#515)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-03-06 07:22:18 +01:00
Marc Brugger
d9419446bf chore(deps): update module golang.org/x/mod to v0.24.0 (#516)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-03-06 07:22:03 +01:00
Marc Brugger
c0cbccb63c chore(deps): update module github.com/prometheus/client_golang to v1.21.1 (#514)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-03-05 07:11:51 +01:00
Marc Brugger
5444dda08a chore(deps): update module github.com/golangci/golangci-lint/cmd/golangci-lint to v1.64.6 (#512)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-03-03 07:11:01 +01:00
Marc Brugger
224617aae6 test: scan released windows files with VirusTotal (#510) 2025-02-28 20:36:35 +01:00
bakito
c941c8a100 test: add virustotal scan for windows files 2025-02-27 21:17:51 +01:00
Stephan
07a25cd094 docs: Add unraid section to README (#509)
Added comment for running in Unraid
2025-02-26 07:54:43 +01:00
bakito
54d98c10fe extend print config comments #507 2025-02-25 21:34:37 +01:00
bakito
4a897c40b5 Print PRINT_CONFIG_ONLY in markdown so it can be used in github issues #507 2025-02-25 21:21:53 +01:00
bakito
9af45d3cab Print unmodified config file in PRINT_CONFIG_ONLY mode #507 2025-02-25 20:52:19 +01:00
bakito
9c60b399a8 Print environment variables in addition of the config in PRINT_CONFIG_ONLY mode #507 2025-02-25 20:26:40 +01:00
bakito
01dbf8e50a regenerate model for v0.107.57 2025-02-21 19:55:24 +01:00
Marc Brugger
58d0302c74 chore(deps): update dependency adguardteam/adguardhome to v0.107.57 (#506)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-02-21 07:24:16 +01:00
Marc Brugger
90ea6a13de chore(deps): update module github.com/prometheus/client_golang to v1.21.0 (#505)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-02-20 07:16:22 +01:00
bakito
4f80a7979f test: format code on running tests 2025-02-19 08:07:38 +01:00
Marc Brugger
3812101c25 chore(deps): update module github.com/spf13/cobra to v1.9.1 (#503)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-02-17 08:08:50 +01:00
Marc Brugger
8c79c8b32d test: extened faker config (#502) 2025-02-15 22:39:53 +01:00
Marc Brugger
0afc437252 chore(deps): update module github.com/santhosh-tekuri/jsonschema/v5 to v6 (#501)
* chore(deps): update module github.com/santhosh-tekuri/jsonschema/v5 to v6

* Update validate.go

---------

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-02-15 22:13:01 +01:00
Marc Brugger
6ae53fea6e chore(deps): update module github.com/spf13/cobra to v1.9.0 (#500)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-02-15 21:33:37 +01:00
Marc Brugger
d925ccf45a feat: add yaml config file schema validation (#499) 2025-02-15 21:27:52 +01:00
Marc Brugger
24243a4b4d chore(deps): update module github.com/golangci/golangci-lint/cmd/golangci-lint to v1.64.5 (#496)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-02-15 10:45:27 +01:00
Marc Brugger
ef9ebc29b7 chore(deps): update k8s to v0.32.2 (#498)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-02-15 10:37:19 +01:00
Marc Brugger
bd4e0f2b28 chore(deps): update module github.com/goreleaser/goreleaser/v2 to v2.7.0 (#494)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-02-12 08:11:48 +01:00
Marc Brugger
e952c9f85a chore(deps): update module github.com/golangci/golangci-lint/cmd/golangci-lint to v1.64.2 (#495)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-02-12 08:11:39 +01:00
Marc Brugger
df67e9a0bd chore(deps): update module golang.org/x/mod to v0.23.0 (#492)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-02-05 21:25:51 +01:00
bakito
7e47ed0879 regenerate model for v0.107.56 2025-02-01 11:23:19 +01:00
Marc Brugger
1952ec1527 chore(deps): update dependency adguardteam/adguardhome to v0.107.56 (#490)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-02-01 09:52:03 +01:00
Marc Brugger
9ca5205d6a chore(deps): update module github.com/go-resty/resty/v2 to v2.16.5 (#491)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-01-28 22:41:52 +01:00
Marc Brugger
8a8da9d162 chore(deps): update module github.com/goreleaser/goreleaser/v2 to v2.6.1 (#489)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-01-23 22:56:08 +01:00
Marc Brugger
6440c71492 chore(deps): update module github.com/go-resty/resty/v2 to v2.16.4 (#488)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-01-20 22:10:49 +01:00
Marc Brugger
0b66b2debb chore(deps): update module github.com/go-resty/resty/v2 to v2.16.3 (#484)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-01-20 21:27:27 +01:00
Marc Brugger
351c142b91 chore(deps): update k8s to v0.32.1 (#486)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-01-20 21:27:13 +01:00
Marc Brugger
8ce6aed66f chore(deps): update module github.com/onsi/ginkgo/v2 to v2.22.2 (#480)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-01-07 18:30:49 +01:00
Marc Brugger
778d18d816 chore(deps): update module github.com/goreleaser/goreleaser/v2 to v2.5.1 (#482)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-01-07 18:30:38 +01:00
Marc Brugger
2ea436bbbc chore(deps): update module github.com/golangci/golangci-lint/cmd/golangci-lint to v1.63.4 (#481)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2025-01-07 18:30:14 +01:00
Marc Brugger
b5a820b6f4 cancel in progress actions (#483) 2025-01-07 18:29:51 +01:00
Marc Brugger
37f5043493 chore(deps): update module github.com/onsi/gomega to v1.36.2 (#476)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-12-30 14:18:26 +01:00
Marc Brugger
7638fb75a3 Update .goreleaser.yml (#478) 2024-12-29 11:54:24 +01:00
bakito
fdad6d3b7b update golang.org/x/net 2024-12-28 10:58:40 +01:00
Marc Brugger
074e300974 Format code with golines (#475) 2024-12-23 08:19:45 +01:00
Marc Brugger
523e068195 run on start if api disabled and cron enabled (#474) 2024-12-23 08:16:19 +01:00
Marc Brugger
9a7c617311 chore(deps): update module github.com/onsi/ginkgo/v2 to v2.22.1 (#471)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-12-23 08:15:29 +01:00
Marc Brugger
c214899b75 chore(deps): update module github.com/caarlos0/env/v11 to v11.3.1 (#472)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-12-23 08:15:13 +01:00
Marc Brugger
e31a8c8064 Correct webpage styling (#469) 2024-12-18 08:33:36 +01:00
bakito
31cc27df1b check for nil in stats 2024-12-18 08:15:44 +01:00
Marc Brugger
d60051b5cb remove golang.org/x/exp dependency (#467) 2024-12-17 20:43:18 +01:00
Marc Brugger
6770ae2a99 chore(deps): update module github.com/caarlos0/env/v11 to v11.3.0 (#465)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-12-17 06:40:43 +01:00
Marc Brugger
84738809ef chore(deps): update golang.org/x/exp digest to 4a55095 (#463)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-12-16 07:30:16 +01:00
Marc Brugger
43a08f7eef chore(deps): update module github.com/goreleaser/goreleaser/v2 to v2.5.0 (#464)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-12-16 07:30:06 +01:00
Marc Brugger
89aeec5f97 Draw also stats of single instances in dashboard diagram (#462)
* allow multiple graphs

* draw single instances
2024-12-15 12:06:09 +01:00
Marc Brugger
00ee5415a5 chore(deps): update k8s.io/utils digest to 24370be (#460)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-12-14 23:56:56 +01:00
Marc Brugger
b5bd79b61e Update README.md add API_METRICS_ENABLED 2024-12-14 22:28:14 +01:00
Marc Brugger
7080c236eb Show aggregated stats in web UI (#459) 2024-12-14 19:28:11 +01:00
Marc Brugger
bb073eb51e chore(deps): update dependency adguardteam/adguardhome to v0.107.55 (#455)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-12-12 07:18:25 +01:00
dependabot[bot]
7372d49aca build(deps): bump golang.org/x/crypto from 0.30.0 to 0.31.0 (#457)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.30.0 to 0.31.0.
- [Commits](https://github.com/golang/crypto/compare/v0.30.0...v0.31.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-12 07:17:34 +01:00
Marc Brugger
1d771d4a0a chore(deps): update k8s to v0.32.0 (#456)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-12-12 06:45:35 +01:00
Marc Brugger
dcffdbd98e chore(deps): update golang.org/x/exp digest to 1829a12 (#453)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-12-11 13:32:02 +01:00
Marc Brugger
abcc2e4125 chore(deps): update module github.com/onsi/gomega to v1.36.1 (#452)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-12-11 13:31:51 +01:00
Marc Brugger
e453381382 chore(deps): update k8s to v0.31.4 (#454)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-12-11 13:31:39 +01:00
Marc Brugger
9a9913998a chore(deps): update golang.org/x/exp digest to 43b7b7c (#451)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-12-06 11:37:24 +01:00
Marc Brugger
a3cdb66fe6 chore(deps): update module github.com/onsi/gomega to v1.36.0 (#444)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-11-26 06:45:08 +01:00
Marc Brugger
39aa84e2fd chore(deps): update module github.com/golangci/golangci-lint/cmd/golangci-lint to v1.62.2 (#449)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-11-26 06:20:41 +01:00
Marc Brugger
1ec2270eb5 Delete .github/dependabot.yml 2024-11-25 07:10:51 +01:00
dependabot[bot]
5d1ee9b538 build(deps): bump github.com/onsi/ginkgo/v2 in the onsi group (#447)
Bumps the onsi group with 1 update: [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo).


Updates `github.com/onsi/ginkgo/v2` from 2.21.0 to 2.22.0
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.21.0...v2.22.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: onsi
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-25 07:06:55 +01:00
Marc Brugger
03d3cf57e9 chore(deps): update module github.com/goreleaser/goreleaser/v2 to v2.4.8 (#438)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-11-25 06:52:10 +01:00
dependabot[bot]
2a5465ddb0 build(deps): bump dcarbone/install-jq-action from 2 to 3 (#441)
Bumps [dcarbone/install-jq-action](https://github.com/dcarbone/install-jq-action) from 2 to 3.
- [Release notes](https://github.com/dcarbone/install-jq-action/releases)
- [Commits](https://github.com/dcarbone/install-jq-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: dcarbone/install-jq-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-25 06:52:02 +01:00
Marc Brugger
84efc786d4 chore(deps): update dcarbone/install-jq-action action to v3 (#442)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-11-25 06:51:45 +01:00
Marc Brugger
12e5fb6f7f chore(deps): update k8s to v0.31.3 (#445)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-11-25 06:51:17 +01:00
dependabot[bot]
5c66900fdb build(deps): bump github.com/go-resty/resty/v2 from 2.16.0 to 2.16.2 (#448)
Bumps [github.com/go-resty/resty/v2](https://github.com/go-resty/resty) from 2.16.0 to 2.16.2.
- [Release notes](https://github.com/go-resty/resty/releases)
- [Commits](https://github.com/go-resty/resty/compare/v2.16.0...v2.16.2)

---
updated-dependencies:
- dependency-name: github.com/go-resty/resty/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-25 06:50:51 +01:00
Jeremy Diaz
2710ead089 Updated README.md (#440)
Co-authored-by: diaznet <jd@diaznet.fr>
2024-11-16 12:56:29 +01:00
Marc Brugger
839aa420e0 chore(deps): update module github.com/go-resty/resty/v2 to v2.16.0 (#436)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-11-11 07:04:24 +01:00
Marc Brugger
4ee42d4092 chore(deps): update module github.com/golangci/golangci-lint/cmd/golangci-lint to v1.62.0 (#437)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-11-11 07:04:13 +01:00
Marc Brugger
b2950f0718 chore(deps): update golang.org/x/exp digest to 2d47ceb (#434)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-11-09 10:35:54 +01:00
Marc Brugger
7b1793476d chore(deps): update dependency adguardteam/adguardhome to v0.107.54 (#432)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-11-08 18:54:34 +01:00
Marc Brugger
f8750ef231 chore(deps): update module golang.org/x/mod to v0.22.0 (#433)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-11-08 07:03:48 +01:00
Marc Brugger
cc9283530f chore(deps): update module github.com/goreleaser/goreleaser/v2 to v2.4.4 (#431)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-11-05 06:21:07 +01:00
Marc Brugger
5a61e56766 chore(deps): update module github.com/goreleaser/goreleaser/v2 to v2.4.3 (#430)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-11-04 21:27:27 +01:00
Marc Brugger
8f71d514ee chore(deps): update module github.com/goreleaser/goreleaser/v2 to v2.4.1 (#429)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-11-02 21:34:53 +01:00
Marc Brugger
1b122b228f chore(deps): update module github.com/onsi/gomega to v1.35.1 (#428)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-11-01 08:26:02 +01:00
Marc Brugger
e88c0cdd38 Update README.md (#427) 2024-10-30 22:04:44 +01:00
Marc Brugger
3ceb188b81 chore(deps): update onsi (#425)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-10-30 07:37:29 +01:00
Marc Brugger
0bc52ecb3c chore(deps): update k8s to v0.31.2 (#424)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-10-24 07:21:34 +02:00
Marc Brugger
36d6c37df3 chore(deps): update module go.uber.org/mock to v0.5.0 (#422)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-10-18 09:38:01 +02:00
Marc Brugger
8fd793fdab chore(deps): update module go.uber.org/mock/mockgen to v0.5.0 (#423)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-10-18 09:37:52 +02:00
Marc Brugger
be1909aedb chore(deps): update module github.com/prometheus/client_golang to v1.20.5 (#421)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-10-16 07:29:55 +02:00
Marc Brugger
e83b4d84ec chore(deps): update golang.org/x/exp digest to f66d83c (#420)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-10-09 23:43:54 +02:00
Marc Brugger
b39b3f38fe print ignored error count (#419)
* print ignored error count
2024-10-05 16:28:40 +02:00
bakito
d50765925b delete Taskfile.yml 2024-10-05 11:03:04 +02:00
Marc Brugger
e0f8971155 Sync DNS settings before filters (#418) 2024-10-05 10:59:12 +02:00
Marc Brugger
f1d53f5610 chore(deps): update golang.org/x/exp digest to 225e2ab (#417)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-10-05 09:01:17 +02:00
Marc Brugger
4b4e6b1c72 ignore more errors (#416) 2024-10-03 22:07:01 +02:00
Marc Brugger
095af716c9 chore(deps): update dependency adguardteam/adguardhome to v0.107.53 (#415)
* chore(deps): update dependency adguardteam/adguardhome to v0.107.53

* update schema

---------

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-10-03 18:45:25 +02:00
bakito
e4e9f050df track AdGuardHome with renovate 2024-10-03 18:30:28 +02:00
bakito
638b9b9428 upgrade toolbox 2024-09-28 15:07:17 +02:00
Marc Brugger
0d4d18c595 chore(deps): update module github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen to v2.4.1 (#414)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-09-28 15:04:13 +02:00
bakito
33350c168d go mod tidy 2024-09-28 09:05:51 +02:00
bakito
1bbb882225 cleanup renovate config 2024-09-28 08:59:41 +02:00
bakito
6e86e8e5c5 correct renovate config regex 2024-09-28 08:57:57 +02:00
bakito
7cb4c8793a update renovate config 2024-09-28 08:46:39 +02:00
Marc Brugger
4d2c636d9b Upgrade to Go 1.23 (#413)
* Update Dockerfile
* Update go.mod
* Update .goreleaser.yml
* Update AdGuardHome.yaml
2024-09-27 21:15:21 +02:00
bakito
9895d6de43 update makefile dependencies 2024-09-27 18:47:32 +02:00
Marc Brugger
f62b6f94c5 chore(deps): update module github.com/go-resty/resty/v2 to v2.15.3 (#412)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-09-26 23:16:19 +02:00
Marc Brugger
e00d9a6c59 #403 add feature to disable theme sync (#411) 2024-09-22 21:24:20 +02:00
Marc Brugger
d2e573bbed chore(deps): update module github.com/go-resty/resty/v2 to v2.15.2 (#409)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-09-22 07:36:51 +02:00
Marc Brugger
8c0b6ee70d chore(deps): update module github.com/go-resty/resty/v2 to v2.15.1 (#408)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-09-20 06:45:23 +02:00
Marc Brugger
b9c7b1f559 chore(deps): update module github.com/prometheus/client_golang to v1.20.4 (#407)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-09-18 11:28:04 +02:00
Marc Brugger
7b5b876b0c chore(deps): update module github.com/go-resty/resty/v2 to v2.15.0 (#406)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-09-15 13:56:06 +02:00
Marc Brugger
848fb50132 chore(deps): update module k8s.io/apimachinery to v0.31.1 (#405)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-09-13 06:30:04 +02:00
Marc Brugger
6206402cae chore(deps): update golang.org/x/exp digest to 701f63a (#404)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-09-10 07:18:49 +02:00
Marc Brugger
b24dc91ef2 chore(deps): update module golang.org/x/mod to v0.21.0 (#401)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-09-06 00:18:13 +02:00
Marc Brugger
d2d58f9058 chore(deps): update module github.com/prometheus/client_golang to v1.20.3 (#402)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-09-06 00:18:01 +02:00
Marc Brugger
e6fb75f715 chore(deps): update golang.org/x/exp digest to e7e105d (#400)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-09-05 07:37:23 +02:00
bakito
3b31049636 correct cron description and add video section to Readme #398 2024-08-29 19:30:38 +02:00
Marc Brugger
5db091d800 chore(deps): update onsi (#399)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-08-29 06:47:25 +02:00
Marc Brugger
8278ecb6a2 chore(deps): update module github.com/prometheus/client_golang to v1.20.2 (#397)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-08-23 21:51:21 +02:00
Marc Brugger
084eeba24a chore(deps): update golang.org/x/exp digest to 9b4947d (#396)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-08-23 07:00:03 +02:00
Marc Brugger
0134840300 chore(deps): update golang.org/x/exp digest to 778ce7b (#395)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-08-22 20:23:40 +02:00
Marc Brugger
05335aaf3f chore(deps): update module github.com/onsi/ginkgo/v2 to v2.20.1 (#394)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-08-22 07:09:14 +02:00
Marc Brugger
fb38741036 chore(deps): update module github.com/prometheus/client_golang to v1.20.1 (#393)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-08-21 07:07:47 +02:00
Marc Brugger
5a07feaffd chore(deps): update module github.com/prometheus/client_golang to v1.20.0 (#392)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-08-14 18:26:21 +02:00
Marc Brugger
e555c287bf chore(deps): update module k8s.io/apimachinery to v0.31.0 (#390)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-08-13 19:06:53 +02:00
Marc Brugger
ad52f32a3b chore(deps): update module github.com/caarlos0/env/v11 to v11.2.2 (#380)
* chore(deps): update module github.com/caarlos0/env/v11 to v11.2.2

* fix replica handling

---------

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-08-09 10:10:32 +02:00
bakito
665b27cfbe cleanup dependencies 2024-08-09 09:24:23 +02:00
Marc Brugger
871d558ffd chore(deps): update golang.org/x/exp digest to 0cdaa3a (#388)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-08-09 08:46:05 +02:00
Marc Brugger
881e00e7e3 chore(deps): update module github.com/onsi/ginkgo/v2 to v2.20.0 (#386)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-08-08 08:02:08 +02:00
Marc Brugger
29963b9d75 chore(deps): update module golang.org/x/mod to v0.20.0 (#385)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-08-05 23:20:26 +02:00
Marc Brugger
bea8ec2d98 chore(deps): update module github.com/go-resty/resty/v2 to v2.14.0 (#384)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-08-05 23:20:01 +02:00
Marc Brugger
851f5fad58 chore(deps): update module github.com/onsi/gomega to v1.34.1 (#379)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-07-30 07:59:58 +02:00
Marc Brugger
1125837420 chore(deps): update module github.com/onsi/ginkgo/v2 to v2.19.1 (#378)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-07-27 08:09:13 +02:00
Marc Brugger
34f9a13da3 chore(deps): update module github.com/onsi/gomega to v1.34.0 (#376)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-07-25 20:14:34 +02:00
Marc Brugger
1432ab4b52 Build more archs (#375)
* Build more archs
2024-07-24 08:59:36 +02:00
Marc Brugger
8f3a8e3572 Build image for linux/arm/v8 #372 (#374) 2024-07-23 20:25:55 +02:00
Marc Brugger
87d5903cfc extend bug issue template with os info (#373) 2024-07-23 06:38:36 +02:00
Marc Brugger
a50639af23 chore(deps): update module github.com/caarlos0/env/v10 to v11 (#371)
* chore(deps): update module github.com/caarlos0/env/v10 to v11

* update code

---------

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-07-22 00:02:14 +02:00
Marc Brugger
c4b1a70dac chore(deps): update module k8s.io/apimachinery to v0.30.3 (#369)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-07-21 23:27:02 +02:00
Marc Brugger
94677c7a39 chore(deps): update golang.org/x/exp digest to 8a7402a (#368)
* chore(deps): update golang.org/x/exp digest to 8a7402a

---------

Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-07-21 23:22:31 +02:00
Marc Brugger
cd2295ef05 Add renovate.json (#367)
Co-authored-by: Renovate Bot <renovate@whitesourcesoftware.com>
2024-07-21 23:14:45 +02:00
bakito
944bb42074 update schema base version 2024-07-08 21:30:10 +02:00
bakito
1350364804 allow e2e test bin files in .gitognore 2024-07-08 21:25:34 +02:00
Marc Brugger
3625395d32 print e2e logs (#365)
* keep origin pre and post logs

* print origin pre and post logs
2024-07-08 21:23:08 +02:00
dependabot[bot]
c32ed90a2e Bump golang.org/x/mod from 0.18.0 to 0.19.0 (#363)
Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.18.0 to 0.19.0.
- [Commits](https://github.com/golang/mod/compare/v0.18.0...v0.19.0)

---
updated-dependencies:
- dependency-name: golang.org/x/mod
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-08 21:05:43 +02:00
Marc Brugger
9af8c00bca fix e2e test for newes agh version (#364) 2024-07-08 20:58:34 +02:00
bakito
0af476ac8e update tools 2024-07-03 07:14:23 +02:00
dependabot[bot]
3b58e8ce3c Bump docker/build-push-action from 5 to 6 (#362)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 5 to 6.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v5...v6)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-24 07:01:37 +02:00
Wei Feng
1fffb80066 docs: add "Run as Linux Service via Systemd" to README (#359)
* docs: add "Run as Linux Service via Systemd" to README

* fix: update config file anchor

* chore: add a section on testing details

* fix: web ui url
2024-06-17 07:10:07 +02:00
dependabot[bot]
5f7e6e69e3 Bump github.com/spf13/cobra from 1.8.0 to 1.8.1 (#358)
Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.8.0 to 1.8.1.
- [Release notes](https://github.com/spf13/cobra/releases)
- [Commits](https://github.com/spf13/cobra/compare/v1.8.0...v1.8.1)

---
updated-dependencies:
- dependency-name: github.com/spf13/cobra
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-17 07:02:30 +02:00
dependabot[bot]
14e84b23d1 Bump k8s.io/apimachinery from 0.30.1 to 0.30.2 in the k8s group (#357)
Bumps the k8s group with 1 update: [k8s.io/apimachinery](https://github.com/kubernetes/apimachinery).


Updates `k8s.io/apimachinery` from 0.30.1 to 0.30.2
- [Commits](https://github.com/kubernetes/apimachinery/compare/v0.30.1...v0.30.2)

---
updated-dependencies:
- dependency-name: k8s.io/apimachinery
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: k8s
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-17 07:02:23 +02:00
bakito
562576b8d4 correct docker compose example #356 2024-06-13 07:38:06 +02:00
bakito
532962527f correct docker compose example #356 2024-06-13 07:16:28 +02:00
Marc Brugger
99d1914542 correct deprecated lint flag 2024-06-10 07:55:47 +02:00
dependabot[bot]
6fa534dac1 Replace goreleaser action with make
* Use make

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Marc Brugger <github@bakito.ch>
2024-06-10 06:50:33 +02:00
dependabot[bot]
3b6da33535 Bump golang.org/x/mod from 0.17.0 to 0.18.0 (#353)
Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.17.0 to 0.18.0.
- [Commits](https://github.com/golang/mod/compare/v0.17.0...v0.18.0)

---
updated-dependencies:
- dependency-name: golang.org/x/mod
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-10 06:34:27 +02:00
dependabot[bot]
ded4a22a67 Bump github.com/onsi/ginkgo/v2 from 2.17.3 to 2.19.0 in the onsi group (#350)
Bumps the onsi group with 1 update: [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo).


Updates `github.com/onsi/ginkgo/v2` from 2.17.3 to 2.19.0
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.17.3...v2.19.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: onsi
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-27 07:06:05 +02:00
bakito
ab0589916f only return 200 for clear log requests 2024-05-25 08:21:49 +02:00
Marc Brugger
3226690d70 add clear log button (#349)
* add clear log button

* use button group for instances
2024-05-25 08:12:07 +02:00
bakito
4d235491f2 update tools 2024-05-25 08:11:24 +02:00
dependabot[bot]
96b7890404 Bump k8s.io/apimachinery from 0.30.0 to 0.30.1 in the k8s group (#347)
Bumps the k8s group with 1 update: [k8s.io/apimachinery](https://github.com/kubernetes/apimachinery).


Updates `k8s.io/apimachinery` from 0.30.0 to 0.30.1
- [Commits](https://github.com/kubernetes/apimachinery/compare/v0.30.0...v0.30.1)

---
updated-dependencies:
- dependency-name: k8s.io/apimachinery
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: k8s
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-20 07:48:20 +02:00
dependabot[bot]
760cc01217 Bump github.com/gin-gonic/gin from 1.9.1 to 1.10.0 (#344)
Bumps [github.com/gin-gonic/gin](https://github.com/gin-gonic/gin) from 1.9.1 to 1.10.0.
- [Release notes](https://github.com/gin-gonic/gin/releases)
- [Changelog](https://github.com/gin-gonic/gin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/gin-gonic/gin/compare/v1.9.1...v1.10.0)

---
updated-dependencies:
- dependency-name: github.com/gin-gonic/gin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-13 07:14:10 +02:00
dependabot[bot]
c394a05fc1 Bump github.com/prometheus/client_golang from 1.19.0 to 1.19.1 (#343)
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.19.0 to 1.19.1.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.19.0...v1.19.1)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-13 06:58:28 +02:00
dependabot[bot]
11ab4483e0 Bump github.com/onsi/ginkgo/v2 from 2.17.2 to 2.17.3 in the onsi group (#342)
Bumps the onsi group with 1 update: [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo).


Updates `github.com/onsi/ginkgo/v2` from 2.17.2 to 2.17.3
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.17.2...v2.17.3)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: onsi
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-13 06:58:11 +02:00
dependabot[bot]
65747a7f28 Bump github.com/go-resty/resty/v2 from 2.12.0 to 2.13.1 (#345)
Bumps [github.com/go-resty/resty/v2](https://github.com/go-resty/resty) from 2.12.0 to 2.13.1.
- [Release notes](https://github.com/go-resty/resty/releases)
- [Commits](https://github.com/go-resty/resty/compare/v2.12.0...v2.13.1)

---
updated-dependencies:
- dependency-name: github.com/go-resty/resty/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-13 06:58:03 +02:00
dependabot[bot]
ca9ddc4222 Bump golangci/golangci-lint-action from 5 to 6 (#346)
Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 5 to 6.
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/v5...v6)

---
updated-dependencies:
- dependency-name: golangci/golangci-lint-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-13 06:57:22 +02:00
dependabot[bot]
6a2237b513 Bump github.com/onsi/gomega from 1.33.0 to 1.33.1 in the onsi group (#341)
Bumps the onsi group with 1 update: [github.com/onsi/gomega](https://github.com/onsi/gomega).


Updates `github.com/onsi/gomega` from 1.33.0 to 1.33.1
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.33.0...v1.33.1)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: onsi
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-06 06:42:31 +02:00
Marc Brugger
4afccfad8d handle empty client blockes service schedule in equals check (#339) 2024-05-03 20:44:50 +02:00
Marc Brugger
9a32a1d638 Update README.md 2024-05-03 01:14:13 +02:00
bakito
e091914a06 fix deepcomygen 2024-05-02 23:11:50 +02:00
bakito
7b584668ac skip sanitize 2024-05-02 23:11:28 +02:00
dependabot[bot]
30019327da Bump github.com/onsi/ginkgo/v2 from 2.17.1 to 2.17.2 in the onsi group (#335)
Bumps the onsi group with 1 update: [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo).


Updates `github.com/onsi/ginkgo/v2` from 2.17.1 to 2.17.2
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.17.1...v2.17.2)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: onsi
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-29 07:04:28 +02:00
dependabot[bot]
fc8eeeaf0a Bump golangci/golangci-lint-action from 4 to 5 (#336)
Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 4 to 5.
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/v4...v5)

---
updated-dependencies:
- dependency-name: golangci/golangci-lint-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-29 07:03:34 +02:00
bakito
11201881c0 remove tools.go 2024-04-22 18:15:58 +02:00
dependabot[bot]
498dcb8bcb Bump github.com/onsi/gomega from 1.32.0 to 1.33.0 in the onsi group (#334)
Bumps the onsi group with 1 update: [github.com/onsi/gomega](https://github.com/onsi/gomega).


Updates `github.com/onsi/gomega` from 1.32.0 to 1.33.0
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.32.0...v1.33.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: onsi
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-22 07:59:35 +02:00
dependabot[bot]
5564239bea Bump golang.org/x/net from 0.22.0 to 0.23.0 (#332)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.22.0 to 0.23.0.
- [Commits](https://github.com/golang/net/compare/v0.22.0...v0.23.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-19 16:51:27 +02:00
bakito
5319c5f53f remove goreleaser from tools 2024-04-08 18:26:35 +02:00
dependabot[bot]
49b9e1ce1f Bump golang.org/x/mod from 0.16.0 to 0.17.0 (#331)
Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.16.0 to 0.17.0.
- [Commits](https://github.com/golang/mod/compare/v0.16.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/mod
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-08 07:48:00 +02:00
Marc Brugger
82a61aef09 support api tls mode (#329)
Add support api tls mode
2024-04-06 11:46:12 +02:00
Marc Brugger
3c58a8f091 Replace deprecated API endpoints (#326)
* extend query log config
* replace deprecated services
* replace more deprecated services
* implement equals for stats config
2024-04-02 20:31:47 +02:00
dependabot[bot]
4a62b80e75 Bump github.com/golangci/golangci-lint from 1.57.1 to 1.57.2 (#325)
Bumps [github.com/golangci/golangci-lint](https://github.com/golangci/golangci-lint) from 1.57.1 to 1.57.2.
- [Release notes](https://github.com/golangci/golangci-lint/releases)
- [Changelog](https://github.com/golangci/golangci-lint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/golangci/golangci-lint/compare/v1.57.1...v1.57.2)

---
updated-dependencies:
- dependency-name: github.com/golangci/golangci-lint
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-01 06:46:21 +02:00
dependabot[bot]
52c1687d83 Bump the onsi group with 2 updates (#322)
Bumps the onsi group with 2 updates: [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) and [github.com/onsi/gomega](https://github.com/onsi/gomega).


Updates `github.com/onsi/ginkgo/v2` from 2.16.0 to 2.17.1
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.16.0...v2.17.1)

Updates `github.com/onsi/gomega` from 1.31.1 to 1.32.0
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.31.1...v1.32.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: onsi
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: onsi
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-25 07:03:08 +01:00
dependabot[bot]
238a0f46e0 Bump github.com/golangci/golangci-lint from 1.56.2 to 1.57.1 (#323)
Bumps [github.com/golangci/golangci-lint](https://github.com/golangci/golangci-lint) from 1.56.2 to 1.57.1.
- [Release notes](https://github.com/golangci/golangci-lint/releases)
- [Changelog](https://github.com/golangci/golangci-lint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/golangci/golangci-lint/compare/v1.56.2...v1.57.1)

---
updated-dependencies:
- dependency-name: github.com/golangci/golangci-lint
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-25 07:02:59 +01:00
bakito
bbf5d2d178 skip upx for darwin 2024-03-24 18:58:19 +01:00
Marc Brugger
1c4ea24da1 Sanitize dns config (#321)
Sanitize dns config for misconfigurations
2024-03-24 18:51:07 +01:00
dependabot[bot]
f277460b9d Bump github.com/docker/docker (#318)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 24.0.7+incompatible to 24.0.9+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v24.0.7...v24.0.9)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-20 21:57:44 +01:00
dependabot[bot]
15c1643d34 Bump the k8s group with 2 updates (#316)
Bumps the k8s group with 2 updates: [k8s.io/apimachinery](https://github.com/kubernetes/apimachinery) and [k8s.io/code-generator](https://github.com/kubernetes/code-generator).


Updates `k8s.io/apimachinery` from 0.29.2 to 0.29.3
- [Commits](https://github.com/kubernetes/apimachinery/compare/v0.29.2...v0.29.3)

Updates `k8s.io/code-generator` from 0.29.2 to 0.29.3
- [Commits](https://github.com/kubernetes/code-generator/compare/v0.29.2...v0.29.3)

---
updated-dependencies:
- dependency-name: k8s.io/apimachinery
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: k8s
- dependency-name: k8s.io/code-generator
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: k8s
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-18 07:10:10 +01:00
dependabot[bot]
e7f0a05e1a Bump github.com/go-resty/resty/v2 from 2.9.1 to 2.12.0 (#317)
Bumps [github.com/go-resty/resty/v2](https://github.com/go-resty/resty) from 2.9.1 to 2.12.0.
- [Release notes](https://github.com/go-resty/resty/releases)
- [Commits](https://github.com/go-resty/resty/compare/v2.9.1...v2.12.0)

---
updated-dependencies:
- dependency-name: github.com/go-resty/resty/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-18 07:09:59 +01:00
dependabot[bot]
8506b5d5a5 Bump google.golang.org/protobuf from 1.32.0 to 1.33.0 (#315)
Bumps google.golang.org/protobuf from 1.32.0 to 1.33.0.

---
updated-dependencies:
- dependency-name: google.golang.org/protobuf
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-14 06:59:50 +01:00
dependabot[bot]
0d629127a9 Bump github.com/prometheus/client_golang from 1.17.0 to 1.19.0 (#314)
* Bump github.com/prometheus/client_golang from 1.17.0 to 1.19.0

Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.17.0 to 1.19.0.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.17.0...v1.19.0)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* go-jose.v2 v2.6.3

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: bakito <github@bakito.ch>
2024-03-12 20:03:12 +01:00
Marc Brugger
d6d8d2148d Implement metrics from adguard-exporter (#303)
* implement metrics
2024-03-12 19:48:29 +01:00
Areg Vrtanesyan
5a3f2004bc Update file.go to load from default .adguardhome-sync.yaml file (#313)
* Updateing file.go to load from default .adguardhome-sync.yaml file

* Update file_test.go to load from default .adguardhome-sync.yaml file
2024-03-12 18:41:12 +01:00
dependabot[bot]
0e86fc8094 Bump golang.org/x/mod from 0.15.0 to 0.16.0 (#311)
Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.15.0 to 0.16.0.
- [Commits](https://github.com/golang/mod/compare/v0.15.0...v0.16.0)

---
updated-dependencies:
- dependency-name: golang.org/x/mod
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-11 07:05:22 +01:00
dependabot[bot]
3c6c4b88ef Bump the onsi group with 1 update (#310)
Bumps the onsi group with 1 update: [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo).


Updates `github.com/onsi/ginkgo/v2` from 2.15.0 to 2.16.0
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.15.0...v2.16.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: onsi
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-11 07:04:42 +01:00
dependabot[bot]
4b4a10be7e Bump gopkg.in/go-jose/go-jose.v2 from 2.6.1 to 2.6.3 (#309)
Bumps gopkg.in/go-jose/go-jose.v2 from 2.6.1 to 2.6.3.

---
updated-dependencies:
- dependency-name: gopkg.in/go-jose/go-jose.v2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-08 01:13:35 +01:00
Marc Brugger
4f1491858b update model to v0.107.44 (#308) 2024-03-05 23:03:37 +01:00
dependabot[bot]
33433b1aaf Bump go.uber.org/zap from 1.26.0 to 1.27.0 (#307)
Bumps [go.uber.org/zap](https://github.com/uber-go/zap) from 1.26.0 to 1.27.0.
- [Release notes](https://github.com/uber-go/zap/releases)
- [Changelog](https://github.com/uber-go/zap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/uber-go/zap/compare/v1.26.0...v1.27.0)

---
updated-dependencies:
- dependency-name: go.uber.org/zap
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-26 07:39:20 +01:00
dependabot[bot]
d85ebe0d34 Bump github.com/golangci/golangci-lint from 1.56.1 to 1.56.2 (#306)
* Bump github.com/golangci/golangci-lint from 1.56.1 to 1.56.2

Bumps [github.com/golangci/golangci-lint](https://github.com/golangci/golangci-lint) from 1.56.1 to 1.56.2.
- [Release notes](https://github.com/golangci/golangci-lint/releases)
- [Changelog](https://github.com/golangci/golangci-lint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/golangci/golangci-lint/compare/v1.56.1...v1.56.2)

---
updated-dependencies:
- dependency-name: github.com/golangci/golangci-lint
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* ignore goseg G402

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: bakito <github@bakito.ch>
2024-02-19 07:56:35 +01:00
dependabot[bot]
235bd85510 Bump the k8s group with 2 updates (#305)
Bumps the k8s group with 2 updates: [k8s.io/apimachinery](https://github.com/kubernetes/apimachinery) and [k8s.io/code-generator](https://github.com/kubernetes/code-generator).


Updates `k8s.io/apimachinery` from 0.29.1 to 0.29.2
- [Commits](https://github.com/kubernetes/apimachinery/compare/v0.29.1...v0.29.2)

Updates `k8s.io/code-generator` from 0.29.1 to 0.29.2
- [Commits](https://github.com/kubernetes/code-generator/compare/v0.29.1...v0.29.2)

---
updated-dependencies:
- dependency-name: k8s.io/apimachinery
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: k8s
- dependency-name: k8s.io/code-generator
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: k8s
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-19 07:21:10 +01:00
Adam Shand
5c29345f48 Adding examples for API_USERNAME & API_PASSWORD (#302)
I couldn't find any documentation on how to configure these.  So adding to help out others.
2024-02-14 09:58:05 +01:00
dependabot[bot]
ebcb2b84ae Bump github.com/goreleaser/goreleaser from 1.23.0 to 1.24.0 (#299)
Bumps [github.com/goreleaser/goreleaser](https://github.com/goreleaser/goreleaser) from 1.23.0 to 1.24.0.
- [Release notes](https://github.com/goreleaser/goreleaser/releases)
- [Changelog](https://github.com/goreleaser/goreleaser/blob/main/.goreleaser.yaml)
- [Commits](https://github.com/goreleaser/goreleaser/compare/v1.23.0...v1.24.0)

---
updated-dependencies:
- dependency-name: github.com/goreleaser/goreleaser
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-12 07:52:37 +01:00
dependabot[bot]
080021e73d Bump golang.org/x/mod from 0.14.0 to 0.15.0 (#300)
Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.14.0 to 0.15.0.
- [Commits](https://github.com/golang/mod/compare/v0.14.0...v0.15.0)

---
updated-dependencies:
- dependency-name: golang.org/x/mod
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-12 07:37:33 +01:00
dependabot[bot]
f447e28c86 Bump github.com/golangci/golangci-lint from 1.55.2 to 1.56.1 (#301)
Bumps [github.com/golangci/golangci-lint](https://github.com/golangci/golangci-lint) from 1.55.2 to 1.56.1.
- [Release notes](https://github.com/golangci/golangci-lint/releases)
- [Changelog](https://github.com/golangci/golangci-lint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/golangci/golangci-lint/compare/v1.55.2...v1.56.1)

---
updated-dependencies:
- dependency-name: github.com/golangci/golangci-lint
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-12 07:37:22 +01:00
dependabot[bot]
f3b2ca644c Bump golangci/golangci-lint-action from 3 to 4 (#298)
Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 3 to 4.
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: golangci/golangci-lint-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-12 07:34:43 +01:00
Marc Brugger
4fdead0add Update go in codeql-analysis.yml (#297) 2024-02-04 16:19:59 +01:00
Marc Brugger
059c6e9df7 Handle nil user rules correctly #295 (#296)
* Handle nil user rules correctly #295
2024-02-04 08:55:53 +01:00
bakito
a3f0efa8bc #275 update examples to show only numbered replica env variables 2024-01-31 18:46:33 +01:00
dependabot[bot]
a79ad5abeb Bump github.com/google/uuid from 1.5.0 to 1.6.0 (#293)
Bumps [github.com/google/uuid](https://github.com/google/uuid) from 1.5.0 to 1.6.0.
- [Release notes](https://github.com/google/uuid/releases)
- [Changelog](https://github.com/google/uuid/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/uuid/compare/v1.5.0...v1.6.0)

---
updated-dependencies:
- dependency-name: github.com/google/uuid
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-29 08:30:51 +01:00
dependabot[bot]
c6f8a14643 Bump github.com/deepmap/oapi-codegen/v2 from 2.0.0 to 2.1.0 (#292)
Bumps [github.com/deepmap/oapi-codegen/v2](https://github.com/deepmap/oapi-codegen) from 2.0.0 to 2.1.0.
- [Release notes](https://github.com/deepmap/oapi-codegen/releases)
- [Commits](https://github.com/deepmap/oapi-codegen/compare/v2.0.0...v2.1.0)

---
updated-dependencies:
- dependency-name: github.com/deepmap/oapi-codegen/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-29 08:30:39 +01:00
Marc Brugger
d262357c87 support for json logs #290 (#291)
* support for json logs #290
2024-01-26 09:14:41 +01:00
dependabot[bot]
5da3730015 Bump the onsi group with 1 update (#288)
Bumps the onsi group with 1 update: [github.com/onsi/gomega](https://github.com/onsi/gomega).


Updates `github.com/onsi/gomega` from 1.31.0 to 1.31.1
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.31.0...v1.31.1)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: onsi
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-22 07:01:30 +01:00
dependabot[bot]
b444a4887b Bump the k8s group with 2 updates (#286)
Bumps the k8s group with 2 updates: [k8s.io/apimachinery](https://github.com/kubernetes/apimachinery) and [k8s.io/code-generator](https://github.com/kubernetes/code-generator).


Updates `k8s.io/apimachinery` from 0.29.0 to 0.29.1
- [Commits](https://github.com/kubernetes/apimachinery/compare/v0.29.0...v0.29.1)

Updates `k8s.io/code-generator` from 0.29.0 to 0.29.1
- [Commits](https://github.com/kubernetes/code-generator/compare/v0.29.0...v0.29.1)

---
updated-dependencies:
- dependency-name: k8s.io/apimachinery
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: k8s
- dependency-name: k8s.io/code-generator
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: k8s
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-19 00:32:20 +01:00
dependabot[bot]
cc7133927f Bump the onsi group with 2 updates (#283)
Bumps the onsi group with 2 updates: [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) and [github.com/onsi/gomega](https://github.com/onsi/gomega).


Updates `github.com/onsi/ginkgo/v2` from 2.14.0 to 2.15.0
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.14.0...v2.15.0)

Updates `github.com/onsi/gomega` from 1.30.0 to 1.31.0
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.30.0...v1.31.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: onsi
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: onsi
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-19 00:22:49 +01:00
Marc Brugger
4b01042a4f Update dependabot.yml 2024-01-19 00:22:12 +01:00
Marc Brugger
4b94ef998d Update dependabot.yml to use groups 2024-01-19 00:19:46 +01:00
bakito
db5764b3d7 simplify test 2024-01-18 21:23:05 +01:00
Marc Brugger
3c0115b71c Dhcp server enabled handling (#282)
* switch to go.uber.org/mock

* #281 special handling for dhcpServerEnabled
2024-01-17 19:29:25 +01:00
Marc Brugger
c401c790bc switch to go.uber.org/mock (#280) 2024-01-16 16:46:13 +01:00
dependabot[bot]
271dcacf19 Bump github.com/go-git/go-git/v5 from 5.7.0 to 5.11.0 (#279)
Bumps [github.com/go-git/go-git/v5](https://github.com/go-git/go-git) from 5.7.0 to 5.11.0.
- [Release notes](https://github.com/go-git/go-git/releases)
- [Commits](https://github.com/go-git/go-git/compare/v5.7.0...v5.11.0)

---
updated-dependencies:
- dependency-name: github.com/go-git/go-git/v5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-15 22:06:42 +01:00
dependabot[bot]
983d29d033 Bump github.com/cloudflare/circl from 1.3.5 to 1.3.7 (#278)
Bumps [github.com/cloudflare/circl](https://github.com/cloudflare/circl) from 1.3.5 to 1.3.7.
- [Release notes](https://github.com/cloudflare/circl/releases)
- [Commits](https://github.com/cloudflare/circl/compare/v1.3.5...v1.3.7)

---
updated-dependencies:
- dependency-name: github.com/cloudflare/circl
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-15 22:06:31 +01:00
Marc Brugger
078f6e1cc4 add dependencies to tools.go (#277) 2024-01-15 21:07:56 +01:00
Marc Brugger
9f8f6bc814 run tests with ginkgo cli (#276) 2024-01-15 18:24:10 +01:00
bakito
ef35178396 move deprecations to wiki 2024-01-15 08:13:52 +01:00
dependabot[bot]
b3781c6d8f Bump github.com/onsi/ginkgo/v2 from 2.13.2 to 2.14.0 (#274)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.13.2 to 2.14.0.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.13.2...v2.14.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-15 07:07:45 +01:00
bakito
2319739f6d ignore wiki from 2024-01-14 21:21:19 +01:00
Marc Brugger
f9ac1ca698 Add e2e status to Readme 2024-01-14 21:02:19 +01:00
bakito
9802bf2f37 add tests #272 2024-01-14 16:38:58 +01:00
bakito
843433e35d extend dhcp checks #272 2024-01-14 16:13:45 +01:00
bakito
15c651ca96 #272 do not sync dhcp if empty IP 2024-01-14 14:03:04 +01:00
Marc Brugger
4c1e56ccce Fix config issues with new env library (#273)
* correct config issues #271 #272

* rename type tags

* replace env lib

* move to config module

* read flags

* show e2e logs on error

* extract env

* replace deprecated env var

* increment index

* check replica numbers do not start with 0

* remove test suite

* error handling

* refactor flags

* flags test

* file test

* file test

* config tests

* extend tests

* test mixed mode

* simplify

* simplify

* test mask

* correct uniqe replicas

* Update types_test.go

* e2e test with file mode
2024-01-14 13:29:36 +01:00
bakito
906dfc680a extend bug issue template wiht 'applied-config' 2024-01-14 09:07:54 +01:00
Marc Brugger
583197f1c8 add deprecated env var table 2024-01-10 20:12:08 +01:00
Marc Brugger
5fca3b1002 better readable env vars (#270)
* better readable env vars
2024-01-08 19:39:47 +01:00
bakito
1edf5ae52a add filter test cases 2024-01-08 19:20:31 +01:00
bakito
39f7f41e6d update readme 2024-01-07 22:44:32 +01:00
Marc Brugger
3139ad605f Refactor sync into separate action functions (#268)
* sync-actions

* dns rewrites and filters

* continue on filter error

* servides

* client settings

* dns

* dhcp

* remove deprecated env var

* fix client tests

* tests

* copy replica config

* map continue on error

* map env var with underscore
2024-01-07 22:03:21 +01:00
bakito
a9de069f6b update dependencies 2024-01-07 10:05:13 +01:00
Marc Brugger
4a8e2aab51 allow definig web URL (#267) 2024-01-07 09:55:21 +01:00
Marc Brugger
749c5f178c add option to print full config for debugging (#266)
* add option to print full config for debugging

* print link to FAQ in auth errors
2024-01-07 03:05:44 +01:00
Marc Brugger
680989bc2e add tzdata to image (#265) 2024-01-05 09:27:36 +01:00
dependabot[bot]
418989989b Bump github.com/go-resty/resty/v2 from 2.9.1 to 2.11.0 (#263)
Bumps [github.com/go-resty/resty/v2](https://github.com/go-resty/resty) from 2.9.1 to 2.11.0.
- [Release notes](https://github.com/go-resty/resty/releases)
- [Commits](https://github.com/go-resty/resty/compare/v2.9.1...v2.11.0)

---
updated-dependencies:
- dependency-name: github.com/go-resty/resty/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-28 08:07:19 +01:00
dependabot[bot]
9a06a6ac10 Bump github.com/spf13/viper from 1.18.1 to 1.18.2 (#261)
Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.18.1 to 1.18.2.
- [Release notes](https://github.com/spf13/viper/releases)
- [Commits](https://github.com/spf13/viper/compare/v1.18.1...v1.18.2)

---
updated-dependencies:
- dependency-name: github.com/spf13/viper
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-25 07:36:08 +01:00
bakito
ff7bae0bba build main image only once a day if needed 2023-12-22 22:45:50 +01:00
Marc Brugger
215ee946dd add protection flag to api status (#260) 2023-12-22 21:03:46 +01:00
bakito
cc13b9318d correct api status host property from 'origin' to 'host' 2023-12-22 17:26:29 +01:00
bakito
9d26eec6b0 asure cacerts are installed in build image 2023-12-20 20:41:34 +01:00
Marc Brugger
bb969a0005 upgrade agh schema to v0.107.43 (#257) 2023-12-20 07:57:41 +01:00
bakito
37b8fda889 extend issue template description 2023-12-19 21:34:02 +01:00
bakito
0c9487a53d logs are required in template 2023-12-19 19:38:37 +01:00
Marc Brugger
a039704f1b update issue templates (#256) 2023-12-19 19:36:53 +01:00
Marc Brugger
ec9be5aed6 Update general_issue.md (#255) 2023-12-19 17:52:48 +01:00
Marc Brugger
542aacb002 Create general_issue.md (#254)
* Create general_issue.md

* Add bug label
2023-12-19 17:27:03 +01:00
Marc Brugger
e7e391f85c Create config.yml (#253) 2023-12-19 17:11:34 +01:00
dependabot[bot]
8ccd773b4b Bump golang.org/x/crypto from 0.16.0 to 0.17.0 (#251)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.16.0 to 0.17.0.
- [Commits](https://github.com/golang/crypto/compare/v0.16.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-19 06:44:47 +01:00
dependabot[bot]
cb52a43940 Bump github/codeql-action from 2 to 3 (#249)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2 to 3.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-18 06:40:39 +01:00
dependabot[bot]
2463809356 Bump k8s.io/apimachinery from 0.28.4 to 0.29.0 (#250)
Bumps [k8s.io/apimachinery](https://github.com/kubernetes/apimachinery) from 0.28.4 to 0.29.0.
- [Commits](https://github.com/kubernetes/apimachinery/compare/v0.28.4...v0.29.0)

---
updated-dependencies:
- dependency-name: k8s.io/apimachinery
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-18 06:40:22 +01:00
dependabot[bot]
b71df4d8ed Bump github.com/google/uuid from 1.4.0 to 1.5.0 (#248)
Bumps [github.com/google/uuid](https://github.com/google/uuid) from 1.4.0 to 1.5.0.
- [Release notes](https://github.com/google/uuid/releases)
- [Changelog](https://github.com/google/uuid/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/uuid/compare/v1.4.0...v1.5.0)

---
updated-dependencies:
- dependency-name: github.com/google/uuid
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-12 23:10:10 +01:00
bakito
4cd60e818a downgrade resty 2023-12-12 22:57:12 +01:00
dependabot[bot]
31aad9471b Bump github.com/spf13/viper from 1.17.0 to 1.18.1 (#245)
Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.17.0 to 1.18.1.
- [Release notes](https://github.com/spf13/viper/releases)
- [Commits](https://github.com/spf13/viper/compare/v1.17.0...v1.18.1)

---
updated-dependencies:
- dependency-name: github.com/spf13/viper
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-11 06:43:00 +01:00
dependabot[bot]
34dac9e091 Bump actions/setup-go from 4 to 5 (#246)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 4 to 5.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-11 06:42:51 +01:00
dependabot[bot]
c1c81bb8f6 Bump github.com/onsi/ginkgo/v2 from 2.13.1 to 2.13.2 (#243)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.13.1 to 2.13.2.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.13.1...v2.13.2)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-04 08:02:14 +01:00
Marc Brugger
012350a8fd #239 do not sync incomplete profile (#240) 2023-11-23 08:11:32 +01:00
dependabot[bot]
fefdda0015 Bump github.com/oapi-codegen/runtime from 1.0.0 to 1.1.0 (#237)
Bumps [github.com/oapi-codegen/runtime](https://github.com/oapi-codegen/runtime) from 1.0.0 to 1.1.0.
- [Release notes](https://github.com/oapi-codegen/runtime/releases)
- [Commits](https://github.com/oapi-codegen/runtime/compare/v1.0.0...v1.1.0)

---
updated-dependencies:
- dependency-name: github.com/oapi-codegen/runtime
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-20 07:10:53 +01:00
dependabot[bot]
55d63df17b Bump k8s.io/apimachinery from 0.28.3 to 0.28.4 (#238)
Bumps [k8s.io/apimachinery](https://github.com/kubernetes/apimachinery) from 0.28.3 to 0.28.4.
- [Commits](https://github.com/kubernetes/apimachinery/compare/v0.28.3...v0.28.4)

---
updated-dependencies:
- dependency-name: k8s.io/apimachinery
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-20 07:10:45 +01:00
bakito
37ee52aa8f add links to dashboard 2023-11-13 23:45:42 +01:00
Marc Brugger
da289017a5 Generate types from openapi (#201)
* generate model from openAPI schema
* implement replica status #231
* Sync "Pause service blocking schedule" #234
* Sync "Safe Search Provider" #200
2023-11-13 21:09:08 +01:00
dependabot[bot]
0fb6f38bab Bump github.com/onsi/ginkgo/v2 from 2.13.0 to 2.13.1 (#235)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.13.0 to 2.13.1.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.13.0...v2.13.1)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-13 07:03:51 +01:00
dependabot[bot]
014e9c8a26 Bump github.com/onsi/gomega from 1.29.0 to 1.30.0 (#236)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.29.0 to 1.30.0.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.29.0...v1.30.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-13 06:56:23 +01:00
dependabot[bot]
d07c005191 Bump github.com/spf13/cobra from 1.7.0 to 1.8.0 (#233)
Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.7.0 to 1.8.0.
- [Release notes](https://github.com/spf13/cobra/releases)
- [Commits](https://github.com/spf13/cobra/compare/v1.7.0...v1.8.0)

---
updated-dependencies:
- dependency-name: github.com/spf13/cobra
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-06 06:49:57 +01:00
dependabot[bot]
a2b9930f92 Bump golang.org/x/mod from 0.13.0 to 0.14.0 (#232)
Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.13.0 to 0.14.0.
- [Commits](https://github.com/golang/mod/compare/v0.13.0...v0.14.0)

---
updated-dependencies:
- dependency-name: golang.org/x/mod
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-06 06:43:53 +01:00
dependabot[bot]
8ac7b9addd Bump github.com/onsi/gomega from 1.28.1 to 1.29.0 (#229)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.28.1 to 1.29.0.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.28.1...v1.29.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-30 07:26:33 +01:00
dependabot[bot]
8115f52d8a Bump github.com/google/uuid from 1.3.1 to 1.4.0 (#230)
Bumps [github.com/google/uuid](https://github.com/google/uuid) from 1.3.1 to 1.4.0.
- [Release notes](https://github.com/google/uuid/releases)
- [Changelog](https://github.com/google/uuid/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/uuid/compare/v1.3.1...v1.4.0)

---
updated-dependencies:
- dependency-name: github.com/google/uuid
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-30 07:26:11 +01:00
dependabot[bot]
1581b82ae5 Bump github.com/onsi/gomega from 1.28.0 to 1.28.1 (#228)
* Bump github.com/onsi/gomega from 1.28.0 to 1.28.1

Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.28.0 to 1.28.1.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.28.0...v1.28.1)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update .golangci.yml

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Marc Brugger <github@bakito.ch>
2023-10-24 19:43:05 +02:00
dependabot[bot]
9a466cb86b Bump github.com/go-resty/resty/v2 from 2.9.1 to 2.10.0 (#226)
Bumps [github.com/go-resty/resty/v2](https://github.com/go-resty/resty) from 2.9.1 to 2.10.0.
- [Release notes](https://github.com/go-resty/resty/releases)
- [Commits](https://github.com/go-resty/resty/compare/v2.9.1...v2.10.0)

---
updated-dependencies:
- dependency-name: github.com/go-resty/resty/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-16 08:01:00 +02:00
dependabot[bot]
fda2ac0a5a Bump github.com/onsi/ginkgo/v2 from 2.12.1 to 2.13.0 (#225)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.12.1 to 2.13.0.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.12.1...v2.13.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-16 08:00:36 +02:00
dependabot[bot]
c1975eebdb Bump golang.org/x/net from 0.15.0 to 0.17.0 (#224)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.15.0 to 0.17.0.
- [Commits](https://github.com/golang/net/compare/v0.15.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-12 06:53:00 +02:00
dependabot[bot]
ab891449cf Bump github.com/spf13/viper from 1.16.0 to 1.17.0 (#222)
Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.16.0 to 1.17.0.
- [Release notes](https://github.com/spf13/viper/releases)
- [Commits](https://github.com/spf13/viper/compare/v1.16.0...v1.17.0)

---
updated-dependencies:
- dependency-name: github.com/spf13/viper
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-09 12:30:42 +02:00
dependabot[bot]
e4f235586e Bump golang.org/x/mod from 0.12.0 to 0.13.0 (#223)
Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.12.0 to 0.13.0.
- [Commits](https://github.com/golang/mod/compare/v0.12.0...v0.13.0)

---
updated-dependencies:
- dependency-name: golang.org/x/mod
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-09 08:22:58 +02:00
dependabot[bot]
49e79274bb Bump github.com/go-resty/resty/v2 from 2.8.0 to 2.9.1 (#220)
Bumps [github.com/go-resty/resty/v2](https://github.com/go-resty/resty) from 2.8.0 to 2.9.1.
- [Release notes](https://github.com/go-resty/resty/releases)
- [Commits](https://github.com/go-resty/resty/compare/v2.8.0...v2.9.1)

---
updated-dependencies:
- dependency-name: github.com/go-resty/resty/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-02 08:02:55 +02:00
dependabot[bot]
4d077eba3c Bump github.com/onsi/gomega from 1.27.10 to 1.28.0 (#221)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.27.10 to 1.28.0.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.27.10...v1.28.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-02 08:02:47 +02:00
dependabot[bot]
ad24071c9c Bump github.com/onsi/ginkgo/v2 from 2.12.0 to 2.12.1 (#219)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.12.0 to 2.12.1.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.12.0...v2.12.1)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-25 07:12:23 +02:00
dependabot[bot]
d99af0c43d Bump go.uber.org/zap from 1.25.0 to 1.26.0 (#212)
Bumps [go.uber.org/zap](https://github.com/uber-go/zap) from 1.25.0 to 1.26.0.
- [Release notes](https://github.com/uber-go/zap/releases)
- [Changelog](https://github.com/uber-go/zap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/uber-go/zap/compare/v1.25.0...v1.26.0)

---
updated-dependencies:
- dependency-name: go.uber.org/zap
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-18 07:05:56 +02:00
dependabot[bot]
0f86e61cdd Bump docker/login-action from 2 to 3 (#214)
Bumps [docker/login-action](https://github.com/docker/login-action) from 2 to 3.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-18 07:05:46 +02:00
dependabot[bot]
dea2d04a63 Bump github.com/go-resty/resty/v2 from 2.7.0 to 2.8.0 (#213)
Bumps [github.com/go-resty/resty/v2](https://github.com/go-resty/resty) from 2.7.0 to 2.8.0.
- [Release notes](https://github.com/go-resty/resty/releases)
- [Commits](https://github.com/go-resty/resty/compare/v2.7.0...v2.8.0)

---
updated-dependencies:
- dependency-name: github.com/go-resty/resty/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-18 07:05:17 +02:00
dependabot[bot]
a34eb1ae57 Bump docker/build-push-action from 4 to 5 (#216)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 4 to 5.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v4...v5)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-18 07:04:38 +02:00
dependabot[bot]
6f426a870f Bump goreleaser/goreleaser-action from 4 to 5 (#215)
Bumps [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action) from 4 to 5.
- [Release notes](https://github.com/goreleaser/goreleaser-action/releases)
- [Commits](https://github.com/goreleaser/goreleaser-action/compare/v4...v5)

---
updated-dependencies:
- dependency-name: goreleaser/goreleaser-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-18 07:04:28 +02:00
dependabot[bot]
0e129d4b10 Bump docker/setup-buildx-action from 2 to 3 (#218)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2 to 3.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-18 07:04:14 +02:00
dependabot[bot]
2d65e7dc9e Bump docker/setup-qemu-action from 2 to 3 (#217)
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 2 to 3.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-18 07:04:03 +02:00
dependabot[bot]
6e47c07aa3 Bump actions/checkout from 3 to 4 (#211)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-11 06:40:45 +02:00
dependabot[bot]
dea159d766 Bump github.com/google/uuid from 1.3.0 to 1.3.1 (#207)
Bumps [github.com/google/uuid](https://github.com/google/uuid) from 1.3.0 to 1.3.1.
- [Release notes](https://github.com/google/uuid/releases)
- [Changelog](https://github.com/google/uuid/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/uuid/compare/v1.3.0...v1.3.1)

---
updated-dependencies:
- dependency-name: github.com/google/uuid
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-28 06:49:32 +02:00
dependabot[bot]
e4221a9ca7 Bump github.com/onsi/ginkgo/v2 from 2.11.0 to 2.12.0 (#208)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.11.0 to 2.12.0.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.11.0...v2.12.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-28 06:43:14 +02:00
dependabot[bot]
240a0285c2 Bump github.com/jinzhu/copier from 0.3.5 to 0.4.0 (#209)
Bumps [github.com/jinzhu/copier](https://github.com/jinzhu/copier) from 0.3.5 to 0.4.0.
- [Commits](https://github.com/jinzhu/copier/compare/v0.3.5...v0.4.0)

---
updated-dependencies:
- dependency-name: github.com/jinzhu/copier
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-28 06:42:56 +02:00
dependabot[bot]
bc69d0388c Bump go.uber.org/zap from 1.24.0 to 1.25.0 (#206)
Bumps [go.uber.org/zap](https://github.com/uber-go/zap) from 1.24.0 to 1.25.0.
- [Release notes](https://github.com/uber-go/zap/releases)
- [Changelog](https://github.com/uber-go/zap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/uber-go/zap/compare/v1.24.0...v1.25.0)

---
updated-dependencies:
- dependency-name: go.uber.org/zap
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-07 08:06:40 +03:00
David Johnson
5043f157fa Add support for using cookie for auth (#205) 2023-08-05 08:38:21 +03:00
dependabot[bot]
2b4877f122 Bump github.com/onsi/gomega from 1.27.9 to 1.27.10 (#204)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.27.9 to 1.27.10.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.27.9...v1.27.10)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-31 05:35:08 +02:00
dependabot[bot]
53b9cf704c Bump github.com/onsi/gomega from 1.27.8 to 1.27.9 (#203)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.27.8 to 1.27.9.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.27.8...v1.27.9)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-24 07:33:25 +02:00
dependabot[bot]
69dd7650e1 Bump golang.org/x/mod from 0.11.0 to 0.12.0 (#202)
Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.11.0 to 0.12.0.
- [Commits](https://github.com/golang/mod/compare/v0.11.0...v0.12.0)

---
updated-dependencies:
- dependency-name: golang.org/x/mod
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-10 07:27:44 +02:00
dependabot[bot]
06b93cbdac Bump golang.org/x/mod from 0.10.0 to 0.11.0 (#197)
Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.10.0 to 0.11.0.
- [Commits](https://github.com/golang/mod/compare/v0.10.0...v0.11.0)

---
updated-dependencies:
- dependency-name: golang.org/x/mod
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-19 20:47:30 +02:00
dependabot[bot]
dd21859cd1 Bump github.com/onsi/ginkgo/v2 from 2.10.0 to 2.11.0 (#198)
* Bump github.com/onsi/ginkgo/v2 from 2.10.0 to 2.11.0

Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.10.0 to 2.11.0.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.10.0...v2.11.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update Dockerfile

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Marc Brugger <github@bakito.ch>
2023-06-19 20:26:42 +02:00
dependabot[bot]
9a19b33116 Bump github.com/onsi/gomega from 1.27.7 to 1.27.8 (#196)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.27.7 to 1.27.8.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.27.7...v1.27.8)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-12 07:23:20 +02:00
dependabot[bot]
5385d0aaed Bump github.com/onsi/ginkgo/v2 from 2.9.7 to 2.10.0 (#195)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.9.7 to 2.10.0.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.9.7...v2.10.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-12 07:17:13 +02:00
bakito
02547975e9 update tools 2023-06-10 08:03:40 +02:00
dependabot[bot]
598324b7aa Bump github.com/onsi/ginkgo/v2 from 2.9.5 to 2.9.7 (#192)
* Bump github.com/onsi/ginkgo/v2 from 2.9.5 to 2.9.7

Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.9.5 to 2.9.7.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.9.5...v2.9.7)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* skip-pkg-cache: true

* Update .golangci.yml

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Marc Brugger <github@bakito.ch>
2023-06-02 07:37:01 +02:00
dependabot[bot]
49cd71daf7 Bump github.com/spf13/viper from 1.15.0 to 1.16.0 (#193)
Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.15.0 to 1.16.0.
- [Release notes](https://github.com/spf13/viper/releases)
- [Commits](https://github.com/spf13/viper/compare/v1.15.0...v1.16.0)

---
updated-dependencies:
- dependency-name: github.com/spf13/viper
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-02 07:36:28 +02:00
Marc Brugger
2f2cd0af58 set provenance: false for image builds (#191) 2023-06-01 22:50:56 +02:00
dependabot[bot]
607238230d Bump github.com/gin-gonic/gin from 1.9.0 to 1.9.1 (#190)
Bumps [github.com/gin-gonic/gin](https://github.com/gin-gonic/gin) from 1.9.0 to 1.9.1.
- [Release notes](https://github.com/gin-gonic/gin/releases)
- [Changelog](https://github.com/gin-gonic/gin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/gin-gonic/gin/compare/v1.9.0...v1.9.1)

---
updated-dependencies:
- dependency-name: github.com/gin-gonic/gin
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-01 22:36:03 +02:00
dependabot[bot]
ed952dd891 Bump github.com/onsi/gomega from 1.27.6 to 1.27.7 (#189)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.27.6 to 1.27.7.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.27.6...v1.27.7)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-22 11:53:21 +02:00
dependabot[bot]
50de6b1d71 Bump github.com/onsi/ginkgo/v2 from 2.9.2 to 2.9.4 (#187)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.9.2 to 2.9.4.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.9.2...v2.9.4)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-08 07:09:28 +02:00
bakito
4db293ccf2 update all builder images packages to get latestav ailable cacerts #185 2023-05-01 07:37:08 +02:00
bakito
3211406ef2 update tools 2023-04-12 20:29:37 +02:00
Marc Brugger
c93084e623 Only sync dhcp config if it is valid (#184)
* handle new install page redirect location

* only sync dhcp config if valid
2023-04-12 20:02:55 +02:00
Marc Brugger
009715ccea handle new install page redirect location (#183) 2023-04-12 19:33:15 +02:00
bakito
c22f38fff2 correct go.mod version to 1.20 2023-04-10 22:22:50 +02:00
dependabot[bot]
e2c6ef40ec Bump golang.org/x/mod from 0.9.0 to 0.10.0 (#181)
Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.9.0 to 0.10.0.
- [Release notes](https://github.com/golang/mod/releases)
- [Commits](https://github.com/golang/mod/compare/v0.9.0...v0.10.0)

---
updated-dependencies:
- dependency-name: golang.org/x/mod
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-10 08:53:09 +02:00
dependabot[bot]
b44e2f8d8d Bump github.com/spf13/cobra from 1.6.1 to 1.7.0 (#180)
Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.6.1 to 1.7.0.
- [Release notes](https://github.com/spf13/cobra/releases)
- [Commits](https://github.com/spf13/cobra/compare/v1.6.1...v1.7.0)

---
updated-dependencies:
- dependency-name: github.com/spf13/cobra
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-10 08:53:01 +02:00
dependabot[bot]
c20bec7a13 Bump github.com/onsi/gomega from 1.27.5 to 1.27.6 (#178)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.27.5 to 1.27.6.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.27.5...v1.27.6)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-03 06:48:08 +02:00
Marc Brugger
f21874c546 log os and arch (#177) 2023-03-28 20:38:48 +02:00
dependabot[bot]
fce8aea40b Bump github.com/onsi/gomega from 1.27.4 to 1.27.5 (#175)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.27.4 to 1.27.5.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.27.4...v1.27.5)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-27 06:50:27 +02:00
Marc Brugger
1dab955843 print config in debug mode (#171) 2023-03-21 14:28:16 +01:00
bakito
92c4d1f41a #170 add log level to readme 2023-03-21 11:14:44 +01:00
dependabot[bot]
89e4f2f0aa Bump github.com/onsi/gomega from 1.27.3 to 1.27.4 (#168)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.27.3 to 1.27.4.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.27.3...v1.27.4)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-20 07:19:52 +01:00
dependabot[bot]
cf0458b4a9 Bump actions/setup-go from 3 to 4 (#169)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 3 to 4.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-20 06:48:39 +01:00
Marc Brugger
c524a8ce2b Update sync.go (#167) 2023-03-17 20:43:06 +01:00
bakito
5df430e694 update readme with api port 2023-03-15 20:57:29 +01:00
Minchao
faaefd726a fix type of replica-interface-name flag to string (#166) 2023-03-15 20:57:18 +01:00
dependabot[bot]
19451db485 Bump github.com/onsi/ginkgo/v2 from 2.9.0 to 2.9.1 (#164)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.9.0 to 2.9.1.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.9.0...v2.9.1)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-13 10:47:34 +01:00
Dustin Essington
abe38e7a2c Adjust origin-insecure-skip-verify to a bool (#161) 2023-03-12 08:49:37 +01:00
Marc Brugger
495e0d261d Allow enabling/disabling target dhcp server (#160)
* Allow enabling/disabling target dhcp server

* add test

* extend tests
2023-03-06 22:07:34 +01:00
Marc Brugger
cfcffab9d1 correct interface name env var from REPLICA%s_INTERFACWENAME to REPLICA%s_INTERFACENAME (#159) 2023-03-06 21:13:50 +01:00
dependabot[bot]
94ed2c6245 Bump github.com/onsi/ginkgo/v2 from 2.8.3 to 2.9.0 (#157)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.8.3 to 2.9.0.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.8.3...v2.9.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-06 08:42:52 +01:00
dependabot[bot]
e3f01f75a2 Bump github.com/onsi/gomega from 1.27.1 to 1.27.2 (#156)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.27.1 to 1.27.2.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.27.1...v1.27.2)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-06 07:56:18 +01:00
dependabot[bot]
5865a8160e Bump golang.org/x/mod from 0.8.0 to 0.9.0 (#158)
Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.8.0 to 0.9.0.
- [Release notes](https://github.com/golang/mod/releases)
- [Commits](https://github.com/golang/mod/compare/v0.8.0...v0.9.0)

---
updated-dependencies:
- dependency-name: golang.org/x/mod
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-06 07:56:02 +01:00
dependabot[bot]
619dd5fcd9 Bump github.com/onsi/ginkgo/v2 from 2.8.1 to 2.8.3 (#154)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.8.1 to 2.8.3.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.8.1...v2.8.3)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-27 06:39:08 +01:00
dependabot[bot]
9310ddddaf Bump golang.org/x/mod (#153)
Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.6.0-dev.0.20220419223038-86c51ed26bb4 to 0.8.0.
- [Release notes](https://github.com/golang/mod/releases)
- [Commits](https://github.com/golang/mod/commits/v0.8.0)

---
updated-dependencies:
- dependency-name: golang.org/x/mod
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-27 05:22:22 +01:00
dependabot[bot]
25265a5f51 Bump github.com/gin-gonic/gin from 1.8.2 to 1.9.0 (#152)
Bumps [github.com/gin-gonic/gin](https://github.com/gin-gonic/gin) from 1.8.2 to 1.9.0.
- [Release notes](https://github.com/gin-gonic/gin/releases)
- [Changelog](https://github.com/gin-gonic/gin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/gin-gonic/gin/compare/v1.8.2...v1.9.0)

---
updated-dependencies:
- dependency-name: github.com/gin-gonic/gin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-27 05:22:10 +01:00
dependabot[bot]
bdc8024ba9 Bump github.com/onsi/gomega from 1.26.0 to 1.27.1 (#151)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.26.0 to 1.27.1.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.26.0...v1.27.1)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-20 08:32:03 +01:00
bakito
116fdf5c11 update makefile 2023-02-07 22:56:29 +01:00
143 changed files with 20898 additions and 4971 deletions

2
.dockerignore Normal file
View File

@@ -0,0 +1,2 @@
dist
bin

View File

@@ -1,28 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. What version of AdGuardHome sync used?
2. What version of AdGuardHome us used?
3. How does the configuration look?
4. What is the error message?
**Expected behavior**
A clear and concise description of what you expected to happen.
**Log Files**
If applicable, add log files or json responses from AdGuardHome to help explain your problem.
Please set the environment variable `LOG_LEVEL=debug` to generate debug logs.
**Additional context**
Add any other context about the problem here.

78
.github/ISSUE_TEMPLATE/bug_report.yaml vendored Normal file
View File

@@ -0,0 +1,78 @@
name: Bug report
description: Create a report to help us improve
labels: ['bug']
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this issue report! If you have usage questions, please try the [FAQ](https://github.com/bakito/adguardhome-sync/wiki/FAQ) first.
- type: textarea
id: what-happened
attributes:
label: What happened?
description: Also tell us, what did you expect to happen?
validations:
required: true
- type: textarea
id: adguardhome-sync-version
attributes:
label: AdguardHome-Sync Version
description: |
- What version of adguardhome-sync was running when you discovered this issue?
- Are you running the docker or binary version?
validations:
required: true
- type: textarea
id: adguardhome-version
attributes:
label: AdguardHome Version
description: What version of adguardhome was running when you discovered this issue?
validations:
required: true
- type: textarea
id: os-information
attributes:
label: OS Information
description: |
- What Operating System are you running? `cat /etc/os-release`
- What is the architecture of your CPU? `uname -m`
validations:
required: true
- type: textarea
id: config
attributes:
label: Configuration
description: |
- How did you configure adguardhome-sync?
- Please provide your configuration
render: shell
validations:
required: true
- type: textarea
id: applied-config
attributes:
label: Current Applied Configuration
description: |
- Run adguardhome-sync with environment variable `PRINT_CONFIG_ONLY=true` and provide the output here
render: shell
- type: textarea
id: logs
attributes:
label: Relevant DEBUG log output
description: |
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`.
Please also check adguardhome logs and paste any relevant logs/errors to this issue.
render: shell
validations:
required: true
- type: textarea
attributes:
label: Anything else?
description: |
Links? References? Anything that will provide more context about the issue you are encountering!
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
validations:
required: false

1
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1 @@
blank_issues_enabled: false

View File

@@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -0,0 +1,36 @@
name: Feature request
description: Suggest an idea for this project
labels: ['enhancement']
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this issue report!
- type: textarea
id: relation
attributes:
label: Is your feature request related to a problem? Please describe.
description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
validations:
required: true
- type: textarea
id: solution
attributes:
label: Describe the solution you'd like*
description: |
A clear and concise description of what you want to happen.
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Describe alternatives you've considered
description: |
A clear and concise description of any alternative solutions or features you've considered.
- type: textarea
attributes:
label: Anything else?
description: |
Add any other context or screenshots about the feature request here.
validations:
required: false

View File

@@ -0,0 +1,60 @@
name: General Issue
description: Report an issue with your setup
labels: []
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this issue report! If you have usage questions, please try the [FAQ](https://github.com/bakito/adguardhome-sync/wiki/FAQ) first.
- type: textarea
id: what-happened
attributes:
label: What happened?
description: Also tell us, what did you expect to happen?
validations:
required: true
- type: textarea
id: adguardhome-sync-version
attributes:
label: AdguardHome-Sync Version
description: What version of adguardhome-sync was running when you discovered this issue?
validations:
required: true
- type: textarea
id: adguardhome-version
attributes:
label: AdguardHome Version
description: What version of adguardhome was running when you discovered this issue?
validations:
required: true
- type: textarea
id: config
attributes:
label: Configuration
description: |
- How did you configure adguardhome-sync?
- Please provide your configuration
render: shell
validations:
required: true
- type: textarea
id: logs
attributes:
label: Relevant log output
description: |
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`.
Please also check adguardhome logs and paste any relevant logs/errors to this issue.
render: shell
validations:
required: true
- type: textarea
attributes:
label: Anything else?
description: |
Links? References? Anything that will provide more context about the issue you are encountering!
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
validations:
required: false

View File

@@ -1,15 +0,0 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "gomod" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"

View File

@@ -20,6 +20,10 @@ on:
schedule: schedule:
- cron: '32 19 * * 6' - cron: '32 19 * * 6'
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs: jobs:
analyze: analyze:
name: Analyze name: Analyze
@@ -38,11 +42,17 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3 uses: actions/checkout@v5
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version-file: "go.mod"
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v2 uses: github/codeql-action/init@v3
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # If you wish to specify custom queries, you can do so here or in a config file.
@@ -53,7 +63,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@v2 uses: github/codeql-action/autobuild@v3
# Command-line programs to run using the OS shell. # Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl # 📚 https://git.io/JvXDl
@@ -67,4 +77,4 @@ jobs:
# make release # make release
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2 uses: github/codeql-action/analyze@v3

View File

@@ -1,13 +1,20 @@
name: docker-image name: docker-images
on: on:
push: workflow_dispatch: # allows manual triggering
branches: schedule:
- main - cron: '0 0 * * *'
#pull_request:
# branches:
# - main
release: release:
types: types:
- published - published
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs: jobs:
images: images:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -22,55 +29,61 @@ jobs:
- name: Get current date - name: Get current date
run: echo "curr_date=$(date --utc +%Y-%m-%dT%H:%M:%SZ)" >> $GITHUB_ENV run: echo "curr_date=$(date --utc +%Y-%m-%dT%H:%M:%SZ)" >> $GITHUB_ENV
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v3
- name: Login to Quay - name: Login to Quay
uses: docker/login-action@v2 uses: docker/login-action@v3
with: with:
registry: quay.io registry: quay.io
username: ${{ secrets.REGISTRY_USERNAME }} username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }} password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Login to ghcr.io - name: Login to ghcr.io
uses: docker/login-action@v2 uses: docker/login-action@v3
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v5
- name: Modify Dockerfile - name: Modify Dockerfile
run: | run: |
sed -i -e "s|FROM scratch|FROM ${{ matrix.build.fromImage }}|g" Dockerfile sed -i -e "s|FROM scratch|FROM ${{ matrix.build.fromImage }}|g" Dockerfile
- name: Build and push ${{github.event.release.tag_name }} - name: Build images ${{github.event.release.tag_name }}
id: docker_build_release id: docker_build_release
uses: docker/build-push-action@v4 uses: docker/build-push-action@v6
if: ${{ github.event.release.tag_name != '' }} if: ${{ github.event.release.tag_name != '' }}
with: with:
context: . context: .
pull: true pull: true
push: true push: true
tags: quay.io/bakito/adguardhome-sync:${{ matrix.build.tagPrefix }}latest,quay.io/bakito/adguardhome-sync:${{ matrix.build.tagPrefix }}${{ github.event.release.tag_name }},ghcr.io/bakito/adguardhome-sync:${{ matrix.build.tagPrefix }}latest,ghcr.io/bakito/adguardhome-sync:${{ matrix.build.tagPrefix }}${{ github.event.release.tag_name }} tags: quay.io/bakito/adguardhome-sync:${{ matrix.build.tagPrefix }}latest,quay.io/bakito/adguardhome-sync:${{ matrix.build.tagPrefix }}${{ github.event.release.tag_name }},ghcr.io/bakito/adguardhome-sync:${{ matrix.build.tagPrefix }}latest,ghcr.io/bakito/adguardhome-sync:${{ matrix.build.tagPrefix }}${{ github.event.release.tag_name }}
platforms: linux/amd64,linux/arm64,linux/arm/v7 platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/arm64,linux/ppc64le
provenance: false
build-args: | build-args: |
VERSION=${{ github.event.release.tag_name }} VERSION=${{ github.event.release.tag_name }}
BUILD=${{ env.curr_date }} BUILD=${{ env.curr_date }}
- name: Build and push main - name: Check for commits in the last 24 hours
id: docker_build_main run: echo "NEW_COMMIT_COUNT=$(git log --oneline --since '24 hours ago' | wc -l)" >> $GITHUB_ENV
uses: docker/build-push-action@v4
if: ${{ github.event.release.tag_name == '' }} if: ${{ github.event.release.tag_name == '' }}
- name: Build images
id: docker_build_main
uses: docker/build-push-action@v6
if: ${{ github.event.release.tag_name == '' && env.NEW_COMMIT_COUNT > 0 }}
with: with:
context: . context: .
pull: true pull: true
push: true push: ${{ github.ref == 'refs/heads/main' }}
tags: quay.io/bakito/adguardhome-sync:${{ matrix.build.tagPrefix }}main,ghcr.io/bakito/adguardhome-sync:${{ matrix.build.tagPrefix }}main tags: quay.io/bakito/adguardhome-sync:${{ matrix.build.tagPrefix }}main,ghcr.io/bakito/adguardhome-sync:${{ matrix.build.tagPrefix }}main
platforms: linux/amd64,linux/arm64,linux/arm/v7 platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/arm64,linux/ppc64le
provenance: false
build-args: | build-args: |
VERSION=main VERSION=main
BUILD=${{ env.curr_date }} BUILD=${{ env.curr_date }}
- name: Image digest - name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }} run: echo ${{ steps.docker_build.outputs.digest }}

View File

@@ -8,12 +8,26 @@ on:
branches: branches:
- main - main
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs: jobs:
e2e: e2e:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy:
matrix:
build:
- mode: env
protocol: https
- mode: file
protocol: http
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v5
- name: Setup jq
uses: dcarbone/install-jq-action@v3
- name: Install kind with registry - name: Install kind with registry
uses: bakito/kind-with-registry-action@main uses: bakito/kind-with-registry-action@main
@@ -22,12 +36,20 @@ jobs:
run: ./testdata/e2e/bin/build-image.sh run: ./testdata/e2e/bin/build-image.sh
- name: Install Helm Chart - name: Install Helm Chart
run: ./testdata/e2e/bin/install-chart.sh run: ./testdata/e2e/bin/install-chart.sh ${{ matrix.build.mode }}
- name: Wait for pod to start
run: ./testdata/e2e/bin/wait-for-start.sh ${{ matrix.build.protocol }}
- name: Show origin pre Logs
run: ./testdata/e2e/bin/show-origin-logs.sh pre
- name: Wait for sync to finish - name: Wait for sync to finish
run: ./testdata/e2e/bin/wait-for-sync.sh run: ./testdata/e2e/bin/wait-for-sync.sh ${{ matrix.build.protocol }}
- name: Show origin Logs - name: Show origin post Logs
run: ./testdata/e2e/bin/show-origin-logs.sh run: ./testdata/e2e/bin/show-origin-logs.sh post
- name: Show Replica Logs - name: Show Replica Logs
run: ./testdata/e2e/bin/show-replica-logs.sh run: ./testdata/e2e/bin/show-replica-logs.sh
- name: Show Sync Logs - name: Show Sync Logs
run: ./testdata/e2e/bin/show-sync-logs.sh run: ./testdata/e2e/bin/show-sync-logs.sh
- name: Show Sync Metrics
run: ./testdata/e2e/bin/show-sync-metrics.sh ${{ matrix.build.protocol }}
- name: Read latest replica config
run: ./testdata/e2e/bin/read-latest-replica-config.sh

View File

@@ -6,39 +6,52 @@ on:
pull_request: pull_request:
branches: [ main ] branches: [ main ]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs: jobs:
golangci: golangci:
name: lint name: lint
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v5
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v3 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@v3 run: make lint
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@v3 uses: actions/checkout@v5
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v3 uses: actions/setup-go@v6
with: with:
go-version-file: "go.mod" go-version-file: "go.mod"
- name: Model
run: make model
- name: Test - name: Test
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
@@ -48,15 +61,12 @@ jobs:
steps: steps:
- name: Check out code into the Go module directory - name: Check out code into the Go module directory
uses: actions/checkout@v3 uses: actions/checkout@v5
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v3 uses: actions/setup-go@v6
with: with:
go-version-file: "go.mod" go-version-file: "go.mod"
- name: Run GoReleaser - name: Run GoReleaser
uses: goreleaser/goreleaser-action@v4 run: make test-release
with:
version: latest
args: --skip-publish --snapshot --rm-dist

17
.github/workflows/stale.yml vendored Normal file
View File

@@ -0,0 +1,17 @@
name: Mark stale issues and pull requests
on:
schedule:
- cron: "0 0 * * *"
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v10
with:
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-pr-message: 'This pull request has been inactive for 60 days. If the pull request is still relevant please comment to re-activate the pull request. If no action is taken within 7 days, the pull request will be marked closed.'

16
.github/workflows/virustotal.yaml vendored Normal file
View 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}}

10
.gitignore vendored
View File

@@ -1,9 +1,15 @@
.vscode
.idea .idea
coverage.out .fleet
.run
coverage.out*
dist dist
adguardhome-sync adguardhome-sync
main main
.adguardhome-sync.yaml .adguardhome-sync.yaml
tmp tmp
bin bin
config.yaml !testdata/e2e/bin
config*.yaml
*.log
wiki

215
.golangci.yaml Normal file
View 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

View File

@@ -1,39 +0,0 @@
run:
timeout: 5m
linters:
enable:
- asciicheck
- bodyclose
- depguard
- dogsled
- durationcheck
- errcheck
- errorlint
- exportloopref
- gci
- gofmt
- gofumpt
- goimports
- gosec
- gosimple
- govet
- importas
- ineffassign
- megacheck
- misspell
- nakedret
- nolintlint
- revive
- staticcheck
- unconvert
- unparam
- unused
linters-settings:
gosec:
# Exclude generated files
exclude-generated: true
gofmt:
# simplify code: gofmt with `-s` option, true by default
simplify: true

View File

@@ -1,3 +1,4 @@
version: 2
# This is an example goreleaser.yaml file with some sane defaults. # This is an example goreleaser.yaml file with some sane defaults.
# Make sure to check the documentation at http://goreleaser.com # Make sure to check the documentation at http://goreleaser.com
builds: builds:
@@ -9,6 +10,7 @@ builds:
- linux - linux
- windows - windows
- darwin - darwin
- openbsd
goarch: goarch:
- 386 - 386
- amd64 - amd64
@@ -27,19 +29,24 @@ 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 ]]; 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:
name_template: "{{ .Tag }}-next" version_template: "{{ .Tag }}-next"
changelog: changelog:
sort: asc sort: asc
filters: filters:
exclude: exclude:
- '^docs:' - '^docs:'
- '^test:' - '^test:'
- '^chore'
release: release:
prerelease: auto prerelease: auto

3
.oapi-codegen.yaml Normal file
View File

@@ -0,0 +1,3 @@
output-options:
client-type-name: AdguardHomeClient
response-type-suffix: Resp

92
.toolbox.mk Normal file
View File

@@ -0,0 +1,92 @@
## toolbox - start
## Generated with https://github.com/bakito/toolbox
## Current working directory
TB_LOCALDIR ?= $(shell which cygpath > /dev/null 2>&1 && cygpath -m $$(pwd) || pwd)
## Location to install dependencies to
TB_LOCALBIN ?= $(TB_LOCALDIR)/bin
$(TB_LOCALBIN):
if [ ! -e $(TB_LOCALBIN) ]; then mkdir -p $(TB_LOCALBIN); fi
# Helper functions
STRIP_V = $(patsubst v%,%,$(1))
## Tool Binaries
TB_CONTROLLER_GEN ?= $(TB_LOCALBIN)/controller-gen
TB_GINKGO ?= $(TB_LOCALBIN)/ginkgo
TB_GOLANGCI_LINT ?= $(TB_LOCALBIN)/golangci-lint
TB_GORELEASER ?= $(TB_LOCALBIN)/goreleaser
TB_MOCKGEN ?= $(TB_LOCALBIN)/mockgen
TB_OAPI_CODEGEN ?= $(TB_LOCALBIN)/oapi-codegen
TB_SEMVER ?= $(TB_LOCALBIN)/semver
## Tool Versions
# renovate: packageName=github.com/kubernetes-sigs/controller-tools
TB_CONTROLLER_GEN_VERSION ?= v0.19.0
# renovate: packageName=github.com/golangci/golangci-lint/v2
TB_GOLANGCI_LINT_VERSION ?= v2.4.0
TB_GOLANGCI_LINT_VERSION_NUM ?= $(call STRIP_V,$(TB_GOLANGCI_LINT_VERSION))
# renovate: packageName=github.com/goreleaser/goreleaser/v2
TB_GORELEASER_VERSION ?= v2.12.0
TB_GORELEASER_VERSION_NUM ?= $(call STRIP_V,$(TB_GORELEASER_VERSION))
# renovate: packageName=github.com/uber-go/mock
TB_MOCKGEN_VERSION ?= v0.6.0
# renovate: packageName=github.com/oapi-codegen/oapi-codegen/v2
TB_OAPI_CODEGEN_VERSION ?= v2.5.0
# renovate: packageName=github.com/bakito/semver
TB_SEMVER_VERSION ?= v1.1.7
TB_SEMVER_VERSION_NUM ?= $(call STRIP_V,$(TB_SEMVER_VERSION))
## Tool Installer
.PHONY: tb.controller-gen
tb.controller-gen: ## Download controller-gen locally if necessary.
@test -s $(TB_CONTROLLER_GEN) || \
GOBIN=$(TB_LOCALBIN) go install sigs.k8s.io/controller-tools/cmd/controller-gen@$(TB_CONTROLLER_GEN_VERSION)
.PHONY: tb.ginkgo
tb.ginkgo: ## Download ginkgo locally if necessary.
@test -s $(TB_GINKGO) || \
GOBIN=$(TB_LOCALBIN) go install github.com/onsi/ginkgo/v2/ginkgo
.PHONY: tb.golangci-lint
tb.golangci-lint: ## Download golangci-lint locally if necessary.
@test -s $(TB_GOLANGCI_LINT) && $(TB_GOLANGCI_LINT) --version | grep -q $(TB_GOLANGCI_LINT_VERSION_NUM) || \
GOBIN=$(TB_LOCALBIN) go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@$(TB_GOLANGCI_LINT_VERSION)
.PHONY: tb.goreleaser
tb.goreleaser: ## Download goreleaser locally if necessary.
@test -s $(TB_GORELEASER) && $(TB_GORELEASER) --version | grep -q $(TB_GORELEASER_VERSION_NUM) || \
GOBIN=$(TB_LOCALBIN) go install github.com/goreleaser/goreleaser/v2@$(TB_GORELEASER_VERSION)
.PHONY: tb.mockgen
tb.mockgen: ## Download mockgen locally if necessary.
@test -s $(TB_MOCKGEN) || \
GOBIN=$(TB_LOCALBIN) go install go.uber.org/mock/mockgen@$(TB_MOCKGEN_VERSION)
.PHONY: tb.oapi-codegen
tb.oapi-codegen: ## Download oapi-codegen locally if necessary.
@test -s $(TB_OAPI_CODEGEN) || \
GOBIN=$(TB_LOCALBIN) go install github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@$(TB_OAPI_CODEGEN_VERSION)
.PHONY: tb.semver
tb.semver: ## Download semver locally if necessary.
@test -s $(TB_SEMVER) && $(TB_SEMVER) -version | grep -q $(TB_SEMVER_VERSION_NUM) || \
GOBIN=$(TB_LOCALBIN) go install github.com/bakito/semver@$(TB_SEMVER_VERSION)
## Reset Tools
.PHONY: tb.reset
tb.reset:
@rm -f \
$(TB_CONTROLLER_GEN) \
$(TB_GINKGO) \
$(TB_GOLANGCI_LINT) \
$(TB_GORELEASER) \
$(TB_MOCKGEN) \
$(TB_OAPI_CODEGEN) \
$(TB_SEMVER)
## Update Tools
.PHONY: tb.update
tb.update: tb.reset
toolbox makefile --renovate -f $(TB_LOCALDIR)/Makefile \
sigs.k8s.io/controller-tools/cmd/controller-gen@github.com/kubernetes-sigs/controller-tools \
github.com/golangci/golangci-lint/v2/cmd/golangci-lint?--version \
github.com/goreleaser/goreleaser/v2?--version \
go.uber.org/mock/mockgen@github.com/uber-go/mock \
github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen \
github.com/bakito/semver?-version
## toolbox - end

View File

@@ -1,8 +1,8 @@
FROM golang:1.20 as builder FROM golang:1.25-alpine AS builder
WORKDIR /go/src/app WORKDIR /go/src/app
RUN apt-get update && apt-get install -y upx RUN apk update && apk add upx ca-certificates tzdata
ARG VERSION=main ARG VERSION=main
ARG BUILD="N/A" ARG BUILD="N/A"
@@ -13,8 +13,10 @@ ENV GO111MODULE=on \
COPY . /go/src/app/ COPY . /go/src/app/
RUN go build -a -installsuffix cgo -ldflags="-w -s -X github.com/bakito/adguardhome-sync/version.Version=${VERSION} -X github.com/bakito/adguardhome-sync/version.Build=${BUILD}" -o adguardhome-sync . \
&& upx -q adguardhome-sync RUN go build -a -installsuffix cgo -ldflags="-w -s -X github.com/bakito/adguardhome-sync/version.Version=${VERSION} -X github.com/bakito/adguardhome-sync/version.Build=${BUILD}" -o adguardhome-sync .
RUN go version && upx -q adguardhome-sync
# application image # application image
FROM scratch FROM scratch
@@ -24,6 +26,7 @@ LABEL maintainer="bakito <github@bakito.ch>"
EXPOSE 8080 EXPOSE 8080
ENTRYPOINT ["/opt/go/adguardhome-sync"] ENTRYPOINT ["/opt/go/adguardhome-sync"]
CMD ["run", "--config", "/config/adguardhome-sync.yaml"] CMD ["run", "--config", "/config/adguardhome-sync.yaml"]
COPY --from=builder /go/src/app/adguardhome-sync /opt/go/adguardhome-sync
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=builder /usr/share/zoneinfo/ /usr/share/zoneinfo/
COPY --from=builder /go/src/app/adguardhome-sync /opt/go/adguardhome-sync
USER 1001 USER 1001

125
Makefile
View File

@@ -1,102 +1,59 @@
# Include toolbox tasks
include ./.toolbox.mk
# Run go lint against code # Run go lint against code
lint: golangci-lint lint: tb.golangci-lint
$(GOLANGCI_LINT) run --fix $(TB_GOLANGCI_LINT) run --fix
# Run go mod tidy # Run go mod tidy
tidy: tidy:
go mod tidy go mod tidy
generate: 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
$(DEEPCOPY_GEN) -h ./tmp/deepcopy-gen-boilerplate.go.txt -i ./pkg/types $(TB_CONTROLLER_GEN) paths=./internal/types object
.PHONY: docs
docs:
go run cmd/docs/main.go
# Run tests # Run tests
test: generate 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 test-ci: mocks tidy tb.ginkgo
go test ./... -coverprofile=coverage.out $(TB_GINKGO) --cover --coverprofile coverage.out.tmp ./...
cat coverage.out.tmp | grep -v "_generated.go" > coverage.out
go tool cover -func=coverage.out go tool cover -func=coverage.out
mocks: mockgen mocks: tb.mockgen
$(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 internal/mocks/flags/mock.go github.com/bakito/adguardhome-sync/internal/config Flags
release: semver goreleaser release: tb.semver tb.goreleaser
@version=$$($(LOCALBIN)/semver); @version=$$($(TB_SEMVER)); \
git tag -s $$version -m"Release $$version" git tag -s $$version -m"Release $$version"
$(GORELEASER) --rm-dist $(TB_GORELEASER) --clean --parallelism 2
test-release: goreleaser test-release: tb.goreleaser
$(GORELEASER) --skip-publish --snapshot --rm-dist $(TB_GORELEASER) --skip=publish --snapshot --clean --parallelism 2
## toolbox - start
## Current working directory
LOCALDIR ?= $(shell which cygpath > /dev/null 2>&1 && cygpath -m $$(pwd) || pwd)
## Location to install dependencies to
LOCALBIN ?= $(LOCALDIR)/bin
$(LOCALBIN):
mkdir -p $(LOCALBIN)
## Tool Binaries
SEMVER ?= $(LOCALBIN)/semver
MOCKGEN ?= $(LOCALBIN)/mockgen
GOLANGCI_LINT ?= $(LOCALBIN)/golangci-lint
GORELEASER ?= $(LOCALBIN)/goreleaser
DEEPCOPY_GEN ?= $(LOCALBIN)/deepcopy-gen
## Tool Versions
SEMVER_VERSION ?= v1.1.3
MOCKGEN_VERSION ?= v1.6.0
GOLANGCI_LINT_VERSION ?= v1.51.1
GORELEASER_VERSION ?= v1.15.1
DEEPCOPY_GEN_VERSION ?= v0.26.1
## Tool Installer
.PHONY: semver
semver: $(SEMVER) ## Download semver locally if necessary.
$(SEMVER): $(LOCALBIN)
test -s $(LOCALBIN)/semver || GOBIN=$(LOCALBIN) go install github.com/bakito/semver@$(SEMVER_VERSION)
.PHONY: mockgen
mockgen: $(MOCKGEN) ## Download mockgen locally if necessary.
$(MOCKGEN): $(LOCALBIN)
test -s $(LOCALBIN)/mockgen || GOBIN=$(LOCALBIN) go install github.com/golang/mock/mockgen@$(MOCKGEN_VERSION)
.PHONY: golangci-lint
golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary.
$(GOLANGCI_LINT): $(LOCALBIN)
test -s $(LOCALBIN)/golangci-lint || GOBIN=$(LOCALBIN) go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION)
.PHONY: goreleaser
goreleaser: $(GORELEASER) ## Download goreleaser locally if necessary.
$(GORELEASER): $(LOCALBIN)
test -s $(LOCALBIN)/goreleaser || GOBIN=$(LOCALBIN) go install github.com/goreleaser/goreleaser@$(GORELEASER_VERSION)
.PHONY: deepcopy-gen
deepcopy-gen: $(DEEPCOPY_GEN) ## Download deepcopy-gen locally if necessary.
$(DEEPCOPY_GEN): $(LOCALBIN)
test -s $(LOCALBIN)/deepcopy-gen || GOBIN=$(LOCALBIN) go install k8s.io/code-generator/cmd/deepcopy-gen@$(DEEPCOPY_GEN_VERSION)
## Update Tools
.PHONY: update-toolbox-tools
update-toolbox-tools:
@rm -f \
$(LOCALBIN)/semver \
$(LOCALBIN)/mockgen \
$(LOCALBIN)/golangci-lint \
$(LOCALBIN)/goreleaser \
$(LOCALBIN)/deepcopy-gen
toolbox makefile -f $(LOCALDIR)/Makefile \
github.com/bakito/semver \
github.com/golang/mock/mockgen \
github.com/golangci/golangci-lint/cmd/golangci-lint \
github.com/goreleaser/goreleaser \
k8s.io/code-generator/cmd/deepcopy-gen@github.com/kubernetes/code-generator
## toolbox - end
start-replica: start-replica:
docker run --pull always --name adguardhome-replica -p 9091:3000 --rm adguard/adguardhome:v0.107.23 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 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
copy-replica-config:
docker cp adguardhome-replica:/opt/adguardhome/conf/AdGuardHome.yaml tmp/AdGuardHome.yaml
start-replica2: start-replica2:
docker run --pull always --name adguardhome-replica2 -p 9093:3000 --rm adguard/adguardhome:v0.107.23 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-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
check_defined = \ check_defined = \
@@ -116,3 +73,19 @@ kind-create:
kind-test: kind-test:
@./testdata/e2e/bin/install-chart.sh @./testdata/e2e/bin/install-chart.sh
# renovate: packageName=AdguardTeam/AdGuardHome
ADGUARD_HOME_VERSION ?= v0.107.66
model: tb.oapi-codegen
@mkdir -p tmp
go run cmd/openapi/main.go $(ADGUARD_HOME_VERSION)
$(TB_OAPI_CODEGEN) -package model -generate types,client -config .oapi-codegen.yaml tmp/schema.yaml > internal/client/model/model_generated.go
model-diff:
go run cmd/openapi/main.go $(ADGUARD_HOME_VERSION)
go run cmd/openapi/main.go
diff tmp/schema.yaml tmp/schema-master.yaml
zellij:
zellij -l ./testdata/test-layout.kdl

275
README.md
View File

@@ -1,11 +1,20 @@
[![Go](https://github.com/bakito/adguardhome-sync/actions/workflows/go.yml/badge.svg)](https://github.com/bakito/adguardhome-sync/actions/workflows/go.yml) [![Go](https://github.com/bakito/adguardhome-sync/actions/workflows/go.yml/badge.svg)](https://github.com/bakito/adguardhome-sync/actions/workflows/go.yml)
[![e2e tests](https://github.com/bakito/adguardhome-sync/actions/workflows/e2e.yaml/badge.svg)](https://github.com/bakito/adguardhome-sync/actions/workflows/e2e.yaml)
[![Go Report Card](https://goreportcard.com/badge/github.com/bakito/adguardhome-sync)](https://goreportcard.com/report/github.com/bakito/adguardhome-sync) [![Go Report Card](https://goreportcard.com/badge/github.com/bakito/adguardhome-sync)](https://goreportcard.com/report/github.com/bakito/adguardhome-sync)
[![Coverage Status](https://coveralls.io/repos/github/bakito/adguardhome-sync/badge.svg?branch=main&service=github)](https://coveralls.io/github/bakito/adguardhome-sync?branch=main) [![Coverage Status](https://coveralls.io/repos/github/bakito/adguardhome-sync/badge.svg?branch=main&service=github)](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.
## FAQ & Deprecations
Please check the wiki
for [FAQ](https://github.com/bakito/adguardhome-sync/wiki/FAQ)
and [Deprecations](https://github.com/bakito/adguardhome-sync/wiki/Deprecations).
## Current sync features ## Current sync features
- General Settings - General Settings
@@ -15,6 +24,7 @@ Synchronize [AdGuardHome](https://github.com/AdguardTeam/AdGuardHome) config to
- Clients - Clients
- DNS Config - DNS Config
- DHCP Config - DHCP Config
- Theme
By default, all features are enabled. Single features can be disabled in the config. By default, all features are enabled. Single features can be disabled in the config.
@@ -35,26 +45,83 @@ 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
Some instances of AdGuard Home do not support basic authentication. For instance, many routers with built-in Adguard
Home support do not. If this is the case, a valid cookie may be provided instead. If the router protects the AdGuard
instance behind its own authentication, the cookie from an authenticated request may allow the sync to succeed.
- This has been tested successfully against GL.Inet routers with AdGuard Home.
- Note: due to the short validity of cookies, this approach is likely only suitable for one-time syncs
## Run Linux/Mac ## Run Linux/Mac
```bash ```bash
export LOG_LEVEL=info
export ORIGIN_URL=https://192.168.1.2:3000 export ORIGIN_URL=https://192.168.1.2:3000
export ORIGIN_USERNAME=username export ORIGIN_USERNAME=username
export ORIGIN_PASSWORD=password export ORIGIN_PASSWORD=password
export REPLICA_URL=http://192.168.1.3 # export ORIGIN_COOKIE=Origin-Cookie-Name=CCCOOOKKKIIIEEE
export REPLICA_USERNAME=username export REPLICA1_URL=http://192.168.1.3
export REPLICA_PASSWORD=password export REPLICA1_USERNAME=username
export REPLICA1_PASSWORD=password
# export REPLICA_COOKIE=Replica-Cookie-Name=CCCOOOKKKIIIEEE
# run once # run once
adguardhome-sync run adguardhome-sync run
# run as daemon # run as daemon
adguardhome-sync run --cron "*/10 * * * *" adguardhome-sync run --cron "0 */2 * * *"
``` ```
### Run as Linux Service via Systemd
> Verified on Ubuntu Linux 24.04
Assume you have downloaded the the `adguardhome-sync` binary to `/opt/adguardhome-sync`.
Create systemd service file `/opt/adguardhome-sync/adguardhome-sync.service`:
```
[Unit]
Description = AdGuardHome Sync
After = network.target
[Service]
ExecStart = /opt/adguardhome-sync/adguardhome-sync --config /opt/adguardhome-sync/adguardhome-sync.yaml run
[Install]
WantedBy = multi-user.target
```
Create a configuration file `/opt/adguardhome-sync/adguardhome-sync.yaml`, please follow [Config file](#config-file-1)
section below for details.
Install and enable service:
```bash
sudo cp /opt/adguardhome-sync/adguardhome-sync.service /etc/systemd/system/
sudo systemctl enable adguardhome-sync.service
sudo systemctl start adguardhome-sync.service
```
Then you can check the status:
```bash
sudo systemctl status adguardhome-sync.service
```
If web UI has been enabled in configuration (default port is 8080), can also check the status via
`http://<server-IP>:8080`
## Run Windows ## Run Windows
```bash ```bash
@@ -69,20 +136,21 @@ REM set LOG_LEVEL=error
set ORIGIN_URL=http://192.168.1.2:3000 set ORIGIN_URL=http://192.168.1.2:3000
set ORIGIN_USERNAME=username set ORIGIN_USERNAME=username
set ORIGIN_PASSWORD=password set ORIGIN_PASSWORD=password
# set ORIGIN_COOKIE=Origin-Cookie-Name=CCCOOOKKKIIIEEE
set REPLICA_URL=http://192.168.2.2:3000 set REPLICA1_URL=http://192.168.2.2:3000
set REPLICA_USERNAME=username set REPLICA1_USERNAME=username
set REPLICA_PASSWORD=password set REPLICA1_PASSWORD=password
# 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
# run as daemon # run as daemon
adguardhome-sync run --cron "*/10 * * * *" adguardhome-sync run --cron "0 */2 * * *"
``` ```
## docker cli ## docker cli
@@ -115,47 +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 |
ORIGIN_URL: 'https://192.168.1.2:3000' | ORIGIN_COOKIE (string) | string | Adguardhome cookie |
ORIGIN_USERNAME: 'username' | ORIGIN_REQUEST_HEADERS (map) | map | Request Headers 'key1:value1,key2:value2' |
ORIGIN_PASSWORD: 'password' | ORIGIN_INSECURE_SKIP_VERIFY (bool) | bool | Skip TLS verification |
REPLICA_URL: 'http://192.168.1.3' | ORIGIN_AUTO_SETUP (bool) | bool | Automatically setup the instance if it is not initialized |
REPLICA_USERNAME: 'username' | ORIGIN_INTERFACE_NAME (string) | string | Network interface name |
REPLICA_PASSWORD: 'password' | ORIGIN_DHCP_SERVER_ENABLED (bool) | bool | Enable DHCP server |
REPLICA1_URL: 'http://192.168.1.4' | REPLICA#_URL (string) | string | URL of adguardhome instance |
REPLICA1_USERNAME: 'username' | REPLICA#_WEB_URL (string) | string | Web URL of adguardhome instance |
REPLICA1_PASSWORD: 'password' | REPLICA#_API_PATH (string) | string | API Path |
REPLICA1_APIPATH: '/some/path/control' | REPLICA#_USERNAME (string) | string | Adguardhome username |
# REPLICA1_AUTOSETUP: true # if true, AdGuardHome is automatically initialized. | REPLICA#_PASSWORD (string) | string | Adguardhome password |
# REPLICA1_INTERFACENAME: 'ens18' # use custom dhcp interface name | REPLICA#_COOKIE (string) | string | Adguardhome cookie |
CRON: '*/10 * * * *' # run every 10 minutes | REPLICA#_REQUEST_HEADERS (map) | map | Request Headers 'key1:value1,key2:value2' |
RUNONSTART: true | REPLICA#_INSECURE_SKIP_VERIFY (bool) | bool | Skip TLS verification |
# Configure sync features; by default all features are enabled. | REPLICA#_AUTO_SETUP (bool) | bool | Automatically setup the instance if it is not initialized |
# FEATURES_GENERALSETTINGS: true | REPLICA#_INTERFACE_NAME (string) | string | Network interface name |
# FEATURES_QUERYLOGCONFIG: true | REPLICA#_DHCP_SERVER_ENABLED (bool) | bool | Enable DHCP server |
# FEATURES_STATSCONFIG: true | CRON (string) | string | Cron expression for the sync interval |
# FEATURES_CLIENTSETTINGS: true | RUN_ON_START (bool) | bool | Run the sync on startup |
# FEATURES_SERVICES: true | PRINT_CONFIG_ONLY (bool) | bool | Print current config only and stop the application |
# FEATURES_FILTERS: true | CONTINUE_ON_ERROR (bool) | bool | Continue sync on errors |
# FEATURES_DHCP_SERVERCONFIG: true | API_PORT (int) | int | API port (API is disabled if port is set to 0) |
# FEATURES_DHCP_STATICLEASES: true | API_USERNAME (string) | string | API username |
# FEATURES_DNS_SERVERCONFIG: true | API_PASSWORD (string) | string | API password |
# FEATURES_DNS_ACCESSLISTS: true | API_DARK_MODE (bool) | bool | API dark mode |
# FEATURES_DNS_REWRITES: true | API_METRICS_ENABLED (bool) | bool | Enable metrics |
ports: | API_METRICS_SCRAPE_INTERVAL (int64) | int64 | Interval for metrics scraping |
- 8080:8080 | API_METRICS_QUERY_LOG_LIMIT (int) | int | Metrics log query limit |
restart: unless-stopped | 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 -->
### Unraid
⚠️ Disclaimer: Tere exists an unraid tepmlate for this application. This template is not managed by this project.
Also, as unraid is not known to me, I can not give any support on unraind templates.
Note when running the Docker container in Unraid please remove unneeded env variables if don't needed.
If replica2 isn't used this can cause sync errors.
### Config file ### Config file
@@ -163,11 +254,14 @@ location: $HOME/.adguardhome-sync.yaml
```yaml ```yaml
# cron expression to run in daemon mode. (default; "" = runs only once) # cron expression to run in daemon mode. (default; "" = runs only once)
cron: "*/10 * * * *" cron: "0 */2 * * *"
# runs the synchronisation on startup # runs the synchronisation on startup
runOnStart: true runOnStart: true
# If enabled, the synchronisation task will not fail on single errors, but will log the errors and continue
continueOnError: false
origin: origin:
# url of the origin instance # url of the origin instance
url: https://192.168.1.2:3000 url: https://192.168.1.2:3000
@@ -175,32 +269,50 @@ origin:
# insecureSkipVerify: true # disable tls check # insecureSkipVerify: true # disable tls check
username: username username: username
password: password password: password
# cookie: Origin-Cookie-Name=CCCOOOKKKIIIEEE
# requestHeaders: # Additional request headers
# AAA: bbb
# replica instance (optional, if only one) # replicas instances
replica:
# url of the replica instance
url: http://192.168.1.3
username: username
password: password
# replicas instances (optional, if more than one)
replicas: replicas:
# url of the replica instance # url of the replica instance
- url: http://192.168.1.3 - url: http://192.168.1.3
username: username username: username
password: password password: password
# cookie: Replica1-Cookie-Name=CCCOOOKKKIIIEEE
- url: http://192.168.1.4 - url: http://192.168.1.4
username: username username: username
password: password password: password
# autoSetup: true # if true, AdGuardHome is automatically initialized. # cookie: Replica2-Cookie-Name=CCCOOOKKKIIIEEE
# autoSetup: true # if true, AdGuardHome is automatically initialized.
# 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:
# Port, default 8080 # Port, default 8080
port: 8080 port: 8080
# if username and password are defined, basic auth is applied to the sync API # if username and password are defined, basic auth is applied to the sync API
username: username username: username
password: password password: password
# enable api dark mode
darkMode: true
# enable metrics on path '/metrics' (api port must be != 0)
# metrics:
# enabled: true
# scrapeInterval: 30s
# queryLogLimit: 10000
# enable tls for the api server
# tls:
# # the directory of the provided tls certs
# certDir: /path/to/certs
# # the name of the cert file (default: tls.crt)
# certName: foo.crt
# # the name of the key file (default: tls.key)
# keyName: bar.key
# Configure sync features; by default all features are enabled. # Configure sync features; by default all features are enabled.
features: features:
@@ -219,9 +331,32 @@ 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
![show-disabled-ports](https://github.com/user-attachments/assets/1df5f352-37a2-4508-82ec-7f270087d0b4)
And then set the port of your choice for the Web interface
![web-interface-port](https://github.com/user-attachments/assets/286ed030-4831-4f49-8b29-53e8802129c3)
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`
The following log levels are supported (default: info) The following log levels are supported (default: info)
@@ -229,3 +364,13 @@ The following log levels are supported (default: info)
- info - info
- warn - warn
- error - error
## Log Format
Default log format is `console`.
It can be changed to `json` by setting the environment variable: `LOG_FORMAT=json`.
## Video Tutorials
- [Como replicar la configuración de tu servidor DNS Adguard automáticamente - Tu servidor Part #12](https://www.youtube.com/watch?v=1LPeu_JG064) (
Spanish) by [Jonatan Castro](https://github.com/jcastro)

96
cmd/docs/main.go Normal file
View 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)
}
}
}

106
cmd/openapi/main.go Normal file
View File

@@ -0,0 +1,106 @@
package main
import (
"context"
"fmt"
"io"
"log"
"net/http"
"os"
"gopkg.in/yaml.v3"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
func main() {
version := "master"
fileName := "schema-master.yaml"
if len(os.Args) > 1 {
version = os.Args[1]
fileName = "schema.yaml"
}
log.Printf("Patching schema version %s\n", version)
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),
http.NoBody,
)
if err != nil {
log.Fatal(err)
}
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
log.Fatalln(err)
}
defer func() { _ = resp.Body.Close() }()
data, err := io.ReadAll(resp.Body)
if err != nil {
log.Println(err)
return
}
schema := make(map[string]any)
err = yaml.Unmarshal(data, &schema)
if err != nil {
log.Println(err)
return
}
if requestBodies, ok, _ := unstructured.NestedMap(schema, "components", "requestBodies"); ok {
for k := range requestBodies {
_ = unstructured.SetNestedField(schema, k+"Body", "components", "requestBodies", k, "x-go-name")
}
}
if dnsInfo, ok, _ := unstructured.NestedMap(schema,
"paths", "/dns_info", "get", "responses", "200", "content", "application/json", "schema"); ok {
if allOf, ok, _ := unstructured.NestedSlice(dnsInfo, "allOf"); ok && len(allOf) == 2 {
delete(dnsInfo, "allOf")
//nolint:forcetypeassert
if err := unstructured.SetNestedMap(schema, allOf[0].(map[string]any),
"paths", "/dns_info", "get", "responses", "200", "content", "application/json", "schema"); err != nil {
log.Println(err)
return
}
}
}
correctEntries(schema)
addFakeTags(schema)
b, err := yaml.Marshal(&schema)
if err != nil {
log.Println(err)
return
}
log.Printf("Writing schema file tmp/%s", fileName)
err = os.WriteFile("tmp/"+fileName, b, 0o600)
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)
}
}

View File

@@ -3,67 +3,19 @@ package cmd
import ( import (
"fmt" "fmt"
"os" "os"
"regexp"
"strings"
"github.com/bakito/adguardhome-sync/pkg/log"
"github.com/bakito/adguardhome-sync/pkg/types"
"github.com/bakito/adguardhome-sync/version"
"github.com/mitchellh/go-homedir"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper"
)
const ( "github.com/bakito/adguardhome-sync/internal/log"
configCron = "cron" "github.com/bakito/adguardhome-sync/version"
configRunOnStart = "runOnStart"
configAPIPort = "api.port"
configAPIUsername = "api.username"
configAPIPassword = "api.password"
configAPIDarkMode = "api.darkMode"
configFeatureDHCPServerConfig = "features.dhcp.serverConfig"
configFeatureDHCPStaticLeases = "features.dhcp.staticLeases"
configFeatureDNServerConfig = "features.dns.serverConfig"
configFeatureDNSPAccessLists = "features.dns.accessLists"
configFeatureDNSRewrites = "features.dns.rewrites"
configFeatureGeneralSettings = "features.generalSettings"
configFeatureQueryLogConfig = "features.queryLogConfig"
configFeatureStatsConfig = "features.statsConfig"
configFeatureClientSettings = "features.clientSettings"
configFeatureServices = "features.services"
configFeatureFilters = "features.filters"
configOriginURL = "origin.url"
configOriginAPIPath = "origin.apiPath"
configOriginUsername = "origin.username"
configOriginPassword = "origin.password"
configOriginInsecureSkipVerify = "origin.insecureSkipVerify"
configReplicaURL = "replica.url"
configReplicaAPIPath = "replica.apiPath"
configReplicaUsername = "replica.username"
configReplicaPassword = "replica.password"
configReplicaInsecureSkipVerify = "replica.insecureSkipVerify"
configReplicaAutoSetup = "replica.autoSetup"
configReplicaInterfaceName = "replica.interfaceName"
envReplicasUsernameFormat = "REPLICA%s_USERNAME" // #nosec G101
envReplicasPasswordFormat = "REPLICA%s_PASSWORD" // #nosec G101
envReplicasAPIPathFormat = "REPLICA%s_APIPATH"
envReplicasInsecureSkipVerifyFormat = "REPLICA%s_INSECURESKIPVERIFY"
envReplicasAutoSetup = "REPLICA%s_AUTOSETUP"
envReplicasInterfaceName = "REPLICA%s_INTERFACWENAME"
) )
var ( var (
cfgFile string cfgFile string
logger = log.GetLogger("root") logger = log.GetLogger("root")
envReplicasURLPattern = regexp.MustCompile(`^REPLICA(\d+)_URL=(.*)`)
) )
// 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",
@@ -74,14 +26,12 @@ 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)
} }
} }
func init() { func init() {
cobra.OnInitialize(initConfig)
// Here you will define your flags and configuration settings. // Here you will define your flags and configuration settings.
// Cobra supports persistent flags, which, if defined here, // Cobra supports persistent flags, which, if defined here,
// will be global for your application. // will be global for your application.
@@ -92,66 +42,3 @@ func init() {
// when this action is called directly. // when this action is called directly.
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
} }
// initConfig reads in config file and ENV variables if set.
func initConfig() {
if cfgFile != "" {
// Use config file from the flag.
viper.SetConfigFile(cfgFile)
} else {
// Find home directory.
home, err := homedir.Dir()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
// Search config in home directory with name ".adguardhome-sync" (without extension).
viper.AddConfigPath(home)
viper.SetConfigName(".adguardhome-sync")
}
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_", ".", "_"))
viper.AutomaticEnv() // read in environment variables that match
// If a config file is found, read it in.
if err := viper.ReadInConfig(); err == nil {
logger.Info("Using config file:", viper.ConfigFileUsed())
} else if cfgFile != "" {
fmt.Println(err)
os.Exit(1)
}
}
func getConfig() (*types.Config, error) {
cfg := &types.Config{}
if err := viper.Unmarshal(cfg); err != nil {
return nil, err
}
if len(cfg.Replicas) == 0 {
cfg.Replicas = append(cfg.Replicas, collectEnvReplicas()...)
}
return cfg, nil
}
// Manually collect replicas from env.
func collectEnvReplicas() []types.AdGuardInstance {
var replicas []types.AdGuardInstance
for _, v := range os.Environ() {
if envReplicasURLPattern.MatchString(v) {
sm := envReplicasURLPattern.FindStringSubmatch(v)
re := types.AdGuardInstance{
URL: sm[2],
Username: os.Getenv(fmt.Sprintf(envReplicasUsernameFormat, sm[1])),
Password: os.Getenv(fmt.Sprintf(envReplicasPasswordFormat, sm[1])),
APIPath: os.Getenv(fmt.Sprintf(envReplicasAPIPathFormat, sm[1])),
InsecureSkipVerify: strings.EqualFold(os.Getenv(fmt.Sprintf(envReplicasInsecureSkipVerifyFormat, sm[1])), "true"),
AutoSetup: strings.EqualFold(os.Getenv(fmt.Sprintf(envReplicasAutoSetup, sm[1])), "true"),
InterfaceName: os.Getenv(fmt.Sprintf(envReplicasInterfaceName, sm[1])),
}
replicas = append(replicas, re)
}
}
return replicas
}

View File

@@ -1,61 +0,0 @@
package cmd
import (
"os"
"github.com/bakito/adguardhome-sync/pkg/types"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var envVars = []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",
}
var _ = Describe("Run", func() {
BeforeEach(func() {
for _, envVar := range envVars {
Ω(os.Unsetenv(envVar)).ShouldNot(HaveOccurred())
}
initConfig()
})
Context("getConfig", func() {
It("features should be true by default", func() {
cfg, err := getConfig()
Ω(err).ShouldNot(HaveOccurred())
verifyFeatures(cfg, true)
})
It("features should be false", func() {
for _, envVar := range envVars {
Ω(os.Setenv(envVar, "false")).ShouldNot(HaveOccurred())
}
cfg, err := getConfig()
Ω(err).ShouldNot(HaveOccurred())
verifyFeatures(cfg, false)
})
})
})
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))
}

View File

@@ -1,92 +1,93 @@
package cmd package cmd
import ( import (
"github.com/bakito/adguardhome-sync/pkg/log"
"github.com/bakito/adguardhome-sync/pkg/sync"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper"
"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, err := getConfig() cfg, err := config.Get(cfgFile, cmd.Flags())
if err != nil { if err != nil {
logger.Error(err) logger.Error(err)
return err return err
} }
return sync.Sync(cfg) if err := cfg.Init(); err != nil {
logger.Error(err)
return err
}
if cfg.PrintConfigOnly() {
if err := cfg.Print(); err != nil {
logger.Error(err)
return err
}
return nil
}
return sync.Sync(cfg.Get())
}, },
} }
func init() { func init() {
rootCmd.AddCommand(doCmd) rootCmd.AddCommand(doCmd)
doCmd.PersistentFlags().String("cron", "", "The cron expression to run in daemon mode") doCmd.PersistentFlags().String(config.FlagCron, "", "The cron expression to run in daemon mode")
_ = viper.BindPFlag(configCron, doCmd.PersistentFlags().Lookup("cron")) doCmd.PersistentFlags().Bool(config.FlagRunOnStart, true, "Run the sync job on start.")
doCmd.PersistentFlags().Bool("runOnStart", true, "Run the sync job on start.") doCmd.PersistentFlags().Bool(config.FlagPrintConfigOnly, false, "Prints the configuration only and exists. "+
_ = viper.BindPFlag(configRunOnStart, doCmd.PersistentFlags().Lookup("runOnStart")) "Can be used to debug the config E.g: when having authentication issues.")
doCmd.PersistentFlags().Int("api-port", 8080, "Sync API Port, the API endpoint will be started to enable remote triggering; if 0 port API is disabled.") doCmd.PersistentFlags().Bool(config.FlagContinueOnError, false, "If enabled, the synchronization task "+
_ = viper.BindPFlag(configAPIPort, doCmd.PersistentFlags().Lookup("api-port")) "will not fail on single errors, but will log the errors and continue.")
doCmd.PersistentFlags().String("api-username", "", "Sync API username")
_ = viper.BindPFlag(configAPIUsername, doCmd.PersistentFlags().Lookup("api-username"))
doCmd.PersistentFlags().String("api-password", "", "Sync API password")
_ = viper.BindPFlag(configAPIPassword, doCmd.PersistentFlags().Lookup("api-password"))
doCmd.PersistentFlags().String("api-darkMode", "", "API UI in dark mode")
_ = viper.BindPFlag(configAPIDarkMode, doCmd.PersistentFlags().Lookup("api-darkMode"))
doCmd.PersistentFlags().Bool("feature-dhcp-server-config", true, "Enable DHCP server config feature") doCmd.PersistentFlags().
_ = viper.BindPFlag(configFeatureDHCPServerConfig, doCmd.PersistentFlags().Lookup("feature-dhcp-server-config")) 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().Bool("feature-dhcp-static-leases", true, "Enable DHCP server static leases feature") doCmd.PersistentFlags().String(config.FlagAPIUsername, "", "Sync API username")
_ = viper.BindPFlag(configFeatureDHCPStaticLeases, doCmd.PersistentFlags().Lookup("feature-dhcp-static-leases")) doCmd.PersistentFlags().String(config.FlagAPIPassword, "", "Sync API password")
doCmd.PersistentFlags().String(config.FlagAPIDarkMode, "", "API UI in dark mode")
doCmd.PersistentFlags().Bool("feature-dns-server-config", true, "Enable DNS server config feature") doCmd.PersistentFlags().Bool(config.FlagFeatureDhcpServerConfig, true, "Enable DHCP server config feature")
_ = viper.BindPFlag(configFeatureDNServerConfig, doCmd.PersistentFlags().Lookup("feature-dns-server-config")) doCmd.PersistentFlags().Bool(config.FlagFeatureDhcpStaticLeases, true, "Enable DHCP server static leases feature")
doCmd.PersistentFlags().Bool("feature-dns-access-lists", true, "Enable DNS server access lists feature")
_ = viper.BindPFlag(configFeatureDNSPAccessLists, doCmd.PersistentFlags().Lookup("feature-dns-access-lists"))
doCmd.PersistentFlags().Bool("feature-dns-rewrites", true, "Enable DNS rewrites feature")
_ = viper.BindPFlag(configFeatureDNSRewrites, doCmd.PersistentFlags().Lookup("feature-dns-rewrites"))
doCmd.PersistentFlags().Bool("feature-general-settings", true, "Enable general settings feature")
_ = viper.BindPFlag(configFeatureGeneralSettings, doCmd.PersistentFlags().Lookup("feature-general-settings"))
_ = viper.BindPFlag("features.generalSettings", doCmd.PersistentFlags().Lookup("feature-general-settings"))
doCmd.PersistentFlags().Bool("feature-query-log-config", true, "Enable query log config feature")
_ = viper.BindPFlag(configFeatureQueryLogConfig, doCmd.PersistentFlags().Lookup("feature-query-log-config"))
doCmd.PersistentFlags().Bool("feature-stats-config", true, "Enable stats config feature")
_ = viper.BindPFlag(configFeatureStatsConfig, doCmd.PersistentFlags().Lookup("feature-stats-config"))
doCmd.PersistentFlags().Bool("feature-client-settings", true, "Enable client settings feature")
_ = viper.BindPFlag(configFeatureClientSettings, doCmd.PersistentFlags().Lookup("feature-client-settings"))
doCmd.PersistentFlags().Bool("feature-services", true, "Enable services sync feature")
_ = viper.BindPFlag(configFeatureServices, doCmd.PersistentFlags().Lookup("feature-services"))
doCmd.PersistentFlags().Bool("feature-filters", true, "Enable filters sync feature")
_ = viper.BindPFlag(configFeatureFilters, doCmd.PersistentFlags().Lookup("feature-filters"))
doCmd.PersistentFlags().String("origin-url", "", "Origin instance url") doCmd.PersistentFlags().Bool(config.FlagFeatureDNSServerConfig, true, "Enable DNS server config feature")
_ = viper.BindPFlag(configOriginURL, doCmd.PersistentFlags().Lookup("origin-url")) doCmd.PersistentFlags().Bool(config.FlagFeatureDNSAccessLists, true, "Enable DNS server access lists feature")
doCmd.PersistentFlags().String("origin-api-path", "/control", "Origin instance API path") doCmd.PersistentFlags().Bool(config.FlagFeatureDNSRewrites, true, "Enable DNS rewrites feature")
_ = viper.BindPFlag(configOriginAPIPath, doCmd.PersistentFlags().Lookup("origin-api-path"))
doCmd.PersistentFlags().String("origin-username", "", "Origin instance username")
_ = viper.BindPFlag(configOriginUsername, doCmd.PersistentFlags().Lookup("origin-username"))
doCmd.PersistentFlags().String("origin-password", "", "Origin instance password")
_ = viper.BindPFlag(configOriginPassword, doCmd.PersistentFlags().Lookup("origin-password"))
doCmd.PersistentFlags().String("origin-insecure-skip-verify", "", "Enable Origin instance InsecureSkipVerify")
_ = viper.BindPFlag(configOriginInsecureSkipVerify, doCmd.PersistentFlags().Lookup("origin-insecure-skip-verify"))
doCmd.PersistentFlags().String("replica-url", "", "Replica instance url") doCmd.PersistentFlags().Bool(config.FlagFeatureGeneral, true, "Enable general settings feature")
_ = viper.BindPFlag(configReplicaURL, doCmd.PersistentFlags().Lookup("replica-url")) doCmd.PersistentFlags().Bool(config.FlagFeatureQueryLog, true, "Enable query log config feature")
doCmd.PersistentFlags().String("replica-api-path", "/control", "Replica instance API path") doCmd.PersistentFlags().Bool(config.FlagFeatureStats, true, "Enable stats config feature")
_ = viper.BindPFlag(configReplicaAPIPath, doCmd.PersistentFlags().Lookup("replica-api-path")) doCmd.PersistentFlags().Bool(config.FlagFeatureClient, true, "Enable client settings feature")
doCmd.PersistentFlags().String("replica-username", "", "Replica instance username") doCmd.PersistentFlags().Bool(config.FlagFeatureServices, true, "Enable services sync feature")
_ = viper.BindPFlag(configReplicaUsername, doCmd.PersistentFlags().Lookup("replica-username")) doCmd.PersistentFlags().Bool(config.FlagFeatureFilters, true, "Enable filters sync feature")
doCmd.PersistentFlags().String("replica-password", "", "Replica instance password") doCmd.PersistentFlags().Bool(config.FlagFeatureTLSConfig, false, "Enable TLS config sync feature")
_ = viper.BindPFlag(configReplicaPassword, doCmd.PersistentFlags().Lookup("replica-password"))
doCmd.PersistentFlags().Bool("replica-insecure-skip-verify", false, "Enable Replica instance InsecureSkipVerify") doCmd.PersistentFlags().String(config.FlagOriginURL, "", "Origin instance url")
_ = viper.BindPFlag(configReplicaInsecureSkipVerify, doCmd.PersistentFlags().Lookup("replica-insecure-skip-verify")) doCmd.PersistentFlags().
doCmd.PersistentFlags().Bool("replica-auto-setup", false, "Enable automatic setup of new AdguardHome instances. This replaces the setup wizard.") String(config.FlagOriginWebURL, "", "Origin instance web url used in the web interface (default: <origin-url>)")
_ = viper.BindPFlag(configReplicaAutoSetup, doCmd.PersistentFlags().Lookup("replica-auto-setup")) doCmd.PersistentFlags().String(config.FlagOriginAPIPath, "/control", "Origin instance API path")
doCmd.PersistentFlags().Bool("replica-interface-name", false, "Optional change the interface name of the replica if it differs from the master") doCmd.PersistentFlags().String(config.FlagOriginUsername, "", "Origin instance username")
_ = viper.BindPFlag(configReplicaInterfaceName, doCmd.PersistentFlags().Lookup("replica-interface-name")) doCmd.PersistentFlags().String(config.FlagOriginPassword, "", "Origin instance password")
doCmd.PersistentFlags().String(config.FlagOriginCookie, "", "If Set, uses a cookie for authentication")
doCmd.PersistentFlags().Bool(config.FlagOriginISV, false, "Enable Origin instance InsecureSkipVerify")
doCmd.PersistentFlags().String(config.FlagReplicaURL, "", "Replica instance url")
doCmd.PersistentFlags().
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.FlagReplicaUsername, "", "Replica instance username")
doCmd.PersistentFlags().String(config.FlagReplicaPassword, "", "Replica instance password")
doCmd.PersistentFlags().String(config.FlagReplicaCookie, "", "If Set, uses a cookie for authentication")
doCmd.PersistentFlags().Bool(config.FlagReplicaISV, false, "Enable Replica instance InsecureSkipVerify")
doCmd.PersistentFlags().
Bool(config.FlagReplicaAutoSetup, false, "Enable automatic setup of new AdguardHome instances. This replaces the setup wizard.")
doCmd.PersistentFlags().
String(config.FlagReplicaInterfaceName, "", "Optional change the interface name of the replica if it differs from the master")
} }

113
go.mod
View File

@@ -1,56 +1,79 @@
module github.com/bakito/adguardhome-sync module github.com/bakito/adguardhome-sync
go 1.19 go 1.25.1
require ( require (
github.com/gin-gonic/gin v1.8.2 github.com/caarlos0/env/v11 v11.3.1
github.com/go-resty/resty/v2 v2.7.0 github.com/gin-gonic/gin v1.10.1
github.com/golang/mock v1.6.0 github.com/go-faker/faker/v4 v4.6.2
github.com/google/uuid v1.3.0 github.com/go-resty/resty/v2 v2.16.5
github.com/jinzhu/copier v0.3.5 github.com/google/uuid v1.6.0
github.com/mitchellh/go-homedir v1.1.0 github.com/jinzhu/copier v0.4.0
github.com/onsi/ginkgo/v2 v2.8.0 github.com/oapi-codegen/runtime v1.1.2
github.com/onsi/gomega v1.26.0 github.com/onsi/ginkgo/v2 v2.25.3
github.com/onsi/gomega v1.38.2
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/spf13/cobra v1.6.1 github.com/santhosh-tekuri/jsonschema/v6 v6.0.2
github.com/spf13/viper v1.15.0 github.com/spf13/cobra v1.10.1
go.uber.org/zap v1.24.0 go.uber.org/mock v0.6.0
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 go.uber.org/zap v1.27.0
golang.org/x/mod v0.28.0
gopkg.in/yaml.v3 v3.0.1
k8s.io/apimachinery v0.34.1
k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d
) )
require ( require (
github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/Masterminds/semver/v3 v3.4.0 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/go-logr/logr v1.2.3 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/go-playground/locales v0.14.0 // indirect github.com/bytedance/sonic v1.13.2 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect github.com/bytedance/sonic/loader v0.2.4 // indirect
github.com/go-playground/validator/v10 v10.11.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/goccy/go-json v0.9.11 // indirect github.com/cloudwego/base64x v0.1.5 // indirect
github.com/google/go-cmp v0.5.9 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect github.com/gabriel-vasile/mimetype v1.4.9 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.26.0 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 // 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/leodido/go-urn v1.2.1 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/magiconair/properties v1.8.7 // indirect github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/mapstructure v1.5.0 // 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/pelletier/go-toml/v2 v2.0.6 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/spf13/afero v1.9.3 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/spf13/cast v1.5.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/prometheus/common v0.66.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/prometheus/procfs v0.16.1 // indirect
github.com/subosito/gotenv v1.4.2 // indirect github.com/spf13/pflag v1.0.9 // indirect
github.com/ugorji/go/codec v1.2.7 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
go.uber.org/atomic v1.9.0 // indirect github.com/ugorji/go/codec v1.2.12 // indirect
go.uber.org/multierr v1.8.0 // indirect github.com/x448/float16 v0.8.4 // indirect
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect go.uber.org/automaxprocs v1.6.0 // indirect
golang.org/x/net v0.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect
golang.org/x/sys v0.4.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/text v0.6.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect
google.golang.org/protobuf v1.28.1 // indirect golang.org/x/arch v0.16.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect golang.org/x/crypto v0.41.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect golang.org/x/net v0.43.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect golang.org/x/sys v0.35.0 // 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
k8s.io/klog/v2 v2.130.1 // indirect
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
) )

692
go.sum
View File

@@ -1,581 +1,225 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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 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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/go-faker/faker/v4 v4.6.2 h1:IR1uQUYotFZnuTL7Iuy0FDGtHM5Rt1Q+2nipH9gnqKs=
github.com/gin-gonic/gin v1.8.2 h1:UzKToD9/PoFj/V4rvlKqTRKnQYyz8Sc1MJlv4JHPtvY= github.com/go-faker/faker/v4 v4.6.2/go.mod h1:u1dIRP5neLB6kTzgyVjdBOV5R1uP7BdxkcWk7tiKQXk=
github.com/gin-gonic/gin v1.8.2/go.mod h1:qw5AYuDrzRTnhvusDsrov+fDIxp9Dleuu12h8nfB398= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/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/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 h1:EEHtgt9IwisQ2AZ4pIsMjahcegHh6rmhqxzIRQIyepY=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg=
github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 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/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
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/onsi/ginkgo/v2 v2.8.0 h1:pAM+oBNPrpXRs+E/8spkeGx9QgekbRVyr74EUvRVOUI= github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/onsi/ginkgo/v2 v2.8.0/go.mod h1:6JsQiECmxCa3V5st74AL/AmsV482EDdVrGaVW6z3oYU= github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/onsi/gomega v1.26.0 h1:03cDLK28U6hWvCAns6NeydX3zIm4SF3ci69ulidS32Q= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/onsi/gomega v1.26.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= github.com/oapi-codegen/runtime v1.1.2 h1:P2+CubHq8fO4Q6fV1tqDBZHCwpVpvPg7oKiYzQgXIyI=
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/oapi-codegen/runtime v1.1.2/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/onsi/ginkgo/v2 v2.25.3 h1:Ty8+Yi/ayDAGtk4XxmmfUy4GabvM+MegeB4cDLRi6nw=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/onsi/ginkgo/v2 v2.25.3/go.mod h1:43uiyQC4Ed2tkOzLsEYm7hnrb7UJTWHYNsuy3bG/snE=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
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 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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
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.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
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/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU=
github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA=
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/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/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.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 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
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/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
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/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.1.32/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=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= 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-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
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.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
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.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
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-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
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-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/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-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
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.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/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-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-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/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.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
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.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
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-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/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-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/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-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
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/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
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-20180628173108-788fd7840127/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=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
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.0-20210107192922-496545a6307b/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=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d h1:wAhiDyZ4Tdtt7e46e9M5ZSAJ/MnPGPs+Ki1gHw4w1R0=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=

View File

@@ -0,0 +1,105 @@
package client
import (
"encoding/json"
"net/http"
"github.com/go-resty/resty/v2"
"go.uber.org/zap"
)
func (cl *client) doGet(req *resty.Request, url string) error {
rl := cl.log.With("method", "GET", "path", url)
if cl.client.UserInfo != nil {
rl = rl.With("username", cl.client.UserInfo.Username)
}
req.ForceContentType("application/json")
rl.Debug("do get")
resp, err := req.Get(url)
if err != nil {
l := rl
if resp != nil {
if resp.StatusCode() == http.StatusFound {
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)
}
l.Debug("error in do get")
return detailedError(resp, err)
}
checkAuthenticationIssue(resp, rl)
rl.With(
"status", resp.StatusCode(),
"body", string(resp.Body()),
"content-type", resp.Header()["Content-Type"],
).Debug("got response")
if resp.StatusCode() != http.StatusOK {
return detailedError(resp, nil)
}
return nil
}
func (cl *client) doPost(req *resty.Request, url string) error {
rl := cl.log.With("method", "POST", "path", url)
if cl.client.UserInfo != nil {
rl = rl.With("username", cl.client.UserInfo.Username)
}
b, _ := json.Marshal(req.Body)
rl.With("body", string(b)).Debug("do post")
resp, err := req.Post(url)
if err != nil {
rl.With("status", resp.StatusCode(), "body", string(resp.Body()), "error", err).Debug("error in do post")
return detailedError(resp, err)
}
checkAuthenticationIssue(resp, rl)
rl.With(
"status", resp.StatusCode(),
"body", string(resp.Body()),
"content-type", contentType(resp),
).Debug("got response")
if resp.StatusCode() != http.StatusOK {
return detailedError(resp, nil)
}
return nil
}
func (cl *client) doPut(req *resty.Request, url string) error {
rl := cl.log.With("method", "PUT", "path", url)
if cl.client.UserInfo != nil {
rl = rl.With("username", cl.client.UserInfo.Username)
}
b, _ := json.Marshal(req.Body)
rl.With("body", string(b)).Debug("do put")
resp, err := req.Put(url)
if err != nil {
rl.With("status", resp.StatusCode(), "body", string(resp.Body()), "error", err).Debug("error in do put")
return detailedError(resp, err)
}
checkAuthenticationIssue(resp, rl)
rl.With(
"status", resp.StatusCode(),
"body", string(resp.Body()),
"content-type", contentType(resp),
).Debug("got response")
if resp.StatusCode() != http.StatusOK {
return detailedError(resp, nil)
}
return nil
}
func checkAuthenticationIssue(resp *resty.Response, rl *zap.SugaredLogger) {
if resp != nil && (resp.StatusCode() == http.StatusUnauthorized || resp.StatusCode() == http.StatusForbidden) {
rl.With("status", resp.StatusCode()).Error("there seems to be an authentication issue - " +
"please check https://github.com/bakito/adguardhome-sync/wiki/FAQ")
}
}

481
internal/client/client.go Normal file
View File

@@ -0,0 +1,481 @@
package client
import (
"crypto/tls"
"errors"
"fmt"
"net/http"
"net/url"
"os"
"path"
"strconv"
"strings"
"github.com/go-resty/resty/v2"
"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"
type Error struct {
message string
errorCode int
}
func (e *Error) Error() string {
return e.message
}
func (e *Error) Code() int {
return e.errorCode
}
var (
l = log.GetLogger("client")
// ErrSetupNeeded custom error.
ErrSetupNeeded = errors.New("setup needed")
)
func detailedError(resp *resty.Response, err error) error {
e := resp.Status()
if len(resp.Body()) > 0 {
e += fmt.Sprintf("(%s)", string(resp.Body()))
}
if err != nil {
e += ": " + err.Error()
}
return &Error{
message: e,
errorCode: resp.StatusCode(),
}
}
// New create a new client.
func New(config types.AdGuardInstance) (Client, error) {
var apiURL string
if config.APIPath == "" {
apiURL = config.URL + "/control"
} else {
apiURL = fmt.Sprintf("%s/%s", config.URL, config.APIPath)
}
u, err := url.Parse(apiURL)
if err != nil {
return nil, err
}
u.Path = path.Clean(u.Path)
cl := resty.New().SetBaseURL(u.String()).SetDisableWarn(true).SetHeaders(config.RequestHeaders)
// #nosec G402 has to be explicitly enabled
cl.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: config.InsecureSkipVerify})
cookieParts := strings.Split(config.Cookie, "=")
if len(cookieParts) == 2 {
cl.SetCookie(&http.Cookie{
Name: cookieParts[0],
Value: cookieParts[1],
})
} else if config.Username != "" && config.Password != "" {
cl = cl.SetBasicAuth(config.Username, config.Password)
}
if v, ok := os.LookupEnv(envRedirectPolicyNoOfRedirects); ok {
nbr, err := strconv.Atoi(v)
if err != nil {
return nil, fmt.Errorf("error parsing env var %q value must be an integer", envRedirectPolicyNoOfRedirects)
}
cl.SetRedirectPolicy(resty.FlexibleRedirectPolicy(nbr))
} else {
// no redirect
cl.SetRedirectPolicy(resty.NoRedirectPolicy())
}
return &client{
host: config.Host,
client: cl,
log: l.With("host", config.Host),
}, nil
}
// Client AdguardHome API client interface.
//
//nolint:interfacebloat
type Client interface {
Host() string
Status() (*model.ServerStatus, error)
Stats() (*model.Stats, error)
QueryLog(limit int) (*model.QueryLog, error)
ToggleProtection(enable bool) error
RewriteList() (*model.RewriteEntries, error)
AddRewriteEntries(e ...model.RewriteEntry) error
DeleteRewriteEntries(e ...model.RewriteEntry) error
Filtering() (*model.FilterStatus, error)
ToggleFiltering(enabled bool, interval int) error
AddFilter(whitelist bool, f model.Filter) error
DeleteFilter(whitelist bool, f model.Filter) error
UpdateFilter(whitelist bool, f model.Filter) error
RefreshFilters(whitelist bool) error
SetCustomRules(rules *[]string) error
SafeBrowsing() (bool, error)
ToggleSafeBrowsing(enable bool) error
Parental() (bool, error)
ToggleParental(enable bool) error
SafeSearchConfig() (*model.SafeSearchConfig, error)
SetSafeSearchConfig(settings *model.SafeSearchConfig) error
ProfileInfo() (*model.ProfileInfo, error)
SetProfileInfo(settings *model.ProfileInfo) error
BlockedServicesSchedule() (*model.BlockedServicesSchedule, error)
SetBlockedServicesSchedule(schedule *model.BlockedServicesSchedule) error
Clients() (*model.Clients, error)
AddClient(client *model.Client) error
UpdateClient(client *model.Client) error
DeleteClient(client *model.Client) error
QueryLogConfig() (*model.QueryLogConfigWithIgnored, error)
SetQueryLogConfig(ql *model.QueryLogConfigWithIgnored) error
StatsConfig() (*model.GetStatsConfigResponse, error)
SetStatsConfig(sc *model.PutStatsConfigUpdateRequest) error
Setup() error
AccessList() (*model.AccessList, error)
SetAccessList(accessList *model.AccessList) error
DNSConfig() (*model.DNSConfig, error)
SetDNSConfig(config *model.DNSConfig) error
DhcpConfig() (*model.DhcpStatus, error)
SetDhcpConfig(status *model.DhcpStatus) error
AddDHCPStaticLease(lease model.DhcpStaticLease) error
DeleteDHCPStaticLease(lease model.DhcpStaticLease) error
TLSConfig() (*model.TlsConfig, error)
SetTLSConfig(tls *model.TlsConfig) error
}
type client struct {
client *resty.Client
log *zap.SugaredLogger
host string
version string
}
func (cl *client) Host() string {
return cl.host
}
func contentType(resp *resty.Response) string {
if ct, ok := resp.Header()["Content-Type"]; ok {
if len(ct) != 1 {
return fmt.Sprintf("%v", ct)
}
return ct[0]
}
return ""
}
func (cl *client) Status() (*model.ServerStatus, error) {
status := &model.ServerStatus{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(status), "status")
cl.version = status.Version
return status, err
}
func (cl *client) Stats() (*model.Stats, error) {
stats := &model.Stats{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(stats), "stats")
return stats, err
}
func (cl *client) QueryLog(limit int) (*model.QueryLog, error) {
ql := &model.QueryLog{}
err := cl.doGet(
cl.client.R().EnableTrace().SetResult(ql),
fmt.Sprintf(`querylog?limit=%d&response_status="all"`, limit),
)
return ql, err
}
func (cl *client) RewriteList() (*model.RewriteEntries, error) {
rewrites := &model.RewriteEntries{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(&rewrites), "/rewrite/list")
return rewrites, err
}
func (cl *client) AddRewriteEntries(entries ...model.RewriteEntry) error {
for _, e := range entries {
cl.log.With("domain", e.Domain, "answer", e.Answer).Info("Add DNS rewrite entry")
err := cl.doPost(cl.client.R().EnableTrace().SetBody(&e), "/rewrite/add")
if err != nil {
return err
}
}
return nil
}
func (cl *client) DeleteRewriteEntries(entries ...model.RewriteEntry) error {
for _, e := range entries {
cl.log.With("domain", e.Domain, "answer", e.Answer).Info("Delete DNS rewrite entry")
err := cl.doPost(cl.client.R().EnableTrace().SetBody(&e), "/rewrite/delete")
if err != nil {
return err
}
}
return nil
}
func (cl *client) SafeBrowsing() (bool, error) {
return cl.toggleStatus("safebrowsing")
}
func (cl *client) ToggleSafeBrowsing(enable bool) error {
return cl.toggleBool("safebrowsing", enable)
}
func (cl *client) Parental() (bool, error) {
return cl.toggleStatus("parental")
}
func (cl *client) ToggleParental(enable bool) error {
return cl.toggleBool("parental", enable)
}
func (cl *client) toggleStatus(mode string) (bool, error) {
fs := &model.EnableConfig{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(fs), fmt.Sprintf("/%s/status", mode))
return fs.Enabled, err
}
func (cl *client) toggleBool(mode string, enable bool) error {
cl.log.With("enable", enable).Info("Toggle " + mode)
var target string
if enable {
target = "enable"
} else {
target = "disable"
}
return cl.doPost(cl.client.R().EnableTrace(), fmt.Sprintf("/%s/%s", mode, target))
}
func (cl *client) Filtering() (*model.FilterStatus, error) {
f := &model.FilterStatus{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(f), "/filtering/status")
return f, err
}
func (cl *client) AddFilter(whitelist bool, f model.Filter) error {
cl.log.With("url", f.Url, "whitelist", whitelist, "enabled", f.Enabled).Info("Add filter")
ff := &model.AddUrlRequest{Name: utils.Ptr(f.Name), Url: utils.Ptr(f.Url), Whitelist: utils.Ptr(whitelist)}
return cl.doPost(cl.client.R().EnableTrace().SetBody(ff), "/filtering/add_url")
}
func (cl *client) DeleteFilter(whitelist bool, f model.Filter) error {
cl.log.With("url", f.Url, "whitelist", whitelist, "enabled", f.Enabled).Info("Delete filter")
ff := &model.RemoveUrlRequest{Url: utils.Ptr(f.Url), Whitelist: utils.Ptr(whitelist)}
return cl.doPost(cl.client.R().EnableTrace().SetBody(ff), "/filtering/remove_url")
}
func (cl *client) UpdateFilter(whitelist bool, f model.Filter) error {
cl.log.With("url", f.Url, "whitelist", whitelist, "enabled", f.Enabled).Info("Update filter")
fu := &model.FilterSetUrl{
Whitelist: utils.Ptr(whitelist), Url: utils.Ptr(f.Url),
Data: &model.FilterSetUrlData{Name: f.Name, Url: f.Url, Enabled: f.Enabled},
}
return cl.doPost(cl.client.R().EnableTrace().SetBody(fu), "/filtering/set_url")
}
func (cl *client) RefreshFilters(whitelist bool) error {
cl.log.With("whitelist", whitelist).Info("Refresh filter")
return cl.doPost(
cl.client.R().EnableTrace().SetBody(&model.FilterRefreshRequest{Whitelist: utils.Ptr(whitelist)}),
"/filtering/refresh",
)
}
func (cl *client) ToggleProtection(enable bool) error {
cl.log.With("enable", enable).Info("Toggle protection")
return cl.doPost(cl.client.R().EnableTrace().SetBody(&types.Protection{ProtectionEnabled: enable}), "/dns_config")
}
func (cl *client) SetCustomRules(rules *[]string) error {
var l int
if rules != nil {
l = len(*rules)
}
cl.log.With("rules", l).Info("Set user rules")
return cl.doPost(cl.client.R().EnableTrace().SetBody(&model.SetRulesRequest{Rules: rules}), "/filtering/set_rules")
}
func (cl *client) ToggleFiltering(enabled bool, interval int) error {
cl.log.With("enabled", enabled, "interval", interval).Info("Toggle filtering")
return cl.doPost(cl.client.R().EnableTrace().SetBody(&model.FilterConfig{
Enabled: utils.Ptr(enabled),
Interval: utils.Ptr(interval),
}), "/filtering/config")
}
func (cl *client) BlockedServicesSchedule() (*model.BlockedServicesSchedule, error) {
sched := &model.BlockedServicesSchedule{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(sched), "/blocked_services/get")
return sched, err
}
func (cl *client) SetBlockedServicesSchedule(schedule *model.BlockedServicesSchedule) error {
cl.log.With("services", schedule.ServicesString()).Info("Set blocked services schedule")
return cl.doPut(cl.client.R().EnableTrace().SetBody(schedule), "/blocked_services/update")
}
func (cl *client) Clients() (*model.Clients, error) {
clients := &model.Clients{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(clients), "/clients")
return clients, err
}
func (cl *client) AddClient(client *model.Client) error {
cl.log.With("name", *client.Name).Info("Add client settings")
return cl.doPost(cl.client.R().EnableTrace().SetBody(client), "/clients/add")
}
func (cl *client) UpdateClient(client *model.Client) error {
cl.log.With("name", *client.Name).Info("Update client settings")
return cl.doPost(
cl.client.R().EnableTrace().SetBody(&model.ClientUpdate{Name: client.Name, Data: client}),
"/clients/update",
)
}
func (cl *client) DeleteClient(client *model.Client) error {
cl.log.With("name", *client.Name).Info("Delete client settings")
return cl.doPost(cl.client.R().EnableTrace().SetBody(client), "/clients/delete")
}
func (cl *client) QueryLogConfig() (*model.QueryLogConfigWithIgnored, error) {
qlc := &model.QueryLogConfigWithIgnored{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(qlc), "/querylog/config")
return qlc, err
}
func (cl *client) SetQueryLogConfig(qlc *model.QueryLogConfigWithIgnored) error {
cl.log.With("enabled", *qlc.Enabled, "interval", *qlc.Interval, "anonymizeClientIP", *qlc.AnonymizeClientIp).
Info("Set query log config")
return cl.doPut(cl.client.R().EnableTrace().SetBody(qlc), "/querylog/config/update")
}
func (cl *client) StatsConfig() (*model.GetStatsConfigResponse, error) {
stats := &model.GetStatsConfigResponse{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(stats), "/stats/config")
return stats, err
}
func (cl *client) SetStatsConfig(sc *model.PutStatsConfigUpdateRequest) error {
cl.log.With("interval", sc.Interval).Info("Set stats config")
return cl.doPut(cl.client.R().EnableTrace().SetBody(sc), "/stats/config/update")
}
func (cl *client) Setup() error {
cl.log.Info("Setup new AdguardHome instance")
cfg := &types.InstallConfig{
Web: types.InstallPort{
IP: "0.0.0.0",
Port: 3000,
Status: "",
CanAutofix: false,
},
DNS: types.InstallPort{
IP: "0.0.0.0",
Port: 53,
Status: "",
CanAutofix: false,
},
}
if cl.client.UserInfo != nil {
cfg.Username = cl.client.UserInfo.Username
cfg.Password = cl.client.UserInfo.Password
}
req := cl.client.R().EnableTrace().SetBody(cfg)
req.UserInfo = nil
return cl.doPost(req, "/install/configure")
}
func (cl *client) AccessList() (*model.AccessList, error) {
al := &model.AccessList{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(al), "/access/list")
return al, err
}
func (cl *client) SetAccessList(list *model.AccessList) error {
cl.log.Info("Set access list")
return cl.doPost(cl.client.R().EnableTrace().SetBody(list), "/access/set")
}
func (cl *client) DNSConfig() (*model.DNSConfig, error) {
cfg := &model.DNSConfig{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(cfg), "/dns_info")
return cfg, err
}
func (cl *client) SetDNSConfig(config *model.DNSConfig) error {
cl.log.Info("Set dns config list")
return cl.doPost(cl.client.R().EnableTrace().SetBody(config), "/dns_config")
}
func (cl *client) DhcpConfig() (*model.DhcpStatus, error) {
cfg := &model.DhcpStatus{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(cfg), "/dhcp/status")
return cfg, err
}
func (cl *client) SetDhcpConfig(config *model.DhcpStatus) error {
cl.log.Info("Set dhcp server config")
return cl.doPost(cl.client.R().EnableTrace().SetBody(config), "/dhcp/set_config")
}
func (cl *client) AddDHCPStaticLease(l model.DhcpStaticLease) error {
cl.log.With("mac", l.Mac, "ip", l.Ip, "hostname", l.Hostname).Info("Add static dhcp lease")
err := cl.doPost(cl.client.R().EnableTrace().SetBody(l), "/dhcp/add_static_lease")
if err != nil {
return err
}
return nil
}
func (cl *client) DeleteDHCPStaticLease(l model.DhcpStaticLease) error {
cl.log.With("mac", l.Mac, "ip", l.Ip, "hostname", l.Hostname).Info("Delete static dhcp lease")
err := cl.doPost(cl.client.R().EnableTrace().SetBody(l), "/dhcp/remove_static_lease")
if err != nil {
return err
}
return nil
}
func (cl *client) SafeSearchConfig() (*model.SafeSearchConfig, error) {
sss := &model.SafeSearchConfig{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(sss), "/safesearch/status")
return sss, err
}
func (cl *client) SetSafeSearchConfig(settings *model.SafeSearchConfig) error {
cl.log.With("enabled", *settings.Enabled).Info("Set safesearch settings")
return cl.doPut(cl.client.R().EnableTrace().SetBody(settings), "/safesearch/settings")
}
func (cl *client) ProfileInfo() (*model.ProfileInfo, error) {
p := &model.ProfileInfo{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(p), "/profile")
return p, err
}
func (cl *client) SetProfileInfo(profile *model.ProfileInfo) error {
cl.log.With("language", profile.Language, "theme", profile.Theme).Info("Set profile")
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")
}

View File

@@ -8,11 +8,14 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"github.com/bakito/adguardhome-sync/pkg/client"
"github.com/bakito/adguardhome-sync/pkg/types"
"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 (
@@ -33,19 +36,22 @@ var _ = Describe("Client", func() {
Context("Host", func() { Context("Host", func() {
It("should read the current host", func() { It("should read the current host", func() {
cl, _ := client.New(types.AdGuardInstance{URL: "https://foo.bar:3000"}) inst := types.AdGuardInstance{URL: "https://foo.bar:3000"}
err := inst.Init()
Ω(err).ShouldNot(HaveOccurred())
cl, _ := client.New(inst)
host := cl.Host() host := cl.Host()
Ω(host).Should(Equal("foo.bar:3000")) Ω(host).Should(Equal("foo.bar:3000"))
}) })
}) })
Context("Filtering", func() { Context("Filter", func() {
It("should read filtering status", func() { It("should read filter status", func() {
ts, cl = ClientGet("filtering-status.json", "/filtering/status") ts, cl = ClientGet("filtering-status.json", "/filtering/status")
fs, err := cl.Filtering() fs, err := cl.Filtering()
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
Ω(fs.Enabled).Should(BeTrue()) Ω(*fs.Enabled).Should(BeTrue())
Ω(fs.Filters).Should(HaveLen(2)) Ω(*fs.Filters).Should(HaveLen(2))
}) })
It("should enable protection", func() { It("should enable protection", func() {
ts, cl = ClientPost("/filtering/config", `{"enabled":true,"interval":123}`) ts, cl = ClientPost("/filtering/config", `{"enabled":true,"interval":123}`)
@@ -64,35 +70,46 @@ var _ = Describe("Client", func() {
}) })
It("should add Filters", func() { It("should add Filters", func() {
ts, cl = ClientPost("/filtering/add_url", ts, cl = ClientPost("/filtering/add_url",
`{"id":0,"enabled":false,"url":"foo","name":"","rules_count":0,"whitelist":true}`, `{"name":"","url":"foo","whitelist":true}`,
`{"id":0,"enabled":false,"url":"bar","name":"","rules_count":0,"whitelist":true}`, `{"name":"","url":"bar","whitelist":true}`,
) )
err := cl.AddFilters(true, types.Filter{URL: "foo"}, types.Filter{URL: "bar"}) err := cl.AddFilter(true, model.Filter{Url: "foo"})
Ω(err).ShouldNot(HaveOccurred())
err = cl.AddFilter(true, model.Filter{Url: "bar"})
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
}) })
It("should update Filters", func() { It("should update Filters", func() {
ts, cl = ClientPost("/filtering/set_url", ts, cl = ClientPost("/filtering/set_url",
`{"url":"foo","data":{"id":0,"enabled":false,"url":"foo","name":"","rules_count":0,"whitelist":true},"whitelist":true}`, `{"data":{"enabled":false,"name":"","url":"foo"},"url":"foo","whitelist":true}`,
`{"url":"bar","data":{"id":0,"enabled":false,"url":"bar","name":"","rules_count":0,"whitelist":true},"whitelist":true}`, `{"data":{"enabled":false,"name":"","url":"bar"},"url":"bar","whitelist":true}`,
) )
err := cl.UpdateFilters(true, types.Filter{URL: "foo"}, types.Filter{URL: "bar"}) err := cl.UpdateFilter(true, model.Filter{Url: "foo"})
Ω(err).ShouldNot(HaveOccurred())
err = cl.UpdateFilter(true, model.Filter{Url: "bar"})
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
}) })
It("should delete Filters", func() { It("should delete Filters", func() {
ts, cl = ClientPost("/filtering/remove_url", ts, cl = ClientPost("/filtering/remove_url",
`{"id":0,"enabled":false,"url":"foo","name":"","rules_count":0,"whitelist":true}`, `{"url":"foo","whitelist":true}`,
`{"id":0,"enabled":false,"url":"bar","name":"","rules_count":0,"whitelist":true}`, `{"url":"bar","whitelist":true}`,
) )
err := cl.DeleteFilters(true, types.Filter{URL: "foo"}, types.Filter{URL: "bar"}) err := cl.DeleteFilter(true, model.Filter{Url: "foo"})
Ω(err).ShouldNot(HaveOccurred())
err = cl.DeleteFilter(true, model.Filter{Url: "bar"})
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
}) })
}) It("should set empty filter rules", func() {
ts, cl = ClientPost("/filtering/set_rules",
Context("CustomRules", func() { `{"rules":[]}`,
It("should set SetCustomRules", func() { )
ts, cl = ClientPost("/filtering/set_rules", `foo err := cl.SetCustomRules(utils.Ptr([]string{}))
bar`) Ω(err).ShouldNot(HaveOccurred())
err := cl.SetCustomRules([]string{"foo", "bar"}) })
It("should set nil filter rules", func() {
ts, cl = ClientPost("/filtering/set_rules",
`{}`,
)
err := cl.SetCustomRules(nil)
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
}) })
}) })
@@ -102,8 +119,8 @@ bar`)
ts, cl = ClientGet("status.json", "/status") ts, cl = ClientGet("status.json", "/status")
fs, err := cl.Status() fs, err := cl.Status()
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
Ω(fs.DNSAddresses).Should(HaveLen(1)) Ω(fs.DnsAddresses).Should(HaveLen(1))
Ω(fs.DNSAddresses[0]).Should(Equal("192.168.1.2")) Ω(fs.DnsAddresses[0]).Should(Equal("192.168.1.2"))
Ω(fs.Version).Should(Equal("v0.105.2")) Ω(fs.Version).Should(Equal("v0.105.2"))
}) })
It("should return ErrSetupNeeded", func() { It("should return ErrSetupNeeded", func() {
@@ -121,7 +138,14 @@ bar`)
Context("Setup", func() { Context("Setup", func() {
It("should add setup the instance", func() { It("should add setup the instance", func() {
ts, cl = ClientPost("/install/configure", 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"}`, username, password)) ts, cl = ClientPost(
"/install/configure",
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":%q,"password":%q}`,
username,
password,
),
)
err := cl.Setup() err := cl.Setup()
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
}) })
@@ -135,13 +159,19 @@ bar`)
Ω(*rwl).Should(HaveLen(2)) Ω(*rwl).Should(HaveLen(2))
}) })
It("should add RewriteList", func() { It("should add RewriteList", func() {
ts, cl = ClientPost("/rewrite/add", `{"domain":"foo","answer":"foo"}`, `{"domain":"bar","answer":"bar"}`) ts, cl = ClientPost("/rewrite/add", `{"answer":"foo","domain":"foo"}`, `{"answer":"bar","domain":"bar"}`)
err := cl.AddRewriteEntries(types.RewriteEntry{Answer: "foo", Domain: "foo"}, types.RewriteEntry{Answer: "bar", Domain: "bar"}) err := cl.AddRewriteEntries(
model.RewriteEntry{Answer: utils.Ptr("foo"), Domain: utils.Ptr("foo")},
model.RewriteEntry{Answer: utils.Ptr("bar"), Domain: utils.Ptr("bar")},
)
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
}) })
It("should delete RewriteList", func() { It("should delete RewriteList", func() {
ts, cl = ClientPost("/rewrite/delete", `{"domain":"foo","answer":"foo"}`, `{"domain":"bar","answer":"bar"}`) ts, cl = ClientPost("/rewrite/delete", `{"answer":"foo","domain":"foo"}`, `{"answer":"bar","domain":"bar"}`)
err := cl.DeleteRewriteEntries(types.RewriteEntry{Answer: "foo", Domain: "foo"}, types.RewriteEntry{Answer: "bar", Domain: "bar"}) err := cl.DeleteRewriteEntries(
model.RewriteEntry{Answer: utils.Ptr("foo"), Domain: utils.Ptr("foo")},
model.RewriteEntry{Answer: utils.Ptr("bar"), Domain: utils.Ptr("bar")},
)
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
}) })
}) })
@@ -165,21 +195,22 @@ bar`)
}) })
}) })
Context("SafeSearch", func() { Context("SafeSearchConfig", func() {
It("should read safesearch status", func() { It("should read safesearch status", func() {
ts, cl = ClientGet("safesearch-status.json", "/safesearch/status") ts, cl = ClientGet("safesearch-status.json", "/safesearch/status")
ss, err := cl.SafeSearch() ss, err := cl.SafeSearchConfig()
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
Ω(ss).Should(BeTrue()) Ω(ss.Enabled).ShouldNot(BeNil())
Ω(*ss.Enabled).Should(BeTrue())
}) })
It("should enable safesearch", func() { It("should enable safesearch", func() {
ts, cl = ClientPost("/safesearch/enable", "") ts, cl = ClientPut("/safesearch/settings", `{"enabled":true}`)
err := cl.ToggleSafeSearch(true) err := cl.SetSafeSearchConfig(&model.SafeSearchConfig{Enabled: utils.Ptr(true)})
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
}) })
It("should disable safesearch", func() { It("should disable safesearch", func() {
ts, cl = ClientPost("/safesearch/disable", "") ts, cl = ClientPut("/safesearch/settings", `{"enabled":false}`)
err := cl.ToggleSafeSearch(false) err := cl.SetSafeSearchConfig(&model.SafeSearchConfig{Enabled: utils.Ptr(false)})
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
}) })
}) })
@@ -216,16 +247,25 @@ bar`)
}) })
}) })
Context("Services", func() { Context("BlockedServicesSchedule", func() {
It("should read Services", func() { It("should read BlockedServicesSchedule", func() {
ts, cl = ClientGet("blockedservices-list.json", "/blocked_services/list") ts, cl = ClientGet("blockedservicesschedule-get.json", "/blocked_services/get")
s, err := cl.Services() s, err := cl.BlockedServicesSchedule()
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
Ω(s).Should(HaveLen(2)) Ω(*s.Ids).Should(HaveLen(3))
}) })
It("should set Services", func() { It("should set BlockedServicesSchedule", func() {
ts, cl = ClientPost("/blocked_services/set", `["foo","bar"]`) ts, cl = ClientPost("/blocked_services/update",
err := cl.SetServices([]string{"foo", "bar"}) `{"ids":["bar","foo"],"schedule":{"mon":{"end":99,"start":1}}}`)
err := cl.SetBlockedServicesSchedule(&model.BlockedServicesSchedule{
Ids: utils.Ptr([]string{"foo", "bar"}),
Schedule: &model.Schedule{
Mon: &model.DayRange{
Start: utils.Ptr(float32(1.0)),
End: utils.Ptr(float32(99.0)),
},
},
})
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
}) })
}) })
@@ -235,55 +275,72 @@ bar`)
ts, cl = ClientGet("clients.json", "/clients") ts, cl = ClientGet("clients.json", "/clients")
c, err := cl.Clients() c, err := cl.Clients()
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
Ω(c.Clients).Should(HaveLen(2)) Ω(*c.Clients).Should(HaveLen(2))
}) })
It("should add Clients", func() { It("should add Clients", func() {
ts, cl = ClientPost("/clients/add", ts, cl = ClientPost("/clients/add",
`{"ids":["id"],"use_global_settings":false,"use_global_blocked_services":false,"name":"foo","filtering_enabled":false,"parental_enabled":false,"safesearch_enabled":false,"safebrowsing_enabled":false,"disallowed":false,"disallowed_rule":""}`, `{"ids":["id"],"name":"foo"}`,
) )
err := cl.AddClients(types.Client{Name: "foo", Ids: []string{"id"}}) err := cl.AddClient(&model.Client{Name: utils.Ptr("foo"), Ids: utils.Ptr([]string{"id"})})
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
}) })
It("should update Clients", func() { It("should update Clients", func() {
ts, cl = ClientPost("/clients/update", ts, cl = ClientPost("/clients/update",
`{"name":"foo","data":{"ids":["id"],"use_global_settings":false,"use_global_blocked_services":false,"name":"foo","filtering_enabled":false,"parental_enabled":false,"safesearch_enabled":false,"safebrowsing_enabled":false,"disallowed":false,"disallowed_rule":""}}`, `{"data":{"ids":["id"],"name":"foo"},"name":"foo"}`,
) )
err := cl.UpdateClients(types.Client{Name: "foo", Ids: []string{"id"}}) err := cl.UpdateClient(&model.Client{Name: utils.Ptr("foo"), Ids: utils.Ptr([]string{"id"})})
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
}) })
It("should delete Clients", func() { It("should delete Clients", func() {
ts, cl = ClientPost("/clients/delete", ts, cl = ClientPost("/clients/delete",
`{"ids":["id"],"use_global_settings":false,"use_global_blocked_services":false,"name":"foo","filtering_enabled":false,"parental_enabled":false,"safesearch_enabled":false,"safebrowsing_enabled":false,"disallowed":false,"disallowed_rule":""}`, `{"ids":["id"],"name":"foo"}`,
) )
err := cl.DeleteClients(types.Client{Name: "foo", Ids: []string{"id"}}) err := cl.DeleteClient(&model.Client{Name: utils.Ptr("foo"), Ids: utils.Ptr([]string{"id"})})
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
}) })
}) })
Context("QueryLogConfig", func() { Context("QueryLogConfig", func() {
It("should read QueryLogConfig", func() { It("should read QueryLogConfig", func() {
ts, cl = ClientGet("querylog_info.json", "/querylog_info") ts, cl = ClientGet("querylog_config.json", "/querylog/config")
qlc, err := cl.QueryLogConfig() qlc, err := cl.QueryLogConfig()
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
Ω(qlc.Enabled).Should(BeTrue()) Ω(qlc.Enabled).ShouldNot(BeNil())
Ω(qlc.Interval).Should(Equal(90.0)) Ω(*qlc.Enabled).Should(BeTrue())
Ω(qlc.Interval).ShouldNot(BeNil())
Ω(*qlc.Interval).Should(Equal(model.QueryLogConfigInterval(90)))
}) })
It("should set QueryLogConfig", func() { It("should set QueryLogConfig", func() {
ts, cl = ClientPost("/querylog_config", `{"enabled":true,"interval":123,"anonymize_client_ip":true}`) ts, cl = ClientPut(
err := cl.SetQueryLogConfig(true, 123, true) "/querylog/config/update",
`{"anonymize_client_ip":true,"enabled":true,"interval":123,"ignored":["foo.bar"]}`,
)
var interval model.QueryLogConfigInterval = 123
err := cl.SetQueryLogConfig(&model.QueryLogConfigWithIgnored{
QueryLogConfig: model.QueryLogConfig{
AnonymizeClientIp: utils.Ptr(true),
Interval: &interval,
Enabled: utils.Ptr(true),
},
Ignored: []string{"foo.bar"},
})
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
}) })
}) })
Context("StatsConfig", func() { Context("StatsConfig", func() {
It("should read StatsConfig", func() { It("should read StatsConfig", func() {
ts, cl = ClientGet("stats_info.json", "/stats_info") ts, cl = ClientGet("stats_info.json", "/stats/config")
sc, err := cl.StatsConfig() sc, err := cl.StatsConfig()
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
Ω(sc.Interval).Should(Equal(1.0)) Ω(sc.Interval).ShouldNot(BeNil())
Ω(sc.Interval).Should(Equal(float32(1)))
}) })
It("should set StatsConfig", func() { It("should set StatsConfig", func() {
ts, cl = ClientPost("/stats_config", `{"interval":123}`) ts, cl = ClientPost("/stats/config/update", `{"enabled":false,"ignored":null,"interval":123}`)
err := cl.SetStatsConfig(123.0)
var interval float32 = 123
err := cl.SetStatsConfig(&model.PutStatsConfigUpdateRequest{Interval: interval})
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
}) })
}) })
@@ -308,7 +365,8 @@ bar`)
Context("doPost", func() { Context("doPost", func() {
It("should return an error on status code != 200", func() { It("should return an error on status code != 200", func() {
err := cl.SetStatsConfig(123) var interval float32 = 123
err := cl.SetStatsConfig(&model.PutStatsConfigUpdateRequest{Interval: interval})
Ω(err).Should(HaveOccurred()) Ω(err).Should(HaveOccurred())
Ω(err.Error()).Should(Equal("401 Unauthorized")) Ω(err.Error()).Should(Equal("401 Unauthorized"))
}) })
@@ -316,10 +374,10 @@ bar`)
}) })
}) })
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)
@@ -344,3 +402,18 @@ func ClientPost(path string, content ...string) (*httptest.Server, client.Client
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
return ts, cl return ts, cl
} }
func ClientPut(path string, content ...string) (*httptest.Server, client.Client) {
index := 0
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Ω(r.URL.Path).Should(Equal(types.DefaultAPIPath + path))
body, err := io.ReadAll(r.Body)
Ω(err).ShouldNot(HaveOccurred())
Ω(body).Should(Equal([]byte(content[index])))
index++
}))
cl, err := client.New(types.AdGuardInstance{URL: ts.URL, Username: username, Password: password})
Ω(err).ShouldNot(HaveOccurred())
return ts, cl
}

View File

@@ -0,0 +1,144 @@
package client
import (
"context"
"crypto/tls"
"encoding/base64"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"path"
"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")
// New create a new api client.
func New(config types.AdGuardInstance) (Client, error) {
var apiURL string
if config.APIPath == "" {
apiURL = config.URL + "/control"
} else {
apiURL = fmt.Sprintf("%s/%s", config.URL, config.APIPath)
}
u, err := url.Parse(apiURL)
if err != nil {
return nil, err
}
u.Path = path.Clean(u.Path)
httpClient := &http.Client{
Transport: &http.Transport{
// #nosec G402 has to be explicitly enabled
TLSClientConfig: &tls.Config{InsecureSkipVerify: config.InsecureSkipVerify},
},
}
aghClient, err := model.NewClient(u.String(), func(client *model.AdguardHomeClient) error {
client.Client = httpClient
client.RequestEditors = append(client.RequestEditors, func(ctx context.Context, req *http.Request) error {
if config.Username != "" && config.Password != "" {
req.Header.Add("Authorization", "Basic "+basicAuth(config.Username, config.Password))
}
return nil
})
return nil
})
if err != nil {
return nil, err
}
return &apiClient{
host: u.Host,
client: aghClient,
log: l.With("host", u.Host),
}, nil
}
func basicAuth(username, password string) string {
auth := username + ":" + password
return base64.StdEncoding.EncodeToString([]byte(auth))
}
type apiClient struct {
host string
client *model.AdguardHomeClient
log *zap.SugaredLogger
}
func (a apiClient) Host(context.Context) string {
return a.host
}
func (a apiClient) GetServerStatus(ctx context.Context) (*model.ServerStatus, error) {
sr, err := read(ctx, a.client.Status, model.ParseStatusResp)
if err != nil {
return nil, err
}
return sr.JSON200, nil
}
func (a apiClient) GetFilteringStatus(ctx context.Context) (*model.FilterStatus, error) {
sr, err := read(ctx, a.client.FilteringStatus, model.ParseFilteringStatusResp)
if err != nil {
return nil, err
}
return sr.JSON200, nil
}
func (a apiClient) SetFilteringConfig(ctx context.Context, config model.FilterConfig) error {
return write(ctx, config, a.client.FilteringConfig)
}
func write[B any](
ctx context.Context,
body B,
req func(ctx context.Context, body B, reqEditors ...model.RequestEditorFn) (*http.Response, error),
) error {
resp, err := req(ctx, body)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return detailedError(resp)
}
return nil
}
func read[I any](
ctx context.Context,
req func(ctx context.Context, reqEditors ...model.RequestEditorFn) (*http.Response, error),
parse func(rsp *http.Response) (*I, error),
) (*I, error) {
resp, err := req(ctx)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, detailedError(resp)
}
return parse(resp)
}
func detailedError(resp *http.Response) error {
e := resp.Status
body, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
if len(body) > 0 {
e += fmt.Sprintf("(%s)", string(body))
}
return errors.New(e)
}

View File

@@ -0,0 +1,15 @@
package client
import (
"context"
"github.com/bakito/adguardhome-sync/internal/client/model"
)
type Client interface {
Host(ctx context.Context) string
GetServerStatus(ctx context.Context) (*model.ServerStatus, error)
GetFilteringStatus(ctx context.Context) (*model.FilterStatus, error)
SetFilteringConfig(ctx context.Context, config model.FilterConfig) error
}

View File

@@ -0,0 +1,28 @@
package client
import (
"net/http"
"github.com/go-resty/resty/v2"
"github.com/bakito/adguardhome-sync/internal/client/model"
)
var _ model.HttpRequestDoer = &adapter{}
func RestyAdapter(r *resty.Client) model.HttpRequestDoer {
return &adapter{
client: r,
}
}
type adapter struct {
client *resty.Client
}
func (a adapter) Do(req *http.Request) (*http.Response, error) {
r, err := a.client.R().
SetHeaderMultiValues(req.Header).
Execute(req.Method, req.URL.String())
return r.RawResponse, err
}

View File

@@ -0,0 +1,496 @@
package model
import (
"fmt"
"sort"
"strings"
"github.com/jinzhu/copier"
"go.uber.org/zap"
"k8s.io/utils/ptr"
"github.com/bakito/adguardhome-sync/internal/utils"
)
// Clone the config.
func (c *DhcpStatus) Clone() *DhcpStatus {
clone := &DhcpStatus{}
_ = copier.Copy(clone, c)
return clone
}
func (c *DhcpStatus) cleanV4V6() {
if c.V4 != nil && !c.V4.isValid() {
c.V4 = nil
}
if c.V6 != nil && !c.V6.isValid() {
c.V6 = nil
}
}
// CleanAndEquals dhcp server config equal check where V4 and V6 are cleaned in advance.
func (c *DhcpStatus) CleanAndEquals(o *DhcpStatus) bool {
c.cleanV4V6()
o.cleanV4V6()
return c.Equals(o)
}
// Equals dhcp server config equal check.
func (c *DhcpStatus) Equals(o *DhcpStatus) bool {
return utils.JSONEquals(c, o)
}
func (c *DhcpStatus) HasConfig() bool {
return (c.V4 != nil && c.V4.isValid()) || (c.V6 != nil && c.V6.isValid())
}
func (j DhcpConfigV4) isValid() bool {
return j.GatewayIp != nil && *j.GatewayIp != "" &&
j.SubnetMask != nil && *j.SubnetMask != "" &&
j.RangeStart != nil && *j.RangeStart != "" &&
j.RangeEnd != nil && *j.RangeEnd != ""
}
func (j DhcpConfigV6) isValid() bool {
return j.RangeStart != nil && *j.RangeStart != ""
}
type DhcpStaticLeases []DhcpStaticLease
// MergeDhcpStaticLeases the leases.
func MergeDhcpStaticLeases(l, other *[]DhcpStaticLease) (adds, removes DhcpStaticLeases) {
var thisLeases []DhcpStaticLease
var otherLeases []DhcpStaticLease
if l != nil {
thisLeases = *l
}
if other != nil {
otherLeases = *other
}
current := make(map[string]DhcpStaticLease)
for _, le := range thisLeases {
current[le.Mac] = le
}
for _, le := range otherLeases {
if _, ok := current[le.Mac]; ok {
delete(current, le.Mac)
} else {
adds = append(adds, le)
}
}
for _, rr := range current {
removes = append(removes, rr)
}
return adds, removes
}
// Equals dns config equal check.
func (c *DNSConfig) Equals(o *DNSConfig) bool {
cc := c.Clone()
oo := o.Clone()
cc.Sort()
oo.Sort()
return utils.JSONEquals(cc, oo)
}
func (c *DNSConfig) Clone() *DNSConfig {
return utils.Clone(c, &DNSConfig{})
}
// Sort dns config.
func (c *DNSConfig) Sort() {
if c.UpstreamDns != nil {
sort.Strings(*c.UpstreamDns)
}
if c.UpstreamDns != nil {
sort.Strings(*c.BootstrapDns)
}
if c.UpstreamDns != nil {
sort.Strings(*c.LocalPtrUpstreams)
}
}
// Equals access list equal check.
func (al *AccessList) Equals(o *AccessList) bool {
return EqualsStringSlice(al.AllowedClients, o.AllowedClients, true) &&
EqualsStringSlice(al.DisallowedClients, o.DisallowedClients, true) &&
EqualsStringSlice(al.BlockedHosts, o.BlockedHosts, true)
}
func EqualsStringSlice(a, b *[]string, sortIt bool) bool {
if a == nil && b == nil {
return true
}
if a == nil || b == nil {
return false
}
aa := *a
bb := *b
if sortIt {
sort.Strings(aa)
sort.Strings(bb)
}
if len(aa) != len(bb) {
return false
}
for i, v := range aa {
if v != bb[i] {
return false
}
}
return true
}
// Sort clients.
func (cl *Client) Sort() {
if cl.Ids != nil {
sort.Strings(*cl.Ids)
}
if cl.Tags != nil {
sort.Strings(*cl.Tags)
}
if cl.BlockedServices != nil {
sort.Strings(*cl.BlockedServices)
}
if cl.Upstreams != nil {
sort.Strings(*cl.Upstreams)
}
}
// PrepareDiff so we skip it in diff.
func (cl *Client) PrepareDiff() *string {
var tz *string
bss := cl.BlockedServicesSchedule
if bss != nil && bss.Mon == nil && bss.Tue == nil && bss.Wed == nil &&
bss.Thu == nil && bss.Fri == nil && bss.Sat == nil && bss.Sun == nil {
tz = cl.BlockedServicesSchedule.TimeZone
cl.BlockedServicesSchedule.TimeZone = nil
}
return tz
}
// AfterDiff reset after diff.
func (cl *Client) AfterDiff(tz *string) {
if cl.BlockedServicesSchedule != nil {
cl.BlockedServicesSchedule.TimeZone = tz
}
}
// Equals Clients equal check.
func (cl *Client) Equals(o *Client) bool {
cl.Sort()
o.Sort()
bssCl := cl.PrepareDiff()
bssO := o.PrepareDiff()
defer func() {
cl.AfterDiff(bssCl)
o.AfterDiff(bssO)
}()
return utils.JSONEquals(cl, o)
}
// Add ac client.
func (clients *Clients) Add(cl Client) {
if clients.Clients == nil {
clients.Clients = &ClientsArray{cl}
} else {
a := append(*clients.Clients, cl)
clients.Clients = &a
}
}
// Merge merge Clients.
func (clients *Clients) Merge(other *Clients) (adds, removes, updates []*Client) {
current := make(map[string]*Client)
if clients.Clients != nil {
cc := *clients.Clients
for _, client := range cc {
current[*client.Name] = &client
}
}
expected := make(map[string]*Client)
if other.Clients != nil {
oc := *other.Clients
for _, client := range oc {
expected[*client.Name] = &client
}
}
for _, cl := range expected {
if oc, ok := current[*cl.Name]; ok {
if !cl.Equals(oc) {
updates = append(updates, cl)
}
delete(current, *cl.Name)
} else {
adds = append(adds, cl)
}
}
for _, rr := range current {
removes = append(removes, rr)
}
return adds, updates, removes
}
// Key RewriteEntry key.
func (re *RewriteEntry) Key() string {
var d string
var a string
if re.Domain != nil {
d = *re.Domain
}
if re.Answer != nil {
a = *re.Answer
}
return fmt.Sprintf("%s#%s", d, a)
}
// RewriteEntries list of RewriteEntry.
type RewriteEntries []RewriteEntry
// Merge RewriteEntries.
func (rwe *RewriteEntries) Merge(other *RewriteEntries) (adds, removes, duplicates RewriteEntries) {
current := make(map[string]RewriteEntry)
processed := make(map[string]bool)
for _, rr := range *rwe {
if _, ok := processed[rr.Key()]; !ok {
current[rr.Key()] = rr
processed[rr.Key()] = true
} else {
// remove duplicate
removes = append(removes, rr)
}
}
for _, rr := range *other {
if _, ok := current[rr.Key()]; ok {
delete(current, rr.Key())
} else {
if _, ok := processed[rr.Key()]; !ok {
adds = append(adds, rr)
processed[rr.Key()] = true
} else {
// skip duplicate
duplicates = append(duplicates, rr)
}
}
}
for _, rr := range current {
removes = append(removes, rr)
}
return adds, removes, duplicates
}
func MergeFilters(this, other *[]Filter) (adds, updates, removes []Filter) {
if this == nil && other == nil {
return nil, nil, nil
}
current := make(map[string]*Filter)
if this != nil {
for _, fi := range *this {
current[fi.Url] = &fi
}
}
if other != nil {
for _, rr := range *other {
if c, ok := current[rr.Url]; ok {
if !c.Equals(&rr) {
updates = append(updates, rr)
}
delete(current, rr.Url)
} else {
adds = append(adds, rr)
}
}
}
for _, rr := range current {
removes = append(removes, *rr)
}
return adds, updates, removes
}
// Equals Filter equal check.
func (f *Filter) Equals(o *Filter) bool {
return f.Enabled == o.Enabled && f.Url == o.Url && f.Name == o.Name
}
type QueryLogConfigWithIgnored struct {
QueryLogConfig
// Ignored List of host names, which should not be written to log
Ignored []string `json:"ignored,omitempty"`
}
// Equals QueryLogConfig equal check.
func (qlc *QueryLogConfigWithIgnored) Equals(o *QueryLogConfigWithIgnored) bool {
return utils.JSONEquals(qlc, o)
}
// Equals QueryLogConfigInterval equal check.
func (qlc *QueryLogConfigInterval) Equals(o *QueryLogConfigInterval) bool {
return ptrEquals(qlc, o)
}
func ptrEquals[T comparable](a, b *T) bool {
if a == nil && b == nil {
return true
}
var aa T
if a != nil {
aa = *a
}
var bb T
if b != nil {
bb = *b
}
return aa == bb
}
// EnableConfig API struct.
type EnableConfig struct {
Enabled bool `json:"enabled"`
}
func (ssc *SafeSearchConfig) Equals(o *SafeSearchConfig) bool {
return ptrEquals(ssc.Enabled, o.Enabled) &&
ptrEquals(ssc.Bing, o.Bing) &&
ptrEquals(ssc.Duckduckgo, o.Duckduckgo) &&
ptrEquals(ssc.Google, o.Google) &&
ptrEquals(ssc.Pixabay, o.Pixabay) &&
ptrEquals(ssc.Yandex, o.Yandex) &&
ptrEquals(ssc.Youtube, o.Youtube)
}
func (pi *ProfileInfo) Equals(o *ProfileInfo, withTheme bool) bool {
return pi.Language == o.Language && (!withTheme || pi.Theme == o.Theme)
}
func (pi *ProfileInfo) ShouldSyncFor(o *ProfileInfo, withTheme bool) *ProfileInfo {
if pi.Equals(o, withTheme) {
return nil
}
merged := &ProfileInfo{Name: pi.Name, Language: pi.Language, Theme: pi.Theme}
if o.Language != "" {
merged.Language = o.Language
}
if withTheme && o.Theme != "" {
merged.Theme = o.Theme
}
if merged.Name == "" || merged.Language == "" || merged.Equals(pi, false) {
return nil
}
return merged
}
func (bss *BlockedServicesSchedule) Equals(o *BlockedServicesSchedule) bool {
return utils.JSONEquals(bss, o)
}
func (bss *BlockedServicesSchedule) ServicesString() string {
return ArrayString(bss.Ids)
}
func ArrayString(a *[]string) string {
if a == nil {
return "[]"
}
sorted := *a
sort.Strings(sorted)
return fmt.Sprintf("[%s]", strings.Join(sorted, ","))
}
func (c *DNSConfig) Sanitize(l *zap.SugaredLogger) {
// disable UsePrivatePtrResolvers if not configured
// https://github.com/AdguardTeam/AdGuardHome/issues/6820
if c.UsePrivatePtrResolvers != nil && *c.UsePrivatePtrResolvers &&
(c.LocalPtrUpstreams == nil || len(*c.LocalPtrUpstreams) == 0) {
l.Warn(
"disabling replica 'Use private reverse DNS resolvers' as no 'Private reverse DNS servers' are configured on origin",
)
c.UsePrivatePtrResolvers = utils.Ptr(false)
}
}
// Equals GetStatsConfigResponse equal check.
func (sc *GetStatsConfigResponse) Equals(o *GetStatsConfigResponse) bool {
return utils.JSONEquals(sc, o)
}
func NewStats() *Stats {
return &Stats{
NumBlockedFiltering: ptr.To(0),
NumReplacedParental: ptr.To(0),
NumReplacedSafesearch: ptr.To(0),
NumReplacedSafebrowsing: ptr.To(0),
NumDnsQueries: ptr.To(0),
BlockedFiltering: ptr.To(make([]int, 24)),
DnsQueries: ptr.To(make([]int, 24)),
ReplacedParental: ptr.To(make([]int, 24)),
ReplacedSafebrowsing: ptr.To(make([]int, 24)),
}
}
func (s *Stats) Add(other *Stats) {
s.NumBlockedFiltering = addInt(s.NumBlockedFiltering, other.NumBlockedFiltering)
s.NumReplacedSafebrowsing = addInt(s.NumReplacedSafebrowsing, other.NumReplacedSafebrowsing)
s.NumDnsQueries = addInt(s.NumDnsQueries, other.NumDnsQueries)
s.NumReplacedSafesearch = addInt(s.NumReplacedSafesearch, other.NumReplacedSafesearch)
s.NumReplacedParental = addInt(s.NumReplacedParental, other.NumReplacedParental)
s.BlockedFiltering = sumUp(s.BlockedFiltering, other.BlockedFiltering)
s.DnsQueries = sumUp(s.DnsQueries, other.DnsQueries)
s.ReplacedParental = sumUp(s.ReplacedParental, other.ReplacedParental)
s.ReplacedSafebrowsing = sumUp(s.ReplacedSafebrowsing, other.ReplacedSafebrowsing)
}
func addInt(t, add *int) *int {
if add != nil {
return ptr.To(*t + *add)
}
return t
}
func sumUp(t, o *[]int) *[]int {
if o != nil {
tt := *t
oo := *o
var sum []int
for i := range tt {
if len(oo) >= i {
sum = append(sum, tt[i]+oo[i])
}
}
return &sum
}
return t
}
func (c *TlsConfig) Equals(config *TlsConfig) bool {
return utils.JSONEquals(c, config)
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,104 @@
package model
import (
. "github.com/onsi/ginkgo/v2"
"github.com/onsi/gomega"
"go.uber.org/zap"
"github.com/bakito/adguardhome-sync/internal/log"
"github.com/bakito/adguardhome-sync/internal/utils"
)
var _ = Describe("Types", func() {
Context("DhcpConfigV4", func() {
DescribeTable("DhcpConfigV4 should not be valid",
func(v4 DhcpConfigV4) {
gomega.Ω(v4.isValid()).Should(gomega.BeFalse())
},
Entry(`When GatewayIp is nil`, DhcpConfigV4{
GatewayIp: nil,
SubnetMask: utils.Ptr("2.2.2.2"),
RangeStart: utils.Ptr("3.3.3.3"),
RangeEnd: utils.Ptr("4.4.4.4"),
}),
Entry(`When GatewayIp is ""`, DhcpConfigV4{
GatewayIp: utils.Ptr(""),
SubnetMask: utils.Ptr("2.2.2.2"),
RangeStart: utils.Ptr("3.3.3.3"),
RangeEnd: utils.Ptr("4.4.4.4"),
}),
Entry(`When SubnetMask is nil`, DhcpConfigV4{
GatewayIp: utils.Ptr("1.1.1.1"),
SubnetMask: nil,
RangeStart: utils.Ptr("3.3.3.3"),
RangeEnd: utils.Ptr("4.4.4.4"),
}),
Entry(`When SubnetMask is ""`, DhcpConfigV4{
GatewayIp: utils.Ptr("1.1.1.1"),
SubnetMask: utils.Ptr(""),
RangeStart: utils.Ptr("3.3.3.3"),
RangeEnd: utils.Ptr("4.4.4.4"),
}),
Entry(`When SubnetMask is nil`, DhcpConfigV4{
GatewayIp: utils.Ptr("1.1.1.1"),
SubnetMask: utils.Ptr("2.2.2.2"),
RangeStart: nil,
RangeEnd: utils.Ptr("4.4.4.4"),
}),
Entry(`When SubnetMask is ""`, DhcpConfigV4{
GatewayIp: utils.Ptr("1.1.1.1"),
SubnetMask: utils.Ptr("2.2.2.2"),
RangeStart: utils.Ptr(""),
RangeEnd: utils.Ptr("4.4.4.4"),
}),
Entry(`When RangeEnd is nil`, DhcpConfigV4{
GatewayIp: utils.Ptr("1.1.1.1"),
SubnetMask: utils.Ptr("2.2.2.2"),
RangeStart: utils.Ptr("3.3.3.3"),
RangeEnd: nil,
}),
Entry(`When RangeEnd is ""`, DhcpConfigV4{
GatewayIp: utils.Ptr("1.1.1.1"),
SubnetMask: utils.Ptr("2.2.2.2"),
RangeStart: utils.Ptr("3.3.3.3"),
RangeEnd: utils.Ptr(""),
}),
)
})
Context("DhcpConfigV6", func() {
DescribeTable("DhcpConfigV6 should not be valid",
func(v6 DhcpConfigV6) {
gomega.Ω(v6.isValid()).Should(gomega.BeFalse())
},
Entry(`When SubnetMask is nil`, DhcpConfigV6{RangeStart: nil}),
Entry(`When SubnetMask is ""`, DhcpConfigV6{RangeStart: utils.Ptr("")}),
)
})
Context("DNSConfig", func() {
var (
cfg *DNSConfig
l *zap.SugaredLogger
)
BeforeEach(func() {
cfg = &DNSConfig{
UsePrivatePtrResolvers: utils.Ptr(true),
}
l = log.GetLogger("test")
})
Context("Sanitize", func() {
It("should disable UsePrivatePtrResolvers resolvers is nil ", func() {
cfg.LocalPtrUpstreams = nil
cfg.Sanitize(l)
gomega.Ω(cfg.UsePrivatePtrResolvers).ShouldNot(gomega.BeNil())
gomega.Ω(*cfg.UsePrivatePtrResolvers).Should(gomega.Equal(false))
})
It("should disable UsePrivatePtrResolvers resolvers is empty ", func() {
cfg.LocalPtrUpstreams = utils.Ptr([]string{})
cfg.Sanitize(l)
gomega.Ω(cfg.UsePrivatePtrResolvers).ShouldNot(gomega.BeNil())
gomega.Ω(*cfg.UsePrivatePtrResolvers).Should(gomega.Equal(false))
})
})
})
})

View File

@@ -1,4 +1,4 @@
package cmd_test package model_test
import ( import (
"testing" "testing"
@@ -7,7 +7,7 @@ import (
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )
func TestCmd(t *testing.T) { func TestTypes(t *testing.T) {
RegisterFailHandler(Fail) RegisterFailHandler(Fail)
RunSpecs(t, "Cmd Suite") RunSpecs(t, "Model Suite")
} }

View File

@@ -0,0 +1,470 @@
package model_test
import (
"encoding/json"
"os"
"github.com/google/uuid"
. "github.com/onsi/ginkgo/v2"
. "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 (
url string
apiPath string
)
BeforeEach(func() {
url = "https://" + uuid.NewString()
apiPath = "/" + uuid.NewString()
})
Context("FilteringStatus", func() {
It("should correctly parse json", func() {
b, err := os.ReadFile("../../../testdata/filtering-status.json")
fs := &model.FilterStatus{}
Ω(err).ShouldNot(HaveOccurred())
err = json.Unmarshal(b, fs)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("Filters", func() {
Context("Merge", func() {
var (
originFilters []model.Filter
replicaFilters []model.Filter
)
BeforeEach(func() {
originFilters = []model.Filter{}
replicaFilters = []model.Filter{}
})
It("should add a missing filter", func() {
originFilters = append(originFilters, model.Filter{Url: url})
a, u, d := model.MergeFilters(&replicaFilters, &originFilters)
Ω(a).Should(HaveLen(1))
Ω(u).Should(BeEmpty())
Ω(d).Should(BeEmpty())
Ω(a[0].Url).Should(Equal(url))
})
It("should remove additional filter", func() {
replicaFilters = append(replicaFilters, model.Filter{Url: url})
a, u, d := model.MergeFilters(&replicaFilters, &originFilters)
Ω(a).Should(BeEmpty())
Ω(u).Should(BeEmpty())
Ω(d).Should(HaveLen(1))
Ω(d[0].Url).Should(Equal(url))
})
It("should update existing filter when enabled differs", func() {
enabled := true
originFilters = append(originFilters, model.Filter{Url: url, Enabled: enabled})
replicaFilters = append(replicaFilters, model.Filter{Url: url, Enabled: !enabled})
a, u, d := model.MergeFilters(&replicaFilters, &originFilters)
Ω(a).Should(BeEmpty())
Ω(u).Should(HaveLen(1))
Ω(d).Should(BeEmpty())
Ω(u[0].Enabled).Should(Equal(enabled))
})
It("should update existing filter when name differs", func() {
name1 := uuid.NewString()
name2 := uuid.NewString()
originFilters = append(originFilters, model.Filter{Url: url, Name: name1})
replicaFilters = append(replicaFilters, model.Filter{Url: url, Name: name2})
a, u, d := model.MergeFilters(&replicaFilters, &originFilters)
Ω(a).Should(BeEmpty())
Ω(u).Should(HaveLen(1))
Ω(d).Should(BeEmpty())
Ω(u[0].Name).Should(Equal(name1))
})
It("should have no changes", func() {
originFilters = append(originFilters, model.Filter{Url: url})
replicaFilters = append(replicaFilters, model.Filter{Url: url})
a, u, d := model.MergeFilters(&replicaFilters, &originFilters)
Ω(a).Should(BeEmpty())
Ω(u).Should(BeEmpty())
Ω(d).Should(BeEmpty())
})
})
})
Context("AdGuardInstance", func() {
It("should build a key with url and api apiPath", func() {
i := &types.AdGuardInstance{URL: url, APIPath: apiPath}
Ω(i.Key()).Should(Equal(url + "#" + apiPath))
})
})
Context("RewriteEntry", func() {
It("should build a key with url and api apiPath", func() {
domain := uuid.NewString()
answer := uuid.NewString()
re := &model.RewriteEntry{Domain: utils.Ptr(domain), Answer: utils.Ptr(answer)}
Ω(re.Key()).Should(Equal(domain + "#" + answer))
})
})
Context("QueryLogConfig", func() {
Context("Equal", func() {
var (
a *model.QueryLogConfigWithIgnored
b *model.QueryLogConfigWithIgnored
)
BeforeEach(func() {
a = &model.QueryLogConfigWithIgnored{}
b = &model.QueryLogConfigWithIgnored{}
})
It("should be equal", func() {
a.Enabled = utils.Ptr(true)
var interval model.QueryLogConfigInterval = 1
a.Interval = &interval
a.AnonymizeClientIp = utils.Ptr(true)
b.Enabled = utils.Ptr(true)
b.Interval = &interval
b.AnonymizeClientIp = utils.Ptr(true)
Ω(a.Equals(b)).Should(BeTrue())
})
It("should not be equal when enabled differs", func() {
a.Enabled = utils.Ptr(true)
b.Enabled = utils.Ptr(false)
Ω(a.Equals(b)).ShouldNot(BeTrue())
})
It("should not be equal when interval differs", func() {
var interval1 model.QueryLogConfigInterval = 1
var interval2 model.QueryLogConfigInterval = 2
a.Interval = &interval1
b.Interval = &interval2
Ω(a.Equals(b)).ShouldNot(BeTrue())
})
It("should not be equal when anonymizeClientIP differs", func() {
a.AnonymizeClientIp = utils.Ptr(true)
b.AnonymizeClientIp = utils.Ptr(false)
Ω(a.Equals(b)).ShouldNot(BeTrue())
})
})
})
Context("RewriteEntries", func() {
Context("Merge", func() {
var (
originRE model.RewriteEntries
replicaRE model.RewriteEntries
domain string
)
BeforeEach(func() {
originRE = model.RewriteEntries{}
replicaRE = model.RewriteEntries{}
domain = uuid.NewString()
})
It("should add a missing rewrite entry", func() {
originRE = append(originRE, model.RewriteEntry{Domain: utils.Ptr(domain)})
a, r, d := replicaRE.Merge(&originRE)
Ω(a).Should(HaveLen(1))
Ω(r).Should(BeEmpty())
Ω(d).Should(BeEmpty())
Ω(*a[0].Domain).Should(Equal(domain))
})
It("should remove additional rewrite entry", func() {
replicaRE = append(replicaRE, model.RewriteEntry{Domain: utils.Ptr(domain)})
a, r, d := replicaRE.Merge(&originRE)
Ω(a).Should(BeEmpty())
Ω(r).Should(HaveLen(1))
Ω(d).Should(BeEmpty())
Ω(*r[0].Domain).Should(Equal(domain))
})
It("should have no changes", func() {
originRE = append(originRE, model.RewriteEntry{Domain: utils.Ptr(domain)})
replicaRE = append(replicaRE, model.RewriteEntry{Domain: utils.Ptr(domain)})
a, r, d := replicaRE.Merge(&originRE)
Ω(a).Should(BeEmpty())
Ω(r).Should(BeEmpty())
Ω(d).Should(BeEmpty())
})
It("should remove target duplicate", func() {
originRE = append(originRE, model.RewriteEntry{Domain: utils.Ptr(domain)})
replicaRE = append(replicaRE, model.RewriteEntry{Domain: utils.Ptr(domain)})
replicaRE = append(replicaRE, model.RewriteEntry{Domain: utils.Ptr(domain)})
a, r, d := replicaRE.Merge(&originRE)
Ω(a).Should(BeEmpty())
Ω(r).Should(HaveLen(1))
Ω(d).Should(BeEmpty())
})
It("should remove target duplicate", func() {
originRE = append(originRE, model.RewriteEntry{Domain: utils.Ptr(domain)})
originRE = append(originRE, model.RewriteEntry{Domain: utils.Ptr(domain)})
replicaRE = append(replicaRE, model.RewriteEntry{Domain: utils.Ptr(domain)})
a, r, d := replicaRE.Merge(&originRE)
Ω(a).Should(BeEmpty())
Ω(r).Should(BeEmpty())
Ω(d).Should(HaveLen(1))
})
})
})
Context("Config", func() {
var cfg *types.Config
BeforeEach(func() {
cfg = &types.Config{}
})
Context("UniqueReplicas", func() {
It("should be empty if noting defined", func() {
r := cfg.UniqueReplicas()
Ω(r).Should(BeEmpty())
})
It("should be empty if replica url is not set", func() {
cfg.Replica = &types.AdGuardInstance{URL: ""}
r := cfg.UniqueReplicas()
Ω(r).Should(BeEmpty())
})
It("should be empty if replicas url is not set", func() {
cfg.Replicas = []types.AdGuardInstance{{URL: ""}}
r := cfg.UniqueReplicas()
Ω(r).Should(BeEmpty())
})
It("should return only one replica if same url and apiPath", func() {
cfg.Replica = &types.AdGuardInstance{URL: url, APIPath: apiPath}
cfg.Replicas = []types.AdGuardInstance{{URL: url, APIPath: apiPath}, {URL: url, APIPath: apiPath}}
r := cfg.UniqueReplicas()
Ω(r).Should(HaveLen(1))
})
It("should return 3 one replicas if urls are different", func() {
cfg.Replica = &types.AdGuardInstance{URL: url, APIPath: apiPath}
cfg.Replicas = []types.AdGuardInstance{
{URL: url + "1", APIPath: apiPath},
{URL: url, APIPath: apiPath + "1"},
}
r := cfg.UniqueReplicas()
Ω(r).Should(HaveLen(3))
})
It("should set default api apiPath if not set", func() {
cfg.Replica = &types.AdGuardInstance{URL: url}
cfg.Replicas = []types.AdGuardInstance{{URL: url + "1"}}
r := cfg.UniqueReplicas()
Ω(r).Should(HaveLen(2))
Ω(r[0].APIPath).Should(Equal(types.DefaultAPIPath))
Ω(r[1].APIPath).Should(Equal(types.DefaultAPIPath))
})
})
})
Context("Clients", func() {
Context("Merge", func() {
var (
originClients *model.Clients
replicaClients model.Clients
name string
)
BeforeEach(func() {
originClients = &model.Clients{}
replicaClients = model.Clients{}
name = uuid.NewString()
})
It("should add a missing client", func() {
originClients.Add(model.Client{Name: utils.Ptr(name)})
a, u, d := replicaClients.Merge(originClients)
Ω(a).Should(HaveLen(1))
Ω(u).Should(BeEmpty())
Ω(d).Should(BeEmpty())
Ω(*a[0].Name).Should(Equal(name))
})
It("should remove additional client", func() {
replicaClients.Add(model.Client{Name: utils.Ptr(name)})
a, u, d := replicaClients.Merge(originClients)
Ω(a).Should(BeEmpty())
Ω(u).Should(BeEmpty())
Ω(d).Should(HaveLen(1))
Ω(*d[0].Name).Should(Equal(name))
})
It("should update existing client when name differs", func() {
disallowed := true
originClients.Add(model.Client{Name: utils.Ptr(name), FilteringEnabled: utils.Ptr(disallowed)})
replicaClients.Add(model.Client{Name: utils.Ptr(name), FilteringEnabled: utils.Ptr(!disallowed)})
a, u, d := replicaClients.Merge(originClients)
Ω(a).Should(BeEmpty())
Ω(u).Should(HaveLen(1))
Ω(d).Should(BeEmpty())
Ω(*u[0].FilteringEnabled).Should(Equal(disallowed))
})
})
})
Context("Client", func() {
Context("Equals", func() {
var (
cl1 *model.Client
cl2 *model.Client
)
BeforeEach(func() {
cl1 = &model.Client{
Name: utils.Ptr("foo"),
BlockedServicesSchedule: &model.Schedule{TimeZone: utils.Ptr("UTC")},
}
cl2 = &model.Client{
Name: utils.Ptr("foo"),
BlockedServicesSchedule: &model.Schedule{TimeZone: utils.Ptr("Local")},
}
})
It("should equal if only timezone differs on empty blocked service schedule", func() {
Ω(cl1.Equals(cl2)).Should(BeTrue())
})
})
})
Context("BlockedServices", func() {
Context("Equals", func() {
It("should be equal", func() {
s1 := &model.BlockedServicesArray{"a", "b"}
s2 := &model.BlockedServicesArray{"b", "a"}
Ω(model.EqualsStringSlice(s1, s2, true)).Should(BeTrue())
})
It("should not be equal different values", func() {
s1 := &model.BlockedServicesArray{"a", "b"}
s2 := &model.BlockedServicesArray{"B", "a"}
Ω(model.EqualsStringSlice(s1, s2, true)).ShouldNot(BeTrue())
})
It("should not be equal different length", func() {
s1 := &model.BlockedServicesArray{"a", "b"}
s2 := &model.BlockedServicesArray{"b", "a", "c"}
Ω(model.EqualsStringSlice(s1, s2, true)).ShouldNot(BeTrue())
})
})
})
Context("DNSConfig", func() {
Context("Equals", func() {
It("should be equal", func() {
dc1 := &model.DNSConfig{LocalPtrUpstreams: utils.Ptr([]string{"a"})}
dc2 := &model.DNSConfig{LocalPtrUpstreams: utils.Ptr([]string{"a"})}
Ω(dc1.Equals(dc2)).Should(BeTrue())
})
It("should not be equal", func() {
dc1 := &model.DNSConfig{LocalPtrUpstreams: utils.Ptr([]string{"a"})}
dc2 := &model.DNSConfig{LocalPtrUpstreams: utils.Ptr([]string{"b"})}
Ω(dc1.Equals(dc2)).ShouldNot(BeTrue())
})
})
})
Context("DHCPServerConfig", func() {
Context("Equals", func() {
It("should be equal", func() {
dc1 := &model.DhcpStatus{
V4: &model.DhcpConfigV4{
GatewayIp: utils.Ptr("1.2.3.4"),
LeaseDuration: utils.Ptr(123),
RangeStart: utils.Ptr("1.2.3.5"),
RangeEnd: utils.Ptr("1.2.3.6"),
SubnetMask: utils.Ptr("255.255.255.0"),
},
}
dc2 := &model.DhcpStatus{
V4: &model.DhcpConfigV4{
GatewayIp: utils.Ptr("1.2.3.4"),
LeaseDuration: utils.Ptr(123),
RangeStart: utils.Ptr("1.2.3.5"),
RangeEnd: utils.Ptr("1.2.3.6"),
SubnetMask: utils.Ptr("255.255.255.0"),
},
}
Ω(dc1.Equals(dc2)).Should(BeTrue())
})
It("should not be equal", func() {
dc1 := &model.DhcpStatus{
V4: &model.DhcpConfigV4{
GatewayIp: utils.Ptr("1.2.3.3"),
LeaseDuration: utils.Ptr(123),
RangeStart: utils.Ptr("1.2.3.5"),
RangeEnd: utils.Ptr("1.2.3.6"),
SubnetMask: utils.Ptr("255.255.255.0"),
},
}
dc2 := &model.DhcpStatus{
V4: &model.DhcpConfigV4{
GatewayIp: utils.Ptr("1.2.3.4"),
LeaseDuration: utils.Ptr(123),
RangeStart: utils.Ptr("1.2.3.5"),
RangeEnd: utils.Ptr("1.2.3.6"),
SubnetMask: utils.Ptr("255.255.255.0"),
},
}
Ω(dc1.Equals(dc2)).ShouldNot(BeTrue())
})
})
Context("Clone", func() {
It("clone should be equal", func() {
dc1 := &model.DhcpStatus{
V4: &model.DhcpConfigV4{
GatewayIp: utils.Ptr("1.2.3.4"),
LeaseDuration: utils.Ptr(123),
RangeStart: utils.Ptr("1.2.3.5"),
RangeEnd: utils.Ptr("1.2.3.6"),
SubnetMask: utils.Ptr("255.255.255.0"),
},
}
Ω(dc1.Clone().Equals(dc1)).Should(BeTrue())
})
})
Context("HasConfig", func() {
It("should not have a config", func() {
dc1 := &model.DhcpStatus{
V4: &model.DhcpConfigV4{},
V6: &model.DhcpConfigV6{},
}
Ω(dc1.HasConfig()).Should(BeFalse())
})
It("should not have a v4 config with nil IP", func() {
dc1 := &model.DhcpStatus{
V4: &model.DhcpConfigV4{
GatewayIp: nil,
},
V6: &model.DhcpConfigV6{
RangeStart: utils.Ptr("1.2.3.5"),
},
}
Ω(dc1.HasConfig()).Should(BeTrue())
})
It("should not have a v4 config with empty IP", func() {
dc1 := &model.DhcpStatus{
V4: &model.DhcpConfigV4{
GatewayIp: utils.Ptr(""),
},
V6: &model.DhcpConfigV6{
RangeStart: utils.Ptr("1.2.3.5"),
},
}
Ω(dc1.HasConfig()).Should(BeTrue())
})
It("should not have a v6 config", func() {
dc1 := &model.DhcpStatus{
V4: &model.DhcpConfigV4{
GatewayIp: utils.Ptr("1.2.3.4"),
LeaseDuration: utils.Ptr(123),
RangeStart: utils.Ptr("1.2.3.5"),
RangeEnd: utils.Ptr("1.2.3.6"),
SubnetMask: utils.Ptr("255.255.255.0"),
},
V6: &model.DhcpConfigV6{},
}
Ω(dc1.HasConfig()).Should(BeTrue())
})
})
})
})

View File

@@ -0,0 +1,181 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": false,
"definitions": {
"Instance": {
"additionalProperties": false,
"properties": {
"apiPath": {
"type": "string"
},
"autoSetup": {
"type": "boolean"
},
"cookie": {
"type": "string"
},
"dhcpServerEnabled": {
"type": "boolean"
},
"insecureSkipVerify": {
"type": "boolean"
},
"interfaceName": {
"type": "string"
},
"password": {
"type": "string"
},
"url": {
"format": "uri",
"type": "string"
},
"username": {
"type": "string"
},
"webURL": {
"format": "uri",
"type": "string"
},
"requestHeaders": {
"type": "object"
}
},
"type": "object"
}
},
"description": "validates only for valid schema. No required fields, as the can be defined via env ars afterwards.",
"properties": {
"api": {
"additionalProperties": false,
"properties": {
"darkMode": {
"type": "boolean"
},
"metrics": {
"additionalProperties": false,
"properties": {
"enabled": {
"type": "boolean"
},
"queryLogLimit": {
"type": "integer"
},
"scrapeInterval": {
"type": "string"
}
},
"type": "object"
},
"password": {
"type": "string"
},
"port": {
"type": "number"
},
"tls": {
"additionalProperties": false,
"properties": {
"certDir": {
"type": "string"
},
"certName": {
"type": "string"
},
"keyName": {
"type": "string"
}
},
"type": "object"
},
"username": {
"type": "string"
}
},
"type": "object"
},
"continueOnError": {
"type": "boolean"
},
"cron": {
"type": "string"
},
"features": {
"additionalProperties": false,
"properties": {
"clientSettings": {
"type": "boolean"
},
"dhcp": {
"additionalProperties": false,
"properties": {
"serverConfig": {
"type": "boolean"
},
"staticLeases": {
"type": "boolean"
}
},
"type": "object"
},
"dns": {
"additionalProperties": false,
"properties": {
"accessLists": {
"type": "boolean"
},
"rewrites": {
"type": "boolean"
},
"serverConfig": {
"type": "boolean"
}
},
"type": "object"
},
"filters": {
"type": "boolean"
},
"generalSettings": {
"type": "boolean"
},
"queryLogConfig": {
"type": "boolean"
},
"services": {
"type": "boolean"
},
"statsConfig": {
"type": "boolean"
},
"theme": {
"type": "boolean"
},
"tlsConfig": {
"type": "boolean"
}
},
"type": "object"
},
"origin": {
"$ref": "#/definitions/Instance"
},
"printConfigOnly": {
"type": "boolean"
},
"replica": {
"$ref": "#/definitions/Instance"
},
"replicas": {
"items": {
"$ref": "#/definitions/Instance"
},
"type": "array"
},
"runOnStart": {
"type": "boolean"
}
},
"title": "adguardhome-sync Configuration",
"type": "object"
}

129
internal/config/config.go Normal file
View File

@@ -0,0 +1,129 @@
package config
import (
"errors"
"regexp"
"github.com/caarlos0/env/v11"
"github.com/bakito/adguardhome-sync/internal/log"
"github.com/bakito/adguardhome-sync/internal/types"
)
var (
envReplicasURLPattern = regexp.MustCompile(`^REPLICA(\d+)_URL=(.*)`)
logger = log.GetLogger("config")
)
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)
if err != nil {
return nil, err
}
if err := validateSchema(path); err != nil {
return nil, err
}
cfg := initialConfig()
// read yaml config
var content string
if content, err = readFile(cfg, path); err != nil {
return nil, err
}
// overwrite from command flags
if err := readFlags(cfg, flags); err != nil {
return nil, err
}
// *bool field creates issues when already not nil
cfg.Origin.DHCPServerEnabled = nil // origin filed makes no sense to be set.
// keep previously set value
replicaDhcpServer := cfg.Replica.DHCPServerEnabled
cfg.Replica.DHCPServerEnabled = nil
// ignore origin and replicas form env parsing as they are handled separately
replicas := cfg.Replicas
cfg.Replicas = nil
replica := cfg.Replica
cfg.Replica = nil
origin := cfg.Origin
cfg.Origin = nil
// overwrite from env vars
if err := env.Parse(cfg); err != nil {
return nil, err
}
if err := env.ParseWithOptions(origin, env.Options{Prefix: "ORIGIN_"}); err != nil {
return nil, err
}
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
// if not set from env, use previous value
if cfg.Replica.DHCPServerEnabled == nil {
cfg.Replica.DHCPServerEnabled = replicaDhcpServer
}
if cfg.Replica != nil &&
cfg.Replica.URL == "" &&
cfg.Replica.Username == "" {
cfg.Replica = nil
}
if len(cfg.Replicas) > 0 && cfg.Replica != nil {
return nil, errors.New("mixed replica config in use. " +
"Do not use single replica and numbered (list) replica config combined")
}
if cfg.Replica != nil {
cfg.Replicas = []types.AdGuardInstance{*cfg.Replica}
cfg.Replica = nil
}
cfg.Replicas, err = enrichReplicasFromEnv(cfg.Replicas)
return &AppConfig{cfg: cfg, filePath: path, content: content}, err
}
func initialConfig() *types.Config {
return &types.Config{
RunOnStart: true,
Origin: &types.AdGuardInstance{
APIPath: "/control",
},
Replica: &types.AdGuardInstance{
APIPath: "/control",
},
API: types.API{
Port: 8080,
},
Features: types.NewFeatures(true),
}
}

View File

@@ -0,0 +1,15 @@
package config_test
import (
"testing"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/format"
)
func TestCmd(t *testing.T) {
format.TruncatedDiff = false
RegisterFailHandler(Fail)
RunSpecs(t, "Config Suite")
}

View File

@@ -0,0 +1,260 @@
package config_test
import (
"os"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
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() {
Context("Get", func() {
var (
flags *flagsmock.MockFlags
mockCtrl *gm.Controller
changedEnvVars []string
setEnv = func(name, value string) {
_ = os.Setenv(name, value)
changedEnvVars = append(changedEnvVars, name)
}
)
BeforeEach(func() {
mockCtrl = gm.NewController(GinkgoT())
flags = flagsmock.NewMockFlags(mockCtrl)
changedEnvVars = nil
})
AfterEach(func() {
for _, envVar := range changedEnvVars {
_ = os.Unsetenv(envVar)
println(envVar)
}
defer mockCtrl.Finish()
})
Context("Get", func() {
Context("Mixed Config", func() {
It("should have the origin URL from the config file", func() {
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
_, err := config.Get("../../testdata/config_test_replicas_and_replica.yaml", flags)
Ω(err).Should(HaveOccurred())
Ω(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() {
It("should have the origin URL 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())
Ω(cfg.Get().Origin.URL).Should(Equal("https://origin-file:443"))
})
It("should have the origin URL from the config flags", func() {
flags.EXPECT().Changed(config.FlagOriginURL).Return(true).AnyTimes()
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
flags.EXPECT().GetString(config.FlagOriginURL).Return("https://origin-flag:443", nil).AnyTimes()
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Get().Origin.URL).Should(Equal("https://origin-flag:443"))
})
It("should have the origin URL from the config env var", func() {
setEnv("ORIGIN_URL", "https://origin-env:443")
flags.EXPECT().Changed(config.FlagOriginURL).Return(true).AnyTimes()
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
flags.EXPECT().GetString(config.FlagOriginURL).Return("https://origin-flag:443", nil).AnyTimes()
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Get().Origin.URL).Should(Equal("https://origin-env:443"))
})
})
Context("Replica insecure skip verify", func() {
It("should have the insecure skip verify from the config file", func() {
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
cfg, err := config.Get("../../testdata/config_test_replica.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Get().Replicas[0].InsecureSkipVerify).Should(BeFalse())
})
It("should have the insecure skip verify from the config flags", func() {
flags.EXPECT().Changed(config.FlagReplicaISV).Return(true).AnyTimes()
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
flags.EXPECT().GetBool(config.FlagReplicaISV).Return(true, nil).AnyTimes()
cfg, err := config.Get("../../testdata/config_test_replica.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Get().Replicas[0].InsecureSkipVerify).Should(BeTrue())
})
It("should have the insecure skip verify from the config env var", func() {
setEnv("REPLICA_INSECURE_SKIP_VERIFY", "false")
flags.EXPECT().Changed(config.FlagReplicaISV).Return(true).AnyTimes()
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
flags.EXPECT().GetBool(config.FlagReplicaISV).Return(true, nil).AnyTimes()
cfg, err := config.Get("../../testdata/config_test_replica.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Get().Replicas[0].InsecureSkipVerify).Should(BeFalse())
})
})
Context("Replica 1 insecure skip verify", func() {
It("should have the insecure skip verify 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())
Ω(cfg.Get().Replicas[0].InsecureSkipVerify).Should(BeFalse())
})
It("should have the insecure skip verify from the config env var", func() {
setEnv("REPLICA1_INSECURE_SKIP_VERIFY", "true")
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].InsecureSkipVerify).Should(BeTrue())
})
})
Context("API Port", func() {
It("should have the api port 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())
Ω(cfg.Get().API.Port).Should(Equal(9090))
})
It("should have the api port from the config flags", func() {
flags.EXPECT().Changed(config.FlagAPIPort).Return(true).AnyTimes()
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
flags.EXPECT().GetInt(config.FlagAPIPort).Return(9990, nil).AnyTimes()
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Get().API.Port).Should(Equal(9990))
})
It("should have the api port from the config env var", func() {
setEnv("API_PORT", "9999")
flags.EXPECT().Changed(config.FlagAPIPort).Return(true).AnyTimes()
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
flags.EXPECT().GetInt(config.FlagAPIPort).Return(9990, nil).AnyTimes()
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Get().API.Port).Should(Equal(9999))
})
})
Context("Replica DHCPServerEnabled", func() {
It("should have the dhcp server enabled from the config file", func() {
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
cfg, err := config.Get("../../testdata/config_test_replica.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Get().Replicas[0].DHCPServerEnabled).ShouldNot(BeNil())
Ω(*cfg.Get().Replicas[0].DHCPServerEnabled).Should(BeFalse())
})
})
Context("Replica 1 DHCPServerEnabled", func() {
It("should have the dhcp server enabled 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())
Ω(cfg.Get().Replicas[0].DHCPServerEnabled).ShouldNot(BeNil())
Ω(*cfg.Get().Replicas[0].DHCPServerEnabled).Should(BeFalse())
})
})
Context("API Port", func() {
It("should have the api port 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())
Ω(cfg.Get().API.Port).Should(Equal(9090))
})
It("should have the api port from the config flags", func() {
flags.EXPECT().Changed(config.FlagAPIPort).Return(true).AnyTimes()
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
flags.EXPECT().GetInt(config.FlagAPIPort).Return(9990, nil).AnyTimes()
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Get().API.Port).Should(Equal(9990))
})
It("should have the api port from the config env var", func() {
setEnv("API_PORT", "9999")
flags.EXPECT().Changed(config.FlagAPIPort).Return(true).AnyTimes()
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
flags.EXPECT().GetInt(config.FlagAPIPort).Return(9990, nil).AnyTimes()
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Get().API.Port).Should(Equal(9999))
})
})
Context("Feature DNS Server Config", func() {
It("should have the feature dns server config 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())
Ω(cfg.Get().Features.DNS.ServerConfig).Should(BeFalse())
})
It("should have the feature dns server config from the config flags", func() {
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)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Get().Features.DNS.ServerConfig).Should(BeTrue())
})
It("should have the feature dns server config from the config env var", func() {
setEnv("FEATURES_DNS_SERVER_CONFIG", "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)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Get().Features.DNS.ServerConfig).Should(BeFalse())
})
})
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())
Ω(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
View 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
}

View File

@@ -0,0 +1,25 @@
package config
import (
"os"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("Config", func() {
Context("env", func() {
Context("enrichReplicasFromEnv", func() {
It("should have the origin URL from the config env var", func() {
_ = os.Setenv("REPLICA0_URL", "https://origin-env:443")
defer func() {
_ = os.Unsetenv("REPLICA0_URL")
}()
_, err := enrichReplicasFromEnv(nil)
Ω(err).Should(HaveOccurred())
Ω(err.Error()).Should(ContainSubstring("numbered replica env variables must have a number id >= 1"))
})
})
})
})

37
internal/config/file.go Normal file
View File

@@ -0,0 +1,37 @@
package config
import (
"os"
"path/filepath"
"gopkg.in/yaml.v3"
"github.com/bakito/adguardhome-sync/internal/types"
)
func readFile(cfg *types.Config, path string) (string, error) {
var content string
if _, err := os.Stat(path); err == nil {
b, err := os.ReadFile(path)
if err != nil {
return "", err
}
content = string(b)
if err := yaml.Unmarshal(b, cfg); err != nil {
return "", err
}
}
return content, nil
}
func configFilePath(configFile string) (string, error) {
if configFile == "" {
// Find home directory.
home, err := os.UserHomeDir()
if err != nil {
return "", err
}
return filepath.Join(home, ".adguardhome-sync.yaml"), nil
}
return configFile, nil
}

View File

@@ -0,0 +1,30 @@
package config
import (
"os"
"path/filepath"
"github.com/google/uuid"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("Config", func() {
Context("configFilePath", func() {
It("should return the same value", func() {
path := uuid.NewString()
result, err := configFilePath(path)
Ω(err).ShouldNot(HaveOccurred())
Ω(result).Should(Equal(path))
})
It("should the file in HOME dir", func() {
home, err := os.UserHomeDir()
Ω(err).ShouldNot(HaveOccurred())
result, err := configFilePath("")
Ω(err).ShouldNot(HaveOccurred())
Ω(result).Should(Equal(filepath.Join(home, ".adguardhome-sync.yaml")))
})
})
})

View File

@@ -0,0 +1,45 @@
package config
const (
FlagCron = "cron"
FlagRunOnStart = "runOnStart"
FlagPrintConfigOnly = "printConfigOnly"
FlagContinueOnError = "continueOnError"
FlagAPIPort = "api-port"
FlagAPIUsername = "api-username"
FlagAPIPassword = "api-password"
FlagAPIDarkMode = "api-dark-mode"
FlagFeatureDhcpServerConfig = "feature-dhcp-server-config"
FlagFeatureDhcpStaticLeases = "feature-dhcp-static-leases"
FlagFeatureDNSServerConfig = "feature-dns-server-config"
FlagFeatureDNSAccessLists = "feature-dns-access-lists"
FlagFeatureDNSRewrites = "feature-dns-rewrites"
FlagFeatureGeneral = "feature-general-settings"
FlagFeatureQueryLog = "feature-query-log-config"
FlagFeatureStats = "feature-stats-config"
FlagFeatureClient = "feature-client-settings"
FlagFeatureServices = "feature-services"
FlagFeatureFilters = "feature-filters"
FlagFeatureTLSConfig = "feature-tls-config"
FlagOriginURL = "origin-url"
FlagOriginWebURL = "origin-web-url"
FlagOriginAPIPath = "origin-api-path"
FlagOriginUsername = "origin-username"
FlagOriginPassword = "origin-password"
FlagOriginCookie = "origin-cookie"
FlagOriginISV = "origin-insecure-skip-verify"
FlagReplicaURL = "replica-url"
FlagReplicaWebURL = "replica-web-url"
FlagReplicaAPIPath = "replica-api-path"
FlagReplicaUsername = "replica-username"
FlagReplicaPassword = "replica-password"
FlagReplicaCookie = "replica-cookie"
FlagReplicaISV = "replica-insecure-skip-verify"
FlagReplicaAutoSetup = "replica-auto-setup"
FlagReplicaInterfaceName = "replica-interface-name"
)

268
internal/config/flags.go Normal file
View File

@@ -0,0 +1,268 @@
package config
import (
"github.com/bakito/adguardhome-sync/internal/types"
)
func readFlags(cfg *types.Config, flags Flags) error {
if flags == nil {
return nil
}
fr := &flagReader{
cfg: cfg,
flags: flags,
}
if err := fr.readRootFlags(); err != nil {
return err
}
if err := fr.readAPIFlags(); err != nil {
return err
}
if err := fr.readFeatureFlags(); err != nil {
return err
}
if err := fr.readOriginFlags(); err != nil {
return err
}
return fr.readReplicaFlags()
}
type flagReader struct {
cfg *types.Config
flags Flags
}
func (fr *flagReader) readReplicaFlags() error {
if err := fr.setStringFlag(FlagReplicaURL, func(cgf *types.Config, value string) {
fr.cfg.Replica.URL = value
}); err != nil {
return err
}
if err := fr.setStringFlag(FlagReplicaWebURL, func(cgf *types.Config, value string) {
fr.cfg.Replica.WebURL = value
}); err != nil {
return err
}
if err := fr.setStringFlag(FlagReplicaAPIPath, func(cgf *types.Config, value string) {
fr.cfg.Replica.APIPath = value
}); err != nil {
return err
}
if err := fr.setStringFlag(FlagReplicaUsername, func(cgf *types.Config, value string) {
fr.cfg.Replica.Username = value
}); err != nil {
return err
}
if err := fr.setStringFlag(FlagReplicaPassword, func(cgf *types.Config, value string) {
fr.cfg.Replica.Password = value
}); err != nil {
return err
}
if err := fr.setStringFlag(FlagReplicaCookie, func(cgf *types.Config, value string) {
fr.cfg.Replica.Cookie = value
}); err != nil {
return err
}
if err := fr.setBoolFlag(FlagReplicaISV, func(cgf *types.Config, value bool) {
fr.cfg.Replica.InsecureSkipVerify = value
}); err != nil {
return err
}
if err := fr.setBoolFlag(FlagReplicaAutoSetup, func(cgf *types.Config, value bool) {
fr.cfg.Replica.AutoSetup = value
}); err != nil {
return err
}
return fr.setStringFlag(FlagReplicaInterfaceName, func(cgf *types.Config, value string) {
fr.cfg.Replica.InterfaceName = value
})
}
func (fr *flagReader) readOriginFlags() error {
if err := fr.setStringFlag(FlagOriginURL, func(cgf *types.Config, value string) {
fr.cfg.Origin.URL = value
}); err != nil {
return err
}
if err := fr.setStringFlag(FlagOriginWebURL, func(cgf *types.Config, value string) {
fr.cfg.Origin.WebURL = value
}); err != nil {
return err
}
if err := fr.setStringFlag(FlagOriginAPIPath, func(cgf *types.Config, value string) {
fr.cfg.Origin.APIPath = value
}); err != nil {
return err
}
if err := fr.setStringFlag(FlagOriginUsername, func(cgf *types.Config, value string) {
fr.cfg.Origin.Username = value
}); err != nil {
return err
}
if err := fr.setStringFlag(FlagOriginPassword, func(cgf *types.Config, value string) {
fr.cfg.Origin.Password = value
}); err != nil {
return err
}
if err := fr.setStringFlag(FlagOriginCookie, func(cgf *types.Config, value string) {
fr.cfg.Origin.Cookie = value
}); err != nil {
return err
}
return fr.setBoolFlag(FlagOriginISV, func(cgf *types.Config, value bool) {
fr.cfg.Origin.InsecureSkipVerify = value
})
}
func (fr *flagReader) readFeatureFlags() error {
if err := fr.setBoolFlag(FlagFeatureDhcpServerConfig, func(cgf *types.Config, value bool) {
fr.cfg.Features.DHCP.ServerConfig = value
}); err != nil {
return err
}
if err := fr.setBoolFlag(FlagFeatureDhcpStaticLeases, func(cgf *types.Config, value bool) {
fr.cfg.Features.DHCP.StaticLeases = value
}); err != nil {
return err
}
if err := fr.setBoolFlag(FlagFeatureDNSServerConfig, func(cgf *types.Config, value bool) {
fr.cfg.Features.DNS.ServerConfig = value
}); err != nil {
return err
}
if err := fr.setBoolFlag(FlagFeatureDNSAccessLists, func(cgf *types.Config, value bool) {
fr.cfg.Features.DNS.AccessLists = value
}); err != nil {
return err
}
if err := fr.setBoolFlag(FlagFeatureDNSRewrites, func(cgf *types.Config, value bool) {
fr.cfg.Features.DNS.Rewrites = value
}); err != nil {
return err
}
if err := fr.setBoolFlag(FlagFeatureGeneral, func(cgf *types.Config, value bool) {
fr.cfg.Features.GeneralSettings = value
}); err != nil {
return err
}
if err := fr.setBoolFlag(FlagFeatureQueryLog, func(cgf *types.Config, value bool) {
fr.cfg.Features.QueryLogConfig = value
}); err != nil {
return err
}
if err := fr.setBoolFlag(FlagFeatureStats, func(cgf *types.Config, value bool) {
fr.cfg.Features.StatsConfig = value
}); err != nil {
return err
}
if err := fr.setBoolFlag(FlagFeatureClient, func(cgf *types.Config, value bool) {
fr.cfg.Features.ClientSettings = value
}); err != nil {
return err
}
if err := fr.setBoolFlag(FlagFeatureServices, func(cgf *types.Config, value bool) {
fr.cfg.Features.Services = value
}); err != nil {
return err
}
if err := fr.setBoolFlag(FlagFeatureFilters, func(cgf *types.Config, value bool) {
fr.cfg.Features.Filters = value
}); err != nil {
return err
}
return fr.setBoolFlag(FlagFeatureTLSConfig, func(cgf *types.Config, value bool) {
fr.cfg.Features.TLSConfig = value
})
}
func (fr *flagReader) readAPIFlags() error {
if err := fr.setIntFlag(FlagAPIPort, func(cgf *types.Config, value int) {
fr.cfg.API.Port = value
}); err != nil {
return err
}
if err := fr.setStringFlag(FlagAPIUsername, func(cgf *types.Config, value string) {
fr.cfg.API.Username = value
}); err != nil {
return err
}
if err := fr.setStringFlag(FlagAPIPassword, func(cgf *types.Config, value string) {
fr.cfg.API.Password = value
}); err != nil {
return err
}
return fr.setBoolFlag(FlagAPIDarkMode, func(cgf *types.Config, value bool) {
fr.cfg.API.DarkMode = value
})
}
func (fr *flagReader) readRootFlags() error {
if err := fr.setStringFlag(FlagCron, func(cgf *types.Config, value string) {
fr.cfg.Cron = value
}); err != nil {
return err
}
if err := fr.setBoolFlag(FlagRunOnStart, func(cgf *types.Config, value bool) {
fr.cfg.RunOnStart = value
}); err != nil {
return err
}
if err := fr.setBoolFlag(FlagPrintConfigOnly, func(cgf *types.Config, value bool) {
fr.cfg.PrintConfigOnly = value
}); err != nil {
return err
}
return fr.setBoolFlag(FlagContinueOnError, func(cgf *types.Config, value bool) {
fr.cfg.ContinueOnError = value
})
}
type Flags interface {
Changed(name string) bool
GetString(name string) (string, error)
GetInt(name string) (int, error)
GetBool(name string) (bool, error)
}
func (fr *flagReader) setStringFlag(name string, cb callback[string]) (err error) {
if fr.flags.Changed(name) {
var value string
if value, err = fr.flags.GetString(name); err != nil {
return err
}
cb(fr.cfg, value)
}
return nil
}
func (fr *flagReader) setBoolFlag(name string, cb callback[bool]) (err error) {
if fr.flags.Changed(name) {
var value bool
if value, err = fr.flags.GetBool(name); err != nil {
return err
}
cb(fr.cfg, value)
}
return nil
}
func (fr *flagReader) setIntFlag(name string, cb callback[int]) (err error) {
if fr.flags.Changed(name) {
var value int
if value, err = fr.flags.GetInt(name); err != nil {
return err
}
cb(fr.cfg, value)
}
return nil
}
type callback[T any] func(cgf *types.Config, value T)

View File

@@ -0,0 +1,237 @@
package config
import (
"strings"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
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 (
cfg *types.Config
flags *flagsmock.MockFlags
mockCtrl *gm.Controller
)
BeforeEach(func() {
cfg = &types.Config{
Origin: &types.AdGuardInstance{},
Replica: &types.AdGuardInstance{},
Features: types.Features{
DNS: types.DNS{
AccessLists: true,
ServerConfig: true,
Rewrites: true,
},
DHCP: types.DHCP{
ServerConfig: true,
StaticLeases: true,
},
GeneralSettings: true,
QueryLogConfig: true,
StatsConfig: true,
ClientSettings: true,
Services: true,
Filters: true,
},
}
mockCtrl = gm.NewController(GinkgoT())
flags = flagsmock.NewMockFlags(mockCtrl)
})
AfterEach(func() {
defer mockCtrl.Finish()
})
Context("readFlags", func() {
It("should not change the config with nil flags", func() {
clone := cfg.DeepCopy()
err := readFlags(cfg, nil)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg).Should(Equal(clone))
})
It("should not change the config with no changed flags", func() {
clone := cfg.DeepCopy()
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
err := readFlags(cfg, flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg).Should(Equal(clone))
})
})
Context("readFeatureFlags", func() {
It("should disable all flags", func() {
flags.EXPECT().Changed(gm.Any()).DoAndReturn(func(name string) bool {
return strings.HasPrefix(name, "feature")
}).AnyTimes()
flags.EXPECT().GetBool(gm.Any()).Return(false, nil).AnyTimes()
err := readFlags(cfg, flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Features).Should(Equal(types.Features{
DNS: types.DNS{
AccessLists: false,
ServerConfig: false,
Rewrites: false,
},
DHCP: types.DHCP{
ServerConfig: false,
StaticLeases: false,
},
GeneralSettings: false,
QueryLogConfig: false,
StatsConfig: false,
ClientSettings: false,
Services: false,
Filters: false,
}))
})
})
Context("readAPIFlags", func() {
It("should change all values", func() {
cfg.API = types.API{
Port: 1111,
Username: "2222",
Password: "3333",
DarkMode: false,
}
flags.EXPECT().Changed(gm.Any()).DoAndReturn(func(name string) bool {
return strings.HasPrefix(name, "api")
}).AnyTimes()
flags.EXPECT().GetInt(FlagAPIPort).Return(9999, nil)
flags.EXPECT().GetString(FlagAPIUsername).Return("aaaa", nil)
flags.EXPECT().GetString(FlagAPIPassword).Return("bbbb", nil)
flags.EXPECT().GetBool(FlagAPIDarkMode).Return(true, nil)
err := readFlags(cfg, flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.API).Should(Equal(types.API{
Port: 9999,
Username: "aaaa",
Password: "bbbb",
DarkMode: true,
}))
})
})
Context("readRootFlags", func() {
It("should change all values", func() {
cfg.Cron = "*/10 * * * *"
cfg.PrintConfigOnly = false
cfg.ContinueOnError = false
cfg.RunOnStart = false
flags.EXPECT().Changed(FlagCron).Return(true)
flags.EXPECT().Changed(FlagRunOnStart).Return(true)
flags.EXPECT().Changed(FlagPrintConfigOnly).Return(true)
flags.EXPECT().Changed(FlagContinueOnError).Return(true)
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
flags.EXPECT().GetString(FlagCron).Return("*/30 * * * *", nil)
flags.EXPECT().GetBool(FlagRunOnStart).Return(true, nil)
flags.EXPECT().GetBool(FlagPrintConfigOnly).Return(true, nil)
flags.EXPECT().GetBool(FlagContinueOnError).Return(true, nil)
err := readFlags(cfg, flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Cron).Should(Equal("*/30 * * * *"))
Ω(cfg.RunOnStart).Should(BeTrue())
Ω(cfg.PrintConfigOnly).Should(BeTrue())
Ω(cfg.ContinueOnError).Should(BeTrue())
})
})
Context("readOriginFlags", func() {
It("should change all values", func() {
cfg.Origin = &types.AdGuardInstance{
URL: "1",
WebURL: "2",
APIPath: "3",
Username: "4",
Password: "5",
Cookie: "6",
InsecureSkipVerify: false,
}
flags.EXPECT().Changed(FlagOriginURL).Return(true)
flags.EXPECT().Changed(FlagOriginWebURL).Return(true)
flags.EXPECT().Changed(FlagOriginAPIPath).Return(true)
flags.EXPECT().Changed(FlagOriginUsername).Return(true)
flags.EXPECT().Changed(FlagOriginPassword).Return(true)
flags.EXPECT().Changed(FlagOriginCookie).Return(true)
flags.EXPECT().Changed(FlagOriginISV).Return(true)
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
flags.EXPECT().GetString(FlagOriginURL).Return("a", nil)
flags.EXPECT().GetString(FlagOriginWebURL).Return("b", nil)
flags.EXPECT().GetString(FlagOriginAPIPath).Return("c", nil)
flags.EXPECT().GetString(FlagOriginUsername).Return("d", nil)
flags.EXPECT().GetString(FlagOriginPassword).Return("e", nil)
flags.EXPECT().GetString(FlagOriginCookie).Return("f", nil)
flags.EXPECT().GetBool(FlagOriginISV).Return(true, nil)
err := readFlags(cfg, flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Origin).Should(Equal(&types.AdGuardInstance{
URL: "a",
WebURL: "b",
APIPath: "c",
Username: "d",
Password: "e",
Cookie: "f",
InsecureSkipVerify: true,
}))
})
})
Context("readReplicaFlags", func() {
It("should change all values", func() {
cfg.Replica = &types.AdGuardInstance{
URL: "1",
WebURL: "2",
APIPath: "3",
Username: "4",
Password: "5",
Cookie: "6",
InsecureSkipVerify: false,
AutoSetup: false,
InterfaceName: "7",
}
flags.EXPECT().Changed(FlagReplicaURL).Return(true)
flags.EXPECT().Changed(FlagReplicaWebURL).Return(true)
flags.EXPECT().Changed(FlagReplicaAPIPath).Return(true)
flags.EXPECT().Changed(FlagReplicaUsername).Return(true)
flags.EXPECT().Changed(FlagReplicaPassword).Return(true)
flags.EXPECT().Changed(FlagReplicaCookie).Return(true)
flags.EXPECT().Changed(FlagReplicaISV).Return(true)
flags.EXPECT().Changed(FlagReplicaAutoSetup).Return(true)
flags.EXPECT().Changed(FlagReplicaInterfaceName).Return(true)
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
flags.EXPECT().GetString(FlagReplicaURL).Return("a", nil)
flags.EXPECT().GetString(FlagReplicaWebURL).Return("b", nil)
flags.EXPECT().GetString(FlagReplicaAPIPath).Return("c", nil)
flags.EXPECT().GetString(FlagReplicaUsername).Return("d", nil)
flags.EXPECT().GetString(FlagReplicaPassword).Return("e", nil)
flags.EXPECT().GetString(FlagReplicaCookie).Return("f", nil)
flags.EXPECT().GetBool(FlagReplicaISV).Return(true, nil)
flags.EXPECT().GetBool(FlagReplicaAutoSetup).Return(true, nil)
flags.EXPECT().GetString(FlagReplicaInterfaceName).Return("g", nil)
err := readFlags(cfg, flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replica).Should(Equal(&types.AdGuardInstance{
URL: "a",
WebURL: "b",
APIPath: "c",
Username: "d",
Password: "e",
Cookie: "f",
InsecureSkipVerify: true,
AutoSetup: true,
InterfaceName: "g",
}))
})
})
})

View 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
}

View 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 -->

View 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)
}

View File

@@ -0,0 +1,59 @@
package config
import (
_ "embed"
"fmt"
"os"
"strings"
"github.com/santhosh-tekuri/jsonschema/v6"
"gopkg.in/yaml.v3"
)
const schemaURL = "config-schema.json"
//go:embed config-schema.json
var schemaData string
func validateSchema(cfgFile string) error {
// ignore if file not exists
if _, err := os.Stat(cfgFile); err != nil {
// Config file does not exist or is not readable - ignore it
//nolint:nilerr
return nil
}
// Load YAML file
yamlContent, err := os.ReadFile(cfgFile)
if err != nil {
return fmt.Errorf("config file %q is invalid: %w", cfgFile, err)
}
return validateYAML(yamlContent)
}
func validateYAML(yamlContent []byte) error {
if yamlContent == nil || strings.TrimSpace(string(yamlContent)) == "" {
return nil
}
// Convert YAML to JSON
var yamlData any
err := yaml.Unmarshal(yamlContent, &yamlData)
if err != nil {
return err
}
// Load JSON schema
sch, err := jsonschema.UnmarshalJSON(strings.NewReader(schemaData))
if err != nil {
return err
}
c := jsonschema.NewCompiler()
if err := c.AddResource(schemaURL, sch); err != nil {
return err
}
schema := c.MustCompile(schemaURL)
// validateSchema
return schema.Validate(yamlData)
}

View File

@@ -0,0 +1,45 @@
package config
import (
"github.com/go-faker/faker/v4"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"gopkg.in/yaml.v3"
"github.com/bakito/adguardhome-sync/internal/types"
)
var _ = Describe("Config", func() {
Context("validateSchema", func() {
DescribeTable("validateSchema config",
func(configFile string, expectFail bool) {
err := validateSchema(configFile)
if expectFail {
Ω(err).Should(HaveOccurred())
} else {
Ω(err).ShouldNot(HaveOccurred())
}
},
Entry(`Should be valid`, "../../testdata/config/config-valid.yaml", false),
Entry(`Should be valid if file doesn't exist`, "../../testdata/config/foo.bar", false),
Entry(`Should fail if file is not yaml`, "../../go.mod", true),
)
It("validate config with all fields randomly populated", func() {
cfg := &types.Config{}
err := faker.FakeData(cfg)
Ω(err).ShouldNot(HaveOccurred())
data, err := yaml.Marshal(&cfg)
Ω(err).ShouldNot(HaveOccurred())
err = validateYAML(data)
Ω(err).ShouldNot(HaveOccurred())
})
It("validate config with empty file", func() {
var data []byte
err := validateYAML(data)
Ω(err).ShouldNot(HaveOccurred())
})
})
})

View File

@@ -10,6 +10,7 @@ import (
const ( const (
logHistorySize = 50 logHistorySize = 50
envLogLevel = "LOG_LEVEL" envLogLevel = "LOG_LEVEL"
envLogFormat = "LOG_FORMAT"
) )
var ( var (
@@ -17,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()
} }
@@ -31,14 +32,23 @@ func init() {
} }
} }
format := "console"
if fmt, ok := os.LookupEnv(envLogFormat); ok {
format = fmt
}
cfg := zap.Config{ cfg := zap.Config{
Level: zap.NewAtomicLevelAt(level), Level: zap.NewAtomicLevelAt(level),
Development: false, Development: false,
Encoding: "console", Encoding: format,
EncoderConfig: zap.NewDevelopmentEncoderConfig(), EncoderConfig: zap.NewProductionEncoderConfig(),
OutputPaths: []string{"stdout"}, OutputPaths: []string{"stdout"},
ErrorOutputPaths: []string{"stderr"}, ErrorOutputPaths: []string{"stderr"},
} }
cfg.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
cfg.EncoderConfig.EncodeDuration = zapcore.StringDurationEncoder
cfg.EncoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
opt := zap.WrapCore(func(c zapcore.Core) zapcore.Core { opt := zap.WrapCore(func(c zapcore.Core) zapcore.Core {
return zapcore.NewTee(c, &logList{ return zapcore.NewTee(c, &logList{
enc: zapcore.NewConsoleEncoder(cfg.EncoderConfig), enc: zapcore.NewConsoleEncoder(cfg.EncoderConfig),
@@ -91,11 +101,16 @@ 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.
func Clear() {
logs = nil
}
func addFields(enc zapcore.ObjectEncoder, fields []zapcore.Field) { func addFields(enc zapcore.ObjectEncoder, fields []zapcore.Field) {
for i := range fields { for i := range fields {
fields[i].AddTo(enc) fields[i].AddTo(enc)

View File

@@ -0,0 +1,14 @@
package metrics
import (
"github.com/gin-gonic/gin"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func Handler() gin.HandlerFunc {
h := promhttp.Handler()
return func(c *gin.Context) {
h.ServeHTTP(c.Writer, c.Request)
}
}

297
internal/metrics/metrics.go Normal file
View File

@@ -0,0 +1,297 @@
package metrics
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/bakito/adguardhome-sync/internal/client/model"
"github.com/bakito/adguardhome-sync/internal/log"
)
const StatsTotal = "total"
var (
l = log.GetLogger("metrics")
// avgProcessingTime - Average processing time for a DNS query.
avgProcessingTime = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "avg_processing_time",
Namespace: "adguard",
Help: "This represent the average processing time for a DNS query in s",
},
[]string{"hostname"},
)
// dnsQueries - Number of DNS queries.
dnsQueries = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "num_dns_queries",
Namespace: "adguard",
Help: "Number of DNS queries",
},
[]string{"hostname"},
)
// blockedFiltering - Number of DNS queries blocked.
blockedFiltering = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "num_blocked_filtering",
Namespace: "adguard",
Help: "This represent the number of domains blocked",
},
[]string{"hostname"},
)
// parentalFiltering - Number of DNS queries replaced by parental control.
parentalFiltering = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "num_replaced_parental",
Namespace: "adguard",
Help: "This represent the number of domains blocked (parental)",
},
[]string{"hostname"},
)
// safeBrowsingFiltering - Number of DNS queries replaced by safe browsing.
safeBrowsingFiltering = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "num_replaced_safebrowsing",
Namespace: "adguard",
Help: "This represent the number of domains blocked (safe browsing)",
},
[]string{"hostname"},
)
// safeSearchFiltering - Number of DNS queries replaced by safe search.
safeSearchFiltering = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "num_replaced_safesearch",
Namespace: "adguard",
Help: "This represent the number of domains blocked (safe search)",
},
[]string{"hostname"},
)
// topQueries - The number of top queries.
topQueries = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "top_queried_domains",
Namespace: "adguard",
Help: "This represent the top queried domains",
},
[]string{"hostname", "domain"},
)
// topBlocked - The number of top domains blocked.
topBlocked = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "top_blocked_domains",
Namespace: "adguard",
Help: "This represent the top bloacked domains",
},
[]string{"hostname", "domain"},
)
// topClients - The number of top clients.
topClients = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "top_clients",
Namespace: "adguard",
Help: "This represent the top clients",
},
[]string{"hostname", "client"},
)
// queryTypes - The type of DNS Queries (A, AAAA...)
queryTypes = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "query_types",
Namespace: "adguard",
Help: "This represent the DNS query types",
},
[]string{"hostname", "type"},
)
// running - If Adguard is running.
running = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "running",
Namespace: "adguard",
Help: "This represent if Adguard is running",
},
[]string{"hostname"},
)
// protectionEnabled - If Adguard protection is enabled.
protectionEnabled = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "protection_enabled",
Namespace: "adguard",
Help: "This represent if Adguard Protection is enabled",
},
[]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{}
)
// Init initializes all Prometheus metrics made available by AdGuard exporter.
func Init() {
initMetric("avg_processing_time", avgProcessingTime)
initMetric("num_dns_queries", dnsQueries)
initMetric("num_blocked_filtering", blockedFiltering)
initMetric("num_replaced_parental", parentalFiltering)
initMetric("num_replaced_safebrowsing", safeBrowsingFiltering)
initMetric("num_replaced_safesearch", safeSearchFiltering)
initMetric("top_queried_domains", topQueries)
initMetric("top_blocked_domains", topBlocked)
initMetric("top_clients", topClients)
initMetric("query_types", queryTypes)
initMetric("running", running)
initMetric("protection_enabled", protectionEnabled)
initMetric("sync_duration_seconds", aghsSyncDuration)
initMetric("sync_successful", aghsSyncSuccessful)
}
func initMetric(name string, metric *prometheus.GaugeVec) {
prometheus.MustRegister(metric)
l.With("name", name).Info("New Prometheus metric registered")
}
func UpdateInstances(iml InstanceMetricsList) {
for _, im := range iml.Metrics {
updateMetrics(im)
stats[im.HostName] = im.Stats
}
l.Debug("updated")
}
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
isRunning := 0
if im.Status.Running {
isRunning = 1
}
running.WithLabelValues(im.HostName).Set(float64(isRunning))
isProtected := 0
if im.Status.ProtectionEnabled {
isProtected = 1
}
protectionEnabled.WithLabelValues(im.HostName).Set(float64(isProtected))
// Stats
avgProcessingTime.WithLabelValues(im.HostName).Set(safeMetric(im.Stats.AvgProcessingTime))
dnsQueries.WithLabelValues(im.HostName).Set(safeMetric(im.Stats.NumDnsQueries))
blockedFiltering.WithLabelValues(im.HostName).Set(safeMetric(im.Stats.NumBlockedFiltering))
parentalFiltering.WithLabelValues(im.HostName).Set(safeMetric(im.Stats.NumReplacedParental))
safeBrowsingFiltering.WithLabelValues(im.HostName).Set(safeMetric(im.Stats.NumReplacedSafebrowsing))
safeSearchFiltering.WithLabelValues(im.HostName).Set(safeMetric(im.Stats.NumReplacedSafesearch))
if im.Stats.TopQueriedDomains != nil {
for _, tq := range *im.Stats.TopQueriedDomains {
for domain, value := range tq.AdditionalProperties {
topQueries.WithLabelValues(im.HostName, domain).Set(float64(value))
}
}
}
if im.Stats.TopBlockedDomains != nil {
for _, tb := range *im.Stats.TopBlockedDomains {
for domain, value := range tb.AdditionalProperties {
topBlocked.WithLabelValues(im.HostName, domain).Set(float64(value))
}
}
}
if im.Stats.TopClients != nil {
for _, tc := range *im.Stats.TopClients {
for source, value := range tc.AdditionalProperties {
topClients.WithLabelValues(im.HostName, source).Set(float64(value))
}
}
}
// LogQuery
m := make(map[string]int)
if im.QueryLog != nil && im.QueryLog.Data != nil {
logdata := *im.QueryLog.Data
for _, ld := range logdata {
if ld.Answer != nil {
dnsanswer := *ld.Answer
if len(dnsanswer) > 0 {
for _, dnsa := range dnsanswer {
dnsType := *dnsa.Type
m[dnsType]++
}
}
}
}
}
for key, value := range m {
queryTypes.WithLabelValues(im.HostName, key).Set(float64(value))
}
}
type InstanceMetricsList struct {
Metrics []InstanceMetrics `faker:"slice_len=5"`
}
type InstanceMetrics struct {
HostName string
Status *model.ServerStatus
Stats *model.Stats
QueryLog *model.QueryLog
}
type OverallStats map[string]*model.Stats
func (os OverallStats) consolidate() OverallStats {
consolidated := OverallStats{StatsTotal: model.NewStats()}
for host, stats := range os {
consolidated[host] = stats
consolidated[StatsTotal].Add(stats)
}
return consolidated
}
func safeMetric[T int | float64 | float32](v *T) float64 {
if v == nil {
return 0
}
return float64(*v)
}
func getStats() OverallStats {
return stats.consolidate()
}
func (os OverallStats) Total() *model.Stats {
return os[StatsTotal]
}

View 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")
}

View 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))
}

130
internal/metrics/stats.go Normal file
View File

@@ -0,0 +1,130 @@
package metrics
import (
"slices"
"strings"
"github.com/bakito/adguardhome-sync/internal/client/model"
)
const labelTotal = "Total"
var (
blue = []int{78, 141, 245}
blueAlternatives = [][]int{
{44, 95, 163},
{122, 166, 247},
{30, 61, 92},
{93, 158, 255},
{58, 123, 213},
}
red = []int{255, 94, 94}
redAlternatives = [][]int{
{204, 59, 59},
{255, 127, 127},
{140, 36, 36},
{255, 153, 153},
{255, 66, 66},
}
yellow = []int{232, 198, 78}
yellowAlternatives = [][]int{
{196, 163, 60},
{255, 220, 110},
{140, 114, 36},
{250, 233, 156},
{212, 180, 84},
}
green = []int{110, 224, 122}
greenAlternatives = [][]int{
{68, 160, 80},
{142, 255, 158},
{44, 140, 63},
{163, 255, 192},
{85, 198, 102},
}
)
func StatsGraph() (t *model.Stats, dns, blocked, malware, adult []Line) {
s := getStats()
t = s.Total()
dns = graphLines(t, s, blue, blueAlternatives, func(s *model.Stats) []int {
return safeStats(s.DnsQueries)
})
blocked = graphLines(t, s, red, redAlternatives, func(s *model.Stats) []int {
return safeStats(s.BlockedFiltering)
})
malware = graphLines(t, s, green, greenAlternatives, func(s *model.Stats) []int {
return safeStats(s.ReplacedSafebrowsing)
})
adult = graphLines(t, s, yellow, yellowAlternatives, func(s *model.Stats) []int {
return safeStats(s.ReplacedParental)
})
return t, dns, blocked, malware, adult
}
func safeStats(stats *[]int) []int {
if stats == nil {
return make([]int, 0)
}
return *stats
}
func graphLines(
t *model.Stats,
s OverallStats,
baseColor []int,
altColors [][]int,
dataCB func(s *model.Stats) []int,
) []Line {
g := &graph{
total: Line{
Fill: true,
Title: labelTotal,
Data: dataCB(t),
R: baseColor[0],
G: baseColor[1],
B: baseColor[2],
},
}
var i int
for name, data := range s {
if name != StatsTotal {
g.replicas = append(g.replicas, Line{
Fill: false,
Title: name,
Data: dataCB(data),
R: altColors[i%len(altColors)][0],
G: altColors[i%len(altColors)][1],
B: altColors[i%len(altColors)][2],
})
i++
}
}
lines := []Line{g.total}
slices.SortFunc(g.replicas, func(a, b Line) int {
return strings.Compare(a.Title, b.Title)
})
lines = append(lines, g.replicas...)
return lines
}
type graph struct {
total Line
replicas []Line
}
type Line struct {
Data []int `json:"data"`
R int `json:"r"`
G int `json:"g"`
B int `json:"b"`
Title string `json:"title"`
Fill bool `json:"fill"`
}

View File

@@ -0,0 +1,682 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/bakito/adguardhome-sync/internal/client (interfaces: Client)
//
// Generated by this command:
//
// 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
import (
reflect "reflect"
model "github.com/bakito/adguardhome-sync/internal/client/model"
gomock "go.uber.org/mock/gomock"
)
// MockClient is a mock of Client interface.
type MockClient struct {
ctrl *gomock.Controller
recorder *MockClientMockRecorder
isgomock struct{}
}
// MockClientMockRecorder is the mock recorder for MockClient.
type MockClientMockRecorder struct {
mock *MockClient
}
// NewMockClient creates a new mock instance.
func NewMockClient(ctrl *gomock.Controller) *MockClient {
mock := &MockClient{ctrl: ctrl}
mock.recorder = &MockClientMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockClient) EXPECT() *MockClientMockRecorder {
return m.recorder
}
// AccessList mocks base method.
func (m *MockClient) AccessList() (*model.AccessList, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AccessList")
ret0, _ := ret[0].(*model.AccessList)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// AccessList indicates an expected call of AccessList.
func (mr *MockClientMockRecorder) AccessList() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AccessList", reflect.TypeOf((*MockClient)(nil).AccessList))
}
// AddClient mocks base method.
func (m *MockClient) AddClient(client *model.Client) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AddClient", client)
ret0, _ := ret[0].(error)
return ret0
}
// AddClient indicates an expected call of AddClient.
func (mr *MockClientMockRecorder) AddClient(client any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddClient", reflect.TypeOf((*MockClient)(nil).AddClient), client)
}
// AddDHCPStaticLease mocks base method.
func (m *MockClient) AddDHCPStaticLease(lease model.DhcpStaticLease) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AddDHCPStaticLease", lease)
ret0, _ := ret[0].(error)
return ret0
}
// AddDHCPStaticLease indicates an expected call of AddDHCPStaticLease.
func (mr *MockClientMockRecorder) AddDHCPStaticLease(lease any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddDHCPStaticLease", reflect.TypeOf((*MockClient)(nil).AddDHCPStaticLease), lease)
}
// AddFilter mocks base method.
func (m *MockClient) AddFilter(whitelist bool, f model.Filter) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AddFilter", whitelist, f)
ret0, _ := ret[0].(error)
return ret0
}
// AddFilter indicates an expected call of AddFilter.
func (mr *MockClientMockRecorder) AddFilter(whitelist, f any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddFilter", reflect.TypeOf((*MockClient)(nil).AddFilter), whitelist, f)
}
// AddRewriteEntries mocks base method.
func (m *MockClient) AddRewriteEntries(e ...model.RewriteEntry) error {
m.ctrl.T.Helper()
varargs := []any{}
for _, a := range e {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "AddRewriteEntries", varargs...)
ret0, _ := ret[0].(error)
return ret0
}
// AddRewriteEntries indicates an expected call of AddRewriteEntries.
func (mr *MockClientMockRecorder) AddRewriteEntries(e ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRewriteEntries", reflect.TypeOf((*MockClient)(nil).AddRewriteEntries), e...)
}
// BlockedServicesSchedule mocks base method.
func (m *MockClient) BlockedServicesSchedule() (*model.BlockedServicesSchedule, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "BlockedServicesSchedule")
ret0, _ := ret[0].(*model.BlockedServicesSchedule)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// BlockedServicesSchedule indicates an expected call of BlockedServicesSchedule.
func (mr *MockClientMockRecorder) BlockedServicesSchedule() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BlockedServicesSchedule", reflect.TypeOf((*MockClient)(nil).BlockedServicesSchedule))
}
// Clients mocks base method.
func (m *MockClient) Clients() (*model.Clients, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Clients")
ret0, _ := ret[0].(*model.Clients)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Clients indicates an expected call of Clients.
func (mr *MockClientMockRecorder) Clients() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Clients", reflect.TypeOf((*MockClient)(nil).Clients))
}
// DNSConfig mocks base method.
func (m *MockClient) DNSConfig() (*model.DNSConfig, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DNSConfig")
ret0, _ := ret[0].(*model.DNSConfig)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// DNSConfig indicates an expected call of DNSConfig.
func (mr *MockClientMockRecorder) DNSConfig() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DNSConfig", reflect.TypeOf((*MockClient)(nil).DNSConfig))
}
// DeleteClient mocks base method.
func (m *MockClient) DeleteClient(client *model.Client) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteClient", client)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteClient indicates an expected call of DeleteClient.
func (mr *MockClientMockRecorder) DeleteClient(client any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteClient", reflect.TypeOf((*MockClient)(nil).DeleteClient), client)
}
// DeleteDHCPStaticLease mocks base method.
func (m *MockClient) DeleteDHCPStaticLease(lease model.DhcpStaticLease) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteDHCPStaticLease", lease)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteDHCPStaticLease indicates an expected call of DeleteDHCPStaticLease.
func (mr *MockClientMockRecorder) DeleteDHCPStaticLease(lease any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteDHCPStaticLease", reflect.TypeOf((*MockClient)(nil).DeleteDHCPStaticLease), lease)
}
// DeleteFilter mocks base method.
func (m *MockClient) DeleteFilter(whitelist bool, f model.Filter) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteFilter", whitelist, f)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteFilter indicates an expected call of DeleteFilter.
func (mr *MockClientMockRecorder) DeleteFilter(whitelist, f any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteFilter", reflect.TypeOf((*MockClient)(nil).DeleteFilter), whitelist, f)
}
// DeleteRewriteEntries mocks base method.
func (m *MockClient) DeleteRewriteEntries(e ...model.RewriteEntry) error {
m.ctrl.T.Helper()
varargs := []any{}
for _, a := range e {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "DeleteRewriteEntries", varargs...)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteRewriteEntries indicates an expected call of DeleteRewriteEntries.
func (mr *MockClientMockRecorder) DeleteRewriteEntries(e ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteRewriteEntries", reflect.TypeOf((*MockClient)(nil).DeleteRewriteEntries), e...)
}
// DhcpConfig mocks base method.
func (m *MockClient) DhcpConfig() (*model.DhcpStatus, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DhcpConfig")
ret0, _ := ret[0].(*model.DhcpStatus)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// DhcpConfig indicates an expected call of DhcpConfig.
func (mr *MockClientMockRecorder) DhcpConfig() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DhcpConfig", reflect.TypeOf((*MockClient)(nil).DhcpConfig))
}
// Filtering mocks base method.
func (m *MockClient) Filtering() (*model.FilterStatus, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Filtering")
ret0, _ := ret[0].(*model.FilterStatus)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Filtering indicates an expected call of Filtering.
func (mr *MockClientMockRecorder) Filtering() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Filtering", reflect.TypeOf((*MockClient)(nil).Filtering))
}
// Host mocks base method.
func (m *MockClient) Host() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Host")
ret0, _ := ret[0].(string)
return ret0
}
// Host indicates an expected call of Host.
func (mr *MockClientMockRecorder) Host() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Host", reflect.TypeOf((*MockClient)(nil).Host))
}
// Parental mocks base method.
func (m *MockClient) Parental() (bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Parental")
ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Parental indicates an expected call of Parental.
func (mr *MockClientMockRecorder) Parental() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Parental", reflect.TypeOf((*MockClient)(nil).Parental))
}
// ProfileInfo mocks base method.
func (m *MockClient) ProfileInfo() (*model.ProfileInfo, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ProfileInfo")
ret0, _ := ret[0].(*model.ProfileInfo)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ProfileInfo indicates an expected call of ProfileInfo.
func (mr *MockClientMockRecorder) ProfileInfo() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ProfileInfo", reflect.TypeOf((*MockClient)(nil).ProfileInfo))
}
// QueryLog mocks base method.
func (m *MockClient) QueryLog(limit int) (*model.QueryLog, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "QueryLog", limit)
ret0, _ := ret[0].(*model.QueryLog)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// QueryLog indicates an expected call of QueryLog.
func (mr *MockClientMockRecorder) QueryLog(limit any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueryLog", reflect.TypeOf((*MockClient)(nil).QueryLog), limit)
}
// QueryLogConfig mocks base method.
func (m *MockClient) QueryLogConfig() (*model.QueryLogConfigWithIgnored, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "QueryLogConfig")
ret0, _ := ret[0].(*model.QueryLogConfigWithIgnored)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// QueryLogConfig indicates an expected call of QueryLogConfig.
func (mr *MockClientMockRecorder) QueryLogConfig() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueryLogConfig", reflect.TypeOf((*MockClient)(nil).QueryLogConfig))
}
// RefreshFilters mocks base method.
func (m *MockClient) RefreshFilters(whitelist bool) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "RefreshFilters", whitelist)
ret0, _ := ret[0].(error)
return ret0
}
// RefreshFilters indicates an expected call of RefreshFilters.
func (mr *MockClientMockRecorder) RefreshFilters(whitelist any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RefreshFilters", reflect.TypeOf((*MockClient)(nil).RefreshFilters), whitelist)
}
// RewriteList mocks base method.
func (m *MockClient) RewriteList() (*model.RewriteEntries, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "RewriteList")
ret0, _ := ret[0].(*model.RewriteEntries)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// RewriteList indicates an expected call of RewriteList.
func (mr *MockClientMockRecorder) RewriteList() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RewriteList", reflect.TypeOf((*MockClient)(nil).RewriteList))
}
// SafeBrowsing mocks base method.
func (m *MockClient) SafeBrowsing() (bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SafeBrowsing")
ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// SafeBrowsing indicates an expected call of SafeBrowsing.
func (mr *MockClientMockRecorder) SafeBrowsing() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SafeBrowsing", reflect.TypeOf((*MockClient)(nil).SafeBrowsing))
}
// SafeSearchConfig mocks base method.
func (m *MockClient) SafeSearchConfig() (*model.SafeSearchConfig, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SafeSearchConfig")
ret0, _ := ret[0].(*model.SafeSearchConfig)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// SafeSearchConfig indicates an expected call of SafeSearchConfig.
func (mr *MockClientMockRecorder) SafeSearchConfig() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SafeSearchConfig", reflect.TypeOf((*MockClient)(nil).SafeSearchConfig))
}
// SetAccessList mocks base method.
func (m *MockClient) SetAccessList(accessList *model.AccessList) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetAccessList", accessList)
ret0, _ := ret[0].(error)
return ret0
}
// SetAccessList indicates an expected call of SetAccessList.
func (mr *MockClientMockRecorder) SetAccessList(accessList any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetAccessList", reflect.TypeOf((*MockClient)(nil).SetAccessList), accessList)
}
// SetBlockedServicesSchedule mocks base method.
func (m *MockClient) SetBlockedServicesSchedule(schedule *model.BlockedServicesSchedule) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetBlockedServicesSchedule", schedule)
ret0, _ := ret[0].(error)
return ret0
}
// SetBlockedServicesSchedule indicates an expected call of SetBlockedServicesSchedule.
func (mr *MockClientMockRecorder) SetBlockedServicesSchedule(schedule any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBlockedServicesSchedule", reflect.TypeOf((*MockClient)(nil).SetBlockedServicesSchedule), schedule)
}
// SetCustomRules mocks base method.
func (m *MockClient) SetCustomRules(rules *[]string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetCustomRules", rules)
ret0, _ := ret[0].(error)
return ret0
}
// SetCustomRules indicates an expected call of SetCustomRules.
func (mr *MockClientMockRecorder) SetCustomRules(rules any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetCustomRules", reflect.TypeOf((*MockClient)(nil).SetCustomRules), rules)
}
// SetDNSConfig mocks base method.
func (m *MockClient) SetDNSConfig(config *model.DNSConfig) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetDNSConfig", config)
ret0, _ := ret[0].(error)
return ret0
}
// SetDNSConfig indicates an expected call of SetDNSConfig.
func (mr *MockClientMockRecorder) SetDNSConfig(config any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDNSConfig", reflect.TypeOf((*MockClient)(nil).SetDNSConfig), config)
}
// SetDhcpConfig mocks base method.
func (m *MockClient) SetDhcpConfig(status *model.DhcpStatus) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetDhcpConfig", status)
ret0, _ := ret[0].(error)
return ret0
}
// SetDhcpConfig indicates an expected call of SetDhcpConfig.
func (mr *MockClientMockRecorder) SetDhcpConfig(status any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDhcpConfig", reflect.TypeOf((*MockClient)(nil).SetDhcpConfig), status)
}
// SetProfileInfo mocks base method.
func (m *MockClient) SetProfileInfo(settings *model.ProfileInfo) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetProfileInfo", settings)
ret0, _ := ret[0].(error)
return ret0
}
// SetProfileInfo indicates an expected call of SetProfileInfo.
func (mr *MockClientMockRecorder) SetProfileInfo(settings any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetProfileInfo", reflect.TypeOf((*MockClient)(nil).SetProfileInfo), settings)
}
// SetQueryLogConfig mocks base method.
func (m *MockClient) SetQueryLogConfig(ql *model.QueryLogConfigWithIgnored) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetQueryLogConfig", ql)
ret0, _ := ret[0].(error)
return ret0
}
// SetQueryLogConfig indicates an expected call of SetQueryLogConfig.
func (mr *MockClientMockRecorder) SetQueryLogConfig(ql any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetQueryLogConfig", reflect.TypeOf((*MockClient)(nil).SetQueryLogConfig), ql)
}
// SetSafeSearchConfig mocks base method.
func (m *MockClient) SetSafeSearchConfig(settings *model.SafeSearchConfig) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetSafeSearchConfig", settings)
ret0, _ := ret[0].(error)
return ret0
}
// SetSafeSearchConfig indicates an expected call of SetSafeSearchConfig.
func (mr *MockClientMockRecorder) SetSafeSearchConfig(settings any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSafeSearchConfig", reflect.TypeOf((*MockClient)(nil).SetSafeSearchConfig), settings)
}
// SetStatsConfig mocks base method.
func (m *MockClient) SetStatsConfig(sc *model.PutStatsConfigUpdateRequest) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetStatsConfig", sc)
ret0, _ := ret[0].(error)
return ret0
}
// SetStatsConfig indicates an expected call of SetStatsConfig.
func (mr *MockClientMockRecorder) SetStatsConfig(sc any) *gomock.Call {
mr.mock.ctrl.T.Helper()
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.
func (m *MockClient) Setup() error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Setup")
ret0, _ := ret[0].(error)
return ret0
}
// Setup indicates an expected call of Setup.
func (mr *MockClientMockRecorder) Setup() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Setup", reflect.TypeOf((*MockClient)(nil).Setup))
}
// Stats mocks base method.
func (m *MockClient) Stats() (*model.Stats, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Stats")
ret0, _ := ret[0].(*model.Stats)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Stats indicates an expected call of Stats.
func (mr *MockClientMockRecorder) Stats() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stats", reflect.TypeOf((*MockClient)(nil).Stats))
}
// StatsConfig mocks base method.
func (m *MockClient) StatsConfig() (*model.GetStatsConfigResponse, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "StatsConfig")
ret0, _ := ret[0].(*model.GetStatsConfigResponse)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// StatsConfig indicates an expected call of StatsConfig.
func (mr *MockClientMockRecorder) StatsConfig() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StatsConfig", reflect.TypeOf((*MockClient)(nil).StatsConfig))
}
// Status mocks base method.
func (m *MockClient) Status() (*model.ServerStatus, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Status")
ret0, _ := ret[0].(*model.ServerStatus)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Status indicates an expected call of Status.
func (mr *MockClientMockRecorder) Status() *gomock.Call {
mr.mock.ctrl.T.Helper()
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.
func (m *MockClient) ToggleFiltering(enabled bool, interval int) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ToggleFiltering", enabled, interval)
ret0, _ := ret[0].(error)
return ret0
}
// ToggleFiltering indicates an expected call of ToggleFiltering.
func (mr *MockClientMockRecorder) ToggleFiltering(enabled, interval any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleFiltering", reflect.TypeOf((*MockClient)(nil).ToggleFiltering), enabled, interval)
}
// ToggleParental mocks base method.
func (m *MockClient) ToggleParental(enable bool) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ToggleParental", enable)
ret0, _ := ret[0].(error)
return ret0
}
// ToggleParental indicates an expected call of ToggleParental.
func (mr *MockClientMockRecorder) ToggleParental(enable any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleParental", reflect.TypeOf((*MockClient)(nil).ToggleParental), enable)
}
// ToggleProtection mocks base method.
func (m *MockClient) ToggleProtection(enable bool) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ToggleProtection", enable)
ret0, _ := ret[0].(error)
return ret0
}
// ToggleProtection indicates an expected call of ToggleProtection.
func (mr *MockClientMockRecorder) ToggleProtection(enable any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleProtection", reflect.TypeOf((*MockClient)(nil).ToggleProtection), enable)
}
// ToggleSafeBrowsing mocks base method.
func (m *MockClient) ToggleSafeBrowsing(enable bool) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ToggleSafeBrowsing", enable)
ret0, _ := ret[0].(error)
return ret0
}
// ToggleSafeBrowsing indicates an expected call of ToggleSafeBrowsing.
func (mr *MockClientMockRecorder) ToggleSafeBrowsing(enable any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleSafeBrowsing", reflect.TypeOf((*MockClient)(nil).ToggleSafeBrowsing), enable)
}
// UpdateClient mocks base method.
func (m *MockClient) UpdateClient(client *model.Client) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateClient", client)
ret0, _ := ret[0].(error)
return ret0
}
// UpdateClient indicates an expected call of UpdateClient.
func (mr *MockClientMockRecorder) UpdateClient(client any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateClient", reflect.TypeOf((*MockClient)(nil).UpdateClient), client)
}
// UpdateFilter mocks base method.
func (m *MockClient) UpdateFilter(whitelist bool, f model.Filter) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateFilter", whitelist, f)
ret0, _ := ret[0].(error)
return ret0
}
// UpdateFilter indicates an expected call of UpdateFilter.
func (mr *MockClientMockRecorder) UpdateFilter(whitelist, f any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateFilter", reflect.TypeOf((*MockClient)(nil).UpdateFilter), whitelist, f)
}

View File

@@ -0,0 +1,99 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/bakito/adguardhome-sync/internal/config (interfaces: Flags)
//
// Generated by this command:
//
// 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
import (
reflect "reflect"
gomock "go.uber.org/mock/gomock"
)
// MockFlags is a mock of Flags interface.
type MockFlags struct {
ctrl *gomock.Controller
recorder *MockFlagsMockRecorder
isgomock struct{}
}
// MockFlagsMockRecorder is the mock recorder for MockFlags.
type MockFlagsMockRecorder struct {
mock *MockFlags
}
// NewMockFlags creates a new mock instance.
func NewMockFlags(ctrl *gomock.Controller) *MockFlags {
mock := &MockFlags{ctrl: ctrl}
mock.recorder = &MockFlagsMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockFlags) EXPECT() *MockFlagsMockRecorder {
return m.recorder
}
// Changed mocks base method.
func (m *MockFlags) Changed(name string) bool {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Changed", name)
ret0, _ := ret[0].(bool)
return ret0
}
// Changed indicates an expected call of Changed.
func (mr *MockFlagsMockRecorder) Changed(name any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Changed", reflect.TypeOf((*MockFlags)(nil).Changed), name)
}
// GetBool mocks base method.
func (m *MockFlags) GetBool(name string) (bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetBool", name)
ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetBool indicates an expected call of GetBool.
func (mr *MockFlagsMockRecorder) GetBool(name any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBool", reflect.TypeOf((*MockFlags)(nil).GetBool), name)
}
// GetInt mocks base method.
func (m *MockFlags) GetInt(name string) (int, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetInt", name)
ret0, _ := ret[0].(int)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetInt indicates an expected call of GetInt.
func (mr *MockFlagsMockRecorder) GetInt(name any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInt", reflect.TypeOf((*MockFlags)(nil).GetInt), name)
}
// GetString mocks base method.
func (m *MockFlags) GetString(name string) (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetString", name)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetString indicates an expected call of GetString.
func (mr *MockFlagsMockRecorder) GetString(name any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetString", reflect.TypeOf((*MockFlags)(nil).GetString), name)
}

View File

@@ -0,0 +1,300 @@
package sync
import (
"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 (
actionProfileInfo = func(ac *actionContext) error {
if pro, err := ac.client.ProfileInfo(); err != nil {
return err
} else if merged := pro.ShouldSyncFor(ac.origin.profileInfo, ac.cfg.Features.Theme); merged != nil {
return ac.client.SetProfileInfo(merged)
}
return nil
}
actionProtection = func(ac *actionContext) error {
if ac.origin.status.ProtectionEnabled != ac.replicaStatus.ProtectionEnabled {
return ac.client.ToggleProtection(ac.origin.status.ProtectionEnabled)
}
return nil
}
actionParental = func(ac *actionContext) error {
if rp, err := ac.client.Parental(); err != nil {
return err
} else if ac.origin.parental != rp {
return ac.client.ToggleParental(ac.origin.parental)
}
return nil
}
actionSafeSearchConfig = func(ac *actionContext) error {
if ssc, err := ac.client.SafeSearchConfig(); err != nil {
return err
} else if !ac.origin.safeSearch.Equals(ssc) {
return ac.client.SetSafeSearchConfig(ac.origin.safeSearch)
}
return nil
}
actionSafeBrowsing = func(ac *actionContext) error {
if rs, err := ac.client.SafeBrowsing(); err != nil {
return err
} else if ac.origin.safeBrowsing != rs {
if err = ac.client.ToggleSafeBrowsing(ac.origin.safeBrowsing); err != nil {
return err
}
}
return nil
}
actionQueryLogConfig = func(ac *actionContext) error {
qlc, err := ac.client.QueryLogConfig()
if err != nil {
return err
}
if !ac.origin.queryLogConfig.Equals(qlc) {
return ac.client.SetQueryLogConfig(ac.origin.queryLogConfig)
}
return nil
}
actionStatsConfig = func(ac *actionContext) error {
sc, err := ac.client.StatsConfig()
if err != nil {
return err
}
if !sc.Equals(ac.origin.statsConfig) {
return ac.client.SetStatsConfig(ac.origin.statsConfig)
}
return nil
}
actionDNSRewrites = func(ac *actionContext) error {
replicaRewrites, err := ac.client.RewriteList()
if err != nil {
return err
}
a, r, d := replicaRewrites.Merge(ac.origin.rewrites)
if err = ac.client.DeleteRewriteEntries(r...); err != nil {
return err
}
if err = ac.client.AddRewriteEntries(a...); err != nil {
return err
}
for _, dupl := range d {
ac.rl.With("domain", dupl.Domain, "answer", dupl.Answer).Warn("Skipping duplicated rewrite from source")
}
return nil
}
actionFilters = func(ac *actionContext) error {
rf, err := ac.client.Filtering()
if err != nil {
return err
}
if err = syncFilterType(ac.rl, ac.origin.filters.Filters, rf.Filters, false, ac.client, ac.cfg.ContinueOnError); err != nil {
return err
}
if err = syncFilterType(ac.rl, ac.origin.filters.WhitelistFilters, rf.WhitelistFilters, true, ac.client, ac.cfg.ContinueOnError); err != nil {
return err
}
if utils.PtrToString(ac.origin.filters.UserRules) != utils.PtrToString(rf.UserRules) {
return ac.client.SetCustomRules(ac.origin.filters.UserRules)
}
if !utils.PtrEquals(ac.origin.filters.Enabled, rf.Enabled) ||
!utils.PtrEquals(ac.origin.filters.Interval, rf.Interval) {
return ac.client.ToggleFiltering(*ac.origin.filters.Enabled, *ac.origin.filters.Interval)
}
return nil
}
actionBlockedServicesSchedule = func(ac *actionContext) error {
rbss, err := ac.client.BlockedServicesSchedule()
if err != nil {
return err
}
if !ac.origin.blockedServicesSchedule.Equals(rbss) {
return ac.client.SetBlockedServicesSchedule(ac.origin.blockedServicesSchedule)
}
return nil
}
actionClientSettings = func(ac *actionContext) error {
rc, err := ac.client.Clients()
if err != nil {
return err
}
a, u, r := rc.Merge(ac.origin.clients)
for _, client := range r {
if err := ac.client.DeleteClient(client); err != nil {
ac.rl.With("client-name", client.Name, "error", err).Error("error deleting client setting")
if !ac.cfg.ContinueOnError {
return err
}
}
}
for _, client := range a {
if err := ac.client.AddClient(client); err != nil {
ac.rl.With("client-name", client.Name, "error", err).Error("error adding client setting")
if !ac.cfg.ContinueOnError {
return err
}
}
}
for _, client := range u {
if err := ac.client.UpdateClient(client); err != nil {
ac.rl.With("client-name", client.Name, "error", err).Error("error updating client setting")
if !ac.cfg.ContinueOnError {
return err
}
}
}
return nil
}
actionDNSAccessLists = func(ac *actionContext) error {
al, err := ac.client.AccessList()
if err != nil {
return err
}
if !al.Equals(ac.origin.accessList) {
return ac.client.SetAccessList(ac.origin.accessList)
}
return nil
}
actionDNSServerConfig = func(ac *actionContext) error {
dc, err := ac.client.DNSConfig()
if err != nil {
return err
}
// dc.Sanitize(ac.rl)
if !dc.Equals(ac.origin.dnsConfig) {
if err = ac.client.SetDNSConfig(ac.origin.dnsConfig); err != nil {
return err
}
}
return nil
}
actionDHCPServerConfig = func(ac *actionContext) error {
if ac.origin.dhcpServerConfig.HasConfig() {
sc, err := ac.client.DhcpConfig()
if err != nil {
return err
}
origClone := ac.origin.dhcpServerConfig.Clone()
if ac.replica.InterfaceName != "" {
// overwrite interface name
origClone.InterfaceName = utils.Ptr(ac.replica.InterfaceName)
}
if ac.replica.DHCPServerEnabled != nil {
// overwrite dhcp enabled
origClone.Enabled = ac.replica.DHCPServerEnabled
}
if !sc.CleanAndEquals(origClone) {
return ac.client.SetDhcpConfig(origClone)
}
}
return nil
}
actionDHCPStaticLeases = func(ac *actionContext) error {
sc, err := ac.client.DhcpConfig()
if err != nil {
return err
}
a, r := model.MergeDhcpStaticLeases(sc.StaticLeases, ac.origin.dhcpServerConfig.StaticLeases)
for _, lease := range r {
if err := ac.client.DeleteDHCPStaticLease(lease); err != nil {
ac.rl.With("hostname", lease.Hostname, "error", err).Error("error deleting dhcp static lease")
if !ac.cfg.ContinueOnError {
return err
}
}
}
for _, lease := range a {
if err := ac.client.AddDHCPStaticLease(lease); err != nil {
ac.rl.With("hostname", lease.Hostname, "error", err).Error("error adding dhcp static lease")
if !ac.cfg.ContinueOnError {
return err
}
}
}
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(
rl *zap.SugaredLogger,
of *[]model.Filter,
rFilters *[]model.Filter,
whitelist bool,
replica client.Client,
continueOnError bool,
) error {
fa, fu, fd := model.MergeFilters(rFilters, of)
for _, f := range fd {
if err := replica.DeleteFilter(whitelist, f); err != nil {
rl.With("filter", f.Name, "url", f.Url, "whitelist", whitelist, "error", err).Error("error deleting filter")
if !continueOnError {
return err
}
}
}
for _, f := range fa {
if err := replica.AddFilter(whitelist, f); err != nil {
rl.With("filter", f.Name, "url", f.Url, "whitelist", whitelist, "error", err).Error("error adding filter")
if !continueOnError {
return err
}
}
}
for _, f := range fu {
if err := replica.UpdateFilter(whitelist, f); err != nil {
rl.With("filter", f.Name, "url", f.Url, "whitelist", whitelist, "error", err).Error("error updating filter")
if !continueOnError {
return err
}
}
}
if len(fa) > 0 || len(fu) > 0 {
if err := replica.RefreshFilters(whitelist); err != nil {
return err
}
}
return nil
}

108
internal/sync/action.go Normal file
View File

@@ -0,0 +1,108 @@
package sync
import (
"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) {
if cfg.Features.GeneralSettings {
actions = append(actions,
action("profile info", actionProfileInfo),
action("protection", actionProtection),
action("parental", actionParental),
action("safe search config", actionSafeSearchConfig),
action("safe browsing", actionSafeBrowsing),
)
}
if cfg.Features.DNS.ServerConfig {
actions = append(actions,
action("DNS server config", actionDNSServerConfig),
)
}
if cfg.Features.QueryLogConfig {
actions = append(actions,
action("query log config", actionQueryLogConfig),
)
}
if cfg.Features.StatsConfig {
actions = append(actions,
action("stats config", actionStatsConfig),
)
}
if cfg.Features.DNS.Rewrites {
actions = append(actions,
action("DNS rewrites", actionDNSRewrites),
)
}
if cfg.Features.Filters {
actions = append(actions,
action("actionFilters", actionFilters),
)
}
if cfg.Features.Services {
actions = append(actions,
action("blocked services schedule", actionBlockedServicesSchedule),
)
}
if cfg.Features.ClientSettings {
actions = append(actions,
action("client settings", actionClientSettings),
)
}
if cfg.Features.DNS.AccessLists {
actions = append(actions,
action("DNS access lists", actionDNSAccessLists),
)
}
if cfg.Features.DHCP.ServerConfig {
actions = append(actions,
action("DHCP server config", actionDHCPServerConfig),
)
}
if cfg.Features.DHCP.StaticLeases {
actions = append(actions,
action("DHCP static leases", actionDHCPStaticLeases),
)
}
if cfg.Features.TLSConfig {
actions = append(actions,
action("TLS config", tlsConfig),
)
}
return actions
}
type syncAction interface {
sync(ac *actionContext) error
name() string
}
type actionContext struct {
rl *zap.SugaredLogger
origin *origin
client client.Client
replicaStatus *model.ServerStatus
replica types.AdGuardInstance
cfg *types.Config
}
type defaultAction struct {
myName string
doSync func(ac *actionContext) error
}
func action(name string, f func(ac *actionContext) error) syncAction {
return &defaultAction{myName: name, doSync: f}
}
func (d *defaultAction) sync(ac *actionContext) error {
return d.doSync(ac)
}
func (d *defaultAction) name() string {
return d.myName
}

222
internal/sync/http.go Normal file
View File

@@ -0,0 +1,222 @@
package sync
import (
"context"
_ "embed"
"errors"
"fmt"
"html/template"
"net"
"net/http"
"os"
"os/signal"
"strings"
"syscall"
"time"
"github.com/gin-gonic/gin"
"github.com/bakito/adguardhome-sync/internal/log"
"github.com/bakito/adguardhome-sync/internal/metrics"
"github.com/bakito/adguardhome-sync/internal/sync/static"
"github.com/bakito/adguardhome-sync/version"
)
func (w *worker) handleSync(c *gin.Context) {
l.With("remote-addr", c.Request.RemoteAddr).Info("Starting sync from API")
w.sync()
}
func (w *worker) handleRoot(c *gin.Context) {
total, dns, blocked, malware, adult := metrics.StatsGraph()
c.HTML(http.StatusOK, "index.html", map[string]any{
"DarkMode": w.cfg.API.DarkMode,
"Metrics": w.cfg.API.Metrics.Enabled,
"Version": version.Version,
"Build": version.Build,
"SyncStatus": w.status(),
"Stats": map[string]any{
"Labels": getLast24Hours(),
"DNS": dns,
"Blocked": blocked,
"BlockedPercentage": percent(total.NumBlockedFiltering, total.NumDnsQueries),
"Malware": malware,
"MalwarePercentage": percent(total.NumReplacedSafebrowsing, total.NumDnsQueries),
"Adult": adult,
"AdultPercentage": percent(total.NumReplacedParental, total.NumDnsQueries),
"TotalDNS": total.NumDnsQueries,
"TotalBlocked": total.NumBlockedFiltering,
"TotalMalware": total.NumReplacedSafebrowsing,
"TotalAdult": total.NumReplacedParental,
},
},
)
}
func percent(a, b *int) string {
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) {
c.Data(http.StatusOK, "text/plain", []byte(strings.Join(log.Logs(), "")))
}
func (w *worker) handleClearLogs(c *gin.Context) {
log.Clear()
c.Status(http.StatusOK)
}
func (w *worker) handleStatus(c *gin.Context) {
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() {
sl := l.With("port", w.cfg.API.Port)
if w.cfg.API.TLS.Enabled() {
c, k := w.cfg.API.TLS.Certs()
sl = sl.With("tls-cert", c).With("tls-key", k)
}
sl.Info("Starting API server")
ctx, cancel := context.WithCancel(context.Background())
gin.SetMode(gin.ReleaseMode)
r := gin.New()
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 != "" {
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{
Addr: fmt.Sprintf(":%d", w.cfg.API.Port),
Handler: r,
BaseContext: func(_ net.Listener) context.Context { return ctx },
ReadHeaderTimeout: 1 * time.Second,
}
r.SetHTMLTemplate(template.Must(template.New("index.html").Parse(static.Index())))
go func() {
var err error
if w.cfg.API.TLS.Enabled() {
err = httpServer.ListenAndServeTLS(w.cfg.API.TLS.Certs())
} else {
err = httpServer.ListenAndServe()
}
if !errors.Is(err, http.ErrServerClosed) {
l.With("error", err).Fatalf("HTTP server ListenAndServe")
}
}()
signalChan := make(chan os.Signal, 1)
signal.Notify(
signalChan,
syscall.SIGHUP, // kill -SIGHUP XXXX
syscall.SIGINT, // kill -SIGINT XXXX or Ctrl+c
syscall.SIGQUIT, // kill -SIGQUIT XXXX
)
<-signalChan
l.Info("os.Interrupt - shutting down...")
go func() {
<-signalChan
l.Fatal("os.Kill - terminating...")
}()
gracefulCtx, cancelShutdown := context.WithTimeout(context.Background(), 5*time.Second)
defer cancelShutdown()
if w.cron != nil {
l.Info("Stopping cron")
w.cron.Stop()
}
if err := httpServer.Shutdown(gracefulCtx); err != nil {
l.With("error", err).Error("Shutdown error")
defer os.Exit(1)
} else {
l.Info("API server stopped")
}
// manually cancel context if not using httpServer.RegisterOnShutdown(cancel)
cancel()
}
type syncStatus struct {
SyncRunning bool `json:"syncRunning"`
Origin replicaStatus `json:"origin"`
Replicas []replicaStatus `json:"replicas"`
}
type replicaStatus struct {
Host string `json:"host"`
URL string `json:"url"`
Status string `json:"status"`
Error string `json:"error,omitempty"`
ProtectionEnabled *bool `json:"protection_enabled"`
}
func getLast24Hours() []string {
var result []string
currentTime := time.Now()
// Loop to get the last 24 hours
for i := range 24 {
// Calculate the time for the current hour in the loop
timeInstance := currentTime.Add(time.Duration(-i) * time.Hour)
timeInstance = timeInstance.Truncate(time.Hour)
// Format the time as "14 Dec 17:00"
formattedTime := timeInstance.Format("02 Jan 15:04")
result = append(result, formattedTime)
}
// Reverse the slice to get the correct order (from oldest to latest)
for i, j := 0, len(result)-1; i < j; i, j = i+1, j-1 {
result[i], result[j] = result[j], result[i]
}
return result
}

View 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
}

51
internal/sync/scrape.go Normal file
View File

@@ -0,0 +1,51 @@
package sync
import (
"time"
"github.com/bakito/adguardhome-sync/internal/metrics"
"github.com/bakito/adguardhome-sync/internal/types"
)
func (w *worker) startScraping() {
metrics.Init()
if w.cfg.API.Metrics.ScrapeInterval == 0 {
w.cfg.API.Metrics.ScrapeInterval = 30 * time.Second
}
if w.cfg.API.Metrics.QueryLogLimit == 0 {
w.cfg.API.Metrics.QueryLogLimit = 10_000
}
l.With(
"scrape-interval", w.cfg.API.Metrics.ScrapeInterval,
"query-log-limit", w.cfg.API.Metrics.QueryLogLimit,
).Info("setup metrics")
w.scrape()
for range time.Tick(w.cfg.API.Metrics.ScrapeInterval) {
w.scrape()
}
}
func (w *worker) scrape() {
var iml metrics.InstanceMetricsList
iml.Metrics = append(iml.Metrics, w.getMetrics(*w.cfg.Origin))
for _, replica := range w.cfg.Replicas {
iml.Metrics = append(iml.Metrics, w.getMetrics(replica))
}
metrics.UpdateInstances(iml)
}
func (w *worker) getMetrics(inst types.AdGuardInstance) metrics.InstanceMetrics {
var im metrics.InstanceMetrics
client, err := w.createClient(inst)
if err != nil {
l.With("error", err, "url", w.cfg.Origin.URL).Error("Error creating origin client")
return im
}
im.HostName = inst.Host
im.Status, _ = client.Status()
im.Stats, _ = client.Stats()
im.QueryLog, _ = client.QueryLog(w.cfg.API.Metrics.QueryLogLimit)
return im
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,242 @@
<html lang="en">
<head>
<title>AdGuard Home sync</title>
<script type="text/javascript" src="lib/jquery.js"></script>
<link rel="stylesheet" href="lib/bootstrap.css">
<script type="text/javascript">
$(document).ready(function () {
$("#showLogs").click(function () {
$.get("api/v1/logs", {}, function (data) {
$('#logs').html(data);
}
);
$.get("api/v1/status", {}, function (status) {
$('#origin').removeClass(function (index, className) {
return (className.match(/(^|\s)btn-\S+/g) || []).join(' ');
}).addClass("btn-" + status.origin.status).attr('title', status.origin.error);
status.replicas.forEach(function (replica, i) {
$('#replica_' + i).removeClass(function (index, className) {
return (className.match(/(^|\s)btn-\S+/g) || []).join(' ');
}).addClass("btn-" + replica.status).attr('title', replica.error);
});
}
);
});
$("#clearLogs").click(function () {
$.post("api/v1/clear-logs", {}, function () {
$('#logs').html("");
}
);
});
$("#sync").click(function () {
$.post("api/v1/sync", {}, function (data) {
});
$("#showLogs").click();
});
$("#showLogs").click();
});
</script>
<link rel="shortcut icon" href="favicon.ico">
<style>
.stat-card {
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
padding: 15px;
text-align: left;
display: flex;
flex-direction: column;
justify-content: space-between;
height: 100%;
}
{{- if .Metrics }}
.stat-card h3 {
margin: 0;
font-size: 2rem;
}
.stat-card p {
margin: 5px 0;
font-size: 0.9rem;
}
.percentage {
font-size: 0.9rem;
text-align: right;
height: 20px;
}
canvas {
flex-grow: 1;
height: 100px !important;
}
{{- end }}
.button-row {
margin-top: 20px;
}
.btn-group {
margin: 5px;
}
</style>
</head>
<body>
<div class="container-fluid px-4">
<div class="row">
<div class="d-flex align-items-center mb-3">
<img src="logo.svg" alt="Logo" class="me-3" style="height: 4em;">
<div>
<h1 class="mb-0">AdGuard Home sync</h1>
<p class="h6 text-muted mb-0">{{ .Version }} ({{ .Build }})</p>
</div>
</div>
</div>
{{- if .Metrics }}
<div class="row g-4 d-flex">
<div class="col-12 col-md-3 d-flex">
<div class="stat-card flex-fill">
<div class="percentage"></div>
<h3 style="color: rgb(78, 141, 245);">{{.Stats.TotalDNS}}</h3>
<p>DNS Queries</p>
<canvas id="dnsQueriesChart"></canvas>
</div>
</div>
<div class="col-12 col-md-3 d-flex">
<div class="stat-card flex-fill">
<div class="percentage" style="color: rgb(255, 94, 94);">{{.Stats.BlockedPercentage}}%</div>
<h3 style="color: rgb(255, 94, 94);">{{.Stats.TotalBlocked}}</h3>
<p>Blocked by Filters</p>
<canvas id="blockedFiltersChart"></canvas>
</div>
</div>
<div class="col-12 col-md-3 d-flex">
<div class="stat-card flex-fill">
<div class="percentage" style="color: rgb(110, 224, 122);">{{.Stats.MalwarePercentage}}%</div>
<h3 style="color: rgb(110, 224, 122);">{{.Stats.TotalMalware}}</h3>
<p>Blocked malware/phishing</p>
<canvas id="malwareChart"></canvas>
</div>
</div>
<div class="col-12 col-md-3 d-flex">
<div class="stat-card flex-fill">
<div class="percentage" style="color: rgb(232, 198, 78);">{{.Stats.AdultPercentage}}%</div>
<h3 style="color: rgb(232, 198, 78);">{{.Stats.TotalAdult}}</h3>
<p>Blocked adult websites</p>
<canvas id="adultWebsitesChart"></canvas>
</div>
</div>
</div>
{{- end }}
<div class="row button-row">
<div class="col">
<div class="btn-group" role="group">
<button type="button" class="btn btn-success" id="sync">Synchronize</button>
<button type="button" class="btn btn-secondary" id="showLogs">Update Logs</button>
<button type="button" class="btn btn-secondary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
</button>
<div class="dropdown-menu">
<a class="dropdown-item" href="#" id="clearLogs">Clear Logs</a>
</div>
</div>
</div>
<div class="col col-md-auto">
<div class="btn-group float-right" role="group">
<a href="{{ .SyncStatus.Origin.URL }}" target="_blank" class="btn btn-{{ .SyncStatus.Origin.Status }}"
type="button" id="origin"
{{ if .SyncStatus.Origin.Error }} title="{{ .SyncStatus.Origin.Error }}" {{ end }}>Origin {{ .SyncStatus.Origin.Host }}</a>
{{ range $i, $r := .SyncStatus.Replicas }}
<a href="{{ $r.URL }}" target="_blank" class="btn btn-{{ $r.Status }}"
type="button" id="replica_{{ $i }}"
{{ if $r.Error }} title="{{ $r.Error }}" {{ end }} >Replica {{ $r.Host }}</a>
{{ end }}
</div>
</div>
</div>
<div class="row mt-3">
<div class="col-12 col-md-12">
<div class="stat-card">
<pre class="p-3 border"><code id="logs"></code></pre>
</div>
</div>
</div>
</div>
<!-- openssl dgst -sha384 -binary popper.min.js | openssl base64 -A -->
<script src="lib/popper.js" ></script>
<script src="lib/bootstrap.js" ></script>
{{- if .Metrics }}
<script src="lib/chart.js" ></script>
<script>
// Function to create minimal line charts
function createChart(canvasId, data) {
const ctx = document.getElementById(canvasId).getContext('2d');
const datasets = Array(data.length);
for (let i = 0; i < data.length; i++) {
datasets[i] = {
data: data[i].data,
title: data[i].title,
backgroundColor: `rgb(${data[i].r}, ${data[i].g}, ${data[i].b}, 0.2)`,
borderColor: `rgb(${data[i].r}, ${data[i].g}, ${data[i].b}, 1)`,
borderWidth: 3,
fill: data[i].fill,
pointRadius: 0,
}
}
new Chart(ctx, {
type: 'line',
data: {
labels: {{.Stats.Labels}},
datasets: datasets
},
options: {
responsive: true,
maintainAspectRatio: false,
interaction: {
mode: 'index',
intersect: false,
},
plugins: {
legend: { display: false },
tooltip: {
enabled: true,
bodyFont: {
size: 20
},
titleFont: {
size: 20
},
displayColors: false,
callbacks: {
label: function(tooltipItem) {
if (tooltipItem.dataset.title) {
return tooltipItem.raw + " - " + tooltipItem.dataset.title;
}
return tooltipItem.raw;
}
}
}
},
scales: {
x: { display: false,
title: {
display: true
}
},
y: { display: false,
min: 0,
title: {
display: true
} }
}
}
});
}
createChart('dnsQueriesChart', {{.Stats.DNS}});
createChart('blockedFiltersChart', {{.Stats.Blocked}});
createChart('malwareChart', {{.Stats.Malware}});
createChart('adultWebsitesChart', {{.Stats.Adult}});
</script>
{{- end }}
</body>
</html>

File diff suppressed because one or more lines are too long

View 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

File diff suppressed because one or more lines are too long

View 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)
}
}

381
internal/sync/sync.go Normal file
View File

@@ -0,0 +1,381 @@
package sync
import (
"errors"
"fmt"
"net/http"
"runtime"
"sort"
"time"
"github.com/robfig/cron/v3"
"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")
// Sync config from origin to replica.
func Sync(cfg *types.Config) error {
if cfg.Origin.URL == "" {
return errors.New("origin URL is required")
}
if len(cfg.UniqueReplicas()) == 0 {
return errors.New("no replicas configured")
}
l.With(
"version", version.Version,
"build", version.Build,
"os", runtime.GOOS,
"arch", runtime.GOARCH,
).Info("AdGuardHome sync")
cfg.Log(l)
cfg.Features.LogDisabled(l)
cfg.Origin.AutoSetup = false
w := &worker{
cfg: cfg,
createClient: client.New,
}
if cfg.Cron != "" {
w.cron = cron.New()
cl := l.With("cron", cfg.Cron)
sched, err := cron.ParseStandard(cfg.Cron)
if err != nil {
cl.With("error", err).Error("Error parsing cron expression")
return err
}
cl = cl.With("next-execution", sched.Next(time.Now()))
_, err = w.cron.AddFunc(cfg.Cron, func() {
w.sync()
})
if err != nil {
cl.With("error", err).Error("Error during cron job setup")
return err
}
cl.Info("Setup cronjob")
if cfg.API.Port != 0 {
w.cron.Start()
} else {
runOnStartAsync(cfg, w)
w.cron.Run()
}
}
if cfg.API.Port != 0 {
runOnStartAsync(cfg, w)
w.listenAndServe()
} else if cfg.RunOnStart {
l.Info("Running sync on startup")
w.sync()
}
return nil
}
func runOnStartAsync(cfg *types.Config, w *worker) {
if cfg.RunOnStart {
go func() {
l.Info("Running sync on startup")
w.sync()
}()
}
}
type worker struct {
cfg *types.Config
running bool
cron *cron.Cron
createClient func(instance types.AdGuardInstance) (client.Client, error)
actions []syncAction
}
func (w *worker) status() *syncStatus {
syncStatus := &syncStatus{
Origin: w.getStatus(*w.cfg.Origin),
}
for _, replica := range w.cfg.Replicas {
st := w.getStatus(replica)
if w.running {
st.Status = "info"
}
syncStatus.Replicas = append(syncStatus.Replicas, st)
}
sort.Slice(syncStatus.Replicas, func(i, j int) bool {
return syncStatus.Replicas[i].Host < syncStatus.Replicas[j].Host
})
syncStatus.SyncRunning = w.running
return syncStatus
}
func (w *worker) getStatus(inst types.AdGuardInstance) replicaStatus {
st := replicaStatus{Host: inst.WebHost, URL: inst.WebURL}
oc, err := w.createClient(inst)
if err != nil {
l.With("error", err, "url", w.cfg.Origin.URL).Error("Error creating origin client")
st.Status = "danger"
st.Error = err.Error()
return st
}
sl := l.With("from", inst.WebHost)
status, err := oc.Status()
if err != nil {
if errors.Is(err, client.ErrSetupNeeded) {
st.Status = "warning"
st.Error = err.Error()
return st
}
sl.With("error", err).Error("Error getting origin status")
st.Status = "danger"
st.Error = err.Error()
return st
}
st.Status = "success"
st.ProtectionEnabled = utils.Ptr(status.ProtectionEnabled)
return st
}
func (w *worker) sync() {
if w.running {
l.Info("Sync already running")
return
}
w.running = true
defer func() {
w.running = false
}()
oc, err := w.createClient(*w.cfg.Origin)
if err != nil {
l.With("error", err, "url", w.cfg.Origin.URL).Error("Error creating origin client")
return
}
sl := l.With("from", oc.Host())
o := &origin{}
o.status, err = oc.Status()
if err != nil {
sl.With("error", err).Error("Error getting origin status")
return
}
if versions.IsNewerThan(versions.MinAgh, o.status.Version) {
sl.With("error", err, "version", o.status.Version).
Errorf("Origin AdGuard Home version must be >= %s", versions.MinAgh)
return
}
sl.With("version", o.status.Version).Info("Connected to origin")
o.profileInfo, err = oc.ProfileInfo()
if err != nil {
sl.With("error", err).Error("Error getting profileInfo info")
// 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()
if err != nil {
sl.With("error", err).Error("Error getting parental status")
return
}
o.safeSearch, err = oc.SafeSearchConfig()
if err != nil {
sl.With("error", err).Error("Error getting safe search status")
return
}
o.safeBrowsing, err = oc.SafeBrowsing()
if err != nil {
sl.With("error", err).Error("Error getting safe browsing status")
return
}
o.rewrites, err = oc.RewriteList()
if err != nil {
sl.With("error", err).Error("Error getting origin rewrites")
return
}
o.blockedServicesSchedule, err = oc.BlockedServicesSchedule()
if err != nil {
sl.With("error", err).Error("Error getting origin blocked services schedule")
return
}
o.filters, err = oc.Filtering()
if err != nil {
sl.With("error", err).Error("Error getting origin actionFilters")
return
}
o.clients, err = oc.Clients()
if err != nil {
sl.With("error", err).Error("Error getting origin clients")
return
}
o.queryLogConfig, err = oc.QueryLogConfig()
if err != nil {
sl.With("error", err).Error("Error getting query log config")
return
}
o.statsConfig, err = oc.StatsConfig()
if err != nil {
sl.With("error", err).Error("Error getting stats config")
return
}
o.accessList, err = oc.AccessList()
if err != nil {
sl.With("error", err).Error("Error getting access list")
return
}
o.dnsConfig, err = oc.DNSConfig()
if err != nil {
sl.With("error", err).Error("Error getting dns config")
return
}
if w.cfg.Features.DHCP.ServerConfig || w.cfg.Features.DHCP.StaticLeases {
o.dhcpServerConfig, err = oc.DhcpConfig()
if err != nil {
sl.With("error", err).Error("Error getting dhcp server config")
return
}
}
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)
replicas := w.cfg.UniqueReplicas()
for _, replica := range replicas {
w.syncTo(sl, o, replica)
}
}
func (w *worker) syncTo(l *zap.SugaredLogger, o *origin, replica types.AdGuardInstance) {
rc, err := w.createClient(replica)
if err != nil {
l.With("error", err, "url", replica.URL).Error("Error creating replica client")
return
}
rl := l.With("to", rc.Host())
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)
if err != nil {
rl.With("error", err).Error("Error getting replica status")
withError = true
return
}
rl.With("version", replicaStatus.Version).Info("Connected to replica")
if versions.IsNewerThan(versions.MinAgh, replicaStatus.Version) {
rl.With("error", err, "version", replicaStatus.Version).
Errorf("Replica AdGuard Home version must be >= %s", versions.MinAgh)
withError = true
return
}
if o.status.Version != replicaStatus.Version {
rl.With("originVersion", o.status.Version, "replicaVersion", replicaStatus.Version).
Warn("Versions do not match")
}
ac := &actionContext{
cfg: w.cfg,
rl: rl,
origin: o,
replicaStatus: replicaStatus,
client: rc,
replica: replica,
}
for _, action := range w.actions {
if err := action.sync(ac); err != nil {
rl.With("error", err).Errorf("Error syncing %s", action.name())
withError = true
if !w.cfg.ContinueOnError {
return
}
}
}
}
func (w *worker) statusWithSetup(
rl *zap.SugaredLogger,
replica types.AdGuardInstance,
rc client.Client,
) (*model.ServerStatus, error) {
rs, err := rc.Status()
if err != nil {
if replica.AutoSetup && errors.Is(err, client.ErrSetupNeeded) {
if serr := rc.Setup(); serr != nil {
rl.With("error", serr).Error("Error setup AdGuardHome")
return nil, err
}
return rc.Status()
}
return nil, err
}
return rs, err
}
type origin struct {
status *model.ServerStatus
rewrites *model.RewriteEntries
blockedServicesSchedule *model.BlockedServicesSchedule
filters *model.FilterStatus
clients *model.Clients
queryLogConfig *model.QueryLogConfigWithIgnored
statsConfig *model.GetStatsConfigResponse
accessList *model.AccessList
dnsConfig *model.DNSConfig
dhcpServerConfig *model.DhcpStatus
parental bool
safeSearch *model.SafeSearchConfig
profileInfo *model.ProfileInfo
safeBrowsing bool
tlsConfig *model.TlsConfig
}

713
internal/sync/sync_test.go Normal file
View File

@@ -0,0 +1,713 @@
package sync
import (
"errors"
"github.com/google/uuid"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
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 (
mockCtrl *gm.Controller
cl *clientmock.MockClient
w *worker
te error
ac *actionContext
)
BeforeEach(func() {
mockCtrl = gm.NewController(GinkgoT())
cl = clientmock.NewMockClient(mockCtrl)
w = &worker{
createClient: func(instance types.AdGuardInstance) (client.Client, error) {
return cl, nil
},
cfg: &types.Config{
Features: types.Features{
DHCP: types.DHCP{
ServerConfig: true,
StaticLeases: true,
},
DNS: types.DNS{
ServerConfig: true,
Rewrites: true,
AccessLists: true,
},
Filters: true,
ClientSettings: true,
Services: true,
GeneralSettings: true,
StatsConfig: true,
QueryLogConfig: true,
Theme: true,
},
Replicas: []types.AdGuardInstance{
{},
},
},
}
te = errors.New(uuid.NewString())
ac = &actionContext{
cfg: w.cfg,
rl: l,
origin: &origin{
profileInfo: &model.ProfileInfo{
Name: "origin",
Language: "en",
Theme: "auto",
},
status: &model.ServerStatus{},
safeSearch: &model.SafeSearchConfig{},
queryLogConfig: &model.QueryLogConfigWithIgnored{},
statsConfig: &model.PutStatsConfigUpdateRequest{},
},
replicaStatus: &model.ServerStatus{},
client: cl,
replica: w.cfg.Replicas[0],
}
})
AfterEach(func() {
defer mockCtrl.Finish()
})
Context("worker", func() {
Context("actionDNSRewrites", func() {
var (
domain string
answer string
reO model.RewriteEntries
reR model.RewriteEntries
)
BeforeEach(func() {
domain = uuid.NewString()
answer = uuid.NewString()
reO = model.RewriteEntries{{Domain: utils.Ptr(domain), Answer: utils.Ptr(answer)}}
reR = model.RewriteEntries{{Domain: utils.Ptr(domain), Answer: utils.Ptr(answer)}}
})
It("should have no changes (empty slices)", func() {
ac.origin.rewrites = &reO
cl.EXPECT().RewriteList().Return(&reR, nil)
cl.EXPECT().AddRewriteEntries()
cl.EXPECT().DeleteRewriteEntries()
err := actionDNSRewrites(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should add one rewrite entry", func() {
reR = []model.RewriteEntry{}
ac.origin.rewrites = &reO
cl.EXPECT().RewriteList().Return(&reR, nil)
cl.EXPECT().AddRewriteEntries(reO[0])
cl.EXPECT().DeleteRewriteEntries()
err := actionDNSRewrites(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should remove one rewrite entry", func() {
reO = []model.RewriteEntry{}
ac.origin.rewrites = &reO
cl.EXPECT().RewriteList().Return(&reR, nil)
cl.EXPECT().AddRewriteEntries()
cl.EXPECT().DeleteRewriteEntries(reR[0])
err := actionDNSRewrites(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should remove one rewrite entry", func() {
reO = []model.RewriteEntry{}
ac.origin.rewrites = &reO
cl.EXPECT().RewriteList().Return(&reR, nil)
cl.EXPECT().AddRewriteEntries()
cl.EXPECT().DeleteRewriteEntries(reR[0])
err := actionDNSRewrites(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should return error when error on RewriteList()", func() {
ac.origin.rewrites = &reO
cl.EXPECT().RewriteList().Return(nil, te)
err := actionDNSRewrites(ac)
Ω(err).Should(HaveOccurred())
})
It("should return error when error on AddRewriteEntries()", func() {
ac.origin.rewrites = &reO
cl.EXPECT().RewriteList().Return(&reR, nil)
cl.EXPECT().DeleteRewriteEntries()
cl.EXPECT().AddRewriteEntries().Return(te)
err := actionDNSRewrites(ac)
Ω(err).Should(HaveOccurred())
})
It("should return error when error on DeleteRewriteEntries()", func() {
ac.origin.rewrites = &reO
cl.EXPECT().RewriteList().Return(&reR, nil)
cl.EXPECT().DeleteRewriteEntries().Return(te)
err := actionDNSRewrites(ac)
Ω(err).Should(HaveOccurred())
})
})
Context("actionClientSettings", func() {
var (
clR *model.Clients
name string
)
BeforeEach(func() {
name = uuid.NewString()
ac.origin.clients = &model.Clients{Clients: &model.ClientsArray{{Name: utils.Ptr(name)}}}
clR = &model.Clients{Clients: &model.ClientsArray{{Name: utils.Ptr(name)}}}
})
It("should have no changes (empty slices)", func() {
cl.EXPECT().Clients().Return(clR, nil)
err := actionClientSettings(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should add one client", func() {
clR.Clients = &model.ClientsArray{}
cl.EXPECT().Clients().Return(clR, nil)
cl.EXPECT().AddClient(&(*ac.origin.clients.Clients)[0])
err := actionClientSettings(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should update one client", func() {
(*clR.Clients)[0].FilteringEnabled = utils.Ptr(true)
cl.EXPECT().Clients().Return(clR, nil)
cl.EXPECT().UpdateClient(&(*ac.origin.clients.Clients)[0])
err := actionClientSettings(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should delete one client", func() {
ac.origin.clients.Clients = &model.ClientsArray{}
cl.EXPECT().Clients().Return(clR, nil)
cl.EXPECT().DeleteClient(&(*clR.Clients)[0])
err := actionClientSettings(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should return error when error on Clients()", func() {
cl.EXPECT().Clients().Return(nil, te)
err := actionClientSettings(ac)
Ω(err).Should(HaveOccurred())
})
})
Context("actionParental", func() {
It("should have no changes", func() {
cl.EXPECT().Parental()
err := actionParental(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have parental enabled changes", func() {
ac.origin.parental = true
cl.EXPECT().Parental()
cl.EXPECT().ToggleParental(true)
err := actionParental(ac)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("actionProtection", func() {
It("should have no changes", func() {
err := actionProtection(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have protection enabled changes", func() {
ac.origin.status.ProtectionEnabled = true
cl.EXPECT().ToggleProtection(true)
err := actionProtection(ac)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("actionSafeSearchConfig", func() {
It("should have no changes", func() {
cl.EXPECT().SafeSearchConfig().Return(ac.origin.safeSearch, nil)
err := actionSafeSearchConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have safeSearch enabled changes", func() {
ac.origin.safeSearch = &model.SafeSearchConfig{Enabled: utils.Ptr(true)}
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil)
cl.EXPECT().SetSafeSearchConfig(ac.origin.safeSearch)
err := actionSafeSearchConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have Duckduckgo safeSearch enabled changed", func() {
ac.origin.safeSearch = &model.SafeSearchConfig{Duckduckgo: utils.Ptr(true)}
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{Google: utils.Ptr(true)}, nil)
cl.EXPECT().SetSafeSearchConfig(ac.origin.safeSearch)
err := actionSafeSearchConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("actionProfileInfo", func() {
It("should have no changes", func() {
cl.EXPECT().ProfileInfo().Return(ac.origin.profileInfo, nil)
err := actionProfileInfo(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have profileInfo language changed", func() {
ac.origin.profileInfo.Language = "de"
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{Name: "replica", Language: "en"}, nil)
cl.EXPECT().SetProfileInfo(&model.ProfileInfo{
Language: "de",
Name: "replica",
Theme: "auto",
})
err := actionProfileInfo(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should not change theme if feature is disabled", func() {
ac.origin.profileInfo.Language = "de"
ac.cfg.Features.Theme = false
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{Name: "replica", Language: "en"}, nil)
cl.EXPECT().SetProfileInfo(&model.ProfileInfo{
Language: "de",
Name: "replica",
Theme: "",
})
err := actionProfileInfo(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should not sync profileInfo if language is not set", func() {
ac.origin.profileInfo.Language = ""
cl.EXPECT().
ProfileInfo().
Return(&model.ProfileInfo{Name: "replica", Language: "en", Theme: "auto"}, nil)
cl.EXPECT().SetProfileInfo(ac.origin.profileInfo).Times(0)
err := actionProfileInfo(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should not sync profileInfo if theme is not set", func() {
ac.origin.profileInfo.Theme = ""
cl.EXPECT().
ProfileInfo().
Return(&model.ProfileInfo{Name: "replica", Language: "en", Theme: "auto"}, nil)
cl.EXPECT().SetProfileInfo(ac.origin.profileInfo).Times(0)
err := actionProfileInfo(ac)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("actionSafeBrowsing", func() {
It("should have no changes", func() {
cl.EXPECT().SafeBrowsing()
err := actionSafeBrowsing(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have safeBrowsing enabled changes", func() {
ac.origin.safeBrowsing = true
cl.EXPECT().SafeBrowsing()
cl.EXPECT().ToggleSafeBrowsing(true)
err := actionSafeBrowsing(ac)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("actionQueryLogConfig", func() {
var qlc *model.QueryLogConfigWithIgnored
BeforeEach(func() {
qlc = &model.QueryLogConfigWithIgnored{}
})
It("should have no changes", func() {
cl.EXPECT().QueryLogConfig().Return(qlc, nil)
err := actionQueryLogConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have QueryLogConfig changes", func() {
var interval model.QueryLogConfigInterval = 123
ac.origin.queryLogConfig.Interval = &interval
cl.EXPECT().QueryLogConfig().Return(qlc, nil)
cl.EXPECT().
SetQueryLogConfig(&model.QueryLogConfigWithIgnored{QueryLogConfig: model.QueryLogConfig{AnonymizeClientIp: nil, Interval: &interval, Enabled: nil}})
err := actionQueryLogConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("syncConfigs", func() {
var sc *model.PutStatsConfigUpdateRequest
BeforeEach(func() {
sc = &model.PutStatsConfigUpdateRequest{}
})
It("should have no changes", func() {
cl.EXPECT().StatsConfig().Return(sc, nil)
err := actionStatsConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have StatsConfig changes", func() {
var interval float32 = 123
ac.origin.statsConfig.Interval = interval
cl.EXPECT().StatsConfig().Return(sc, nil)
cl.EXPECT().SetStatsConfig(&model.PutStatsConfigUpdateRequest{Interval: interval})
err := actionStatsConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("statusWithSetup", func() {
var (
status *model.ServerStatus
inst types.AdGuardInstance
)
BeforeEach(func() {
status = &model.ServerStatus{}
inst = types.AdGuardInstance{
AutoSetup: true,
}
})
It("should get the replica status", func() {
cl.EXPECT().Status().Return(status, nil)
st, err := w.statusWithSetup(l, inst, cl)
Ω(err).ShouldNot(HaveOccurred())
Ω(st).Should(Equal(status))
})
It("should runs setup before getting replica status", func() {
cl.EXPECT().Status().Return(nil, client.ErrSetupNeeded)
cl.EXPECT().Setup()
cl.EXPECT().Status().Return(status, nil)
st, err := w.statusWithSetup(l, inst, cl)
Ω(err).ShouldNot(HaveOccurred())
Ω(st).Should(Equal(status))
})
It("should fail on setup", func() {
cl.EXPECT().Status().Return(nil, client.ErrSetupNeeded)
cl.EXPECT().Setup().Return(te)
st, err := w.statusWithSetup(l, inst, cl)
Ω(err).Should(HaveOccurred())
Ω(st).Should(BeNil())
})
})
Context("actionBlockedServicesSchedule", func() {
var rbss *model.BlockedServicesSchedule
BeforeEach(func() {
ac.origin.blockedServicesSchedule = &model.BlockedServicesSchedule{}
rbss = &model.BlockedServicesSchedule{}
})
It("should have no changes", func() {
cl.EXPECT().BlockedServicesSchedule().Return(ac.origin.blockedServicesSchedule, nil)
err := actionBlockedServicesSchedule(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have blockedServices schedule changes", func() {
ac.origin.blockedServicesSchedule = &model.BlockedServicesSchedule{Ids: utils.Ptr([]string{"bar"})}
cl.EXPECT().BlockedServicesSchedule().Return(rbss, nil)
cl.EXPECT().SetBlockedServicesSchedule(ac.origin.blockedServicesSchedule)
err := actionBlockedServicesSchedule(ac)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("syncFilters", func() {
var rf *model.FilterStatus
BeforeEach(func() {
ac.origin.filters = &model.FilterStatus{}
rf = &model.FilterStatus{}
})
It("should have no changes", func() {
cl.EXPECT().Filtering().Return(rf, nil)
err := actionFilters(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have changes user roles", func() {
ac.origin.filters.UserRules = utils.Ptr([]string{"foo"})
cl.EXPECT().Filtering().Return(rf, nil)
cl.EXPECT().SetCustomRules(ac.origin.filters.UserRules)
err := actionFilters(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have changed filtering config", func() {
ac.origin.filters.Enabled = utils.Ptr(true)
ac.origin.filters.Interval = utils.Ptr(123)
cl.EXPECT().Filtering().Return(rf, nil)
cl.EXPECT().ToggleFiltering(*ac.origin.filters.Enabled, *ac.origin.filters.Interval)
err := actionFilters(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should add a filter", func() {
ac.origin.filters.Filters = utils.Ptr([]model.Filter{{Name: "foo", Url: "https://foo.bar"}})
cl.EXPECT().Filtering().Return(rf, nil)
cl.EXPECT().AddFilter(false, model.Filter{Name: "foo", Url: "https://foo.bar"})
cl.EXPECT().RefreshFilters(gm.Any())
err := actionFilters(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should delete a filter", func() {
rf.Filters = utils.Ptr([]model.Filter{{Name: "foo", Url: "https://foo.bar"}})
cl.EXPECT().Filtering().Return(rf, nil)
cl.EXPECT().DeleteFilter(false, model.Filter{Name: "foo", Url: "https://foo.bar"})
err := actionFilters(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should update a filter", func() {
ac.origin.filters.Filters = utils.Ptr(
[]model.Filter{{Name: "foo", Url: "https://foo.bar", Enabled: true}},
)
rf.Filters = utils.Ptr([]model.Filter{{Name: "foo", Url: "https://foo.bar"}})
cl.EXPECT().Filtering().Return(rf, nil)
cl.EXPECT().UpdateFilter(false, model.Filter{Name: "foo", Url: "https://foo.bar", Enabled: true})
cl.EXPECT().RefreshFilters(gm.Any())
err := actionFilters(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should abort after failed added filter", func() {
ac.cfg.ContinueOnError = false
ac.origin.filters.Filters = utils.Ptr([]model.Filter{{Name: "foo", Url: "https://foo.bar"}})
cl.EXPECT().Filtering().Return(rf, nil)
cl.EXPECT().
AddFilter(false, model.Filter{Name: "foo", Url: "https://foo.bar"}).
Return(errors.New("test failure"))
err := actionFilters(ac)
Ω(err).Should(HaveOccurred())
})
It("should continue after failed added filter", func() {
ac.cfg.ContinueOnError = true
ac.origin.filters.Filters = utils.Ptr(
[]model.Filter{{Name: "foo", Url: "https://foo.bar"}, {Name: "bar", Url: "https://bar.foo"}},
)
cl.EXPECT().Filtering().Return(rf, nil)
cl.EXPECT().
AddFilter(false, model.Filter{Name: "foo", Url: "https://foo.bar"}).
Return(errors.New("test failure"))
cl.EXPECT().AddFilter(false, model.Filter{Name: "bar", Url: "https://bar.foo"})
cl.EXPECT().RefreshFilters(gm.Any())
err := actionFilters(ac)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("actionDNSAccessLists", func() {
var ral *model.AccessList
BeforeEach(func() {
ac.origin.accessList = &model.AccessList{}
ral = &model.AccessList{}
})
It("should have no changes", func() {
cl.EXPECT().AccessList().Return(ral, nil)
err := actionDNSAccessLists(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have access list changes", func() {
ral.BlockedHosts = utils.Ptr([]string{"foo"})
cl.EXPECT().AccessList().Return(ral, nil)
cl.EXPECT().SetAccessList(ac.origin.accessList)
err := actionDNSAccessLists(ac)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("actionDNSServerConfig", func() {
var rdc *model.DNSConfig
BeforeEach(func() {
ac.origin.dnsConfig = &model.DNSConfig{}
rdc = &model.DNSConfig{}
})
It("should have no changes", func() {
cl.EXPECT().DNSConfig().Return(rdc, nil)
err := actionDNSServerConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have dns config changes", func() {
rdc.BootstrapDns = utils.Ptr([]string{"foo"})
cl.EXPECT().DNSConfig().Return(rdc, nil)
cl.EXPECT().SetDNSConfig(ac.origin.dnsConfig)
err := actionDNSServerConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("actionDHCPServerConfig", func() {
var rsc *model.DhcpStatus
BeforeEach(func() {
ac.origin.dhcpServerConfig = &model.DhcpStatus{
V4: &model.DhcpConfigV4{
GatewayIp: utils.Ptr("1.2.3.4"),
RangeStart: utils.Ptr("1.2.3.5"),
RangeEnd: utils.Ptr("1.2.3.6"),
SubnetMask: utils.Ptr("255.255.255.0"),
},
}
rsc = &model.DhcpStatus{}
w.cfg.Features.DHCP.StaticLeases = false
})
It("should have no changes", func() {
rsc.V4 = ac.origin.dhcpServerConfig.V4
cl.EXPECT().DhcpConfig().Return(rsc, nil)
err := actionDHCPServerConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have changes", func() {
rsc.Enabled = utils.Ptr(true)
cl.EXPECT().DhcpConfig().Return(rsc, nil)
cl.EXPECT().SetDhcpConfig(ac.origin.dhcpServerConfig)
err := actionDHCPServerConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should use replica interface name", func() {
ac.replica.InterfaceName = "foo"
cl.EXPECT().DhcpConfig().Return(rsc, nil)
oscClone := ac.origin.dhcpServerConfig.Clone()
oscClone.InterfaceName = utils.Ptr("foo")
cl.EXPECT().SetDhcpConfig(oscClone)
err := actionDHCPServerConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should enable the target dhcp server", func() {
ac.replica.DHCPServerEnabled = utils.Ptr(true)
cl.EXPECT().DhcpConfig().Return(rsc, nil)
oscClone := ac.origin.dhcpServerConfig.Clone()
oscClone.Enabled = utils.Ptr(true)
cl.EXPECT().SetDhcpConfig(oscClone)
err := actionDHCPServerConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should not sync empty IPv4", func() {
ac.replica.DHCPServerEnabled = utils.Ptr(false)
ac.origin.dhcpServerConfig.V4 = &model.DhcpConfigV4{
GatewayIp: utils.Ptr(""),
}
err := actionDHCPServerConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("sync", func() {
BeforeEach(func() {
w.cfg = &types.Config{
Origin: &types.AdGuardInstance{},
Replica: &types.AdGuardInstance{URL: "foo"},
Features: types.Features{
DHCP: types.DHCP{
ServerConfig: true,
StaticLeases: true,
},
DNS: types.DNS{
ServerConfig: true,
Rewrites: true,
AccessLists: true,
},
Filters: true,
ClientSettings: true,
Services: true,
GeneralSettings: true,
StatsConfig: true,
QueryLogConfig: true,
TLSConfig: true,
},
}
})
It("should have no changes", func() {
// origin
cl.EXPECT().Host().Times(2)
cl.EXPECT().Status().Return(&model.ServerStatus{Version: versions.MinAgh}, nil)
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{}, nil)
cl.EXPECT().Parental()
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil)
cl.EXPECT().SafeBrowsing()
cl.EXPECT().RewriteList().Return(&model.RewriteEntries{}, nil)
cl.EXPECT().BlockedServicesSchedule()
cl.EXPECT().Filtering().Return(&model.FilterStatus{}, nil)
cl.EXPECT().Clients().Return(&model.Clients{}, nil)
cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfigWithIgnored{}, nil)
cl.EXPECT().StatsConfig().Return(&model.PutStatsConfigUpdateRequest{}, nil)
cl.EXPECT().AccessList().Return(&model.AccessList{}, nil)
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
cl.EXPECT().DhcpConfig().Return(&model.DhcpStatus{}, nil)
cl.EXPECT().TLSConfig().Return(&model.TlsConfig{}, nil)
// replica
cl.EXPECT().Host()
cl.EXPECT().Status().Return(&model.ServerStatus{Version: versions.MinAgh}, nil)
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{}, nil)
cl.EXPECT().Parental()
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil)
cl.EXPECT().SafeBrowsing()
cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfigWithIgnored{}, nil)
cl.EXPECT().StatsConfig().Return(&model.PutStatsConfigUpdateRequest{}, nil)
cl.EXPECT().RewriteList().Return(&model.RewriteEntries{}, nil)
cl.EXPECT().AddRewriteEntries()
cl.EXPECT().DeleteRewriteEntries()
cl.EXPECT().Filtering().Return(&model.FilterStatus{}, nil)
cl.EXPECT().BlockedServicesSchedule()
cl.EXPECT().Clients().Return(&model.Clients{}, nil)
cl.EXPECT().AccessList().Return(&model.AccessList{}, nil)
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
cl.EXPECT().DhcpConfig().Return(&model.DhcpStatus{}, nil)
cl.EXPECT().TLSConfig().Return(&model.TlsConfig{}, nil)
w.sync()
})
It("should not sync DHCP", func() {
w.cfg.Features.DHCP.ServerConfig = false
w.cfg.Features.DHCP.StaticLeases = false
// origin
cl.EXPECT().Host().Times(2)
cl.EXPECT().Status().Return(&model.ServerStatus{Version: versions.MinAgh}, nil)
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{}, nil)
cl.EXPECT().Parental()
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil)
cl.EXPECT().SafeBrowsing()
cl.EXPECT().RewriteList().Return(&model.RewriteEntries{}, nil)
cl.EXPECT().BlockedServicesSchedule()
cl.EXPECT().Filtering().Return(&model.FilterStatus{}, nil)
cl.EXPECT().Clients().Return(&model.Clients{}, nil)
cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfigWithIgnored{}, nil)
cl.EXPECT().StatsConfig().Return(&model.PutStatsConfigUpdateRequest{}, nil)
cl.EXPECT().AccessList().Return(&model.AccessList{}, nil)
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
cl.EXPECT().TLSConfig().Return(&model.TlsConfig{}, nil)
// replica
cl.EXPECT().Host()
cl.EXPECT().Status().Return(&model.ServerStatus{Version: versions.MinAgh}, nil)
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{}, nil)
cl.EXPECT().Parental()
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil)
cl.EXPECT().SafeBrowsing()
cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfigWithIgnored{}, nil)
cl.EXPECT().StatsConfig().Return(&model.PutStatsConfigUpdateRequest{}, nil)
cl.EXPECT().RewriteList().Return(&model.RewriteEntries{}, nil)
cl.EXPECT().AddRewriteEntries()
cl.EXPECT().DeleteRewriteEntries()
cl.EXPECT().Filtering().Return(&model.FilterStatus{}, nil)
cl.EXPECT().BlockedServicesSchedule()
cl.EXPECT().Clients().Return(&model.Clients{}, nil)
cl.EXPECT().AccessList().Return(&model.AccessList{}, nil)
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
cl.EXPECT().TLSConfig().Return(&model.TlsConfig{}, nil)
w.sync()
})
It("origin version is too small", func() {
// origin
cl.EXPECT().Host()
cl.EXPECT().Status().Return(&model.ServerStatus{Version: "v0.106.9"}, nil)
w.sync()
})
It("replica version is too small", func() {
// origin
cl.EXPECT().Host()
cl.EXPECT().Status().Return(&model.ServerStatus{Version: versions.MinAgh}, nil)
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{}, nil)
cl.EXPECT().Parental()
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil)
cl.EXPECT().SafeBrowsing()
cl.EXPECT().RewriteList().Return(&model.RewriteEntries{}, nil)
cl.EXPECT().BlockedServicesSchedule()
cl.EXPECT().Filtering().Return(&model.FilterStatus{}, nil)
cl.EXPECT().Clients().Return(&model.Clients{}, nil)
cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfigWithIgnored{}, nil)
cl.EXPECT().StatsConfig().Return(&model.PutStatsConfigUpdateRequest{}, nil)
cl.EXPECT().AccessList().Return(&model.AccessList{}, nil)
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
cl.EXPECT().DhcpConfig().Return(&model.DhcpStatus{}, nil)
cl.EXPECT().TLSConfig().Return(&model.TlsConfig{}, nil)
// replica
cl.EXPECT().Host().Times(2)
cl.EXPECT().Status().Return(&model.ServerStatus{Version: "v0.106.9"}, nil)
w.sync()
})
})
})
})

View 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,
}
}

104
internal/types/features.go Normal file
View File

@@ -0,0 +1,104 @@
package types
import (
"go.uber.org/zap"
)
func NewFeatures(enabled bool) Features {
return Features{
DNS: DNS{
AccessLists: enabled,
ServerConfig: enabled,
Rewrites: enabled,
},
DHCP: DHCP{
ServerConfig: enabled,
StaticLeases: enabled,
},
GeneralSettings: enabled,
QueryLogConfig: enabled,
StatsConfig: enabled,
ClientSettings: enabled,
Services: enabled,
Filters: enabled,
Theme: enabled,
TLSConfig: false,
}
}
// Features feature flags.
type Features struct {
DNS DNS `json:"dns" yaml:"dns"`
DHCP DHCP `json:"dhcp" yaml:"dhcp"`
GeneralSettings bool `json:"generalSettings" yaml:"generalSettings" documentation:"Sync general settings" env:"FEATURES_GENERAL_SETTINGS"`
QueryLogConfig bool `json:"queryLogConfig" yaml:"queryLogConfig" documentation:"Sync query log config" env:"FEATURES_QUERY_LOG_CONFIG"`
StatsConfig bool `json:"statsConfig" yaml:"statsConfig" documentation:"Sync stats config" env:"FEATURES_STATS_CONFIG"`
ClientSettings bool `json:"clientSettings" yaml:"clientSettings" documentation:"Sync client settings" env:"FEATURES_CLIENT_SETTINGS"`
Services bool `json:"services" yaml:"services" documentation:"Sync services" env:"FEATURES_SERVICES"`
Filters bool `json:"filters" yaml:"filters" documentation:"Sync filters" env:"FEATURES_FILTERS"`
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.
type DHCP struct {
ServerConfig bool `documentation:"Sync DHCP server config" env:"FEATURES_DHCP_SERVER_CONFIG" json:"serverConfig" yaml:"serverConfig"`
StaticLeases bool `documentation:"Sync DHCP static leases" env:"FEATURES_DHCP_STATIC_LEASES" json:"staticLeases" yaml:"staticLeases"`
}
// DNS features.
type DNS struct {
AccessLists bool `documentation:"Sync DNS access lists" env:"FEATURES_DNS_ACCESS_LISTS" json:"accessLists" yaml:"accessLists"`
ServerConfig bool `documentation:"Sync DNS server config" env:"FEATURES_DNS_SERVER_CONFIG" json:"serverConfig" yaml:"serverConfig"`
Rewrites bool `documentation:"Sync DNS rewrites" env:"FEATURES_DNS_REWRITES" json:"rewrites" yaml:"rewrites"`
}
// LogDisabled log all disabled features.
func (f *Features) LogDisabled(l *zap.SugaredLogger) {
features := f.collectDisabled()
if len(features) > 0 {
l.With("features", features).Info("Disabled features")
}
}
func (f *Features) collectDisabled() []string {
var features []string
if !f.DHCP.ServerConfig {
features = append(features, "DHCP.ServerConfig")
}
if !f.DHCP.StaticLeases {
features = append(features, "DHCP.StaticLeases")
}
if !f.DNS.AccessLists {
features = append(features, "DNS.AccessLists")
}
if !f.DNS.ServerConfig {
features = append(features, "DNS.ServerConfig")
}
if !f.DNS.Rewrites {
features = append(features, "DNS.Rewrites")
}
if !f.GeneralSettings {
features = append(features, "GeneralSettings")
}
if !f.QueryLogConfig {
features = append(features, "QueryLogConfig")
}
if !f.StatsConfig {
features = append(features, "StatsConfig")
}
if !f.ClientSettings {
features = append(features, "ClientSettings")
}
if !f.Services {
features = append(features, "BlockedServices")
}
if !f.Filters {
features = append(features, "Filters")
}
if !f.TLSConfig {
features = append(features, "TLSConfig")
}
return features
}

222
internal/types/types.go Normal file
View 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"`
}

View 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)
})
}

View File

@@ -7,7 +7,7 @@ import (
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )
func TestTypes(t *testing.T) { func TestSync(t *testing.T) {
RegisterFailHandler(Fail) RegisterFailHandler(Fail)
RunSpecs(t, "Types Suite") RunSpecs(t, "Types Suite")
} }

View File

@@ -0,0 +1,152 @@
package types
import (
"strings"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("Types", func() {
Context("AdGuardInstance", func() {
var inst AdGuardInstance
BeforeEach(func() {
inst = AdGuardInstance{}
})
Context("Instance Init", func() {
BeforeEach(func() {
inst.URL = "https://localhost:3000"
})
It("should correctly set Host and WebHost if only URL is set", func() {
err := inst.Init()
Ω(err).ShouldNot(HaveOccurred())
Ω(inst.Host).Should(Equal("localhost:3000"))
Ω(inst.WebHost).Should(Equal("localhost:3000"))
Ω(inst.URL).Should(Equal("https://localhost:3000"))
Ω(inst.WebURL).Should(Equal("https://localhost:3000"))
})
It("should correctly set Host and WebHost if URL and WebURL are set", func() {
inst.WebURL = "https://127.0.0.1:4000"
err := inst.Init()
Ω(err).ShouldNot(HaveOccurred())
Ω(inst.Host).Should(Equal("localhost:3000"))
Ω(inst.WebHost).Should(Equal("127.0.0.1:4000"))
Ω(inst.WebURL).Should(Equal(inst.WebURL))
Ω(inst.URL).Should(Equal("https://localhost:3000"))
Ω(inst.WebURL).Should(Equal("https://127.0.0.1:4000"))
})
})
})
Context("Config", func() {
Context("init", func() {
cfg := Config{
Origin: &AdGuardInstance{},
Replicas: []AdGuardInstance{
{URL: "https://localhost:3000"},
},
}
err := cfg.Init()
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].Host).Should(Equal("localhost:3000"))
Ω(cfg.Replicas[0].WebHost).Should(Equal("localhost:3000"))
Ω(cfg.Replicas[0].URL).Should(Equal("https://localhost:3000"))
Ω(cfg.Replicas[0].WebURL).Should(Equal("https://localhost:3000"))
})
Context("UniqueReplicas", func() {
It("should return unique replicas in the array", func() {
cfg := Config{
Origin: &AdGuardInstance{},
Replicas: []AdGuardInstance{
{URL: "a"},
{URL: "a", APIPath: DefaultAPIPath},
{URL: "a", APIPath: "foo"},
{URL: "b", APIPath: DefaultAPIPath},
},
Replica: &AdGuardInstance{URL: "b"},
}
replicas := cfg.UniqueReplicas()
Ω(replicas).Should(HaveLen(3))
})
})
Context("mask", func() {
It("should mask all names and passwords", func() {
cfg := Config{
Origin: &AdGuardInstance{},
Replicas: []AdGuardInstance{
{URL: "a", Username: "user", Password: "pass"},
},
Replica: &AdGuardInstance{URL: "a", Username: "user", Password: "pass"},
API: API{Username: "user", Password: "pass"},
}
masked := cfg.mask()
Ω(masked.Replicas[0].Username).Should(Equal("u**r"))
Ω(masked.Replicas[0].Password).Should(Equal("p**s"))
Ω(masked.Replica.Username).Should(Equal("u**r"))
Ω(masked.Replica.Password).Should(Equal("p**s"))
Ω(masked.API.Username).Should(Equal("u**r"))
Ω(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("LogDisabled", func() {
It("should log all features", func() {
f := NewFeatures(false)
Ω(f.collectDisabled()).Should(HaveLen(12))
})
It("should log no features", func() {
f := NewFeatures(true)
Ω(f.collectDisabled()).Should(HaveLen(1))
})
})
})
Context("TLS", func() {
var t TLS
BeforeEach(func() {
t = TLS{
CertDir: "/path/to/certs",
}
})
Context("Enabled", func() {
It("should use enabled", func() {
Ω(t.Enabled()).Should(BeTrue())
})
It("should use disabled", func() {
t.CertDir = " "
Ω(t.Enabled()).Should(BeFalse())
})
})
Context("Certs", func() {
It("should use default crt and key", func() {
crt, key := t.Certs()
crt = normalizePath(crt)
key = normalizePath(key)
Ω(crt).Should(Equal("/path/to/certs/tls.crt"))
Ω(key).Should(Equal("/path/to/certs/tls.key"))
})
It("should use custom crt and key", func() {
t.CertName = "foo.crt"
t.KeyName = "bar.key"
crt, key := t.Certs()
crt = normalizePath(crt)
key = normalizePath(key)
Ω(crt).Should(Equal("/path/to/certs/foo.crt"))
Ω(key).Should(Equal("/path/to/certs/bar.key"))
})
})
})
})
func normalizePath(path string) string {
return strings.ReplaceAll(path, "\\", "/")
}

View 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
View 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)
}

24
internal/utils/ptr.go Normal file
View File

@@ -0,0 +1,24 @@
package utils
import "fmt"
func Ptr[I any](i I) *I {
return &i
}
func PtrToString[I any](i *I) string {
if i == nil {
return ""
}
return fmt.Sprintf("%v", i)
}
func PtrEquals[I comparable](a, b *I) bool {
if a == nil && b == nil {
return true
}
if a == nil || b == nil {
return false
}
return *a == *b
}

View File

@@ -0,0 +1,23 @@
package versions
import "golang.org/x/mod/semver"
const (
// MinAgh minimal adguardhome version.
MinAgh = "v0.107.40"
)
func IsNewerThan(v1, v2 string) bool {
return semver.Compare(sanitize(v1), sanitize(v2)) == 1
}
func IsSame(v1, v2 string) bool {
return semver.Compare(sanitize(v1), sanitize(v2)) == 0
}
func sanitize(v string) string {
if v == "" || v[0] == 'v' {
return v
}
return "v" + v
}

View File

@@ -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() {

View 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

View File

@@ -1,496 +0,0 @@
package client
import (
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"os"
"path"
"strconv"
"github.com/bakito/adguardhome-sync/pkg/log"
"github.com/bakito/adguardhome-sync/pkg/types"
"github.com/go-resty/resty/v2"
"go.uber.org/zap"
)
const envRedirectPolicyNoOfRedirects = "REDIRECT_POLICY_NO_OF_REDIRECTS"
var (
l = log.GetLogger("client")
// ErrSetupNeeded custom error
ErrSetupNeeded = errors.New("setup needed")
)
func detailedError(resp *resty.Response, err error) error {
e := resp.Status()
if len(resp.Body()) > 0 {
e += fmt.Sprintf("(%s)", string(resp.Body()))
}
if err != nil {
e += fmt.Sprintf(": %s", err.Error())
}
return errors.New(e)
}
// New create a new client
func New(config types.AdGuardInstance) (Client, error) {
var apiURL string
if config.APIPath == "" {
apiURL = fmt.Sprintf("%s/control", config.URL)
} else {
apiURL = fmt.Sprintf("%s/%s", config.URL, config.APIPath)
}
u, err := url.Parse(apiURL)
if err != nil {
return nil, err
}
u.Path = path.Clean(u.Path)
cl := resty.New().SetBaseURL(u.String()).SetDisableWarn(true)
if config.InsecureSkipVerify {
// #nosec G402 has to be explicitly enabled
cl.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
}
if config.Username != "" && config.Password != "" {
cl = cl.SetBasicAuth(config.Username, config.Password)
}
if v, ok := os.LookupEnv(envRedirectPolicyNoOfRedirects); ok {
nbr, err := strconv.Atoi(v)
if err != nil {
return nil, fmt.Errorf("error parsing env var %q value must be an integer", envRedirectPolicyNoOfRedirects)
}
cl.SetRedirectPolicy(resty.FlexibleRedirectPolicy(nbr))
} else {
// no redirect
cl.SetRedirectPolicy(resty.NoRedirectPolicy())
}
return &client{
host: u.Host,
client: cl,
log: l.With("host", u.Host),
}, nil
}
// Client AdguardHome API client interface
type Client interface {
Host() string
Status() (*types.Status, error)
ToggleProtection(enable bool) error
RewriteList() (*types.RewriteEntries, error)
AddRewriteEntries(e ...types.RewriteEntry) error
DeleteRewriteEntries(e ...types.RewriteEntry) error
Filtering() (*types.FilteringStatus, error)
ToggleFiltering(enabled bool, interval float64) error
AddFilters(whitelist bool, e ...types.Filter) error
DeleteFilters(whitelist bool, e ...types.Filter) error
UpdateFilters(whitelist bool, e ...types.Filter) error
RefreshFilters(whitelist bool) error
SetCustomRules(rules types.UserRules) error
SafeBrowsing() (bool, error)
ToggleSafeBrowsing(enable bool) error
Parental() (bool, error)
ToggleParental(enable bool) error
SafeSearch() (bool, error)
ToggleSafeSearch(enable bool) error
Services() (types.Services, error)
SetServices(services types.Services) error
Clients() (*types.Clients, error)
AddClients(client ...types.Client) error
UpdateClients(client ...types.Client) error
DeleteClients(client ...types.Client) error
QueryLogConfig() (*types.QueryLogConfig, error)
SetQueryLogConfig(enabled bool, interval float64, anonymizeClientIP bool) error
StatsConfig() (*types.IntervalConfig, error)
SetStatsConfig(interval float64) error
Setup() error
AccessList() (*types.AccessList, error)
SetAccessList(*types.AccessList) error
DNSConfig() (*types.DNSConfig, error)
SetDNSConfig(*types.DNSConfig) error
DHCPServerConfig() (*types.DHCPServerConfig, error)
SetDHCPServerConfig(*types.DHCPServerConfig) error
AddDHCPStaticLeases(leases ...types.Lease) error
DeleteDHCPStaticLeases(leases ...types.Lease) error
}
type client struct {
client *resty.Client
log *zap.SugaredLogger
host string
version string
}
func (cl *client) Host() string {
return cl.host
}
func (cl *client) doGet(req *resty.Request, url string) error {
rl := cl.log.With("method", "GET", "path", url)
if cl.client.UserInfo != nil {
rl = rl.With("username", cl.client.UserInfo.Username)
}
req.ForceContentType("application/json")
rl.Debug("do get")
resp, err := req.Get(url)
if err != nil {
if resp != nil && resp.StatusCode() == http.StatusFound {
loc := resp.Header().Get("Location")
if loc == "/install.html" {
return ErrSetupNeeded
}
}
rl.With("status", resp.StatusCode(), "body", string(resp.Body()), "error", err).Debug("error in do get")
return detailedError(resp, err)
}
rl.With(
"status", resp.StatusCode(),
"body", string(resp.Body()),
"content-type", resp.Header()["Content-Type"],
).Debug("got response")
if resp.StatusCode() != http.StatusOK {
return detailedError(resp, nil)
}
return nil
}
func (cl *client) doPost(req *resty.Request, url string) error {
rl := cl.log.With("method", "POST", "path", url)
if cl.client.UserInfo != nil {
rl = rl.With("username", cl.client.UserInfo.Username)
}
b, _ := json.Marshal(req.Body)
rl.With("body", string(b)).Debug("do post")
resp, err := req.Post(url)
if err != nil {
rl.With("status", resp.StatusCode(), "body", string(resp.Body()), "error", err).Debug("error in do post")
return detailedError(resp, err)
}
rl.With(
"status", resp.StatusCode(),
"body", string(resp.Body()),
"content-type", contentType(resp),
).Debug("got response")
if resp.StatusCode() != http.StatusOK {
return detailedError(resp, nil)
}
return nil
}
func contentType(resp *resty.Response) string {
if ct, ok := resp.Header()["Content-Type"]; ok {
if len(ct) != 1 {
return fmt.Sprintf("%v", ct)
}
return ct[0]
}
return ""
}
func (cl *client) Status() (*types.Status, error) {
status := &types.Status{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(status), "status")
cl.version = status.Version
return status, err
}
func (cl *client) RewriteList() (*types.RewriteEntries, error) {
rewrites := &types.RewriteEntries{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(&rewrites), "/rewrite/list")
return rewrites, err
}
func (cl *client) AddRewriteEntries(entries ...types.RewriteEntry) error {
for i := range entries {
e := entries[i]
cl.log.With("domain", e.Domain, "answer", e.Answer).Info("Add rewrite entry")
err := cl.doPost(cl.client.R().EnableTrace().SetBody(&e), "/rewrite/add")
if err != nil {
return err
}
}
return nil
}
func (cl *client) DeleteRewriteEntries(entries ...types.RewriteEntry) error {
for i := range entries {
e := entries[i]
cl.log.With("domain", e.Domain, "answer", e.Answer).Info("Delete rewrite entry")
err := cl.doPost(cl.client.R().EnableTrace().SetBody(&e), "/rewrite/delete")
if err != nil {
return err
}
}
return nil
}
func (cl *client) SafeBrowsing() (bool, error) {
return cl.toggleStatus("safebrowsing")
}
func (cl *client) ToggleSafeBrowsing(enable bool) error {
return cl.toggleBool("safebrowsing", enable)
}
func (cl *client) Parental() (bool, error) {
return cl.toggleStatus("parental")
}
func (cl *client) ToggleParental(enable bool) error {
return cl.toggleBool("parental", enable)
}
func (cl *client) SafeSearch() (bool, error) {
return cl.toggleStatus("safesearch")
}
func (cl *client) ToggleSafeSearch(enable bool) error {
return cl.toggleBool("safesearch", enable)
}
func (cl *client) toggleStatus(mode string) (bool, error) {
fs := &types.EnableConfig{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(fs), fmt.Sprintf("/%s/status", mode))
return fs.Enabled, err
}
func (cl *client) toggleBool(mode string, enable bool) error {
cl.log.With("enable", enable).Info(fmt.Sprintf("Toggle %s", mode))
var target string
if enable {
target = "enable"
} else {
target = "disable"
}
return cl.doPost(cl.client.R().EnableTrace(), fmt.Sprintf("/%s/%s", mode, target))
}
func (cl *client) Filtering() (*types.FilteringStatus, error) {
f := &types.FilteringStatus{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(f), "/filtering/status")
return f, err
}
func (cl *client) AddFilters(whitelist bool, filters ...types.Filter) error {
for _, f := range filters {
cl.log.With("url", f.URL, "whitelist", whitelist, "enabled", f.Enabled).Info("Add filter")
ff := &types.Filter{Name: f.Name, URL: f.URL, Whitelist: whitelist}
err := cl.doPost(cl.client.R().EnableTrace().SetBody(ff), "/filtering/add_url")
if err != nil {
return err
}
}
return nil
}
func (cl *client) DeleteFilters(whitelist bool, filters ...types.Filter) error {
for _, f := range filters {
cl.log.With("url", f.URL, "whitelist", whitelist, "enabled", f.Enabled).Info("Delete filter")
ff := &types.Filter{URL: f.URL, Whitelist: whitelist}
err := cl.doPost(cl.client.R().EnableTrace().SetBody(ff), "/filtering/remove_url")
if err != nil {
return err
}
}
return nil
}
func (cl *client) UpdateFilters(whitelist bool, filters ...types.Filter) error {
for _, f := range filters {
cl.log.With("url", f.URL, "whitelist", whitelist, "enabled", f.Enabled).Info("Update filter")
fu := &types.FilterUpdate{Whitelist: whitelist, URL: f.URL, Data: types.Filter{ID: f.ID, Name: f.Name, URL: f.URL, Whitelist: whitelist, Enabled: f.Enabled}}
err := cl.doPost(cl.client.R().EnableTrace().SetBody(fu), "/filtering/set_url")
if err != nil {
return err
}
}
return nil
}
func (cl *client) RefreshFilters(whitelist bool) error {
cl.log.With("whitelist", whitelist).Info("Refresh filter")
return cl.doPost(cl.client.R().EnableTrace().SetBody(&types.RefreshFilter{Whitelist: whitelist}), "/filtering/refresh")
}
func (cl *client) ToggleProtection(enable bool) error {
cl.log.With("enable", enable).Info("Toggle protection")
return cl.doPost(cl.client.R().EnableTrace().SetBody(&types.Protection{ProtectionEnabled: enable}), "/dns_config")
}
func (cl *client) SetCustomRules(rules types.UserRules) error {
cl.log.With("rules", len(rules)).Info("Set user rules")
return cl.doPost(cl.client.R().EnableTrace().SetBody(rules.ToPayload(cl.version)), "/filtering/set_rules")
}
func (cl *client) ToggleFiltering(enabled bool, interval float64) error {
cl.log.With("enabled", enabled, "interval", interval).Info("Toggle filtering")
return cl.doPost(cl.client.R().EnableTrace().SetBody(&types.FilteringConfig{
EnableConfig: types.EnableConfig{Enabled: enabled},
IntervalConfig: types.IntervalConfig{Interval: interval},
}), "/filtering/config")
}
func (cl *client) Services() (types.Services, error) {
svcs := types.Services{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(&svcs), "/blocked_services/list")
return svcs, err
}
func (cl *client) SetServices(services types.Services) error {
cl.log.With("services", len(services)).Info("Set services")
return cl.doPost(cl.client.R().EnableTrace().SetBody(&services), "/blocked_services/set")
}
func (cl *client) Clients() (*types.Clients, error) {
clients := &types.Clients{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(clients), "/clients")
return clients, err
}
func (cl *client) AddClients(clients ...types.Client) error {
for i := range clients {
client := clients[i]
cl.log.With("name", client.Name).Info("Add client")
err := cl.doPost(cl.client.R().EnableTrace().SetBody(&client), "/clients/add")
if err != nil {
return err
}
}
return nil
}
func (cl *client) UpdateClients(clients ...types.Client) error {
for _, client := range clients {
cl.log.With("name", client.Name).Info("Update client")
err := cl.doPost(cl.client.R().EnableTrace().SetBody(&types.ClientUpdate{Name: client.Name, Data: client}), "/clients/update")
if err != nil {
return err
}
}
return nil
}
func (cl *client) DeleteClients(clients ...types.Client) error {
for i := range clients {
client := clients[i]
cl.log.With("name", client.Name).Info("Delete client")
err := cl.doPost(cl.client.R().EnableTrace().SetBody(&client), "/clients/delete")
if err != nil {
return err
}
}
return nil
}
func (cl *client) QueryLogConfig() (*types.QueryLogConfig, error) {
qlc := &types.QueryLogConfig{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(qlc), "/querylog_info")
return qlc, err
}
func (cl *client) SetQueryLogConfig(enabled bool, interval float64, anonymizeClientIP bool) error {
cl.log.With("enabled", enabled, "interval", interval, "anonymizeClientIP", anonymizeClientIP).Info("Set query log config")
return cl.doPost(cl.client.R().EnableTrace().SetBody(&types.QueryLogConfig{
EnableConfig: types.EnableConfig{Enabled: enabled},
IntervalConfig: types.IntervalConfig{Interval: interval},
AnonymizeClientIP: anonymizeClientIP,
}), "/querylog_config")
}
func (cl *client) StatsConfig() (*types.IntervalConfig, error) {
stats := &types.IntervalConfig{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(stats), "/stats_info")
return stats, err
}
func (cl *client) SetStatsConfig(interval float64) error {
cl.log.With("interval", interval).Info("Set stats config")
return cl.doPost(cl.client.R().EnableTrace().SetBody(&types.IntervalConfig{Interval: interval}), "/stats_config")
}
func (cl *client) Setup() error {
cl.log.Info("Setup new AdguardHome instance")
cfg := &types.InstallConfig{
Web: types.InstallPort{
IP: "0.0.0.0",
Port: 3000,
Status: "",
CanAutofix: false,
},
DNS: types.InstallPort{
IP: "0.0.0.0",
Port: 53,
Status: "",
CanAutofix: false,
},
}
if cl.client.UserInfo != nil {
cfg.Username = cl.client.UserInfo.Username
cfg.Password = cl.client.UserInfo.Password
}
req := cl.client.R().EnableTrace().SetBody(cfg)
req.UserInfo = nil
return cl.doPost(req, "/install/configure")
}
func (cl *client) AccessList() (*types.AccessList, error) {
al := &types.AccessList{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(al), "/access/list")
return al, err
}
func (cl *client) SetAccessList(list *types.AccessList) error {
cl.log.Info("Set access list")
return cl.doPost(cl.client.R().EnableTrace().SetBody(list), "/access/set")
}
func (cl *client) DNSConfig() (*types.DNSConfig, error) {
cfg := &types.DNSConfig{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(cfg), "/dns_info")
return cfg, err
}
func (cl *client) SetDNSConfig(config *types.DNSConfig) error {
cl.log.Info("Set dns config list")
return cl.doPost(cl.client.R().EnableTrace().SetBody(config), "/dns_config")
}
func (cl *client) DHCPServerConfig() (*types.DHCPServerConfig, error) {
cfg := &types.DHCPServerConfig{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(cfg), "/dhcp/status")
return cfg, err
}
func (cl *client) SetDHCPServerConfig(config *types.DHCPServerConfig) error {
cl.log.Info("Set dhcp server config")
return cl.doPost(cl.client.R().EnableTrace().SetBody(config), "/dhcp/set_config")
}
func (cl *client) AddDHCPStaticLeases(leases ...types.Lease) error {
for _, l := range leases {
cl.log.With("mac", l.HWAddr, "ip", l.IP, "hostname", l.Hostname).Info("Add static dhcp lease")
err := cl.doPost(cl.client.R().EnableTrace().SetBody(l), "/dhcp/add_static_lease")
if err != nil {
return err
}
}
return nil
}
func (cl *client) DeleteDHCPStaticLeases(leases ...types.Lease) error {
for _, l := range leases {
cl.log.With("mac", l.HWAddr, "ip", l.IP, "hostname", l.Hostname).Info("Delete static dhcp lease")
err := cl.doPost(cl.client.R().EnableTrace().SetBody(l), "/dhcp/remove_static_lease")
if err != nil {
return err
}
}
return nil
}

View File

@@ -1,623 +0,0 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/bakito/adguardhome-sync/pkg/client (interfaces: Client)
// Package client is a generated GoMock package.
package client
import (
reflect "reflect"
types "github.com/bakito/adguardhome-sync/pkg/types"
gomock "github.com/golang/mock/gomock"
)
// MockClient is a mock of Client interface.
type MockClient struct {
ctrl *gomock.Controller
recorder *MockClientMockRecorder
}
// MockClientMockRecorder is the mock recorder for MockClient.
type MockClientMockRecorder struct {
mock *MockClient
}
// NewMockClient creates a new mock instance.
func NewMockClient(ctrl *gomock.Controller) *MockClient {
mock := &MockClient{ctrl: ctrl}
mock.recorder = &MockClientMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockClient) EXPECT() *MockClientMockRecorder {
return m.recorder
}
// AccessList mocks base method.
func (m *MockClient) AccessList() (*types.AccessList, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AccessList")
ret0, _ := ret[0].(*types.AccessList)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// AccessList indicates an expected call of AccessList.
func (mr *MockClientMockRecorder) AccessList() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AccessList", reflect.TypeOf((*MockClient)(nil).AccessList))
}
// AddClients mocks base method.
func (m *MockClient) AddClients(arg0 ...types.Client) error {
m.ctrl.T.Helper()
varargs := []interface{}{}
for _, a := range arg0 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "AddClients", varargs...)
ret0, _ := ret[0].(error)
return ret0
}
// AddClients indicates an expected call of AddClients.
func (mr *MockClientMockRecorder) AddClients(arg0 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddClients", reflect.TypeOf((*MockClient)(nil).AddClients), arg0...)
}
// AddDHCPStaticLeases mocks base method.
func (m *MockClient) AddDHCPStaticLeases(arg0 ...types.Lease) error {
m.ctrl.T.Helper()
varargs := []interface{}{}
for _, a := range arg0 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "AddDHCPStaticLeases", varargs...)
ret0, _ := ret[0].(error)
return ret0
}
// AddDHCPStaticLeases indicates an expected call of AddDHCPStaticLeases.
func (mr *MockClientMockRecorder) AddDHCPStaticLeases(arg0 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddDHCPStaticLeases", reflect.TypeOf((*MockClient)(nil).AddDHCPStaticLeases), arg0...)
}
// AddFilters mocks base method.
func (m *MockClient) AddFilters(arg0 bool, arg1 ...types.Filter) error {
m.ctrl.T.Helper()
varargs := []interface{}{arg0}
for _, a := range arg1 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "AddFilters", varargs...)
ret0, _ := ret[0].(error)
return ret0
}
// AddFilters indicates an expected call of AddFilters.
func (mr *MockClientMockRecorder) AddFilters(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0}, arg1...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddFilters", reflect.TypeOf((*MockClient)(nil).AddFilters), varargs...)
}
// AddRewriteEntries mocks base method.
func (m *MockClient) AddRewriteEntries(arg0 ...types.RewriteEntry) error {
m.ctrl.T.Helper()
varargs := []interface{}{}
for _, a := range arg0 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "AddRewriteEntries", varargs...)
ret0, _ := ret[0].(error)
return ret0
}
// AddRewriteEntries indicates an expected call of AddRewriteEntries.
func (mr *MockClientMockRecorder) AddRewriteEntries(arg0 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRewriteEntries", reflect.TypeOf((*MockClient)(nil).AddRewriteEntries), arg0...)
}
// Clients mocks base method.
func (m *MockClient) Clients() (*types.Clients, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Clients")
ret0, _ := ret[0].(*types.Clients)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Clients indicates an expected call of Clients.
func (mr *MockClientMockRecorder) Clients() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Clients", reflect.TypeOf((*MockClient)(nil).Clients))
}
// DHCPServerConfig mocks base method.
func (m *MockClient) DHCPServerConfig() (*types.DHCPServerConfig, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DHCPServerConfig")
ret0, _ := ret[0].(*types.DHCPServerConfig)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// DHCPServerConfig indicates an expected call of DHCPServerConfig.
func (mr *MockClientMockRecorder) DHCPServerConfig() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DHCPServerConfig", reflect.TypeOf((*MockClient)(nil).DHCPServerConfig))
}
// DNSConfig mocks base method.
func (m *MockClient) DNSConfig() (*types.DNSConfig, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DNSConfig")
ret0, _ := ret[0].(*types.DNSConfig)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// DNSConfig indicates an expected call of DNSConfig.
func (mr *MockClientMockRecorder) DNSConfig() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DNSConfig", reflect.TypeOf((*MockClient)(nil).DNSConfig))
}
// DeleteClients mocks base method.
func (m *MockClient) DeleteClients(arg0 ...types.Client) error {
m.ctrl.T.Helper()
varargs := []interface{}{}
for _, a := range arg0 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "DeleteClients", varargs...)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteClients indicates an expected call of DeleteClients.
func (mr *MockClientMockRecorder) DeleteClients(arg0 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteClients", reflect.TypeOf((*MockClient)(nil).DeleteClients), arg0...)
}
// DeleteDHCPStaticLeases mocks base method.
func (m *MockClient) DeleteDHCPStaticLeases(arg0 ...types.Lease) error {
m.ctrl.T.Helper()
varargs := []interface{}{}
for _, a := range arg0 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "DeleteDHCPStaticLeases", varargs...)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteDHCPStaticLeases indicates an expected call of DeleteDHCPStaticLeases.
func (mr *MockClientMockRecorder) DeleteDHCPStaticLeases(arg0 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteDHCPStaticLeases", reflect.TypeOf((*MockClient)(nil).DeleteDHCPStaticLeases), arg0...)
}
// DeleteFilters mocks base method.
func (m *MockClient) DeleteFilters(arg0 bool, arg1 ...types.Filter) error {
m.ctrl.T.Helper()
varargs := []interface{}{arg0}
for _, a := range arg1 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "DeleteFilters", varargs...)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteFilters indicates an expected call of DeleteFilters.
func (mr *MockClientMockRecorder) DeleteFilters(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0}, arg1...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteFilters", reflect.TypeOf((*MockClient)(nil).DeleteFilters), varargs...)
}
// DeleteRewriteEntries mocks base method.
func (m *MockClient) DeleteRewriteEntries(arg0 ...types.RewriteEntry) error {
m.ctrl.T.Helper()
varargs := []interface{}{}
for _, a := range arg0 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "DeleteRewriteEntries", varargs...)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteRewriteEntries indicates an expected call of DeleteRewriteEntries.
func (mr *MockClientMockRecorder) DeleteRewriteEntries(arg0 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteRewriteEntries", reflect.TypeOf((*MockClient)(nil).DeleteRewriteEntries), arg0...)
}
// Filtering mocks base method.
func (m *MockClient) Filtering() (*types.FilteringStatus, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Filtering")
ret0, _ := ret[0].(*types.FilteringStatus)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Filtering indicates an expected call of Filtering.
func (mr *MockClientMockRecorder) Filtering() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Filtering", reflect.TypeOf((*MockClient)(nil).Filtering))
}
// Host mocks base method.
func (m *MockClient) Host() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Host")
ret0, _ := ret[0].(string)
return ret0
}
// Host indicates an expected call of Host.
func (mr *MockClientMockRecorder) Host() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Host", reflect.TypeOf((*MockClient)(nil).Host))
}
// Parental mocks base method.
func (m *MockClient) Parental() (bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Parental")
ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Parental indicates an expected call of Parental.
func (mr *MockClientMockRecorder) Parental() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Parental", reflect.TypeOf((*MockClient)(nil).Parental))
}
// QueryLogConfig mocks base method.
func (m *MockClient) QueryLogConfig() (*types.QueryLogConfig, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "QueryLogConfig")
ret0, _ := ret[0].(*types.QueryLogConfig)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// QueryLogConfig indicates an expected call of QueryLogConfig.
func (mr *MockClientMockRecorder) QueryLogConfig() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueryLogConfig", reflect.TypeOf((*MockClient)(nil).QueryLogConfig))
}
// RefreshFilters mocks base method.
func (m *MockClient) RefreshFilters(arg0 bool) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "RefreshFilters", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// RefreshFilters indicates an expected call of RefreshFilters.
func (mr *MockClientMockRecorder) RefreshFilters(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RefreshFilters", reflect.TypeOf((*MockClient)(nil).RefreshFilters), arg0)
}
// RewriteList mocks base method.
func (m *MockClient) RewriteList() (*types.RewriteEntries, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "RewriteList")
ret0, _ := ret[0].(*types.RewriteEntries)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// RewriteList indicates an expected call of RewriteList.
func (mr *MockClientMockRecorder) RewriteList() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RewriteList", reflect.TypeOf((*MockClient)(nil).RewriteList))
}
// SafeBrowsing mocks base method.
func (m *MockClient) SafeBrowsing() (bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SafeBrowsing")
ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// SafeBrowsing indicates an expected call of SafeBrowsing.
func (mr *MockClientMockRecorder) SafeBrowsing() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SafeBrowsing", reflect.TypeOf((*MockClient)(nil).SafeBrowsing))
}
// SafeSearch mocks base method.
func (m *MockClient) SafeSearch() (bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SafeSearch")
ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// SafeSearch indicates an expected call of SafeSearch.
func (mr *MockClientMockRecorder) SafeSearch() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SafeSearch", reflect.TypeOf((*MockClient)(nil).SafeSearch))
}
// Services mocks base method.
func (m *MockClient) Services() (types.Services, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Services")
ret0, _ := ret[0].(types.Services)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Services indicates an expected call of Services.
func (mr *MockClientMockRecorder) Services() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Services", reflect.TypeOf((*MockClient)(nil).Services))
}
// SetAccessList mocks base method.
func (m *MockClient) SetAccessList(arg0 *types.AccessList) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetAccessList", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// SetAccessList indicates an expected call of SetAccessList.
func (mr *MockClientMockRecorder) SetAccessList(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetAccessList", reflect.TypeOf((*MockClient)(nil).SetAccessList), arg0)
}
// SetCustomRules mocks base method.
func (m *MockClient) SetCustomRules(arg0 types.UserRules) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetCustomRules", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// SetCustomRules indicates an expected call of SetCustomRules.
func (mr *MockClientMockRecorder) SetCustomRules(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetCustomRules", reflect.TypeOf((*MockClient)(nil).SetCustomRules), arg0)
}
// SetDHCPServerConfig mocks base method.
func (m *MockClient) SetDHCPServerConfig(arg0 *types.DHCPServerConfig) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetDHCPServerConfig", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// SetDHCPServerConfig indicates an expected call of SetDHCPServerConfig.
func (mr *MockClientMockRecorder) SetDHCPServerConfig(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDHCPServerConfig", reflect.TypeOf((*MockClient)(nil).SetDHCPServerConfig), arg0)
}
// SetDNSConfig mocks base method.
func (m *MockClient) SetDNSConfig(arg0 *types.DNSConfig) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetDNSConfig", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// SetDNSConfig indicates an expected call of SetDNSConfig.
func (mr *MockClientMockRecorder) SetDNSConfig(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDNSConfig", reflect.TypeOf((*MockClient)(nil).SetDNSConfig), arg0)
}
// SetQueryLogConfig mocks base method.
func (m *MockClient) SetQueryLogConfig(arg0 bool, arg1 float64, arg2 bool) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetQueryLogConfig", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
return ret0
}
// SetQueryLogConfig indicates an expected call of SetQueryLogConfig.
func (mr *MockClientMockRecorder) SetQueryLogConfig(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetQueryLogConfig", reflect.TypeOf((*MockClient)(nil).SetQueryLogConfig), arg0, arg1, arg2)
}
// SetServices mocks base method.
func (m *MockClient) SetServices(arg0 types.Services) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetServices", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// SetServices indicates an expected call of SetServices.
func (mr *MockClientMockRecorder) SetServices(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetServices", reflect.TypeOf((*MockClient)(nil).SetServices), arg0)
}
// SetStatsConfig mocks base method.
func (m *MockClient) SetStatsConfig(arg0 float64) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetStatsConfig", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// SetStatsConfig indicates an expected call of SetStatsConfig.
func (mr *MockClientMockRecorder) SetStatsConfig(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetStatsConfig", reflect.TypeOf((*MockClient)(nil).SetStatsConfig), arg0)
}
// Setup mocks base method.
func (m *MockClient) Setup() error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Setup")
ret0, _ := ret[0].(error)
return ret0
}
// Setup indicates an expected call of Setup.
func (mr *MockClientMockRecorder) Setup() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Setup", reflect.TypeOf((*MockClient)(nil).Setup))
}
// StatsConfig mocks base method.
func (m *MockClient) StatsConfig() (*types.IntervalConfig, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "StatsConfig")
ret0, _ := ret[0].(*types.IntervalConfig)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// StatsConfig indicates an expected call of StatsConfig.
func (mr *MockClientMockRecorder) StatsConfig() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StatsConfig", reflect.TypeOf((*MockClient)(nil).StatsConfig))
}
// Status mocks base method.
func (m *MockClient) Status() (*types.Status, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Status")
ret0, _ := ret[0].(*types.Status)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Status indicates an expected call of Status.
func (mr *MockClientMockRecorder) Status() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Status", reflect.TypeOf((*MockClient)(nil).Status))
}
// ToggleFiltering mocks base method.
func (m *MockClient) ToggleFiltering(arg0 bool, arg1 float64) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ToggleFiltering", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// ToggleFiltering indicates an expected call of ToggleFiltering.
func (mr *MockClientMockRecorder) ToggleFiltering(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleFiltering", reflect.TypeOf((*MockClient)(nil).ToggleFiltering), arg0, arg1)
}
// ToggleParental mocks base method.
func (m *MockClient) ToggleParental(arg0 bool) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ToggleParental", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// ToggleParental indicates an expected call of ToggleParental.
func (mr *MockClientMockRecorder) ToggleParental(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleParental", reflect.TypeOf((*MockClient)(nil).ToggleParental), arg0)
}
// ToggleProtection mocks base method.
func (m *MockClient) ToggleProtection(arg0 bool) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ToggleProtection", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// ToggleProtection indicates an expected call of ToggleProtection.
func (mr *MockClientMockRecorder) ToggleProtection(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleProtection", reflect.TypeOf((*MockClient)(nil).ToggleProtection), arg0)
}
// ToggleSafeBrowsing mocks base method.
func (m *MockClient) ToggleSafeBrowsing(arg0 bool) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ToggleSafeBrowsing", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// ToggleSafeBrowsing indicates an expected call of ToggleSafeBrowsing.
func (mr *MockClientMockRecorder) ToggleSafeBrowsing(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleSafeBrowsing", reflect.TypeOf((*MockClient)(nil).ToggleSafeBrowsing), arg0)
}
// ToggleSafeSearch mocks base method.
func (m *MockClient) ToggleSafeSearch(arg0 bool) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ToggleSafeSearch", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// ToggleSafeSearch indicates an expected call of ToggleSafeSearch.
func (mr *MockClientMockRecorder) ToggleSafeSearch(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleSafeSearch", reflect.TypeOf((*MockClient)(nil).ToggleSafeSearch), arg0)
}
// UpdateClients mocks base method.
func (m *MockClient) UpdateClients(arg0 ...types.Client) error {
m.ctrl.T.Helper()
varargs := []interface{}{}
for _, a := range arg0 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "UpdateClients", varargs...)
ret0, _ := ret[0].(error)
return ret0
}
// UpdateClients indicates an expected call of UpdateClients.
func (mr *MockClientMockRecorder) UpdateClients(arg0 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateClients", reflect.TypeOf((*MockClient)(nil).UpdateClients), arg0...)
}
// UpdateFilters mocks base method.
func (m *MockClient) UpdateFilters(arg0 bool, arg1 ...types.Filter) error {
m.ctrl.T.Helper()
varargs := []interface{}{arg0}
for _, a := range arg1 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "UpdateFilters", varargs...)
ret0, _ := ret[0].(error)
return ret0
}
// UpdateFilters indicates an expected call of UpdateFilters.
func (mr *MockClientMockRecorder) UpdateFilters(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0}, arg1...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateFilters", reflect.TypeOf((*MockClient)(nil).UpdateFilters), varargs...)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

Some files were not shown because too many files have changed in this diff Show More