Compare commits

...

266 Commits

Author SHA1 Message Date
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
97 changed files with 4340 additions and 1356 deletions

2
.dockerignore Normal file
View File

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

View File

@@ -17,7 +17,9 @@ body:
id: adguardhome-sync-version id: adguardhome-sync-version
attributes: attributes:
label: AdguardHome-Sync Version label: AdguardHome-Sync Version
description: What version of adguardhome-sync was running when you discovered this issue? description: |
- What version of adguardhome-sync was running when you discovered this issue?
- Are you running the docker or binary version?
validations: validations:
required: true required: true
- type: textarea - type: textarea
@@ -27,6 +29,15 @@ body:
description: What version of adguardhome was running when you discovered this issue? description: What version of adguardhome was running when you discovered this issue?
validations: validations:
required: true 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 - type: textarea
id: config id: config
attributes: attributes:
@@ -47,7 +58,7 @@ body:
- type: textarea - type: textarea
id: logs id: logs
attributes: attributes:
label: Relevant log output label: Relevant DEBUG log output
description: | description: |
Please copy and paste any relevant **debug** log output. This will be automatically formatted into code, so no need for backticks. Please copy and paste any relevant **debug** log output. This will be automatically formatted into code, so no need for backticks.
Enable debug logs by defining the following environment variable `LOG_LEVEL=debug`. Enable debug logs by defining the following environment variable `LOG_LEVEL=debug`.

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
@@ -40,6 +44,12 @@ jobs:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
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@v3 uses: github/codeql-action/init@v3

View File

@@ -1,13 +1,20 @@
name: docker-image name: docker-images
on: on:
workflow_dispatch: # allows manual triggering workflow_dispatch: # allows manual triggering
schedule: schedule:
- cron: '0 0 * * *' - 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
@@ -44,16 +51,16 @@ jobs:
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@v5 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 provenance: false
build-args: | build-args: |
VERSION=${{ github.event.release.tag_name }} VERSION=${{ github.event.release.tag_name }}
@@ -63,16 +70,16 @@ jobs:
run: echo "NEW_COMMIT_COUNT=$(git log --oneline --since '24 hours ago' | wc -l)" >> $GITHUB_ENV run: echo "NEW_COMMIT_COUNT=$(git log --oneline --since '24 hours ago' | wc -l)" >> $GITHUB_ENV
if: ${{ github.event.release.tag_name == '' }} if: ${{ github.event.release.tag_name == '' }}
- name: Build and push main - name: Build images
id: docker_build_main id: docker_build_main
uses: docker/build-push-action@v5 uses: docker/build-push-action@v6
if: ${{ github.event.release.tag_name == '' && env.NEW_COMMIT_COUNT > 0 }} 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 provenance: false
build-args: | build-args: |
VERSION=main VERSION=main
@@ -80,4 +87,3 @@ jobs:
- name: Image digest - name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }} run: echo ${{ steps.docker_build.outputs.digest }}

View File

@@ -8,6 +8,10 @@ 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
@@ -15,11 +19,16 @@ jobs:
matrix: matrix:
build: build:
- mode: env - mode: env
protocol: https
- mode: file - mode: file
protocol: http
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- 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
@@ -28,13 +37,19 @@ jobs:
- name: Install Helm Chart - name: Install Helm Chart
run: ./testdata/e2e/bin/install-chart.sh ${{ matrix.build.mode }} 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 - name: Read latest replica config
run: ./testdata/e2e/bin/read-latest-replica-config.sh run: ./testdata/e2e/bin/read-latest-replica-config.sh

View File

@@ -6,6 +6,10 @@ 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
@@ -18,14 +22,16 @@ jobs:
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
with:
skip-pkg-cache: true
test: test:
name: test name: test
runs-on: ubuntu-latest runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
steps: steps:
- name: Check out code into the Go module directory - name: Check out code into the Go module directory
@@ -36,10 +42,14 @@ jobs:
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
with: with:
path-to-profile: coverage.out path-to-profile: coverage.out
@@ -58,7 +68,4 @@ jobs:
go-version-file: "go.mod" go-version-file: "go.mod"
- name: Run GoReleaser - name: Run GoReleaser
uses: goreleaser/goreleaser-action@v5 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@v9
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}}

3
.gitignore vendored
View File

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

209
.golangci.yaml Normal file
View File

@@ -0,0 +1,209 @@
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
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,37 +0,0 @@
run:
timeout: 5m
linters:
enable:
- asciicheck
- bodyclose
- dogsled
- durationcheck
- errcheck
- errorlint
- exportloopref
- gci
- gofmt
- gofumpt
- goimports
- gosec
- gosimple
- govet
- importas
- ineffassign
- megacheck
- misspell
- nakedret
- nolintlint
- 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:
@@ -30,16 +31,17 @@ builds:
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* ]]; 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

104
.toolbox.mk Normal file
View File

@@ -0,0 +1,104 @@
## 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):
mkdir -p $(TB_LOCALBIN)
## Tool Binaries
TB_CONTROLLER_GEN ?= $(TB_LOCALBIN)/controller-gen
TB_GINKGO ?= $(TB_LOCALBIN)/ginkgo
TB_GOFUMPT ?= $(TB_LOCALBIN)/gofumpt
TB_GOLANGCI_LINT ?= $(TB_LOCALBIN)/golangci-lint
TB_GOLINES ?= $(TB_LOCALBIN)/golines
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=sigs.k8s.io/controller-tools/cmd/controller-gen
TB_CONTROLLER_GEN_VERSION ?= v0.17.3
# renovate: packageName=mvdan.cc/gofumpt
TB_GOFUMPT_VERSION ?= v0.8.0
# renovate: packageName=github.com/golangci/golangci-lint/v2/cmd/golangci-lint
TB_GOLANGCI_LINT_VERSION ?= v2.1.5
# renovate: packageName=github.com/segmentio/golines
TB_GOLINES_VERSION ?= v0.12.2
# renovate: packageName=github.com/goreleaser/goreleaser/v2
TB_GORELEASER_VERSION ?= v2.8.2
# renovate: packageName=go.uber.org/mock/mockgen
TB_MOCKGEN_VERSION ?= v0.5.1
# renovate: packageName=github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen
TB_OAPI_CODEGEN_VERSION ?= v2.4.1
# renovate: packageName=github.com/bakito/semver
TB_SEMVER_VERSION ?= v1.1.3
## Tool Installer
.PHONY: tb.controller-gen
tb.controller-gen: $(TB_CONTROLLER_GEN) ## Download controller-gen locally if necessary.
$(TB_CONTROLLER_GEN): $(TB_LOCALBIN)
test -s $(TB_LOCALBIN)/controller-gen || GOBIN=$(TB_LOCALBIN) go install sigs.k8s.io/controller-tools/cmd/controller-gen@$(TB_CONTROLLER_GEN_VERSION)
.PHONY: tb.ginkgo
tb.ginkgo: $(TB_GINKGO) ## Download ginkgo locally if necessary.
$(TB_GINKGO): $(TB_LOCALBIN)
test -s $(TB_LOCALBIN)/ginkgo || GOBIN=$(TB_LOCALBIN) go install github.com/onsi/ginkgo/v2/ginkgo
.PHONY: tb.gofumpt
tb.gofumpt: $(TB_GOFUMPT) ## Download gofumpt locally if necessary.
$(TB_GOFUMPT): $(TB_LOCALBIN)
test -s $(TB_LOCALBIN)/gofumpt || GOBIN=$(TB_LOCALBIN) go install mvdan.cc/gofumpt@$(TB_GOFUMPT_VERSION)
.PHONY: tb.golangci-lint
tb.golangci-lint: $(TB_GOLANGCI_LINT) ## Download golangci-lint locally if necessary.
$(TB_GOLANGCI_LINT): $(TB_LOCALBIN)
test -s $(TB_LOCALBIN)/golangci-lint || GOBIN=$(TB_LOCALBIN) go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@$(TB_GOLANGCI_LINT_VERSION)
.PHONY: tb.golines
tb.golines: $(TB_GOLINES) ## Download golines locally if necessary.
$(TB_GOLINES): $(TB_LOCALBIN)
test -s $(TB_LOCALBIN)/golines || GOBIN=$(TB_LOCALBIN) go install github.com/segmentio/golines@$(TB_GOLINES_VERSION)
.PHONY: tb.goreleaser
tb.goreleaser: $(TB_GORELEASER) ## Download goreleaser locally if necessary.
$(TB_GORELEASER): $(TB_LOCALBIN)
test -s $(TB_LOCALBIN)/goreleaser || GOBIN=$(TB_LOCALBIN) go install github.com/goreleaser/goreleaser/v2@$(TB_GORELEASER_VERSION)
.PHONY: tb.mockgen
tb.mockgen: $(TB_MOCKGEN) ## Download mockgen locally if necessary.
$(TB_MOCKGEN): $(TB_LOCALBIN)
test -s $(TB_LOCALBIN)/mockgen || GOBIN=$(TB_LOCALBIN) go install go.uber.org/mock/mockgen@$(TB_MOCKGEN_VERSION)
.PHONY: tb.oapi-codegen
tb.oapi-codegen: $(TB_OAPI_CODEGEN) ## Download oapi-codegen locally if necessary.
$(TB_OAPI_CODEGEN): $(TB_LOCALBIN)
test -s $(TB_LOCALBIN)/oapi-codegen || GOBIN=$(TB_LOCALBIN) go install github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@$(TB_OAPI_CODEGEN_VERSION)
.PHONY: tb.semver
tb.semver: $(TB_SEMVER) ## Download semver locally if necessary.
$(TB_SEMVER): $(TB_LOCALBIN)
test -s $(TB_LOCALBIN)/semver || GOBIN=$(TB_LOCALBIN) go install github.com/bakito/semver@$(TB_SEMVER_VERSION)
## Reset Tools
.PHONY: tb.reset
tb.reset:
@rm -f \
$(TB_LOCALBIN)/controller-gen \
$(TB_LOCALBIN)/ginkgo \
$(TB_LOCALBIN)/gofumpt \
$(TB_LOCALBIN)/golangci-lint \
$(TB_LOCALBIN)/golines \
$(TB_LOCALBIN)/goreleaser \
$(TB_LOCALBIN)/mockgen \
$(TB_LOCALBIN)/oapi-codegen \
$(TB_LOCALBIN)/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 \
mvdan.cc/gofumpt@github.com/mvdan/gofumpt \
github.com/golangci/golangci-lint/v2/cmd/golangci-lint \
github.com/segmentio/golines \
github.com/goreleaser/goreleaser/v2 \
go.uber.org/mock/mockgen@github.com/uber-go/mock \
github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen \
github.com/bakito/semver
## toolbox - end

View File

@@ -1,10 +1,8 @@
FROM golang:1.21-bullseye as builder FROM golang:1.24-alpine AS builder
WORKDIR /go/src/app WORKDIR /go/src/app
RUN apt-get update && \ RUN apk update && apk add upx ca-certificates tzdata
apt-get install -y upx ca-certificates tzdata && \
apt-get upgrade -y # upgrade to get latest ca-certs
ARG VERSION=main ARG VERSION=main
ARG BUILD="N/A" ARG BUILD="N/A"
@@ -15,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

124
Makefile
View File

@@ -1,107 +1,48 @@
# 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: 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=./pkg/types object
fmt: tb.golines tb.gofumpt
$(TB_GOLINES) --base-formatter="$(TB_GOFUMPT)" --max-len=120 --write-output .
# Run tests # Run tests
test: generate lint test-ci test: generate fmt lint test-ci
fuzz:
go test -fuzz=FuzzMask -v ./pkg/types/ -fuzztime=60s
# Run ci tests # Run ci tests
test-ci: mocks tidy test-ci: mocks tidy tb.ginkgo
go test ./... -coverprofile=coverage.out.tmp $(TB_GINKGO) --cover --coverprofile coverage.out.tmp ./...
cat coverage.out.tmp | grep -v "_generated.go" > coverage.out 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 pkg/mocks/client/mock.go github.com/bakito/adguardhome-sync/pkg/client Client
$(MOCKGEN) -package client -destination pkg/mocks/flags/mock.go github.com/bakito/adguardhome-sync/pkg/config Flags $(TB_MOCKGEN) -package client -destination pkg/mocks/flags/mock.go github.com/bakito/adguardhome-sync/pkg/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) --clean $(TB_GORELEASER) --clean
test-release: goreleaser test-release: tb.goreleaser
$(GORELEASER) --skip-publish --snapshot --clean $(TB_GORELEASER) --skip=publish --snapshot --clean
## 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
OAPI_CODEGEN ?= $(LOCALBIN)/oapi-codegen
MOCKGEN ?= $(LOCALBIN)/mockgen
GOLANGCI_LINT ?= $(LOCALBIN)/golangci-lint
GORELEASER ?= $(LOCALBIN)/goreleaser
DEEPCOPY_GEN ?= $(LOCALBIN)/deepcopy-gen
## Tool Versions
SEMVER_VERSION ?= v1.1.3
OAPI_CODEGEN_VERSION ?= v2.0.0
MOCKGEN_VERSION ?= v1.6.0
GOLANGCI_LINT_VERSION ?= v1.55.2
GORELEASER_VERSION ?= v1.23.0
DEEPCOPY_GEN_VERSION ?= v0.29.0
## 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: oapi-codegen
oapi-codegen: $(OAPI_CODEGEN) ## Download oapi-codegen locally if necessary.
$(OAPI_CODEGEN): $(LOCALBIN)
test -s $(LOCALBIN)/oapi-codegen || GOBIN=$(LOCALBIN) go install github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen@$(OAPI_CODEGEN_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)/oapi-codegen \
$(LOCALBIN)/mockgen \
$(LOCALBIN)/golangci-lint \
$(LOCALBIN)/goreleaser \
$(LOCALBIN)/deepcopy-gen
toolbox makefile -f $(LOCALDIR)/Makefile \
github.com/bakito/semver \
github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen \
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 rm -f adguardhome-replica
docker run --pull always --name adguardhome-replica -p 9091:3000 --rm adguard/adguardhome:latest docker run --pull always --name adguardhome-replica -p 9091:3000 --rm adguard/adguardhome:latest
# docker run --pull always --name adguardhome-replica -p 9090:80 -p 9091:3000 --rm adguard/adguardhome:v0.107.13 # docker run --pull always --name adguardhome-replica -p 9090:80 -p 9091:3000 --rm adguard/adguardhome:v0.107.13
@@ -109,6 +50,7 @@ copy-replica-config:
docker cp adguardhome-replica:/opt/adguardhome/conf/AdGuardHome.yaml tmp/AdGuardHome.yaml docker cp adguardhome-replica:/opt/adguardhome/conf/AdGuardHome.yaml tmp/AdGuardHome.yaml
start-replica2: start-replica2:
docker rm -f adguardhome-replica2
docker run --pull always --name adguardhome-replica2 -p 9093:3000 --rm adguard/adguardhome:latest docker run --pull always --name adguardhome-replica2 -p 9093:3000 --rm adguard/adguardhome:latest
# docker run --pull always --name adguardhome-replica -p 9090:80 -p 9091:3000 --rm adguard/adguardhome:v0.107.13 # docker run --pull always --name adguardhome-replica -p 9090:80 -p 9091:3000 --rm adguard/adguardhome:v0.107.13
@@ -130,12 +72,18 @@ kind-create:
kind-test: kind-test:
@./testdata/e2e/bin/install-chart.sh @./testdata/e2e/bin/install-chart.sh
model: oapi-codegen # renovate: packageName=AdguardTeam/AdGuardHome
ADGUARD_HOME_VERSION ?= v0.107.61
model: tb.oapi-codegen
@mkdir -p tmp @mkdir -p tmp
go run openapi/main.go v0.107.43 go run openapi/main.go $(ADGUARD_HOME_VERSION)
$(OAPI_CODEGEN) -package model -generate types,client -config .oapi-codegen.yaml tmp/schema.yaml > pkg/client/model/model_generated.go $(TB_OAPI_CODEGEN) -package model -generate types,client -config .oapi-codegen.yaml tmp/schema.yaml > pkg/client/model/model_generated.go
model-diff: model-diff:
go run openapi/main.go v0.107.43 go run openapi/main.go $(ADGUARD_HOME_VERSION)
go run openapi/main.go go run openapi/main.go
diff tmp/schema.yaml tmp/schema-master.yaml diff tmp/schema.yaml tmp/schema-master.yaml
zellij:
zellij -l ./testdata/test-layout.kdl

254
README.md
View File

@@ -1,4 +1,5 @@
[![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)
@@ -6,38 +7,11 @@
Synchronize [AdGuardHome](https://github.com/AdguardTeam/AdGuardHome) config to replica instances. Synchronize [AdGuardHome](https://github.com/AdguardTeam/AdGuardHome) config to replica instances.
## FAQ ## FAQ & Deprecations
Please check the wiki for [FAQ](https://github.com/bakito/adguardhome-sync/wiki/FAQ). Please check the wiki
for [FAQ](https://github.com/bakito/adguardhome-sync/wiki/FAQ)
## Deprecation of Environment Variables as of v0.6.0 and [Deprecations](https://github.com/bakito/adguardhome-sync/wiki/Deprecations).
The following environment variables have been deprecated and replaced with better readable ones:
Deprecated variables are still supported but will be logged as warning.
| Deprecated | Replacement |
| :----------- |:----------- |
| RUNONSTART | RUN_ON_START |
| PRINTCONFIGONLY | PRINT_CONFIG_ONLY |
| CONTINUE_ON_ERROR | CONTINUE_ON_ERROR |
| API_DARKMODE | API_DARK_MODE |
| FEATURES_DHCP_SERVERCONFIG | FEATURES_DHCP_SERVER_CONFIG |
| FEATURES_DHCP_STATICLEASES | FEATURES_DHCP_STATIC_LEASES |
| FEATURES_DNS_ACCESSLISTS | FEATURES_DNS_ACCESS_LISTS |
| FEATURES_DNS_SERVERCONFIG | FEATURES_DNS_SERVER_CONFIG |
| FEATURES_GENERALSETTINGS | FEATURES_GENERAL_SETTINGS |
| FEATURES_QUERYLOGCONFIG | FEATURES_QUERY_LOG_CONFIG |
| FEATURES_STATSCONFIG | FEATURES_STATS_CONFIG |
| FEATURES_CLIENTSETTINGS | FEATURES_CLIENT_SETTINGS |
| ORIGIN_WEBURL | ORIGIN_WEB_URL |
| ORIGIN_APIPATH | ORIGIN_API_PATH |
| ORIGIN_INSECURE_SKIP_VERIFY | ORIGIN_INSECURE_SKIP_VERIFY |
| *REPLICA_WEBURL | REPLICA_WEB_URL |
| *REPLICA_APIPATH | REPLICA_API_PATH |
| *REPLICA_INSECURE_SKIP_VERIFY | REPLICA_INSECURE_SKIP_VERIFY |
| *REPLICA_AUTOSETUP | REPLICA_AUTO_SETUP |
| *REPLICA_INTERFACENAME | REPLICA_INTERFACE_NAME |
\* is also valid for numbered `REPLICA#_` variables
## Current sync features ## Current sync features
@@ -48,6 +22,7 @@ Deprecated variables are still supported but will be logged as warning.
- 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.
@@ -88,18 +63,63 @@ 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 ORIGIN_COOKIE=Origin-Cookie-Name=CCCOOOKKKIIIEEE # export ORIGIN_COOKIE=Origin-Cookie-Name=CCCOOOKKKIIIEEE
export REPLICA_URL=http://192.168.1.3 export REPLICA1_URL=http://192.168.1.3
export REPLICA_USERNAME=username export REPLICA1_USERNAME=username
export REPLICA_PASSWORD=password export REPLICA1_PASSWORD=password
# export REPLICA_COOKIE=Replica-Cookie-Name=CCCOOOKKKIIIEEE # 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
@@ -116,20 +136,19 @@ set ORIGIN_USERNAME=username
set ORIGIN_PASSWORD=password set ORIGIN_PASSWORD=password
# set ORIGIN_COOKIE=Origin-Cookie-Name=CCCOOOKKKIIIEEE # 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 REPLICA_COOKIE=Replica-Cookie-Name=CCCOOOKKKIIIEEE # set REPLICA1_COOKIE=Replica-Cookie-Name=CCCOOOKKKIIIEEE
set FEATURES_DHCP=false set FEATURES_DHCP_SERVER_CONFIG=false
set FEATURES_DHCP_SERVERCONFIG=false set FEATURES_DHCP_STATIC_LEASES=false
set FEATURES_DHCP_STATICLEASES=false
# run once # run once
adguardhome-sync run adguardhome-sync run
# run as daemon # run as daemon
adguardhome-sync run --cron "*/10 * * * *" adguardhome-sync run --cron "0 */2 * * *"
``` ```
## docker cli ## docker cli
@@ -162,57 +181,66 @@ 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`
---
version: "2.1"
services:
adguardhome-sync:
image: ghcr.io/bakito/adguardhome-sync
container_name: adguardhome-sync
command: run
environment:
LOG_LEVEL: "info"
ORIGIN_URL: "https://192.168.1.2:3000"
# ORIGIN_WEB_URL: "https://some-other.url" # used in the web interface (default: <origin-url>
ORIGIN_USERNAME: "username" | Name | Type | Description |
ORIGIN_PASSWORD: "password" |:-------------------------------------|--------|:----------------------------------------------------------|
REPLICA_URL: "http://192.168.1.3" | ORIGIN_URL (string) | string | URL of adguardhome instance |
REPLICA_USERNAME: "username" | ORIGIN_WEB_URL (string) | string | Web URL of adguardhome instance |
REPLICA_PASSWORD: "password" | ORIGIN_API_PATH (string) | string | API Path |
REPLICA1_URL: "http://192.168.1.4" | ORIGIN_USERNAME (string) | string | Adguardhome username |
REPLICA1_USERNAME: "username" | ORIGIN_PASSWORD (string) | string | Adguardhome password |
REPLICA1_PASSWORD: "password" | ORIGIN_COOKIE (string) | string | Adguardhome cookie |
REPLICA1_API_PATH: "/some/path/control" | ORIGIN_INSECURE_SKIP_VERIFY (bool) | bool | Skip TLS verification |
# REPLICA1_WEB_URL: "https://some-other.url" # used in the web interface (default: <replica-url> | ORIGIN_AUTO_SETUP (bool) | bool | Automatically setup the instance if it is not initialized |
# REPLICA1_AUTO_SETUP: true # if true, AdGuardHome is automatically initialized. | ORIGIN_INTERFACE_NAME (string) | string | Network interface name |
# REPLICA1_INTERFACE_NAME: 'ens18' # use custom dhcp interface name | ORIGIN_DHCP_SERVER_ENABLED (bool) | bool | Enable DHCP server |
# REPLICA1_DHCP_SERVER_ENABLED: true/false (optional) enables/disables the dhcp server on the replica | REPLICA#_URL (string) | string | URL of adguardhome instance |
CRON: "*/10 * * * *" # run every 10 minutes | REPLICA#_WEB_URL (string) | string | Web URL of adguardhome instance |
RUNONSTART: true | REPLICA#_API_PATH (string) | string | API Path |
# CONTINUE_ON_ERROR: false # If enabled, the synchronisation task will not fail on single errors, but will log the errors and continue | REPLICA#_USERNAME (string) | string | Adguardhome username |
| REPLICA#_PASSWORD (string) | string | Adguardhome password |
| REPLICA#_COOKIE (string) | string | Adguardhome cookie |
| REPLICA#_INSECURE_SKIP_VERIFY (bool) | bool | Skip TLS verification |
| REPLICA#_AUTO_SETUP (bool) | bool | Automatically setup the instance if it is not initialized |
| REPLICA#_INTERFACE_NAME (string) | string | Network interface name |
| REPLICA#_DHCP_SERVER_ENABLED (bool) | bool | Enable DHCP server |
| CRON (string) | string | Cron expression for the sync interval |
| RUN_ON_START (bool) | bool | Run the sung on startup |
| PRINT_CONFIG_ONLY (bool) | bool | Print current config only and stop the application |
| CONTINUE_ON_ERROR (bool) | bool | Continue sync on errors |
| API_PORT (int) | int | API port |
| API_USERNAME (string) | string | API username |
| API_PASSWORD (string) | string | API password |
| API_DARK_MODE (bool) | bool | API dark mode |
| API_METRICS_ENABLED (bool) | bool | Enable metrics |
| API_METRICS_SCRAPE_INTERVAL (int64) | int64 | Interval for metrics scraping |
| API_METRICS_QUERY_LOG_LIMIT (int) | int | Metrics log query limit |
| API_TLS_CERT_DIR (string) | string | API TLS certificate directory |
| API_TLS_CERT_NAME (string) | string | API TLS certificate file name |
| API_TLS_KEY_NAME (string) | string | API TLS key file name |
| FEATURES_DNS_ACCESS_LISTS (bool) | bool | Sync DNS access lists |
| FEATURES_DNS_SERVER_CONFIG (bool) | bool | Sync DNS server config |
| FEATURES_DNS_REWRITES (bool) | bool | Sync DNS rewrites |
| FEATURES_DHCP_SERVER_CONFIG (bool) | bool | Sync DHCP server config |
| FEATURES_DHCP_STATIC_LEASES (bool) | bool | Sync DHCP static leases |
| FEATURES_GENERAL_SETTINGS (bool) | bool | Sync general settings |
| FEATURES_QUERY_LOG_CONFIG (bool) | bool | Sync query log config |
| FEATURES_STATS_CONFIG (bool) | bool | Sync stats config |
| FEATURES_CLIENT_SETTINGS (bool) | bool | Sync client settings |
| FEATURES_SERVICES (bool) | bool | Sync services |
| FEATURES_FILTERS (bool) | bool | Sync filters |
| FEATURES_THEME (bool) | bool | Sync the weg UI theme |
# Configure the sync API server, disabled if api port is 0 ### Unraid
API_PORT: 8080
# Configure sync features; by default all features are enabled. ⚠️ Disclaimer: Tere exists an unraid tepmlate for this application. This template is not managed by this project.
# FEATURES_GENERAL_SETTINGS: true Also, as unraid is not known to me, I can not give any support on unraind templates.
# FEATURES_QUERY_LOG_CONFIG: true
# FEATURES_STATS_CONFIG: true Note when running the Docker container in Unraid please remove unneeded env variables if don't needed.
# FEATURES_CLIENT_SETTINGS: true If replica2 isn't used this can cause sync errors.
# FEATURES_SERVICES: true
# FEATURES_FILTERS: true
# FEATURES_DHCP_SERVER_CONFIG: true
# FEATURES_DHCP_STATIC_LEASES: true
# FEATURES_DNS_SERVER_CONFIG: true
# FEATURES_DNS_ACCESS_LISTS: true
# FEATURES_DNS_REWRITES: true
ports:
- 8080:8080
restart: unless-stopped
```
### Config file ### Config file
@@ -220,7 +248,7 @@ 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
@@ -261,6 +289,21 @@ api:
# enable api dark mode # enable api dark mode
darkMode: true 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:
generalSettings: true generalSettings: true
@@ -277,10 +320,27 @@ features:
accessLists: true accessLists: true
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)
@@ -288,3 +348,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)

View File

@@ -4,9 +4,10 @@ import (
"fmt" "fmt"
"os" "os"
"github.com/spf13/cobra"
"github.com/bakito/adguardhome-sync/pkg/log" "github.com/bakito/adguardhome-sync/pkg/log"
"github.com/bakito/adguardhome-sync/version" "github.com/bakito/adguardhome-sync/version"
"github.com/spf13/cobra"
) )
var ( var (
@@ -14,7 +15,7 @@ var (
logger = log.GetLogger("root") logger = log.GetLogger("root")
) )
// rootCmd represents the base command when called without any subcommands // rootCmd represents the base command when called without any subcommands.
var rootCmd = &cobra.Command{ var rootCmd = &cobra.Command{
Use: "adguardhome-sync", Use: "adguardhome-sync",
Short: "Synchronize config from one AdGuardHome instance to another", Short: "Synchronize config from one AdGuardHome instance to another",
@@ -25,7 +26,7 @@ var rootCmd = &cobra.Command{
// This is called by main.main(). It only needs to happen once to the rootCmd. // This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() { func Execute() {
if err := rootCmd.Execute(); err != nil { if err := rootCmd.Execute(); err != nil {
fmt.Println(err) _, _ = fmt.Println(err)
os.Exit(1) os.Exit(1)
} }
} }

View File

@@ -1,17 +1,17 @@
package cmd package cmd
import ( import (
"github.com/spf13/cobra"
"github.com/bakito/adguardhome-sync/pkg/config" "github.com/bakito/adguardhome-sync/pkg/config"
"github.com/bakito/adguardhome-sync/pkg/log" "github.com/bakito/adguardhome-sync/pkg/log"
"github.com/bakito/adguardhome-sync/pkg/sync" "github.com/bakito/adguardhome-sync/pkg/sync"
"github.com/spf13/cobra"
"gopkg.in/yaml.v3"
) )
// 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")
@@ -26,18 +26,16 @@ var doCmd = &cobra.Command{
return err return err
} }
if cfg.PrintConfigOnly { if cfg.PrintConfigOnly() {
config, err := yaml.Marshal(cfg) if err := cfg.Print(); err != nil {
if err != nil {
logger.Error(err) logger.Error(err)
return err return err
} }
logger.Infof("Printing adguardhome-sync config (THE APPLICATION WILL NOT START IN THIS MODE): \n%s",
string(config))
return nil return nil
} }
return sync.Sync(cfg) return sync.Sync(cfg.Get())
}, },
} }
@@ -47,20 +45,21 @@ func init() {
doCmd.PersistentFlags().Bool(config.FlagRunOnStart, true, "Run the sync job on start.") doCmd.PersistentFlags().Bool(config.FlagRunOnStart, true, "Run the sync job on start.")
doCmd.PersistentFlags().Bool(config.FlagPrintConfigOnly, false, "Prints the configuration only and exists. "+ doCmd.PersistentFlags().Bool(config.FlagPrintConfigOnly, false, "Prints the configuration only and exists. "+
"Can be used to debug the config E.g: when having authentication issues.") "Can be used to debug the config E.g: when having authentication issues.")
doCmd.PersistentFlags().Bool(config.FlagContinueOnError, false, "If enabled, the synchronisation task "+ doCmd.PersistentFlags().Bool(config.FlagContinueOnError, false, "If enabled, the synchronization task "+
"will not fail on single errors, but will log the errors and continue.") "will not fail on single errors, but will log the errors and continue.")
doCmd.PersistentFlags().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().
doCmd.PersistentFlags().String(config.FlagApiUsername, "", "Sync API username") Int(config.FlagAPIPort, 8080, "Sync API Port, the API endpoint will be started to enable remote triggering; if 0 port API is disabled.")
doCmd.PersistentFlags().String(config.FlagApiPassword, "", "Sync API password") doCmd.PersistentFlags().String(config.FlagAPIUsername, "", "Sync API username")
doCmd.PersistentFlags().String(config.FlagApiDarkMode, "", "API UI in dark mode") doCmd.PersistentFlags().String(config.FlagAPIPassword, "", "Sync API password")
doCmd.PersistentFlags().String(config.FlagAPIDarkMode, "", "API UI in dark mode")
doCmd.PersistentFlags().Bool(config.FlagFeatureDhcpServerConfig, true, "Enable DHCP server config feature") doCmd.PersistentFlags().Bool(config.FlagFeatureDhcpServerConfig, true, "Enable DHCP server config feature")
doCmd.PersistentFlags().Bool(config.FlagFeatureDhcpStaticLeases, true, "Enable DHCP server static leases feature") doCmd.PersistentFlags().Bool(config.FlagFeatureDhcpStaticLeases, true, "Enable DHCP server static leases feature")
doCmd.PersistentFlags().Bool(config.FlagFeatureDnsServerConfig, true, "Enable DNS server config feature") doCmd.PersistentFlags().Bool(config.FlagFeatureDNSServerConfig, true, "Enable DNS server config feature")
doCmd.PersistentFlags().Bool(config.FlagFeatureDnsAccessLists, true, "Enable DNS server access lists feature") doCmd.PersistentFlags().Bool(config.FlagFeatureDNSAccessLists, true, "Enable DNS server access lists feature")
doCmd.PersistentFlags().Bool(config.FlagFeatureDnsRewrites, true, "Enable DNS rewrites feature") doCmd.PersistentFlags().Bool(config.FlagFeatureDNSRewrites, true, "Enable DNS rewrites feature")
doCmd.PersistentFlags().Bool(config.FlagFeatureGeneral, true, "Enable general settings feature") doCmd.PersistentFlags().Bool(config.FlagFeatureGeneral, true, "Enable general settings feature")
doCmd.PersistentFlags().Bool(config.FlagFeatureQueryLog, true, "Enable query log config feature") doCmd.PersistentFlags().Bool(config.FlagFeatureQueryLog, true, "Enable query log config feature")
@@ -70,20 +69,24 @@ func init() {
doCmd.PersistentFlags().Bool(config.FlagFeatureFilters, true, "Enable filters sync feature") doCmd.PersistentFlags().Bool(config.FlagFeatureFilters, true, "Enable filters sync feature")
doCmd.PersistentFlags().String(config.FlagOriginURL, "", "Origin instance url") doCmd.PersistentFlags().String(config.FlagOriginURL, "", "Origin instance url")
doCmd.PersistentFlags().String(config.FlagOriginWebURL, "", "Origin instance web url used in the web interface (default: <origin-url>)") doCmd.PersistentFlags().
doCmd.PersistentFlags().String(config.FlagOriginApiPath, "/control", "Origin instance API path") String(config.FlagOriginWebURL, "", "Origin instance web url used in the web interface (default: <origin-url>)")
doCmd.PersistentFlags().String(config.FlagOriginAPIPath, "/control", "Origin instance API path")
doCmd.PersistentFlags().String(config.FlagOriginUsername, "", "Origin instance username") doCmd.PersistentFlags().String(config.FlagOriginUsername, "", "Origin instance username")
doCmd.PersistentFlags().String(config.FlagOriginPassword, "", "Origin instance password") doCmd.PersistentFlags().String(config.FlagOriginPassword, "", "Origin instance password")
doCmd.PersistentFlags().String(config.FlagOriginCookie, "", "If Set, uses a cookie for authentication") doCmd.PersistentFlags().String(config.FlagOriginCookie, "", "If Set, uses a cookie for authentication")
doCmd.PersistentFlags().Bool(config.FlagOriginISV, false, "Enable Origin instance InsecureSkipVerify") doCmd.PersistentFlags().Bool(config.FlagOriginISV, false, "Enable Origin instance InsecureSkipVerify")
doCmd.PersistentFlags().String(config.FlagReplicaURL, "", "Replica instance url") 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().
doCmd.PersistentFlags().String(config.FlagReplicaApiPath, "/control", "Replica instance API path") 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.FlagReplicaUsername, "", "Replica instance username")
doCmd.PersistentFlags().String(config.FlagReplicaPassword, "", "Replica instance password") doCmd.PersistentFlags().String(config.FlagReplicaPassword, "", "Replica instance password")
doCmd.PersistentFlags().String(config.FlagReplicaCookie, "", "If Set, uses a cookie for authentication") doCmd.PersistentFlags().String(config.FlagReplicaCookie, "", "If Set, uses a cookie for authentication")
doCmd.PersistentFlags().Bool(config.FlagReplicaISV, false, "Enable Replica instance InsecureSkipVerify") 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().
doCmd.PersistentFlags().String(config.FlagReplicaInterfaceName, "", "Optional change the interface name of the replica if it differs from the master") 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")
} }

57
docs/main.go Normal file
View File

@@ -0,0 +1,57 @@
// Print the available environment variables
package main
import (
"fmt"
"reflect"
"strings"
"github.com/bakito/adguardhome-sync/pkg/types"
)
func main() {
_, _ = fmt.Println("| Name | Type | Description |")
_, _ = fmt.Println("| :--- | ---- |:----------- |")
printEnvTags(reflect.TypeOf(types.Config{}), "")
}
// printEnvTags recursively prints all fields with `env` tags.
func printEnvTags(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 == "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(ft, strings.TrimSuffix(combinedTag, "_"))
} else if envTag != "" {
envVar := strings.Trim(combinedTag, "_") + " (" + ft.Kind().String() + ")"
docs := field.Tag.Get("documentation")
_, _ = fmt.Printf("| %s | %s | %s |\n", envVar, ft.Kind().String(), docs)
}
}
}

96
go.mod
View File

@@ -1,69 +1,77 @@
module github.com/bakito/adguardhome-sync module github.com/bakito/adguardhome-sync
go 1.21 go 1.24.2
require ( require (
github.com/caarlos0/env/v10 v10.0.0 github.com/caarlos0/env/v11 v11.3.1
github.com/gin-gonic/gin v1.9.1 github.com/gin-gonic/gin v1.10.0
github.com/go-resty/resty/v2 v2.9.1 github.com/go-faker/faker/v4 v4.6.0
github.com/golang/mock v1.6.0 github.com/go-resty/resty/v2 v2.16.5
github.com/google/uuid v1.5.0 github.com/google/uuid v1.6.0
github.com/jinzhu/copier v0.4.0 github.com/jinzhu/copier v0.4.0
github.com/oapi-codegen/runtime v1.1.1 github.com/oapi-codegen/runtime v1.1.1
github.com/onsi/ginkgo/v2 v2.13.2 github.com/onsi/ginkgo/v2 v2.23.4
github.com/onsi/gomega v1.30.0 github.com/onsi/gomega v1.37.0
github.com/prometheus/client_golang v1.22.0
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/spf13/cobra v1.8.0 github.com/santhosh-tekuri/jsonschema/v6 v6.0.1
go.uber.org/zap v1.26.0 github.com/spf13/cobra v1.9.1
golang.org/x/mod v0.14.0 go.uber.org/mock v0.5.1
go.uber.org/zap v1.27.0
golang.org/x/mod v0.24.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
k8s.io/apimachinery v0.29.0 k8s.io/apimachinery v0.33.0
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e
) )
require ( require (
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/bytedance/sonic v1.10.2 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect github.com/bytedance/sonic v1.13.2 // indirect
github.com/chenzhuoyu/iasm v0.9.1 // indirect github.com/bytedance/sonic/loader v0.2.4 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/cloudwego/base64x v0.1.5 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect github.com/fxamacker/cbor/v2 v2.8.0 // indirect
github.com/go-logr/logr v1.3.0 // indirect github.com/gabriel-vasile/mimetype v1.4.9 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.16.0 // indirect github.com/go-playground/validator/v10 v10.26.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect github.com/goccy/go-json v0.10.5 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-cmp v0.7.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect github.com/google/pprof v0.0.0-20250423184734-337e5dd93bb4 // indirect
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/leodido/go-urn v1.2.4 // indirect github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pelletier/go-toml/v2 v2.1.1 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.2 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/prometheus/common v0.63.0 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect github.com/ugorji/go/codec v1.2.12 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.uber.org/automaxprocs v1.6.0 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
golang.org/x/arch v0.7.0 // indirect golang.org/x/arch v0.16.0 // indirect
golang.org/x/crypto v0.18.0 // indirect golang.org/x/crypto v0.37.0 // indirect
golang.org/x/net v0.19.0 // indirect golang.org/x/net v0.39.0 // indirect
golang.org/x/sys v0.16.0 // indirect golang.org/x/sys v0.32.0 // indirect
golang.org/x/text v0.14.0 // indirect golang.org/x/text v0.24.0 // indirect
golang.org/x/tools v0.16.0 // indirect golang.org/x/tools v0.32.0 // indirect
google.golang.org/protobuf v1.32.0 // indirect google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/klog/v2 v2.110.1 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect sigs.k8s.io/yaml v1.4.0 // indirect
) )

280
go.sum
View File

@@ -1,67 +1,63 @@
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
github.com/caarlos0/env/v10 v10.0.0 h1:yIHUBZGsyqCnpTkbjk8asUlx6RFhhEs+h7TOBdgdzXA= github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/caarlos0/env/v10 v10.0.0/go.mod h1:ZfulV76NvVPw3tm591U4SwL3Xx9ldzBP9aGxzeN7G18= github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
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/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-faker/faker/v4 v4.6.0 h1:6aOPzNptRiDwD14HuAnEtlTa+D1IfFuEHO8+vEFwjTs=
github.com/go-faker/faker/v4 v4.6.0/go.mod h1:ZmrHuVtTTm2Em9e0Du6CJ9CADaLEzGXW62z1YqFH0m0=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE= github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
github.com/go-resty/resty/v2 v2.9.1 h1:PIgGx4VrHvag0juCJ4dDv3MiFRlDmP0vicBucwf+gLM= github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=
github.com/go-resty/resty/v2 v2.9.1/go.mod h1:4/GYJVjh9nhkhGR6AUNW3XhpDYNUr+Uvy9gV/VGZIy4= github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/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.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/pprof v0.0.0-20250423184734-337e5dd93bb4 h1:gD0vax+4I+mAj+jEChEf25Ia07Jq7kYOFO5PPhAxFl4=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20250423184734-337e5dd93bb4/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
@@ -71,16 +67,20 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 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.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
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=
@@ -88,157 +88,133 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
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 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/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro= github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro=
github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg=
github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs= github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus=
github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM= github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8=
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y=
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
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.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k=
github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18=
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.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/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.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/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 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.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=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.5.1 h1:ASgazW/qBmR+A32MYFDB6E2POoTgOwT509VP0CT/fjs=
go.uber.org/mock v0.5.1/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.16.0 h1:foMtLTdyOmIniqWCHjY6+JxuC54XP1fDwx4N0ASyW+U=
golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= golang.org/x/arch v0.16.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
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.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/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.0.0-20210330210617-4fbd30eecc44/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-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM=
golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
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-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v2 v2.2.8/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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/apimachinery v0.29.0 h1:+ACVktwyicPz0oc6MTMLwa2Pw3ouLAfAon1wPLtG48o= k8s.io/apimachinery v0.33.0 h1:1a6kHrJxb2hs4t8EE5wuR/WxKDwGN1FKH3JvDtA0CIQ=
k8s.io/apimachinery v0.29.0/go.mod h1:eVBxQ/cwiJxH58eK/jd/vAk4mrxmVlnpBH5J2GbMeis= k8s.io/apimachinery v0.33.0/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e h1:KqK5c/ghOm8xkHYhlodbp6i6+r+ChV2vuAuVRdFbLro=
k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/structured-merge-diff/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=

View File

@@ -1,6 +1,7 @@
package main package main
import ( import (
"context"
"fmt" "fmt"
"io" "io"
"log" "log"
@@ -20,19 +21,33 @@ func main() {
} }
log.Printf("Patching schema version %s\n", version) log.Printf("Patching schema version %s\n", version)
resp, err := http.Get(fmt.Sprintf("https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/%s/openapi/openapi.yaml", 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 { if err != nil {
log.Fatalln(err) log.Fatalln(err)
} }
defer func() { _ = resp.Body.Close() }() defer func() { _ = resp.Body.Close() }()
data, err := io.ReadAll(resp.Body) data, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
log.Fatalln(err) log.Println(err)
return
} }
schema := make(map[string]interface{}) schema := make(map[string]any)
err = yaml.Unmarshal(data, &schema) err = yaml.Unmarshal(data, &schema)
if err != nil { if err != nil {
log.Fatalln(err) log.Println(err)
return
} }
if requestBodies, ok, _ := unstructured.NestedMap(schema, "components", "requestBodies"); ok { if requestBodies, ok, _ := unstructured.NestedMap(schema, "components", "requestBodies"); ok {
@@ -45,19 +60,51 @@ func main() {
"paths", "/dns_info", "get", "responses", "200", "content", "application/json", "schema"); ok { "paths", "/dns_info", "get", "responses", "200", "content", "application/json", "schema"); ok {
if allOf, ok, _ := unstructured.NestedSlice(dnsInfo, "allOf"); ok && len(allOf) == 2 { if allOf, ok, _ := unstructured.NestedSlice(dnsInfo, "allOf"); ok && len(allOf) == 2 {
delete(dnsInfo, "allOf") delete(dnsInfo, "allOf")
if err := unstructured.SetNestedMap(schema, allOf[0].(map[string]interface{}), //nolint:forcetypeassert
if err := unstructured.SetNestedMap(schema, allOf[0].(map[string]any),
"paths", "/dns_info", "get", "responses", "200", "content", "application/json", "schema"); err != nil { "paths", "/dns_info", "get", "responses", "200", "content", "application/json", "schema"); err != nil {
log.Fatalln(err) log.Println(err)
return
} }
} }
} }
correctEntries(schema)
addFakeTags(schema)
b, err := yaml.Marshal(&schema) b, err := yaml.Marshal(&schema)
if err != nil { if err != nil {
log.Fatalln(err) log.Println(err)
return
} }
log.Printf("Writing schema file tmp/%s", fileName) log.Printf("Writing schema file tmp/%s", fileName)
err = os.WriteFile("tmp/"+fileName, b, 0o600) err = os.WriteFile("tmp/"+fileName, b, 0o600)
if err != nil { if err != nil {
log.Println(err)
return
}
}
func correctEntries(schema map[string]any) {
// https://github.com/AdguardTeam/AdGuardHome/pull/7678
if err := unstructured.SetNestedField(schema, "string", "components", "schemas", "QueryLogItem", "properties", "client_proto", "type"); err != nil {
log.Fatalln(err)
}
}
func addFakeTags(schema map[string]any) {
fake := map[string]any{"faker": `slice_len=24`}
if err := unstructured.SetNestedMap(schema, fake, "components", "schemas", "Stats", "properties", "blocked_filtering", "x-oapi-codegen-extra-tags"); err != nil {
log.Fatalln(err)
}
if err := unstructured.SetNestedMap(schema, fake, "components", "schemas", "Stats", "properties", "dns_queries", "x-oapi-codegen-extra-tags"); err != nil {
log.Fatalln(err)
}
if err := unstructured.SetNestedMap(schema, fake, "components", "schemas", "Stats", "properties", "replaced_parental", "x-oapi-codegen-extra-tags"); err != nil {
log.Fatalln(err)
}
if err := unstructured.SetNestedMap(schema, fake, "components", "schemas", "Stats", "properties", "replaced_safebrowsing", "x-oapi-codegen-extra-tags"); err != nil {
log.Fatalln(err) log.Fatalln(err)
} }
} }

View File

@@ -17,14 +17,18 @@ func (cl *client) doGet(req *resty.Request, url string) error {
rl.Debug("do get") rl.Debug("do get")
resp, err := req.Get(url) resp, err := req.Get(url)
if err != nil { if err != nil {
if resp != nil && resp.StatusCode() == http.StatusFound { l := rl
loc := resp.Header().Get("Location") if resp != nil {
if loc == "/install.html" || loc == "/control/install.html" { if resp.StatusCode() == http.StatusFound {
return ErrSetupNeeded loc := resp.Header().Get("Location")
if loc == "/install.html" || loc == "/control/install.html" {
return ErrSetupNeeded
}
} }
l = l.With("status", resp.StatusCode(), "body", string(resp.Body()), "error", err)
} }
rl.With("status", resp.StatusCode(), "body", string(resp.Body()), "error", err).Debug("error in do get") l.Debug("error in do get")
return detailedError(resp, err) return detailedError(resp, err)
} }

View File

@@ -11,19 +11,20 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/go-resty/resty/v2"
"go.uber.org/zap"
"github.com/bakito/adguardhome-sync/pkg/client/model" "github.com/bakito/adguardhome-sync/pkg/client/model"
"github.com/bakito/adguardhome-sync/pkg/log" "github.com/bakito/adguardhome-sync/pkg/log"
"github.com/bakito/adguardhome-sync/pkg/types" "github.com/bakito/adguardhome-sync/pkg/types"
"github.com/bakito/adguardhome-sync/pkg/utils" "github.com/bakito/adguardhome-sync/pkg/utils"
"github.com/go-resty/resty/v2"
"go.uber.org/zap"
) )
const envRedirectPolicyNoOfRedirects = "REDIRECT_POLICY_NO_OF_REDIRECTS" const envRedirectPolicyNoOfRedirects = "REDIRECT_POLICY_NO_OF_REDIRECTS"
var ( var (
l = log.GetLogger("client") l = log.GetLogger("client")
// ErrSetupNeeded custom error // ErrSetupNeeded custom error.
ErrSetupNeeded = errors.New("setup needed") ErrSetupNeeded = errors.New("setup needed")
) )
@@ -33,16 +34,16 @@ func detailedError(resp *resty.Response, err error) error {
e += fmt.Sprintf("(%s)", string(resp.Body())) e += fmt.Sprintf("(%s)", string(resp.Body()))
} }
if err != nil { if err != nil {
e += fmt.Sprintf(": %s", err.Error()) e += ": " + err.Error()
} }
return errors.New(e) return errors.New(e)
} }
// New create a new client // New create a new client.
func New(config types.AdGuardInstance) (Client, error) { func New(config types.AdGuardInstance) (Client, error) {
var apiURL string var apiURL string
if config.APIPath == "" { if config.APIPath == "" {
apiURL = fmt.Sprintf("%s/control", config.URL) apiURL = config.URL + "/control"
} else { } else {
apiURL = fmt.Sprintf("%s/%s", config.URL, config.APIPath) apiURL = fmt.Sprintf("%s/%s", config.URL, config.APIPath)
} }
@@ -53,10 +54,8 @@ func New(config types.AdGuardInstance) (Client, error) {
u.Path = path.Clean(u.Path) u.Path = path.Clean(u.Path)
cl := resty.New().SetBaseURL(u.String()).SetDisableWarn(true) cl := resty.New().SetBaseURL(u.String()).SetDisableWarn(true)
if config.InsecureSkipVerify { // #nosec G402 has to be explicitly enabled
// #nosec G402 has to be explicitly enabled cl.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: config.InsecureSkipVerify})
cl.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
}
cookieParts := strings.Split(config.Cookie, "=") cookieParts := strings.Split(config.Cookie, "=")
if len(cookieParts) == 2 { if len(cookieParts) == 2 {
@@ -86,10 +85,14 @@ func New(config types.AdGuardInstance) (Client, error) {
}, nil }, nil
} }
// Client AdguardHome API client interface // Client AdguardHome API client interface.
//
//nolint:interfacebloat
type Client interface { type Client interface {
Host() string Host() string
Status() (*model.ServerStatus, error) Status() (*model.ServerStatus, error)
Stats() (*model.Stats, error)
QueryLog(limit int) (*model.QueryLog, error)
ToggleProtection(enable bool) error ToggleProtection(enable bool) error
RewriteList() (*model.RewriteEntries, error) RewriteList() (*model.RewriteEntries, error)
AddRewriteEntries(e ...model.RewriteEntry) error AddRewriteEntries(e ...model.RewriteEntry) error
@@ -109,25 +112,23 @@ type Client interface {
SetSafeSearchConfig(settings *model.SafeSearchConfig) error SetSafeSearchConfig(settings *model.SafeSearchConfig) error
ProfileInfo() (*model.ProfileInfo, error) ProfileInfo() (*model.ProfileInfo, error)
SetProfileInfo(settings *model.ProfileInfo) error SetProfileInfo(settings *model.ProfileInfo) error
BlockedServices() (*model.BlockedServicesArray, error)
BlockedServicesSchedule() (*model.BlockedServicesSchedule, error) BlockedServicesSchedule() (*model.BlockedServicesSchedule, error)
SetBlockedServices(services *model.BlockedServicesArray) error
SetBlockedServicesSchedule(schedule *model.BlockedServicesSchedule) error SetBlockedServicesSchedule(schedule *model.BlockedServicesSchedule) error
Clients() (*model.Clients, error) Clients() (*model.Clients, error)
AddClient(client *model.Client) error AddClient(client *model.Client) error
UpdateClient(client *model.Client) error UpdateClient(client *model.Client) error
DeleteClient(client *model.Client) error DeleteClient(client *model.Client) error
QueryLogConfig() (*model.QueryLogConfig, error) QueryLogConfig() (*model.QueryLogConfigWithIgnored, error)
SetQueryLogConfig(*model.QueryLogConfig) error SetQueryLogConfig(ql *model.QueryLogConfigWithIgnored) error
StatsConfig() (*model.StatsConfig, error) StatsConfig() (*model.GetStatsConfigResponse, error)
SetStatsConfig(sc *model.StatsConfig) error SetStatsConfig(sc *model.PutStatsConfigUpdateRequest) error
Setup() error Setup() error
AccessList() (*model.AccessList, error) AccessList() (*model.AccessList, error)
SetAccessList(*model.AccessList) error SetAccessList(accessList *model.AccessList) error
DNSConfig() (*model.DNSConfig, error) DNSConfig() (*model.DNSConfig, error)
SetDNSConfig(*model.DNSConfig) error SetDNSConfig(config *model.DNSConfig) error
DhcpConfig() (*model.DhcpStatus, error) DhcpConfig() (*model.DhcpStatus, error)
SetDhcpConfig(*model.DhcpStatus) error SetDhcpConfig(status *model.DhcpStatus) error
AddDHCPStaticLease(lease model.DhcpStaticLease) error AddDHCPStaticLease(lease model.DhcpStaticLease) error
DeleteDHCPStaticLease(lease model.DhcpStaticLease) error DeleteDHCPStaticLease(lease model.DhcpStaticLease) error
} }
@@ -160,6 +161,21 @@ func (cl *client) Status() (*model.ServerStatus, error) {
return status, err 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) { func (cl *client) RewriteList() (*model.RewriteEntries, error) {
rewrites := &model.RewriteEntries{} rewrites := &model.RewriteEntries{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(&rewrites), "/rewrite/list") err := cl.doGet(cl.client.R().EnableTrace().SetResult(&rewrites), "/rewrite/list")
@@ -167,8 +183,7 @@ func (cl *client) RewriteList() (*model.RewriteEntries, error) {
} }
func (cl *client) AddRewriteEntries(entries ...model.RewriteEntry) error { func (cl *client) AddRewriteEntries(entries ...model.RewriteEntry) error {
for i := range entries { for _, e := range entries {
e := entries[i]
cl.log.With("domain", e.Domain, "answer", e.Answer).Info("Add DNS rewrite entry") 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") err := cl.doPost(cl.client.R().EnableTrace().SetBody(&e), "/rewrite/add")
if err != nil { if err != nil {
@@ -179,8 +194,7 @@ func (cl *client) AddRewriteEntries(entries ...model.RewriteEntry) error {
} }
func (cl *client) DeleteRewriteEntries(entries ...model.RewriteEntry) error { func (cl *client) DeleteRewriteEntries(entries ...model.RewriteEntry) error {
for i := range entries { for _, e := range entries {
e := entries[i]
cl.log.With("domain", e.Domain, "answer", e.Answer).Info("Delete DNS rewrite entry") 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") err := cl.doPost(cl.client.R().EnableTrace().SetBody(&e), "/rewrite/delete")
if err != nil { if err != nil {
@@ -213,7 +227,7 @@ func (cl *client) toggleStatus(mode string) (bool, error) {
} }
func (cl *client) toggleBool(mode string, enable bool) error { func (cl *client) toggleBool(mode string, enable bool) error {
cl.log.With("enable", enable).Info(fmt.Sprintf("Toggle %s", mode)) cl.log.With("enable", enable).Info("Toggle " + mode)
var target string var target string
if enable { if enable {
target = "enable" target = "enable"
@@ -252,7 +266,10 @@ func (cl *client) UpdateFilter(whitelist bool, f model.Filter) error {
func (cl *client) RefreshFilters(whitelist bool) error { func (cl *client) RefreshFilters(whitelist bool) error {
cl.log.With("whitelist", whitelist).Info("Refresh filter") cl.log.With("whitelist", whitelist).Info("Refresh filter")
return cl.doPost(cl.client.R().EnableTrace().SetBody(&model.FilterRefreshRequest{Whitelist: utils.Ptr(whitelist)}), "/filtering/refresh") return cl.doPost(
cl.client.R().EnableTrace().SetBody(&model.FilterRefreshRequest{Whitelist: utils.Ptr(whitelist)}),
"/filtering/refresh",
)
} }
func (cl *client) ToggleProtection(enable bool) error { func (cl *client) ToggleProtection(enable bool) error {
@@ -261,7 +278,11 @@ func (cl *client) ToggleProtection(enable bool) error {
} }
func (cl *client) SetCustomRules(rules *[]string) error { func (cl *client) SetCustomRules(rules *[]string) error {
cl.log.With("rules", len(*rules)).Info("Set user rules") 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") return cl.doPost(cl.client.R().EnableTrace().SetBody(&model.SetRulesRequest{Rules: rules}), "/filtering/set_rules")
} }
@@ -273,17 +294,6 @@ func (cl *client) ToggleFiltering(enabled bool, interval int) error {
}), "/filtering/config") }), "/filtering/config")
} }
func (cl *client) BlockedServices() (*model.BlockedServicesArray, error) {
svcs := &model.BlockedServicesArray{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(svcs), "/blocked_services/list")
return svcs, err
}
func (cl *client) SetBlockedServices(services *model.BlockedServicesArray) error {
cl.log.With("services", model.ArrayString(services)).Info("Set blocked services")
return cl.doPost(cl.client.R().EnableTrace().SetBody(services), "/blocked_services/set")
}
func (cl *client) BlockedServicesSchedule() (*model.BlockedServicesSchedule, error) { func (cl *client) BlockedServicesSchedule() (*model.BlockedServicesSchedule, error) {
sched := &model.BlockedServicesSchedule{} sched := &model.BlockedServicesSchedule{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(sched), "/blocked_services/get") err := cl.doGet(cl.client.R().EnableTrace().SetResult(sched), "/blocked_services/get")
@@ -308,7 +318,10 @@ func (cl *client) AddClient(client *model.Client) error {
func (cl *client) UpdateClient(client *model.Client) error { func (cl *client) UpdateClient(client *model.Client) error {
cl.log.With("name", *client.Name).Info("Update client settings") 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") 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 { func (cl *client) DeleteClient(client *model.Client) error {
@@ -316,26 +329,27 @@ func (cl *client) DeleteClient(client *model.Client) error {
return cl.doPost(cl.client.R().EnableTrace().SetBody(client), "/clients/delete") return cl.doPost(cl.client.R().EnableTrace().SetBody(client), "/clients/delete")
} }
func (cl *client) QueryLogConfig() (*model.QueryLogConfig, error) { func (cl *client) QueryLogConfig() (*model.QueryLogConfigWithIgnored, error) {
qlc := &model.QueryLogConfig{} qlc := &model.QueryLogConfigWithIgnored{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(qlc), "/querylog_info") err := cl.doGet(cl.client.R().EnableTrace().SetResult(qlc), "/querylog/config")
return qlc, err return qlc, err
} }
func (cl *client) SetQueryLogConfig(qlc *model.QueryLogConfig) error { 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") cl.log.With("enabled", *qlc.Enabled, "interval", *qlc.Interval, "anonymizeClientIP", *qlc.AnonymizeClientIp).
return cl.doPost(cl.client.R().EnableTrace().SetBody(qlc), "/querylog_config") Info("Set query log config")
return cl.doPut(cl.client.R().EnableTrace().SetBody(qlc), "/querylog/config/update")
} }
func (cl *client) StatsConfig() (*model.StatsConfig, error) { func (cl *client) StatsConfig() (*model.GetStatsConfigResponse, error) {
stats := &model.StatsConfig{} stats := &model.GetStatsConfigResponse{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(stats), "/stats_info") err := cl.doGet(cl.client.R().EnableTrace().SetResult(stats), "/stats/config")
return stats, err return stats, err
} }
func (cl *client) SetStatsConfig(sc *model.StatsConfig) error { func (cl *client) SetStatsConfig(sc *model.PutStatsConfigUpdateRequest) error {
cl.log.With("interval", *sc.Interval).Info("Set stats config") cl.log.With("interval", sc.Interval).Info("Set stats config")
return cl.doPost(cl.client.R().EnableTrace().SetBody(sc), "/stats_config") return cl.doPut(cl.client.R().EnableTrace().SetBody(sc), "/stats/config/update")
} }
func (cl *client) Setup() error { func (cl *client) Setup() error {

View File

@@ -8,13 +8,14 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"github.com/google/uuid"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/bakito/adguardhome-sync/pkg/client" "github.com/bakito/adguardhome-sync/pkg/client"
"github.com/bakito/adguardhome-sync/pkg/client/model" "github.com/bakito/adguardhome-sync/pkg/client/model"
"github.com/bakito/adguardhome-sync/pkg/types" "github.com/bakito/adguardhome-sync/pkg/types"
"github.com/bakito/adguardhome-sync/pkg/utils" "github.com/bakito/adguardhome-sync/pkg/utils"
"github.com/google/uuid"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
) )
var ( var (
@@ -97,6 +98,20 @@ var _ = Describe("Client", func() {
err = cl.DeleteFilter(true, model.Filter{Url: "bar"}) 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",
`{"rules":[]}`,
)
err := cl.SetCustomRules(utils.Ptr([]string{}))
Ω(err).ShouldNot(HaveOccurred())
})
It("should set nil filter rules", func() {
ts, cl = ClientPost("/filtering/set_rules",
`{}`,
)
err := cl.SetCustomRules(nil)
Ω(err).ShouldNot(HaveOccurred())
})
}) })
Context("Status", func() { Context("Status", func() {
@@ -123,7 +138,14 @@ var _ = Describe("Client", func() {
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())
}) })
@@ -225,20 +247,6 @@ var _ = Describe("Client", func() {
}) })
}) })
Context("BlockedServices", func() {
It("should read BlockedServices", func() {
ts, cl = ClientGet("blockedservices-list.json", "/blocked_services/list")
s, err := cl.BlockedServices()
Ω(err).ShouldNot(HaveOccurred())
Ω(*s).Should(HaveLen(2))
})
It("should set BlockedServices", func() {
ts, cl = ClientPost("/blocked_services/set", `["bar","foo"]`)
err := cl.SetBlockedServices(&model.BlockedServicesArray{"foo", "bar"})
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("BlockedServicesSchedule", func() { Context("BlockedServicesSchedule", func() {
It("should read BlockedServicesSchedule", func() { It("should read BlockedServicesSchedule", func() {
ts, cl = ClientGet("blockedservicesschedule-get.json", "/blocked_services/get") ts, cl = ClientGet("blockedservicesschedule-get.json", "/blocked_services/get")
@@ -294,7 +302,7 @@ var _ = Describe("Client", func() {
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).ShouldNot(BeNil()) Ω(qlc.Enabled).ShouldNot(BeNil())
@@ -303,26 +311,36 @@ var _ = Describe("Client", func() {
Ω(*qlc.Interval).Should(Equal(model.QueryLogConfigInterval(90))) Ω(*qlc.Interval).Should(Equal(model.QueryLogConfigInterval(90)))
}) })
It("should set QueryLogConfig", func() { It("should set QueryLogConfig", func() {
ts, cl = ClientPost("/querylog_config", `{"anonymize_client_ip":true,"enabled":true,"interval":123}`) ts, cl = ClientPut(
"/querylog/config/update",
`{"anonymize_client_ip":true,"enabled":true,"interval":123,"ignored":["foo.bar"]}`,
)
var interval model.QueryLogConfigInterval = 123 var interval model.QueryLogConfigInterval = 123
err := cl.SetQueryLogConfig(&model.QueryLogConfig{AnonymizeClientIp: utils.Ptr(true), Interval: &interval, Enabled: utils.Ptr(true)}) 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).ShouldNot(BeNil()) Ω(sc.Interval).ShouldNot(BeNil())
Ω(*sc.Interval).Should(Equal(model.StatsConfigInterval(1))) Ω(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}`)
var interval model.StatsConfigInterval = 123 var interval float32 = 123
err := cl.SetStatsConfig(&model.StatsConfig{Interval: &interval}) err := cl.SetStatsConfig(&model.PutStatsConfigUpdateRequest{Interval: interval})
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
}) })
}) })
@@ -347,8 +365,8 @@ var _ = Describe("Client", func() {
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() {
var interval model.StatsConfigInterval = 123 var interval float32 = 123
err := cl.SetStatsConfig(&model.StatsConfig{Interval: &interval}) 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"))
}) })
@@ -356,10 +374,10 @@ var _ = Describe("Client", func() {
}) })
}) })
func ClientGet(file string, path string) (*httptest.Server, client.Client) { func ClientGet(file, path string) (*httptest.Server, client.Client) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Ω(r.URL.Path).Should(Equal(types.DefaultAPIPath + path)) Ω(r.URL.Path).Should(Equal(types.DefaultAPIPath + path))
b, err := os.ReadFile(filepath.Join("../../testdata", file)) b, err := os.ReadFile(filepath.Join("..", "..", "testdata", file))
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
_, err = w.Write(b) _, err = w.Write(b)

View File

@@ -11,19 +11,20 @@ import (
"net/url" "net/url"
"path" "path"
"go.uber.org/zap"
"github.com/bakito/adguardhome-sync/pkg/client/model" "github.com/bakito/adguardhome-sync/pkg/client/model"
"github.com/bakito/adguardhome-sync/pkg/log" "github.com/bakito/adguardhome-sync/pkg/log"
"github.com/bakito/adguardhome-sync/pkg/types" "github.com/bakito/adguardhome-sync/pkg/types"
"go.uber.org/zap"
) )
var l = log.GetLogger("client") var l = log.GetLogger("client")
// New create a new api client // New create a new api client.
func New(config types.AdGuardInstance) (Client, error) { func New(config types.AdGuardInstance) (Client, error) {
var apiURL string var apiURL string
if config.APIPath == "" { if config.APIPath == "" {
apiURL = fmt.Sprintf("%s/control", config.URL) apiURL = config.URL + "/control"
} else { } else {
apiURL = fmt.Sprintf("%s/%s", config.URL, config.APIPath) apiURL = fmt.Sprintf("%s/%s", config.URL, config.APIPath)
} }
@@ -33,12 +34,11 @@ func New(config types.AdGuardInstance) (Client, error) {
} }
u.Path = path.Clean(u.Path) u.Path = path.Clean(u.Path)
httpClient := &http.Client{} httpClient := &http.Client{
if config.InsecureSkipVerify { Transport: &http.Transport{
// #nosec G402 has to be explicitly enabled // #nosec G402 has to be explicitly enabled
httpClient.Transport = &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: config.InsecureSkipVerify},
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, },
}
} }
aghClient, err := model.NewClient(u.String(), func(client *model.AdguardHomeClient) error { aghClient, err := model.NewClient(u.String(), func(client *model.AdguardHomeClient) error {
@@ -97,7 +97,7 @@ func (a apiClient) SetFilteringConfig(ctx context.Context, config model.FilterCo
return write(ctx, config, a.client.FilteringConfig) return write(ctx, config, a.client.FilteringConfig)
} }
func write[B interface{}]( func write[B any](
ctx context.Context, ctx context.Context,
body B, body B,
req func(ctx context.Context, body B, reqEditors ...model.RequestEditorFn) (*http.Response, error), req func(ctx context.Context, body B, reqEditors ...model.RequestEditorFn) (*http.Response, error),
@@ -113,7 +113,7 @@ func write[B interface{}](
return nil return nil
} }
func read[I interface{}]( func read[I any](
ctx context.Context, ctx context.Context,
req func(ctx context.Context, reqEditors ...model.RequestEditorFn) (*http.Response, error), req func(ctx context.Context, reqEditors ...model.RequestEditorFn) (*http.Response, error),
parse func(rsp *http.Response) (*I, error), parse func(rsp *http.Response) (*I, error),

View File

@@ -3,8 +3,9 @@ package client
import ( import (
"net/http" "net/http"
"github.com/bakito/adguardhome-sync/pkg/client/model"
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
"github.com/bakito/adguardhome-sync/pkg/client/model"
) )
var _ model.HttpRequestDoer = &adapter{} var _ model.HttpRequestDoer = &adapter{}

View File

@@ -5,20 +5,39 @@ import (
"sort" "sort"
"strings" "strings"
"github.com/bakito/adguardhome-sync/pkg/utils"
"github.com/jinzhu/copier" "github.com/jinzhu/copier"
"go.uber.org/zap"
"k8s.io/utils/ptr"
"github.com/bakito/adguardhome-sync/pkg/utils"
) )
// Clone the config // Clone the config.
func (c *DhcpStatus) Clone() *DhcpStatus { func (c *DhcpStatus) Clone() *DhcpStatus {
clone := &DhcpStatus{} clone := &DhcpStatus{}
_ = copier.Copy(clone, c) _ = copier.Copy(clone, c)
return clone return clone
} }
// Equals dhcp server config equal check 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 { func (c *DhcpStatus) Equals(o *DhcpStatus) bool {
return utils.JsonEquals(c, o) return utils.JSONEquals(c, o)
} }
func (c *DhcpStatus) HasConfig() bool { func (c *DhcpStatus) HasConfig() bool {
@@ -26,17 +45,20 @@ func (c *DhcpStatus) HasConfig() bool {
} }
func (j DhcpConfigV4) isValid() bool { func (j DhcpConfigV4) isValid() bool {
return j.GatewayIp != nil && *j.GatewayIp != "" && j.SubnetMask != nil && j.RangeStart != nil && j.RangeEnd != nil 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 { func (j DhcpConfigV6) isValid() bool {
return j.RangeStart != nil return j.RangeStart != nil && *j.RangeStart != ""
} }
type DhcpStaticLeases []DhcpStaticLease type DhcpStaticLeases []DhcpStaticLease
// MergeDhcpStaticLeases the leases // MergeDhcpStaticLeases the leases.
func MergeDhcpStaticLeases(l *[]DhcpStaticLease, other *[]DhcpStaticLease) (DhcpStaticLeases, DhcpStaticLeases) { func MergeDhcpStaticLeases(l, other *[]DhcpStaticLease) (adds, removes DhcpStaticLeases) {
var thisLeases []DhcpStaticLease var thisLeases []DhcpStaticLease
var otherLeases []DhcpStaticLease var otherLeases []DhcpStaticLease
@@ -48,8 +70,6 @@ func MergeDhcpStaticLeases(l *[]DhcpStaticLease, other *[]DhcpStaticLease) (Dhcp
} }
current := make(map[string]DhcpStaticLease) current := make(map[string]DhcpStaticLease)
var adds DhcpStaticLeases
var removes DhcpStaticLeases
for _, le := range thisLeases { for _, le := range thisLeases {
current[le.Mac] = le current[le.Mac] = le
} }
@@ -69,21 +89,21 @@ func MergeDhcpStaticLeases(l *[]DhcpStaticLease, other *[]DhcpStaticLease) (Dhcp
return adds, removes return adds, removes
} }
// Equals dns config equal check // Equals dns config equal check.
func (c *DNSConfig) Equals(o *DNSConfig) bool { func (c *DNSConfig) Equals(o *DNSConfig) bool {
cc := c.Clone() cc := c.Clone()
oo := o.Clone() oo := o.Clone()
cc.Sort() cc.Sort()
oo.Sort() oo.Sort()
return utils.JsonEquals(cc, oo) return utils.JSONEquals(cc, oo)
} }
func (c *DNSConfig) Clone() *DNSConfig { func (c *DNSConfig) Clone() *DNSConfig {
return utils.Clone(c, &DNSConfig{}) return utils.Clone(c, &DNSConfig{})
} }
// Sort sort dns config // Sort dns config.
func (c *DNSConfig) Sort() { func (c *DNSConfig) Sort() {
if c.UpstreamDns != nil { if c.UpstreamDns != nil {
sort.Strings(*c.UpstreamDns) sort.Strings(*c.UpstreamDns)
@@ -98,14 +118,14 @@ func (c *DNSConfig) Sort() {
} }
} }
// Equals access list equal check // Equals access list equal check.
func (al *AccessList) Equals(o *AccessList) bool { func (al *AccessList) Equals(o *AccessList) bool {
return EqualsStringSlice(al.AllowedClients, o.AllowedClients, true) && return EqualsStringSlice(al.AllowedClients, o.AllowedClients, true) &&
EqualsStringSlice(al.DisallowedClients, o.DisallowedClients, true) && EqualsStringSlice(al.DisallowedClients, o.DisallowedClients, true) &&
EqualsStringSlice(al.BlockedHosts, o.BlockedHosts, true) EqualsStringSlice(al.BlockedHosts, o.BlockedHosts, true)
} }
func EqualsStringSlice(a *[]string, b *[]string, sortIt bool) bool { func EqualsStringSlice(a, b *[]string, sortIt bool) bool {
if a == nil && b == nil { if a == nil && b == nil {
return true return true
} }
@@ -131,7 +151,7 @@ func EqualsStringSlice(a *[]string, b *[]string, sortIt bool) bool {
return true return true
} }
// Sort clients // Sort clients.
func (cl *Client) Sort() { func (cl *Client) Sort() {
if cl.Ids != nil { if cl.Ids != nil {
sort.Strings(*cl.Ids) sort.Strings(*cl.Ids)
@@ -147,15 +167,42 @@ func (cl *Client) Sort() {
} }
} }
// Equals Clients equal check // 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 { func (cl *Client) Equals(o *Client) bool {
cl.Sort() cl.Sort()
o.Sort() o.Sort()
return utils.JsonEquals(cl, o) bssCl := cl.PrepareDiff()
bssO := o.PrepareDiff()
defer func() {
cl.AfterDiff(bssCl)
o.AfterDiff(bssO)
}()
return utils.JSONEquals(cl, o)
} }
// Add ac client // Add ac client.
func (clients *Clients) Add(cl Client) { func (clients *Clients) Add(cl Client) {
if clients.Clients == nil { if clients.Clients == nil {
clients.Clients = &ClientsArray{cl} clients.Clients = &ClientsArray{cl}
@@ -165,13 +212,12 @@ func (clients *Clients) Add(cl Client) {
} }
} }
// Merge merge Clients // Merge merge Clients.
func (clients *Clients) Merge(other *Clients) ([]*Client, []*Client, []*Client) { func (clients *Clients) Merge(other *Clients) (adds, removes, updates []*Client) {
current := make(map[string]*Client) current := make(map[string]*Client)
if clients.Clients != nil { if clients.Clients != nil {
cc := *clients.Clients cc := *clients.Clients
for i := range cc { for _, client := range cc {
client := cc[i]
current[*client.Name] = &client current[*client.Name] = &client
} }
} }
@@ -179,16 +225,11 @@ func (clients *Clients) Merge(other *Clients) ([]*Client, []*Client, []*Client)
expected := make(map[string]*Client) expected := make(map[string]*Client)
if other.Clients != nil { if other.Clients != nil {
oc := *other.Clients oc := *other.Clients
for i := range oc { for _, client := range oc {
client := oc[i]
expected[*client.Name] = &client expected[*client.Name] = &client
} }
} }
var adds []*Client
var removes []*Client
var updates []*Client
for _, cl := range expected { for _, cl := range expected {
if oc, ok := current[*cl.Name]; ok { if oc, ok := current[*cl.Name]; ok {
if !cl.Equals(oc) { if !cl.Equals(oc) {
@@ -207,7 +248,7 @@ func (clients *Clients) Merge(other *Clients) ([]*Client, []*Client, []*Client)
return adds, updates, removes return adds, updates, removes
} }
// Key RewriteEntry key // Key RewriteEntry key.
func (re *RewriteEntry) Key() string { func (re *RewriteEntry) Key() string {
var d string var d string
var a string var a string
@@ -220,16 +261,13 @@ func (re *RewriteEntry) Key() string {
return fmt.Sprintf("%s#%s", d, a) return fmt.Sprintf("%s#%s", d, a)
} }
// RewriteEntries list of RewriteEntry // RewriteEntries list of RewriteEntry.
type RewriteEntries []RewriteEntry type RewriteEntries []RewriteEntry
// Merge RewriteEntries // Merge RewriteEntries.
func (rwe *RewriteEntries) Merge(other *RewriteEntries) (RewriteEntries, RewriteEntries, RewriteEntries) { func (rwe *RewriteEntries) Merge(other *RewriteEntries) (adds, removes, duplicates RewriteEntries) {
current := make(map[string]RewriteEntry) current := make(map[string]RewriteEntry)
var adds RewriteEntries
var removes RewriteEntries
var duplicates RewriteEntries
processed := make(map[string]bool) processed := make(map[string]bool)
for _, rr := range *rwe { for _, rr := range *rwe {
if _, ok := processed[rr.Key()]; !ok { if _, ok := processed[rr.Key()]; !ok {
@@ -262,26 +300,21 @@ func (rwe *RewriteEntries) Merge(other *RewriteEntries) (RewriteEntries, Rewrite
return adds, removes, duplicates return adds, removes, duplicates
} }
func MergeFilters(this *[]Filter, other *[]Filter) ([]Filter, []Filter, []Filter) { func MergeFilters(this, other *[]Filter) (adds, updates, removes []Filter) {
if this == nil && other == nil { if this == nil && other == nil {
return nil, nil, nil return nil, nil, nil
} }
current := make(map[string]*Filter) current := make(map[string]*Filter)
var adds []Filter
var updates []Filter
var removes []Filter
if this != nil { if this != nil {
for i := range *this { for _, fi := range *this {
fi := (*this)[i]
current[fi.Url] = &fi current[fi.Url] = &fi
} }
} }
if other != nil { if other != nil {
for i := range *other { for _, rr := range *other {
rr := (*other)[i]
if c, ok := current[rr.Url]; ok { if c, ok := current[rr.Url]; ok {
if !c.Equals(&rr) { if !c.Equals(&rr) {
updates = append(updates, rr) updates = append(updates, rr)
@@ -300,24 +333,29 @@ func MergeFilters(this *[]Filter, other *[]Filter) ([]Filter, []Filter, []Filter
return adds, updates, removes return adds, updates, removes
} }
// Equals Filter equal check // Equals Filter equal check.
func (f *Filter) Equals(o *Filter) bool { func (f *Filter) Equals(o *Filter) bool {
return f.Enabled == o.Enabled && f.Url == o.Url && f.Name == o.Name return f.Enabled == o.Enabled && f.Url == o.Url && f.Name == o.Name
} }
// Equals QueryLogConfig equal check type QueryLogConfigWithIgnored struct {
func (qlc *QueryLogConfig) Equals(o *QueryLogConfig) bool { QueryLogConfig
return ptrEquals(qlc.Enabled, o.Enabled) &&
ptrEquals(qlc.AnonymizeClientIp, o.AnonymizeClientIp) && // Ignored List of host names, which should not be written to log
qlc.Interval.Equals(o.Interval) Ignored []string `json:"ignored,omitempty"`
} }
// Equals QueryLogConfigInterval equal check // 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 { func (qlc *QueryLogConfigInterval) Equals(o *QueryLogConfigInterval) bool {
return ptrEquals(qlc, o) return ptrEquals(qlc, o)
} }
func ptrEquals[T comparable](a *T, b *T) bool { func ptrEquals[T comparable](a, b *T) bool {
if a == nil && b == nil { if a == nil && b == nil {
return true return true
} }
@@ -333,7 +371,7 @@ func ptrEquals[T comparable](a *T, b *T) bool {
return aa == bb return aa == bb
} }
// EnableConfig API struct // EnableConfig API struct.
type EnableConfig struct { type EnableConfig struct {
Enabled bool `json:"enabled"` Enabled bool `json:"enabled"`
} }
@@ -348,30 +386,29 @@ func (ssc *SafeSearchConfig) Equals(o *SafeSearchConfig) bool {
ptrEquals(ssc.Youtube, o.Youtube) ptrEquals(ssc.Youtube, o.Youtube)
} }
func (pi *ProfileInfo) Equals(o *ProfileInfo) bool { func (pi *ProfileInfo) Equals(o *ProfileInfo, withTheme bool) bool {
return pi.Language == o.Language && return pi.Language == o.Language && (!withTheme || pi.Theme == o.Theme)
pi.Theme == o.Theme
} }
func (pi *ProfileInfo) ShouldSyncFor(o *ProfileInfo) *ProfileInfo { func (pi *ProfileInfo) ShouldSyncFor(o *ProfileInfo, withTheme bool) *ProfileInfo {
if pi.Equals(o) { if pi.Equals(o, withTheme) {
return nil return nil
} }
merged := &ProfileInfo{Name: pi.Name, Language: pi.Language, Theme: pi.Theme} merged := &ProfileInfo{Name: pi.Name, Language: pi.Language, Theme: pi.Theme}
if o.Language != "" { if o.Language != "" {
merged.Language = o.Language merged.Language = o.Language
} }
if o.Theme != "" { if withTheme && o.Theme != "" {
merged.Theme = o.Theme merged.Theme = o.Theme
} }
if merged.Name == "" || merged.Language == "" || merged.Theme == "" || merged.Equals(pi) { if merged.Name == "" || merged.Language == "" || merged.Equals(pi, false) {
return nil return nil
} }
return merged return merged
} }
func (bss *BlockedServicesSchedule) Equals(o *BlockedServicesSchedule) bool { func (bss *BlockedServicesSchedule) Equals(o *BlockedServicesSchedule) bool {
return utils.JsonEquals(bss, o) return utils.JSONEquals(bss, o)
} }
func (bss *BlockedServicesSchedule) ServicesString() string { func (bss *BlockedServicesSchedule) ServicesString() string {
@@ -386,3 +423,70 @@ func ArrayString(a *[]string) string {
sort.Strings(sorted) sort.Strings(sorted)
return fmt.Sprintf("[%s]", strings.Join(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
}

View File

@@ -1,6 +1,6 @@
// Package model provides primitives to interact with the openapi HTTP API. // Package model provides primitives to interact with the openapi HTTP API.
// //
// Code generated by github.com/deepmap/oapi-codegen/v2 version v2.0.0 DO NOT EDIT. // Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.4.1 DO NOT EDIT.
package model package model
import ( import (
@@ -37,6 +37,14 @@ const (
Refused DNSConfigBlockingMode = "refused" Refused DNSConfigBlockingMode = "refused"
) )
// Defines values for DNSConfigUpstreamMode.
const (
MapConstDeprecatedTrueDescriptionUseloadBalanceInstead DNSConfigUpstreamMode = "map[const: deprecated:true description:Use `load_balance` instead.]"
MapConstFastestAddr DNSConfigUpstreamMode = "map[const:fastest_addr]"
MapConstLoadBalance DNSConfigUpstreamMode = "map[const:load_balance]"
MapConstParallel DNSConfigUpstreamMode = "map[const:parallel]"
)
// Defines values for DhcpSearchResultOtherServerFound. // Defines values for DhcpSearchResultOtherServerFound.
const ( const (
DhcpSearchResultOtherServerFoundError DhcpSearchResultOtherServerFound = "error" DhcpSearchResultOtherServerFoundError DhcpSearchResultOtherServerFound = "error"
@@ -83,6 +91,15 @@ const (
QueryLogConfigIntervalN90 QueryLogConfigInterval = 90 QueryLogConfigIntervalN90 QueryLogConfigInterval = 90
) )
// Defines values for QueryLogItemClientProto.
const (
Dnscrypt QueryLogItemClientProto = "dnscrypt"
Doh QueryLogItemClientProto = "doh"
Doq QueryLogItemClientProto = "doq"
Dot QueryLogItemClientProto = "dot"
Empty QueryLogItemClientProto = ""
)
// Defines values for QueryLogItemReason. // Defines values for QueryLogItemReason.
const ( const (
QueryLogItemReasonFilteredBlackList QueryLogItemReason = "FilteredBlackList" QueryLogItemReasonFilteredBlackList QueryLogItemReason = "FilteredBlackList"
@@ -306,7 +323,7 @@ type Client struct {
// changed. // changed.
// //
// This behaviour can be changed in the future versions. // This behaviour can be changed in the future versions.
UpstreamsCacheSize *int `json:"upstreams_cache_size,omitempty"` UpstreamsCacheSize *int `json:"upstreams_cache_size,omitempty"`
UseGlobalBlockedServices *bool `json:"use_global_blocked_services,omitempty"` UseGlobalBlockedServices *bool `json:"use_global_blocked_services,omitempty"`
UseGlobalSettings *bool `json:"use_global_settings,omitempty"` UseGlobalSettings *bool `json:"use_global_settings,omitempty"`
} }
@@ -389,6 +406,17 @@ type ClientsFindEntry map[string]ClientFindSubEntry
// ClientsFindResponse Client search results. // ClientsFindResponse Client search results.
type ClientsFindResponse = []ClientsFindEntry type ClientsFindResponse = []ClientsFindEntry
// ClientsSearchRequest Client search request
type ClientsSearchRequest struct {
Clients *[]ClientsSearchRequestItem `json:"clients,omitempty"`
}
// ClientsSearchRequestItem defines model for ClientsSearchRequestItem.
type ClientsSearchRequestItem struct {
// Id Client IP address, CIDR, MAC address, or ClientID
Id *string `json:"id,omitempty"`
}
// DNSConfig DNS server configuration // DNSConfig DNS server configuration
type DNSConfig struct { type DNSConfig struct {
// BlockedResponseTtl TTL for blocked responses. // BlockedResponseTtl TTL for blocked responses.
@@ -431,15 +459,23 @@ type DNSConfig struct {
ResolveClients *bool `json:"resolve_clients,omitempty"` ResolveClients *bool `json:"resolve_clients,omitempty"`
// UpstreamDns Upstream servers, port is optional after colon. Empty value will reset it to default values. // UpstreamDns Upstream servers, port is optional after colon. Empty value will reset it to default values.
UpstreamDns *[]string `json:"upstream_dns,omitempty"` UpstreamDns *[]string `json:"upstream_dns,omitempty"`
UpstreamDnsFile *string `json:"upstream_dns_file,omitempty"` UpstreamDnsFile *string `json:"upstream_dns_file,omitempty"`
UpstreamMode *interface{} `json:"upstream_mode,omitempty"`
UsePrivatePtrResolvers *bool `json:"use_private_ptr_resolvers,omitempty"` // UpstreamMode Upstream modes enumeration.
UpstreamMode *DNSConfigUpstreamMode `json:"upstream_mode,omitempty"`
// UpstreamTimeout The number of seconds to wait for a response from the upstream server
UpstreamTimeout *int `json:"upstream_timeout,omitempty"`
UsePrivatePtrResolvers *bool `json:"use_private_ptr_resolvers,omitempty"`
} }
// DNSConfigBlockingMode defines model for DNSConfig.BlockingMode. // DNSConfigBlockingMode defines model for DNSConfig.BlockingMode.
type DNSConfigBlockingMode string type DNSConfigBlockingMode string
// DNSConfigUpstreamMode Upstream modes enumeration.
type DNSConfigUpstreamMode string
// DayRange The single interval within a day. It begins at the `start` and ends before the `end`. // DayRange The single interval within a day. It begins at the `start` and ends before the `end`.
type DayRange struct { type DayRange struct {
// End The number of milliseconds elapsed from the start of a day. It is expected to be rounded to minutes. The maximum value is `86400000` (24 hours). // End The number of milliseconds elapsed from the start of a day. It is expected to be rounded to minutes. The maximum value is `86400000` (24 hours).
@@ -712,11 +748,18 @@ type Login struct {
// NetInterface Network interface info // NetInterface Network interface info
type NetInterface struct { type NetInterface struct {
// Flags Flags could be any combination of the following values, divided by the "|" character: "up", "broadcast", "loopback", "pointtopoint" and "multicast". // Flags Flags could be any combination of the following values, divided by the "|" character: "up", "broadcast", "loopback", "pointtopoint" and "multicast".
Flags string `json:"flags"` Flags string `json:"flags"`
HardwareAddress string `json:"hardware_address"`
IpAddresses *[]string `json:"ip_addresses,omitempty"` // GatewayIp The IP address of the gateway.
Mtu int `json:"mtu"` GatewayIp string `json:"gateway_ip"`
Name string `json:"name"` HardwareAddress string `json:"hardware_address"`
// Ipv4Addresses The addresses of the interface of v4 family.
Ipv4Addresses []string `json:"ipv4_addresses"`
// Ipv6Addresses The addresses of the interface of v6 family.
Ipv6Addresses []string `json:"ipv6_addresses"`
Name string `json:"name"`
} }
// NetInterfaces Network interfaces dictionary, keys are interface names. // NetInterfaces Network interfaces dictionary, keys are interface names.
@@ -778,8 +821,8 @@ type QueryLogItem struct {
ClientId *string `json:"client_id,omitempty"` ClientId *string `json:"client_id,omitempty"`
// ClientInfo Client information for a query log item. // ClientInfo Client information for a query log item.
ClientInfo *QueryLogItemClient `json:"client_info,omitempty"` ClientInfo *QueryLogItemClient `json:"client_info,omitempty"`
ClientProto *interface{} `json:"client_proto,omitempty"` ClientProto *QueryLogItemClientProto `json:"client_proto,omitempty"`
// Ecs The IP network defined by an EDNS Client-Subnet option in the request message if any. // Ecs The IP network defined by an EDNS Client-Subnet option in the request message if any.
Ecs *string `json:"ecs,omitempty"` Ecs *string `json:"ecs,omitempty"`
@@ -820,6 +863,9 @@ type QueryLogItem struct {
Upstream *string `json:"upstream,omitempty"` Upstream *string `json:"upstream,omitempty"`
} }
// QueryLogItemClientProto defines model for QueryLogItem.ClientProto.
type QueryLogItemClientProto string
// QueryLogItemReason Request filtering status. // QueryLogItemReason Request filtering status.
type QueryLogItemReason string type QueryLogItemReason string
@@ -891,6 +937,7 @@ type RewriteUpdate struct {
type SafeSearchConfig struct { type SafeSearchConfig struct {
Bing *bool `json:"bing,omitempty"` Bing *bool `json:"bing,omitempty"`
Duckduckgo *bool `json:"duckduckgo,omitempty"` Duckduckgo *bool `json:"duckduckgo,omitempty"`
Ecosia *bool `json:"ecosia,omitempty"`
Enabled *bool `json:"enabled,omitempty"` Enabled *bool `json:"enabled,omitempty"`
Google *bool `json:"google,omitempty"` Google *bool `json:"google,omitempty"`
Pixabay *bool `json:"pixabay,omitempty"` Pixabay *bool `json:"pixabay,omitempty"`
@@ -954,8 +1001,8 @@ type SetRulesRequest struct {
type Stats struct { type Stats struct {
// AvgProcessingTime Average time in seconds on processing a DNS request // AvgProcessingTime Average time in seconds on processing a DNS request
AvgProcessingTime *float32 `json:"avg_processing_time,omitempty"` AvgProcessingTime *float32 `json:"avg_processing_time,omitempty"`
BlockedFiltering *[]int `json:"blocked_filtering,omitempty"` BlockedFiltering *[]int `faker:"slice_len=24" json:"blocked_filtering,omitempty"`
DnsQueries *[]int `json:"dns_queries,omitempty"` DnsQueries *[]int `faker:"slice_len=24" json:"dns_queries,omitempty"`
// NumBlockedFiltering Number of requests blocked by filtering rules // NumBlockedFiltering Number of requests blocked by filtering rules
NumBlockedFiltering *int `json:"num_blocked_filtering,omitempty"` NumBlockedFiltering *int `json:"num_blocked_filtering,omitempty"`
@@ -971,8 +1018,8 @@ type Stats struct {
// NumReplacedSafesearch Number of requests blocked by safesearch module // NumReplacedSafesearch Number of requests blocked by safesearch module
NumReplacedSafesearch *int `json:"num_replaced_safesearch,omitempty"` NumReplacedSafesearch *int `json:"num_replaced_safesearch,omitempty"`
ReplacedParental *[]int `json:"replaced_parental,omitempty"` ReplacedParental *[]int `faker:"slice_len=24" json:"replaced_parental,omitempty"`
ReplacedSafebrowsing *[]int `json:"replaced_safebrowsing,omitempty"` ReplacedSafebrowsing *[]int `faker:"slice_len=24" json:"replaced_safebrowsing,omitempty"`
// TimeUnits Time units // TimeUnits Time units
TimeUnits *StatsTimeUnits `json:"time_units,omitempty"` TimeUnits *StatsTimeUnits `json:"time_units,omitempty"`
@@ -1046,6 +1093,9 @@ type TlsConfig struct {
// PrivateKeySaved Set to true if the user has previously saved a private key as a string. This is used so that the server and the client don't have to send the private key between each other every time, which might lead to security issues. // PrivateKeySaved Set to true if the user has previously saved a private key as a string. This is used so that the server and the client don't have to send the private key between each other every time, which might lead to security issues.
PrivateKeySaved *bool `json:"private_key_saved,omitempty"` PrivateKeySaved *bool `json:"private_key_saved,omitempty"`
// ServePlainDns Set to true if plain DNS is allowed for incoming requests.
ServePlainDns *bool `json:"serve_plain_dns,omitempty"`
// ServerName server_name is the hostname of your HTTPS/TLS server // ServerName server_name is the hostname of your HTTPS/TLS server
ServerName *string `json:"server_name,omitempty"` ServerName *string `json:"server_name,omitempty"`
@@ -1188,6 +1238,9 @@ type ClientsAddJSONRequestBody = Client
// ClientsDeleteJSONRequestBody defines body for ClientsDelete for application/json ContentType. // ClientsDeleteJSONRequestBody defines body for ClientsDelete for application/json ContentType.
type ClientsDeleteJSONRequestBody = ClientDelete type ClientsDeleteJSONRequestBody = ClientDelete
// ClientsSearchJSONRequestBody defines body for ClientsSearch for application/json ContentType.
type ClientsSearchJSONRequestBody = ClientsSearchRequest
// ClientsUpdateJSONRequestBody defines body for ClientsUpdate for application/json ContentType. // ClientsUpdateJSONRequestBody defines body for ClientsUpdate for application/json ContentType.
type ClientsUpdateJSONRequestBody = ClientUpdate type ClientsUpdateJSONRequestBody = ClientUpdate
@@ -1477,6 +1530,11 @@ type ClientInterface interface {
// ClientsFind request // ClientsFind request
ClientsFind(ctx context.Context, params *ClientsFindParams, reqEditors ...RequestEditorFn) (*http.Response, error) ClientsFind(ctx context.Context, params *ClientsFindParams, reqEditors ...RequestEditorFn) (*http.Response, error)
// ClientsSearchWithBody request with any body
ClientsSearchWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error)
ClientsSearch(ctx context.Context, body ClientsSearchJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error)
// ClientsUpdateWithBody request with any body // ClientsUpdateWithBody request with any body
ClientsUpdateWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) ClientsUpdateWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error)
@@ -1969,6 +2027,30 @@ func (c *AdguardHomeClient) ClientsFind(ctx context.Context, params *ClientsFind
return c.Client.Do(req) return c.Client.Do(req)
} }
func (c *AdguardHomeClient) ClientsSearchWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewClientsSearchRequestWithBody(c.Server, contentType, body)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
return nil, err
}
return c.Client.Do(req)
}
func (c *AdguardHomeClient) ClientsSearch(ctx context.Context, body ClientsSearchJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewClientsSearchRequest(c.Server, body)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
return nil, err
}
return c.Client.Do(req)
}
func (c *AdguardHomeClient) ClientsUpdateWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { func (c *AdguardHomeClient) ClientsUpdateWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewClientsUpdateRequestWithBody(c.Server, contentType, body) req, err := NewClientsUpdateRequestWithBody(c.Server, contentType, body)
if err != nil { if err != nil {
@@ -3657,6 +3739,46 @@ func NewClientsFindRequest(server string, params *ClientsFindParams) (*http.Requ
return req, nil return req, nil
} }
// NewClientsSearchRequest calls the generic ClientsSearch builder with application/json body
func NewClientsSearchRequest(server string, body ClientsSearchJSONRequestBody) (*http.Request, error) {
var bodyReader io.Reader
buf, err := json.Marshal(body)
if err != nil {
return nil, err
}
bodyReader = bytes.NewReader(buf)
return NewClientsSearchRequestWithBody(server, "application/json", bodyReader)
}
// NewClientsSearchRequestWithBody generates requests for ClientsSearch with any type of body
func NewClientsSearchRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) {
var err error
serverURL, err := url.Parse(server)
if err != nil {
return nil, err
}
operationPath := fmt.Sprintf("/clients/search")
if operationPath[0] == '/' {
operationPath = "." + operationPath
}
queryURL, err := serverURL.Parse(operationPath)
if err != nil {
return nil, err
}
req, err := http.NewRequest("POST", queryURL.String(), body)
if err != nil {
return nil, err
}
req.Header.Add("Content-Type", contentType)
return req, nil
}
// NewClientsUpdateRequest calls the generic ClientsUpdate builder with application/json body // NewClientsUpdateRequest calls the generic ClientsUpdate builder with application/json body
func NewClientsUpdateRequest(server string, body ClientsUpdateJSONRequestBody) (*http.Request, error) { func NewClientsUpdateRequest(server string, body ClientsUpdateJSONRequestBody) (*http.Request, error) {
var bodyReader io.Reader var bodyReader io.Reader
@@ -5967,6 +6089,11 @@ type ClientWithResponsesInterface interface {
// ClientsFindWithResponse request // ClientsFindWithResponse request
ClientsFindWithResponse(ctx context.Context, params *ClientsFindParams, reqEditors ...RequestEditorFn) (*ClientsFindResp, error) ClientsFindWithResponse(ctx context.Context, params *ClientsFindParams, reqEditors ...RequestEditorFn) (*ClientsFindResp, error)
// ClientsSearchWithBodyWithResponse request with any body
ClientsSearchWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ClientsSearchResp, error)
ClientsSearchWithResponse(ctx context.Context, body ClientsSearchJSONRequestBody, reqEditors ...RequestEditorFn) (*ClientsSearchResp, error)
// ClientsUpdateWithBodyWithResponse request with any body // ClientsUpdateWithBodyWithResponse request with any body
ClientsUpdateWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ClientsUpdateResp, error) ClientsUpdateWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ClientsUpdateResp, error)
@@ -6543,6 +6670,28 @@ func (r ClientsFindResp) StatusCode() int {
return 0 return 0
} }
type ClientsSearchResp struct {
Body []byte
HTTPResponse *http.Response
JSON200 *ClientsFindResponse
}
// Status returns HTTPResponse.Status
func (r ClientsSearchResp) Status() string {
if r.HTTPResponse != nil {
return r.HTTPResponse.Status
}
return http.StatusText(0)
}
// StatusCode returns HTTPResponse.StatusCode
func (r ClientsSearchResp) StatusCode() int {
if r.HTTPResponse != nil {
return r.HTTPResponse.StatusCode
}
return 0
}
type ClientsUpdateResp struct { type ClientsUpdateResp struct {
Body []byte Body []byte
HTTPResponse *http.Response HTTPResponse *http.Response
@@ -8082,6 +8231,23 @@ func (c *ClientWithResponses) ClientsFindWithResponse(ctx context.Context, param
return ParseClientsFindResp(rsp) return ParseClientsFindResp(rsp)
} }
// ClientsSearchWithBodyWithResponse request with arbitrary body returning *ClientsSearchResp
func (c *ClientWithResponses) ClientsSearchWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ClientsSearchResp, error) {
rsp, err := c.ClientsSearchWithBody(ctx, contentType, body, reqEditors...)
if err != nil {
return nil, err
}
return ParseClientsSearchResp(rsp)
}
func (c *ClientWithResponses) ClientsSearchWithResponse(ctx context.Context, body ClientsSearchJSONRequestBody, reqEditors ...RequestEditorFn) (*ClientsSearchResp, error) {
rsp, err := c.ClientsSearch(ctx, body, reqEditors...)
if err != nil {
return nil, err
}
return ParseClientsSearchResp(rsp)
}
// ClientsUpdateWithBodyWithResponse request with arbitrary body returning *ClientsUpdateResp // ClientsUpdateWithBodyWithResponse request with arbitrary body returning *ClientsUpdateResp
func (c *ClientWithResponses) ClientsUpdateWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ClientsUpdateResp, error) { func (c *ClientWithResponses) ClientsUpdateWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ClientsUpdateResp, error) {
rsp, err := c.ClientsUpdateWithBody(ctx, contentType, body, reqEditors...) rsp, err := c.ClientsUpdateWithBody(ctx, contentType, body, reqEditors...)
@@ -9227,6 +9393,32 @@ func ParseClientsFindResp(rsp *http.Response) (*ClientsFindResp, error) {
return response, nil return response, nil
} }
// ParseClientsSearchResp parses an HTTP response from a ClientsSearchWithResponse call
func ParseClientsSearchResp(rsp *http.Response) (*ClientsSearchResp, error) {
bodyBytes, err := io.ReadAll(rsp.Body)
defer func() { _ = rsp.Body.Close() }()
if err != nil {
return nil, err
}
response := &ClientsSearchResp{
Body: bodyBytes,
HTTPResponse: rsp,
}
switch {
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
var dest ClientsFindResponse
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}
response.JSON200 = &dest
}
return response, nil
}
// ParseClientsUpdateResp parses an HTTP response from a ClientsUpdateWithResponse call // ParseClientsUpdateResp parses an HTTP response from a ClientsUpdateWithResponse call
func ParseClientsUpdateResp(rsp *http.Response) (*ClientsUpdateResp, error) { func ParseClientsUpdateResp(rsp *http.Response) (*ClientsUpdateResp, error) {
bodyBytes, err := io.ReadAll(rsp.Body) bodyBytes, err := io.ReadAll(rsp.Body)

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/pkg/log"
"github.com/bakito/adguardhome-sync/pkg/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

@@ -4,12 +4,13 @@ import (
"encoding/json" "encoding/json"
"os" "os"
"github.com/bakito/adguardhome-sync/pkg/client/model"
"github.com/bakito/adguardhome-sync/pkg/types"
"github.com/bakito/adguardhome-sync/pkg/utils"
"github.com/google/uuid" "github.com/google/uuid"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/bakito/adguardhome-sync/pkg/client/model"
"github.com/bakito/adguardhome-sync/pkg/types"
"github.com/bakito/adguardhome-sync/pkg/utils"
) )
var _ = Describe("Types", func() { var _ = Describe("Types", func() {
@@ -115,12 +116,12 @@ var _ = Describe("Types", func() {
Context("QueryLogConfig", func() { Context("QueryLogConfig", func() {
Context("Equal", func() { Context("Equal", func() {
var ( var (
a *model.QueryLogConfig a *model.QueryLogConfigWithIgnored
b *model.QueryLogConfig b *model.QueryLogConfigWithIgnored
) )
BeforeEach(func() { BeforeEach(func() {
a = &model.QueryLogConfig{} a = &model.QueryLogConfigWithIgnored{}
b = &model.QueryLogConfig{} b = &model.QueryLogConfigWithIgnored{}
}) })
It("should be equal", func() { It("should be equal", func() {
a.Enabled = utils.Ptr(true) a.Enabled = utils.Ptr(true)
@@ -242,7 +243,10 @@ var _ = Describe("Types", func() {
}) })
It("should return 3 one replicas if urls are different", func() { It("should return 3 one replicas if urls are different", func() {
cfg.Replica = &types.AdGuardInstance{URL: url, APIPath: apiPath} cfg.Replica = &types.AdGuardInstance{URL: url, APIPath: apiPath}
cfg.Replicas = []types.AdGuardInstance{{URL: url + "1", APIPath: apiPath}, {URL: url, APIPath: apiPath + "1"}} cfg.Replicas = []types.AdGuardInstance{
{URL: url + "1", APIPath: apiPath},
{URL: url, APIPath: apiPath + "1"},
}
r := cfg.UniqueReplicas() r := cfg.UniqueReplicas()
Ω(r).Should(HaveLen(3)) Ω(r).Should(HaveLen(3))
}) })
@@ -303,6 +307,29 @@ var _ = Describe("Types", func() {
}) })
}) })
}) })
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("BlockedServices", func() {
Context("Equals", func() { Context("Equals", func() {
It("should be equal", func() { It("should be equal", func() {
@@ -403,7 +430,29 @@ var _ = Describe("Types", func() {
} }
Ω(dc1.HasConfig()).Should(BeFalse()) Ω(dc1.HasConfig()).Should(BeFalse())
}) })
It("should not have a v4 config", func() { 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{ dc1 := &model.DhcpStatus{
V4: &model.DhcpConfigV4{ V4: &model.DhcpConfigV4{
GatewayIp: utils.Ptr("1.2.3.4"), GatewayIp: utils.Ptr("1.2.3.4"),
@@ -416,15 +465,6 @@ var _ = Describe("Types", func() {
} }
Ω(dc1.HasConfig()).Should(BeTrue()) Ω(dc1.HasConfig()).Should(BeTrue())
}) })
It("should not have a v6 config", func() {
dc1 := &model.DhcpStatus{
V4: &model.DhcpConfigV4{},
V6: &model.DhcpConfigV6{
RangeStart: utils.Ptr("1.2.3.5"),
},
}
Ω(dc1.HasConfig()).Should(BeTrue())
})
}) })
}) })
}) })

View File

@@ -0,0 +1,175 @@
{
"$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"
}
},
"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"
}
},
"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"
}

View File

@@ -4,9 +4,10 @@ import (
"errors" "errors"
"regexp" "regexp"
"github.com/caarlos0/env/v11"
"github.com/bakito/adguardhome-sync/pkg/log" "github.com/bakito/adguardhome-sync/pkg/log"
"github.com/bakito/adguardhome-sync/pkg/types" "github.com/bakito/adguardhome-sync/pkg/types"
"github.com/caarlos0/env/v10"
) )
var ( var (
@@ -14,16 +15,39 @@ var (
logger = log.GetLogger("config") logger = log.GetLogger("config")
) )
func Get(configFile string, flags Flags) (*types.Config, error) { type AppConfig struct {
cfg *types.Config
filePath string
content string
}
func (ac *AppConfig) PrintConfigOnly() bool {
return ac.cfg.PrintConfigOnly
}
func (ac *AppConfig) Get() *types.Config {
return ac.cfg
}
func (ac *AppConfig) Init() error {
return ac.cfg.Init()
}
func Get(configFile string, flags Flags) (*AppConfig, error) {
path, err := configFilePath(configFile) path, err := configFilePath(configFile)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err := validateSchema(path); err != nil {
return nil, err
}
cfg := initialConfig() cfg := initialConfig()
// read yaml config // read yaml config
if err := readFile(cfg, path); err != nil { var content string
if content, err = readFile(cfg, path); err != nil {
return nil, err return nil, err
} }
@@ -32,17 +56,40 @@ func Get(configFile string, flags Flags) (*types.Config, error) {
return nil, err return nil, err
} }
// overwrite from env vars // *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 { if err := env.Parse(cfg); err != nil {
return nil, err return nil, err
} }
if err := env.ParseWithOptions(&cfg.Origin, env.Options{Prefix: "ORIGIN_"}); err != nil { if err := env.ParseWithOptions(origin, env.Options{Prefix: "ORIGIN_"}); err != nil {
return nil, err return nil, err
} }
if err := env.ParseWithOptions(cfg.Replica, env.Options{Prefix: "REPLICA_"}); err != nil { if err := env.ParseWithOptions(replica, env.Options{Prefix: "REPLICA_"}); err != nil {
return nil, err 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 && if cfg.Replica != nil &&
cfg.Replica.URL == "" && cfg.Replica.URL == "" &&
@@ -64,13 +111,13 @@ func Get(configFile string, flags Flags) (*types.Config, error) {
cfg.Replicas, err = enrichReplicasFromEnv(cfg.Replicas) cfg.Replicas, err = enrichReplicasFromEnv(cfg.Replicas)
return cfg, err return &AppConfig{cfg: cfg, filePath: path, content: content}, err
} }
func initialConfig() *types.Config { func initialConfig() *types.Config {
return &types.Config{ return &types.Config{
RunOnStart: true, RunOnStart: true,
Origin: types.AdGuardInstance{ Origin: &types.AdGuardInstance{
APIPath: "/control", APIPath: "/control",
}, },
Replica: &types.AdGuardInstance{ Replica: &types.AdGuardInstance{

View File

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

View File

@@ -3,24 +3,35 @@ package config_test
import ( import (
"os" "os"
"github.com/bakito/adguardhome-sync/pkg/config"
flagsmock "github.com/bakito/adguardhome-sync/pkg/mocks/flags"
gm "github.com/golang/mock/gomock"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
gm "go.uber.org/mock/gomock"
"github.com/bakito/adguardhome-sync/pkg/config"
flagsmock "github.com/bakito/adguardhome-sync/pkg/mocks/flags"
) )
var _ = Describe("Config", func() { var _ = Describe("Config", func() {
Context("Get", func() { Context("Get", func() {
var ( var (
flags *flagsmock.MockFlags flags *flagsmock.MockFlags
mockCtrl *gm.Controller mockCtrl *gm.Controller
changedEnvVars []string
setEnv = func(name, value string) {
_ = os.Setenv(name, value)
changedEnvVars = append(changedEnvVars, name)
}
) )
BeforeEach(func() { BeforeEach(func() {
mockCtrl = gm.NewController(GinkgoT()) mockCtrl = gm.NewController(GinkgoT())
flags = flagsmock.NewMockFlags(mockCtrl) flags = flagsmock.NewMockFlags(mockCtrl)
changedEnvVars = nil
}) })
AfterEach(func() { AfterEach(func() {
for _, envVar := range changedEnvVars {
_ = os.Unsetenv(envVar)
println(envVar)
}
defer mockCtrl.Finish() defer mockCtrl.Finish()
}) })
Context("Get", func() { Context("Get", func() {
@@ -33,13 +44,25 @@ var _ = Describe("Config", func() {
Ω(err.Error()).Should(ContainSubstring("mixed replica config in use")) Ω(err.Error()).Should(ContainSubstring("mixed replica config in use"))
}) })
}) })
Context("Env Var Clash", func() {
It("should not use USERNAME env variable if it is defined (#570)", func() {
incorrect := "ThisIsNotTheCorrectPassword"
setEnv("PASSWORD", 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.Password).ShouldNot(Equal(incorrect))
Ω(c.Get().Replicas[0].Password).ShouldNot(Equal(incorrect))
})
})
Context("Origin Url", func() { Context("Origin Url", func() {
It("should have the origin URL from the config file", func() { It("should have the origin URL from the config file", func() {
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes() flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags) cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Origin.URL).Should(Equal("https://origin-file:443")) Ω(cfg.Get().Origin.URL).Should(Equal("https://origin-file:443"))
}) })
It("should have the origin URL from the config flags", func() { It("should have the origin URL from the config flags", func() {
flags.EXPECT().Changed(config.FlagOriginURL).Return(true).AnyTimes() flags.EXPECT().Changed(config.FlagOriginURL).Return(true).AnyTimes()
@@ -48,20 +71,17 @@ var _ = Describe("Config", func() {
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags) cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Origin.URL).Should(Equal("https://origin-flag:443")) Ω(cfg.Get().Origin.URL).Should(Equal("https://origin-flag:443"))
}) })
It("should have the origin URL from the config env var", func() { It("should have the origin URL from the config env var", func() {
os.Setenv("ORIGIN_URL", "https://origin-env:443") setEnv("ORIGIN_URL", "https://origin-env:443")
defer func() {
_ = os.Unsetenv("ORIGIN_URL")
}()
flags.EXPECT().Changed(config.FlagOriginURL).Return(true).AnyTimes() flags.EXPECT().Changed(config.FlagOriginURL).Return(true).AnyTimes()
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes() flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
flags.EXPECT().GetString(config.FlagOriginURL).Return("https://origin-flag:443", nil).AnyTimes() flags.EXPECT().GetString(config.FlagOriginURL).Return("https://origin-flag:443", nil).AnyTimes()
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags) cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Origin.URL).Should(Equal("https://origin-env:443")) Ω(cfg.Get().Origin.URL).Should(Equal("https://origin-env:443"))
}) })
}) })
Context("Replica insecure skip verify", func() { Context("Replica insecure skip verify", func() {
@@ -70,7 +90,7 @@ var _ = Describe("Config", func() {
cfg, err := config.Get("../../testdata/config_test_replica.yaml", flags) cfg, err := config.Get("../../testdata/config_test_replica.yaml", flags)
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].InsecureSkipVerify).Should(BeFalse()) Ω(cfg.Get().Replicas[0].InsecureSkipVerify).Should(BeFalse())
}) })
It("should have the insecure skip verify from the config flags", func() { It("should have the insecure skip verify from the config flags", func() {
flags.EXPECT().Changed(config.FlagReplicaISV).Return(true).AnyTimes() flags.EXPECT().Changed(config.FlagReplicaISV).Return(true).AnyTimes()
@@ -79,20 +99,17 @@ var _ = Describe("Config", func() {
cfg, err := config.Get("../../testdata/config_test_replica.yaml", flags) cfg, err := config.Get("../../testdata/config_test_replica.yaml", flags)
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].InsecureSkipVerify).Should(BeTrue()) Ω(cfg.Get().Replicas[0].InsecureSkipVerify).Should(BeTrue())
}) })
It("should have the insecure skip verify from the config env var", func() { It("should have the insecure skip verify from the config env var", func() {
os.Setenv("REPLICA_INSECURE_SKIP_VERIFY", "false") setEnv("REPLICA_INSECURE_SKIP_VERIFY", "false")
defer func() {
_ = os.Unsetenv("REPLICA_INSECURE_SKIP_VERIFY")
}()
flags.EXPECT().Changed(config.FlagReplicaISV).Return(true).AnyTimes() flags.EXPECT().Changed(config.FlagReplicaISV).Return(true).AnyTimes()
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes() flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
flags.EXPECT().GetBool(config.FlagReplicaISV).Return(true, nil).AnyTimes() flags.EXPECT().GetBool(config.FlagReplicaISV).Return(true, nil).AnyTimes()
cfg, err := config.Get("../../testdata/config_test_replica.yaml", flags) cfg, err := config.Get("../../testdata/config_test_replica.yaml", flags)
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].InsecureSkipVerify).Should(BeFalse()) Ω(cfg.Get().Replicas[0].InsecureSkipVerify).Should(BeFalse())
}) })
}) })
@@ -102,18 +119,15 @@ var _ = Describe("Config", func() {
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags) cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].InsecureSkipVerify).Should(BeFalse()) Ω(cfg.Get().Replicas[0].InsecureSkipVerify).Should(BeFalse())
}) })
It("should have the insecure skip verify from the config env var", func() { It("should have the insecure skip verify from the config env var", func() {
os.Setenv("REPLICA1_INSECURE_SKIP_VERIFY", "true") setEnv("REPLICA1_INSECURE_SKIP_VERIFY", "true")
defer func() {
_ = os.Unsetenv("REPLICA1_INSECURE_SKIP_VERIFY")
}()
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes() flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags) cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].InsecureSkipVerify).Should(BeTrue()) Ω(cfg.Get().Replicas[0].InsecureSkipVerify).Should(BeTrue())
}) })
}) })
Context("API Port", func() { Context("API Port", func() {
@@ -121,72 +135,113 @@ var _ = Describe("Config", func() {
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes() flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags) cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.API.Port).Should(Equal(9090)) Ω(cfg.Get().API.Port).Should(Equal(9090))
}) })
It("should have the api port from the config flags", func() { It("should have the api port from the config flags", func() {
flags.EXPECT().Changed(config.FlagApiPort).Return(true).AnyTimes() flags.EXPECT().Changed(config.FlagAPIPort).Return(true).AnyTimes()
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes() flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
flags.EXPECT().GetInt(config.FlagApiPort).Return(9990, nil).AnyTimes() flags.EXPECT().GetInt(config.FlagAPIPort).Return(9990, nil).AnyTimes()
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags) cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.API.Port).Should(Equal(9990)) Ω(cfg.Get().API.Port).Should(Equal(9990))
}) })
It("should have the api port from the config env var", func() { It("should have the api port from the config env var", func() {
os.Setenv("API_PORT", "9999") setEnv("API_PORT", "9999")
defer func() { flags.EXPECT().Changed(config.FlagAPIPort).Return(true).AnyTimes()
_ = os.Unsetenv("API_PORT")
}()
flags.EXPECT().Changed(config.FlagApiPort).Return(true).AnyTimes()
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes() flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
flags.EXPECT().GetInt(config.FlagApiPort).Return(9990, nil).AnyTimes() flags.EXPECT().GetInt(config.FlagAPIPort).Return(9990, nil).AnyTimes()
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags) cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.API.Port).Should(Equal(9999)) Ω(cfg.Get().API.Port).Should(Equal(9999))
}) })
}) })
Context("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() { Context("Feature DNS Server Config", func() {
It("should have the feature dns server config from the config file", func() { It("should have the feature dns server config from the config file", func() {
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes() flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags) cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Features.DNS.ServerConfig).Should(BeFalse()) Ω(cfg.Get().Features.DNS.ServerConfig).Should(BeFalse())
}) })
It("should have the feature dns server config from the config flags", func() { It("should have the feature dns server config from the config flags", func() {
flags.EXPECT().Changed(config.FlagFeatureDnsServerConfig).Return(true).AnyTimes() flags.EXPECT().Changed(config.FlagFeatureDNSServerConfig).Return(true).AnyTimes()
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes() flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
flags.EXPECT().GetBool(config.FlagFeatureDnsServerConfig).Return(true, nil).AnyTimes() flags.EXPECT().GetBool(config.FlagFeatureDNSServerConfig).Return(true, nil).AnyTimes()
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags) cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Features.DNS.ServerConfig).Should(BeTrue()) Ω(cfg.Get().Features.DNS.ServerConfig).Should(BeTrue())
}) })
It("should have the feature dns server config from the config env var", func() { It("should have the feature dns server config from the config env var", func() {
os.Setenv("FEATURES_DNS_SERVER_CONFIG", "false") setEnv("FEATURES_DNS_SERVER_CONFIG", "false")
defer func() { flags.EXPECT().Changed(config.FlagFeatureDNSServerConfig).Return(true).AnyTimes()
_ = os.Unsetenv("FEATURES_DNS_SERVER_CONFIG")
}()
flags.EXPECT().Changed(config.FlagFeatureDnsServerConfig).Return(true).AnyTimes()
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes() flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
flags.EXPECT().GetBool(config.FlagFeatureDnsServerConfig).Return(true, nil).AnyTimes() flags.EXPECT().GetBool(config.FlagFeatureDNSServerConfig).Return(true, nil).AnyTimes()
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags) cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Features.DNS.ServerConfig).Should(BeFalse()) Ω(cfg.Get().Features.DNS.ServerConfig).Should(BeFalse())
}) })
It("should have the feature dns server config from the config DEPRECATED env var", func() { It("should have the feature dns server config from the config DEPRECATED env var", func() {
os.Setenv("FEATURES_DNS_SERVERCONFIG", "false") setEnv("FEATURES_DNS_SERVERCONFIG", "false")
defer func() { flags.EXPECT().Changed(config.FlagFeatureDNSServerConfig).Return(true).AnyTimes()
_ = os.Unsetenv("FEATURES_DNS_SERVERCONFIG")
}()
flags.EXPECT().Changed(config.FlagFeatureDnsServerConfig).Return(true).AnyTimes()
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes() flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
flags.EXPECT().GetBool(config.FlagFeatureDnsServerConfig).Return(true, nil).AnyTimes() flags.EXPECT().GetBool(config.FlagFeatureDNSServerConfig).Return(true, nil).AnyTimes()
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags) cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Features.DNS.ServerConfig).Should(BeFalse()) Ω(cfg.Get().Features.DNS.ServerConfig).Should(BeFalse())
}) })
}) })
}) })

View File

@@ -4,10 +4,10 @@ import (
"fmt" "fmt"
"os" "os"
"github.com/bakito/adguardhome-sync/pkg/config"
"github.com/bakito/adguardhome-sync/pkg/types"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/bakito/adguardhome-sync/pkg/config"
) )
var envVars = []string{ var envVars = []string{
@@ -17,6 +17,7 @@ var envVars = []string{
"FEATURES_CLIENT_SETTINGS", "FEATURES_CLIENT_SETTINGS",
"FEATURES_SERVICES", "FEATURES_SERVICES",
"FEATURES_FILTERS", "FEATURES_FILTERS",
"FEATURES_THEME",
"FEATURES_DHCP_SERVER_CONFIG", "FEATURES_DHCP_SERVER_CONFIG",
"FEATURES_DHCP_STATIC_LEASES", "FEATURES_DHCP_STATIC_LEASES",
"FEATURES_DNS_SERVER_CONFIG", "FEATURES_DNS_SERVER_CONFIG",
@@ -98,7 +99,7 @@ var _ = Describe("Config", func() {
Ω(os.Setenv(fmt.Sprintf("REPLICA%s_INTERFACE_NAME", "1"), "eth0")).ShouldNot(HaveOccurred()) Ω(os.Setenv(fmt.Sprintf("REPLICA%s_INTERFACE_NAME", "1"), "eth0")).ShouldNot(HaveOccurred())
cfg, err := config.Get("", nil) cfg, err := config.Get("", nil)
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].InterfaceName).Should(Equal("eth0")) Ω(cfg.Get().Replicas[0].InterfaceName).Should(Equal("eth0"))
}) })
}) })
Context("dhcp server", func() { Context("dhcp server", func() {
@@ -107,32 +108,32 @@ var _ = Describe("Config", func() {
Ω(os.Setenv(fmt.Sprintf("REPLICA%s_DHCPSERVERENABLED", "1"), "true")).ShouldNot(HaveOccurred()) Ω(os.Setenv(fmt.Sprintf("REPLICA%s_DHCPSERVERENABLED", "1"), "true")).ShouldNot(HaveOccurred())
cfg, err := config.Get("", nil) cfg, err := config.Get("", nil)
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].DHCPServerEnabled).ShouldNot(BeNil()) Ω(cfg.Get().Replicas[0].DHCPServerEnabled).ShouldNot(BeNil())
Ω(*cfg.Replicas[0].DHCPServerEnabled).Should(BeTrue()) Ω(*cfg.Get().Replicas[0].DHCPServerEnabled).Should(BeTrue())
}) })
It("should disable the dhcp server of replica 1", func() { It("should disable the dhcp server of replica 1", func() {
Ω(os.Setenv("REPLICA1_URL", "https://foo.bar")).ShouldNot(HaveOccurred()) Ω(os.Setenv("REPLICA1_URL", "https://foo.bar")).ShouldNot(HaveOccurred())
Ω(os.Setenv(fmt.Sprintf("REPLICA%s_DHCPSERVERENABLED", "1"), "false")).ShouldNot(HaveOccurred()) Ω(os.Setenv(fmt.Sprintf("REPLICA%s_DHCPSERVERENABLED", "1"), "false")).ShouldNot(HaveOccurred())
cfg, err := config.Get("", nil) cfg, err := config.Get("", nil)
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].DHCPServerEnabled).ShouldNot(BeNil()) Ω(cfg.Get().Replicas[0].DHCPServerEnabled).ShouldNot(BeNil())
Ω(*cfg.Replicas[0].DHCPServerEnabled).Should(BeFalse()) Ω(*cfg.Get().Replicas[0].DHCPServerEnabled).Should(BeFalse())
}) })
}) })
}) })
}) })
}) })
func verifyFeatures(cfg *types.Config, value bool) { func verifyFeatures(cfg *config.AppConfig, value bool) {
Ω(cfg.Features.GeneralSettings).Should(Equal(value)) Ω(cfg.Get().Features.GeneralSettings).Should(Equal(value))
Ω(cfg.Features.QueryLogConfig).Should(Equal(value)) Ω(cfg.Get().Features.QueryLogConfig).Should(Equal(value))
Ω(cfg.Features.StatsConfig).Should(Equal(value)) Ω(cfg.Get().Features.StatsConfig).Should(Equal(value))
Ω(cfg.Features.ClientSettings).Should(Equal(value)) Ω(cfg.Get().Features.ClientSettings).Should(Equal(value))
Ω(cfg.Features.Services).Should(Equal(value)) Ω(cfg.Get().Features.Services).Should(Equal(value))
Ω(cfg.Features.Filters).Should(Equal(value)) Ω(cfg.Get().Features.Filters).Should(Equal(value))
Ω(cfg.Features.DHCP.ServerConfig).Should(Equal(value)) Ω(cfg.Get().Features.DHCP.ServerConfig).Should(Equal(value))
Ω(cfg.Features.DHCP.StaticLeases).Should(Equal(value)) Ω(cfg.Get().Features.DHCP.StaticLeases).Should(Equal(value))
Ω(cfg.Features.DNS.ServerConfig).Should(Equal(value)) Ω(cfg.Get().Features.DNS.ServerConfig).Should(Equal(value))
Ω(cfg.Features.DNS.AccessLists).Should(Equal(value)) Ω(cfg.Get().Features.DNS.AccessLists).Should(Equal(value))
Ω(cfg.Features.DNS.Rewrites).Should(Equal(value)) Ω(cfg.Get().Features.DNS.Rewrites).Should(Equal(value))
} }

View File

@@ -6,9 +6,10 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/caarlos0/env/v11"
"github.com/bakito/adguardhome-sync/pkg/types" "github.com/bakito/adguardhome-sync/pkg/types"
"github.com/bakito/adguardhome-sync/pkg/utils" "github.com/bakito/adguardhome-sync/pkg/utils"
"github.com/caarlos0/env/v10"
) )
func handleDeprecatedEnvVars(cfg *types.Config) { func handleDeprecatedEnvVars(cfg *types.Config) {
@@ -61,20 +62,20 @@ func handleDeprecatedEnvVars(cfg *types.Config) {
} }
} }
func checkDeprecatedEnvVar(oldName string, newName string) (string, bool) { func checkDeprecatedEnvVar(oldName, newName string) (string, bool) {
old, oldOK := os.LookupEnv(oldName) old, oldOK := os.LookupEnv(oldName)
if oldOK { if oldOK {
logger.With("deprecated", oldName, "replacement", newName). logger.With("deprecated", oldName, "replacement", newName).
Warn("Deprecated env variable is used, please use the correct one") Warn("Deprecated env variable is used, please use the correct one")
} }
new, newOK := os.LookupEnv(newName) newVal, newOK := os.LookupEnv(newName)
if newOK { if newOK {
return new, true return newVal, true
} }
return old, oldOK return old, oldOK
} }
func checkDeprecatedReplicaEnvVar(oldPattern string, newPattern string, replicaID int) (string, bool) { func checkDeprecatedReplicaEnvVar(oldPattern, newPattern string, replicaID int) (string, bool) {
return checkDeprecatedEnvVar(fmt.Sprintf(oldPattern, replicaID), fmt.Sprintf(newPattern, replicaID)) return checkDeprecatedEnvVar(fmt.Sprintf(oldPattern, replicaID), fmt.Sprintf(newPattern, replicaID))
} }
@@ -107,9 +108,15 @@ func enrichReplicasFromEnv(initialReplicas []types.AdGuardInstance) ([]types.AdG
for i := range replicas { for i := range replicas {
reID := i + 1 reID := i + 1
// keep previously set value
replicaDhcpServer := replicas[i].DHCPServerEnabled
replicas[i].DHCPServerEnabled = nil
if err := env.ParseWithOptions(&replicas[i], env.Options{Prefix: fmt.Sprintf("REPLICA%d_", reID)}); err != nil { if err := env.ParseWithOptions(&replicas[i], env.Options{Prefix: fmt.Sprintf("REPLICA%d_", reID)}); err != nil {
return nil, err return nil, err
} }
if replicas[i].DHCPServerEnabled == nil {
replicas[i].DHCPServerEnabled = replicaDhcpServer
}
if val, ok := checkDeprecatedReplicaEnvVar("REPLICA%d_APIPATH", "REPLICA%d_API_PATH", reID); ok { if val, ok := checkDeprecatedReplicaEnvVar("REPLICA%d_APIPATH", "REPLICA%d_API_PATH", reID); ok {
replicas[i].APIPath = val replicas[i].APIPath = val
} }

View File

@@ -4,21 +4,24 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"github.com/bakito/adguardhome-sync/pkg/types"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"github.com/bakito/adguardhome-sync/pkg/types"
) )
func readFile(cfg *types.Config, path string) error { func readFile(cfg *types.Config, path string) (string, error) {
var content string
if _, err := os.Stat(path); err == nil { if _, err := os.Stat(path); err == nil {
b, err := os.ReadFile(path) b, err := os.ReadFile(path)
if err != nil { if err != nil {
return err return "", err
} }
content = string(b)
if err := yaml.Unmarshal(b, cfg); err != nil { if err := yaml.Unmarshal(b, cfg); err != nil {
return err return "", err
} }
} }
return nil return content, nil
} }
func configFilePath(configFile string) (string, error) { func configFilePath(configFile string) (string, error) {
@@ -28,7 +31,7 @@ func configFilePath(configFile string) (string, error) {
if err != nil { if err != nil {
return "", err return "", err
} }
return filepath.Join(home, ".adguardhome-sync"), nil return filepath.Join(home, ".adguardhome-sync.yaml"), nil
} }
return configFile, nil return configFile, nil
} }

View File

@@ -10,9 +10,6 @@ import (
) )
var _ = Describe("Config", func() { var _ = Describe("Config", func() {
var ()
BeforeEach(func() {
})
Context("configFilePath", func() { Context("configFilePath", func() {
It("should return the same value", func() { It("should return the same value", func() {
path := uuid.NewString() path := uuid.NewString()
@@ -22,11 +19,12 @@ var _ = Describe("Config", func() {
Ω(result).Should(Equal(path)) Ω(result).Should(Equal(path))
}) })
It("should the file in HOME dir", func() { It("should the file in HOME dir", func() {
home := os.Getenv("HOME") home, err := os.UserHomeDir()
Ω(err).ShouldNot(HaveOccurred())
result, err := configFilePath("") result, err := configFilePath("")
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
Ω(result).Should(Equal(filepath.Join(home, "/.adguardhome-sync"))) Ω(result).Should(Equal(filepath.Join(home, ".adguardhome-sync.yaml")))
}) })
}) })
}) })

View File

@@ -6,16 +6,16 @@ const (
FlagPrintConfigOnly = "printConfigOnly" FlagPrintConfigOnly = "printConfigOnly"
FlagContinueOnError = "continueOnError" FlagContinueOnError = "continueOnError"
FlagApiPort = "api-port" FlagAPIPort = "api-port"
FlagApiUsername = "api-username" FlagAPIUsername = "api-username"
FlagApiPassword = "api-password" FlagAPIPassword = "api-password"
FlagApiDarkMode = "api-dark-mode" FlagAPIDarkMode = "api-dark-mode"
FlagFeatureDhcpServerConfig = "feature-dhcp-server-config" FlagFeatureDhcpServerConfig = "feature-dhcp-server-config"
FlagFeatureDhcpStaticLeases = "feature-dhcp-static-leases" FlagFeatureDhcpStaticLeases = "feature-dhcp-static-leases"
FlagFeatureDnsServerConfig = "feature-dns-server-config" FlagFeatureDNSServerConfig = "feature-dns-server-config"
FlagFeatureDnsAccessLists = "feature-dns-access-lists" FlagFeatureDNSAccessLists = "feature-dns-access-lists"
FlagFeatureDnsRewrites = "feature-dns-rewrites" FlagFeatureDNSRewrites = "feature-dns-rewrites"
FlagFeatureGeneral = "feature-general-settings" FlagFeatureGeneral = "feature-general-settings"
FlagFeatureQueryLog = "feature-query-log-config" FlagFeatureQueryLog = "feature-query-log-config"
FlagFeatureStats = "feature-stats-config" FlagFeatureStats = "feature-stats-config"
@@ -25,7 +25,7 @@ const (
FlagOriginURL = "origin-url" FlagOriginURL = "origin-url"
FlagOriginWebURL = "origin-web-url" FlagOriginWebURL = "origin-web-url"
FlagOriginApiPath = "origin-api-path" FlagOriginAPIPath = "origin-api-path"
FlagOriginUsername = "origin-username" FlagOriginUsername = "origin-username"
FlagOriginPassword = "origin-password" FlagOriginPassword = "origin-password"
@@ -34,7 +34,7 @@ const (
FlagReplicaURL = "replica-url" FlagReplicaURL = "replica-url"
FlagReplicaWebURL = "replica-web-url" FlagReplicaWebURL = "replica-web-url"
FlagReplicaApiPath = "replica-api-path" FlagReplicaAPIPath = "replica-api-path"
FlagReplicaUsername = "replica-username" FlagReplicaUsername = "replica-username"
FlagReplicaPassword = "replica-password" FlagReplicaPassword = "replica-password"
FlagReplicaCookie = "replica-cookie" FlagReplicaCookie = "replica-cookie"

View File

@@ -18,7 +18,7 @@ func readFlags(cfg *types.Config, flags Flags) error {
return err return err
} }
if err := fr.readApiFlags(); err != nil { if err := fr.readAPIFlags(); err != nil {
return err return err
} }
@@ -30,11 +30,7 @@ func readFlags(cfg *types.Config, flags Flags) error {
return err return err
} }
if err := fr.readReplicaFlags(); err != nil { return fr.readReplicaFlags()
return err
}
return nil
} }
type flagReader struct { type flagReader struct {
@@ -53,7 +49,7 @@ func (fr *flagReader) readReplicaFlags() error {
}); err != nil { }); err != nil {
return err return err
} }
if err := fr.setStringFlag(FlagReplicaApiPath, func(cgf *types.Config, value string) { if err := fr.setStringFlag(FlagReplicaAPIPath, func(cgf *types.Config, value string) {
fr.cfg.Replica.APIPath = value fr.cfg.Replica.APIPath = value
}); err != nil { }); err != nil {
return err return err
@@ -83,12 +79,9 @@ func (fr *flagReader) readReplicaFlags() error {
}); err != nil { }); err != nil {
return err return err
} }
if err := fr.setStringFlag(FlagReplicaInterfaceName, func(cgf *types.Config, value string) { return fr.setStringFlag(FlagReplicaInterfaceName, func(cgf *types.Config, value string) {
fr.cfg.Replica.InterfaceName = value fr.cfg.Replica.InterfaceName = value
}); err != nil { })
return err
}
return nil
} }
func (fr *flagReader) readOriginFlags() error { func (fr *flagReader) readOriginFlags() error {
@@ -102,7 +95,7 @@ func (fr *flagReader) readOriginFlags() error {
}); err != nil { }); err != nil {
return err return err
} }
if err := fr.setStringFlag(FlagOriginApiPath, func(cgf *types.Config, value string) { if err := fr.setStringFlag(FlagOriginAPIPath, func(cgf *types.Config, value string) {
fr.cfg.Origin.APIPath = value fr.cfg.Origin.APIPath = value
}); err != nil { }); err != nil {
return err return err
@@ -122,12 +115,9 @@ func (fr *flagReader) readOriginFlags() error {
}); err != nil { }); err != nil {
return err return err
} }
if err := fr.setBoolFlag(FlagOriginISV, func(cgf *types.Config, value bool) { return fr.setBoolFlag(FlagOriginISV, func(cgf *types.Config, value bool) {
fr.cfg.Origin.InsecureSkipVerify = value fr.cfg.Origin.InsecureSkipVerify = value
}); err != nil { })
return err
}
return nil
} }
func (fr *flagReader) readFeatureFlags() error { func (fr *flagReader) readFeatureFlags() error {
@@ -142,17 +132,17 @@ func (fr *flagReader) readFeatureFlags() error {
return err return err
} }
if err := fr.setBoolFlag(FlagFeatureDnsServerConfig, func(cgf *types.Config, value bool) { if err := fr.setBoolFlag(FlagFeatureDNSServerConfig, func(cgf *types.Config, value bool) {
fr.cfg.Features.DNS.ServerConfig = value fr.cfg.Features.DNS.ServerConfig = value
}); err != nil { }); err != nil {
return err return err
} }
if err := fr.setBoolFlag(FlagFeatureDnsAccessLists, func(cgf *types.Config, value bool) { if err := fr.setBoolFlag(FlagFeatureDNSAccessLists, func(cgf *types.Config, value bool) {
fr.cfg.Features.DNS.AccessLists = value fr.cfg.Features.DNS.AccessLists = value
}); err != nil { }); err != nil {
return err return err
} }
if err := fr.setBoolFlag(FlagFeatureDnsRewrites, func(cgf *types.Config, value bool) { if err := fr.setBoolFlag(FlagFeatureDNSRewrites, func(cgf *types.Config, value bool) {
fr.cfg.Features.DNS.Rewrites = value fr.cfg.Features.DNS.Rewrites = value
}); err != nil { }); err != nil {
return err return err
@@ -183,61 +173,51 @@ func (fr *flagReader) readFeatureFlags() error {
}); err != nil { }); err != nil {
return err return err
} }
if err := fr.setBoolFlag(FlagFeatureFilters, func(cgf *types.Config, value bool) { return fr.setBoolFlag(FlagFeatureFilters, func(cgf *types.Config, value bool) {
fr.cfg.Features.Filters = value fr.cfg.Features.Filters = value
})
}
func (fr *flagReader) readAPIFlags() error {
if err := fr.setIntFlag(FlagAPIPort, func(cgf *types.Config, value int) {
fr.cfg.API.Port = value
}); err != nil { }); err != nil {
return err return err
} }
if err := fr.setStringFlag(FlagAPIUsername, func(cgf *types.Config, value string) {
return nil
}
func (fr *flagReader) readApiFlags() (err error) {
if err = fr.setIntFlag(FlagApiPort, func(cgf *types.Config, value int) {
fr.cfg.API.Port = value
}); err != nil {
return
}
if err = fr.setStringFlag(FlagApiUsername, func(cgf *types.Config, value string) {
fr.cfg.API.Username = value fr.cfg.API.Username = value
}); err != nil { }); err != nil {
return return err
} }
if err = fr.setStringFlag(FlagApiPassword, func(cgf *types.Config, value string) { if err := fr.setStringFlag(FlagAPIPassword, func(cgf *types.Config, value string) {
fr.cfg.API.Password = value fr.cfg.API.Password = value
}); err != nil { }); err != nil {
return return err
} }
if err = fr.setBoolFlag(FlagApiDarkMode, func(cgf *types.Config, value bool) { return fr.setBoolFlag(FlagAPIDarkMode, func(cgf *types.Config, value bool) {
fr.cfg.API.DarkMode = value fr.cfg.API.DarkMode = value
}); err != nil { })
return
}
return
} }
func (fr *flagReader) readRootFlags() (err error) { func (fr *flagReader) readRootFlags() error {
if err = fr.setStringFlag(FlagCron, func(cgf *types.Config, value string) { if err := fr.setStringFlag(FlagCron, func(cgf *types.Config, value string) {
fr.cfg.Cron = value fr.cfg.Cron = value
}); err != nil { }); err != nil {
return return err
} }
if err = fr.setBoolFlag(FlagRunOnStart, func(cgf *types.Config, value bool) { if err := fr.setBoolFlag(FlagRunOnStart, func(cgf *types.Config, value bool) {
fr.cfg.RunOnStart = value fr.cfg.RunOnStart = value
}); err != nil { }); err != nil {
return return err
} }
if err = fr.setBoolFlag(FlagPrintConfigOnly, func(cgf *types.Config, value bool) { if err := fr.setBoolFlag(FlagPrintConfigOnly, func(cgf *types.Config, value bool) {
fr.cfg.PrintConfigOnly = value fr.cfg.PrintConfigOnly = value
}); err != nil { }); err != nil {
return return err
} }
if err = fr.setBoolFlag(FlagContinueOnError, func(cgf *types.Config, value bool) { return fr.setBoolFlag(FlagContinueOnError, func(cgf *types.Config, value bool) {
fr.cfg.ContinueOnError = value fr.cfg.ContinueOnError = value
}); err != nil { })
return
}
return
} }
type Flags interface { type Flags interface {
@@ -249,33 +229,33 @@ type Flags interface {
func (fr *flagReader) setStringFlag(name string, cb callback[string]) (err error) { func (fr *flagReader) setStringFlag(name string, cb callback[string]) (err error) {
if fr.flags.Changed(name) { if fr.flags.Changed(name) {
if value, err := fr.flags.GetString(name); err != nil { var value string
if value, err = fr.flags.GetString(name); err != nil {
return err return err
} else {
cb(fr.cfg, value)
} }
cb(fr.cfg, value)
} }
return nil return nil
} }
func (fr *flagReader) setBoolFlag(name string, cb callback[bool]) (err error) { func (fr *flagReader) setBoolFlag(name string, cb callback[bool]) (err error) {
if fr.flags.Changed(name) { if fr.flags.Changed(name) {
if value, err := fr.flags.GetBool(name); err != nil { var value bool
if value, err = fr.flags.GetBool(name); err != nil {
return err return err
} else {
cb(fr.cfg, value)
} }
cb(fr.cfg, value)
} }
return nil return nil
} }
func (fr *flagReader) setIntFlag(name string, cb callback[int]) (err error) { func (fr *flagReader) setIntFlag(name string, cb callback[int]) (err error) {
if fr.flags.Changed(name) { if fr.flags.Changed(name) {
if value, err := fr.flags.GetInt(name); err != nil { var value int
if value, err = fr.flags.GetInt(name); err != nil {
return err return err
} else {
cb(fr.cfg, value)
} }
cb(fr.cfg, value)
} }
return nil return nil
} }

View File

@@ -3,11 +3,12 @@ package config
import ( import (
"strings" "strings"
flagsmock "github.com/bakito/adguardhome-sync/pkg/mocks/flags"
"github.com/bakito/adguardhome-sync/pkg/types"
gm "github.com/golang/mock/gomock"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
gm "go.uber.org/mock/gomock"
flagsmock "github.com/bakito/adguardhome-sync/pkg/mocks/flags"
"github.com/bakito/adguardhome-sync/pkg/types"
) )
var _ = Describe("Config", func() { var _ = Describe("Config", func() {
@@ -18,6 +19,7 @@ var _ = Describe("Config", func() {
) )
BeforeEach(func() { BeforeEach(func() {
cfg = &types.Config{ cfg = &types.Config{
Origin: &types.AdGuardInstance{},
Replica: &types.AdGuardInstance{}, Replica: &types.AdGuardInstance{},
Features: types.Features{ Features: types.Features{
DNS: types.DNS{ DNS: types.DNS{
@@ -88,7 +90,7 @@ var _ = Describe("Config", func() {
})) }))
}) })
}) })
Context("readApiFlags", func() { Context("readAPIFlags", func() {
It("should change all values", func() { It("should change all values", func() {
cfg.API = types.API{ cfg.API = types.API{
Port: 1111, Port: 1111,
@@ -99,10 +101,10 @@ var _ = Describe("Config", func() {
flags.EXPECT().Changed(gm.Any()).DoAndReturn(func(name string) bool { flags.EXPECT().Changed(gm.Any()).DoAndReturn(func(name string) bool {
return strings.HasPrefix(name, "api") return strings.HasPrefix(name, "api")
}).AnyTimes() }).AnyTimes()
flags.EXPECT().GetInt(FlagApiPort).Return(9999, nil) flags.EXPECT().GetInt(FlagAPIPort).Return(9999, nil)
flags.EXPECT().GetString(FlagApiUsername).Return("aaaa", nil) flags.EXPECT().GetString(FlagAPIUsername).Return("aaaa", nil)
flags.EXPECT().GetString(FlagApiPassword).Return("bbbb", nil) flags.EXPECT().GetString(FlagAPIPassword).Return("bbbb", nil)
flags.EXPECT().GetBool(FlagApiDarkMode).Return(true, nil) flags.EXPECT().GetBool(FlagAPIDarkMode).Return(true, nil)
err := readFlags(cfg, flags) err := readFlags(cfg, flags)
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
@@ -142,7 +144,7 @@ var _ = Describe("Config", func() {
}) })
Context("readOriginFlags", func() { Context("readOriginFlags", func() {
It("should change all values", func() { It("should change all values", func() {
cfg.Origin = types.AdGuardInstance{ cfg.Origin = &types.AdGuardInstance{
URL: "1", URL: "1",
WebURL: "2", WebURL: "2",
APIPath: "3", APIPath: "3",
@@ -154,7 +156,7 @@ var _ = Describe("Config", func() {
flags.EXPECT().Changed(FlagOriginURL).Return(true) flags.EXPECT().Changed(FlagOriginURL).Return(true)
flags.EXPECT().Changed(FlagOriginWebURL).Return(true) flags.EXPECT().Changed(FlagOriginWebURL).Return(true)
flags.EXPECT().Changed(FlagOriginApiPath).Return(true) flags.EXPECT().Changed(FlagOriginAPIPath).Return(true)
flags.EXPECT().Changed(FlagOriginUsername).Return(true) flags.EXPECT().Changed(FlagOriginUsername).Return(true)
flags.EXPECT().Changed(FlagOriginPassword).Return(true) flags.EXPECT().Changed(FlagOriginPassword).Return(true)
flags.EXPECT().Changed(FlagOriginCookie).Return(true) flags.EXPECT().Changed(FlagOriginCookie).Return(true)
@@ -163,7 +165,7 @@ var _ = Describe("Config", func() {
flags.EXPECT().GetString(FlagOriginURL).Return("a", nil) flags.EXPECT().GetString(FlagOriginURL).Return("a", nil)
flags.EXPECT().GetString(FlagOriginWebURL).Return("b", nil) flags.EXPECT().GetString(FlagOriginWebURL).Return("b", nil)
flags.EXPECT().GetString(FlagOriginApiPath).Return("c", nil) flags.EXPECT().GetString(FlagOriginAPIPath).Return("c", nil)
flags.EXPECT().GetString(FlagOriginUsername).Return("d", nil) flags.EXPECT().GetString(FlagOriginUsername).Return("d", nil)
flags.EXPECT().GetString(FlagOriginPassword).Return("e", nil) flags.EXPECT().GetString(FlagOriginPassword).Return("e", nil)
flags.EXPECT().GetString(FlagOriginCookie).Return("f", nil) flags.EXPECT().GetString(FlagOriginCookie).Return("f", nil)
@@ -171,7 +173,7 @@ var _ = Describe("Config", func() {
err := readFlags(cfg, flags) err := readFlags(cfg, flags)
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Origin).Should(Equal(types.AdGuardInstance{ Ω(cfg.Origin).Should(Equal(&types.AdGuardInstance{
URL: "a", URL: "a",
WebURL: "b", WebURL: "b",
APIPath: "c", APIPath: "c",
@@ -198,7 +200,7 @@ var _ = Describe("Config", func() {
flags.EXPECT().Changed(FlagReplicaURL).Return(true) flags.EXPECT().Changed(FlagReplicaURL).Return(true)
flags.EXPECT().Changed(FlagReplicaWebURL).Return(true) flags.EXPECT().Changed(FlagReplicaWebURL).Return(true)
flags.EXPECT().Changed(FlagReplicaApiPath).Return(true) flags.EXPECT().Changed(FlagReplicaAPIPath).Return(true)
flags.EXPECT().Changed(FlagReplicaUsername).Return(true) flags.EXPECT().Changed(FlagReplicaUsername).Return(true)
flags.EXPECT().Changed(FlagReplicaPassword).Return(true) flags.EXPECT().Changed(FlagReplicaPassword).Return(true)
flags.EXPECT().Changed(FlagReplicaCookie).Return(true) flags.EXPECT().Changed(FlagReplicaCookie).Return(true)
@@ -209,7 +211,7 @@ var _ = Describe("Config", func() {
flags.EXPECT().GetString(FlagReplicaURL).Return("a", nil) flags.EXPECT().GetString(FlagReplicaURL).Return("a", nil)
flags.EXPECT().GetString(FlagReplicaWebURL).Return("b", nil) flags.EXPECT().GetString(FlagReplicaWebURL).Return("b", nil)
flags.EXPECT().GetString(FlagReplicaApiPath).Return("c", nil) flags.EXPECT().GetString(FlagReplicaAPIPath).Return("c", nil)
flags.EXPECT().GetString(FlagReplicaUsername).Return("d", nil) flags.EXPECT().GetString(FlagReplicaUsername).Return("d", nil)
flags.EXPECT().GetString(FlagReplicaPassword).Return("e", nil) flags.EXPECT().GetString(FlagReplicaPassword).Return("e", nil)
flags.EXPECT().GetString(FlagReplicaCookie).Return("f", nil) flags.EXPECT().GetString(FlagReplicaCookie).Return("f", nil)

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/pkg/client"
"github.com/bakito/adguardhome-sync/pkg/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/pkg/test/matchers"
"github.com/bakito/adguardhome-sync/pkg/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)
}

59
pkg/config/validate.go Normal file
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/pkg/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)

14
pkg/metrics/handler.go Normal file
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
pkg/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/pkg/client/model"
"github.com/bakito/adguardhome-sync/pkg/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")
}

102
pkg/metrics/metrics_test.go Normal file
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/pkg/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
pkg/metrics/stats.go Normal file
View File

@@ -0,0 +1,130 @@
package metrics
import (
"slices"
"strings"
"github.com/bakito/adguardhome-sync/pkg/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

@@ -1,5 +1,10 @@
// Code generated by MockGen. DO NOT EDIT. // Code generated by MockGen. DO NOT EDIT.
// Source: github.com/bakito/adguardhome-sync/pkg/client (interfaces: Client) // Source: github.com/bakito/adguardhome-sync/pkg/client (interfaces: Client)
//
// Generated by this command:
//
// mockgen -package client -destination pkg/mocks/client/mock.go github.com/bakito/adguardhome-sync/pkg/client Client
//
// Package client is a generated GoMock package. // Package client is a generated GoMock package.
package client package client
@@ -8,13 +13,14 @@ import (
reflect "reflect" reflect "reflect"
model "github.com/bakito/adguardhome-sync/pkg/client/model" model "github.com/bakito/adguardhome-sync/pkg/client/model"
gomock "github.com/golang/mock/gomock" gomock "go.uber.org/mock/gomock"
) )
// MockClient is a mock of Client interface. // MockClient is a mock of Client interface.
type MockClient struct { type MockClient struct {
ctrl *gomock.Controller ctrl *gomock.Controller
recorder *MockClientMockRecorder recorder *MockClientMockRecorder
isgomock struct{}
} }
// MockClientMockRecorder is the mock recorder for MockClient. // MockClientMockRecorder is the mock recorder for MockClient.
@@ -50,52 +56,52 @@ func (mr *MockClientMockRecorder) AccessList() *gomock.Call {
} }
// AddClient mocks base method. // AddClient mocks base method.
func (m *MockClient) AddClient(arg0 *model.Client) error { func (m *MockClient) AddClient(client *model.Client) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AddClient", arg0) ret := m.ctrl.Call(m, "AddClient", client)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)
return ret0 return ret0
} }
// AddClient indicates an expected call of AddClient. // AddClient indicates an expected call of AddClient.
func (mr *MockClientMockRecorder) AddClient(arg0 interface{}) *gomock.Call { func (mr *MockClientMockRecorder) AddClient(client any) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddClient", reflect.TypeOf((*MockClient)(nil).AddClient), arg0) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddClient", reflect.TypeOf((*MockClient)(nil).AddClient), client)
} }
// AddDHCPStaticLease mocks base method. // AddDHCPStaticLease mocks base method.
func (m *MockClient) AddDHCPStaticLease(arg0 model.DhcpStaticLease) error { func (m *MockClient) AddDHCPStaticLease(lease model.DhcpStaticLease) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AddDHCPStaticLease", arg0) ret := m.ctrl.Call(m, "AddDHCPStaticLease", lease)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)
return ret0 return ret0
} }
// AddDHCPStaticLease indicates an expected call of AddDHCPStaticLease. // AddDHCPStaticLease indicates an expected call of AddDHCPStaticLease.
func (mr *MockClientMockRecorder) AddDHCPStaticLease(arg0 interface{}) *gomock.Call { func (mr *MockClientMockRecorder) AddDHCPStaticLease(lease any) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddDHCPStaticLease", reflect.TypeOf((*MockClient)(nil).AddDHCPStaticLease), arg0) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddDHCPStaticLease", reflect.TypeOf((*MockClient)(nil).AddDHCPStaticLease), lease)
} }
// AddFilter mocks base method. // AddFilter mocks base method.
func (m *MockClient) AddFilter(arg0 bool, arg1 model.Filter) error { func (m *MockClient) AddFilter(whitelist bool, f model.Filter) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AddFilter", arg0, arg1) ret := m.ctrl.Call(m, "AddFilter", whitelist, f)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)
return ret0 return ret0
} }
// AddFilter indicates an expected call of AddFilter. // AddFilter indicates an expected call of AddFilter.
func (mr *MockClientMockRecorder) AddFilter(arg0, arg1 interface{}) *gomock.Call { func (mr *MockClientMockRecorder) AddFilter(whitelist, f any) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddFilter", reflect.TypeOf((*MockClient)(nil).AddFilter), arg0, arg1) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddFilter", reflect.TypeOf((*MockClient)(nil).AddFilter), whitelist, f)
} }
// AddRewriteEntries mocks base method. // AddRewriteEntries mocks base method.
func (m *MockClient) AddRewriteEntries(arg0 ...model.RewriteEntry) error { func (m *MockClient) AddRewriteEntries(e ...model.RewriteEntry) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
varargs := []interface{}{} varargs := []any{}
for _, a := range arg0 { for _, a := range e {
varargs = append(varargs, a) varargs = append(varargs, a)
} }
ret := m.ctrl.Call(m, "AddRewriteEntries", varargs...) ret := m.ctrl.Call(m, "AddRewriteEntries", varargs...)
@@ -104,24 +110,9 @@ func (m *MockClient) AddRewriteEntries(arg0 ...model.RewriteEntry) error {
} }
// AddRewriteEntries indicates an expected call of AddRewriteEntries. // AddRewriteEntries indicates an expected call of AddRewriteEntries.
func (mr *MockClientMockRecorder) AddRewriteEntries(arg0 ...interface{}) *gomock.Call { func (mr *MockClientMockRecorder) AddRewriteEntries(e ...any) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRewriteEntries", reflect.TypeOf((*MockClient)(nil).AddRewriteEntries), arg0...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRewriteEntries", reflect.TypeOf((*MockClient)(nil).AddRewriteEntries), e...)
}
// BlockedServices mocks base method.
func (m *MockClient) BlockedServices() (*[]string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "BlockedServices")
ret0, _ := ret[0].(*[]string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// BlockedServices indicates an expected call of BlockedServices.
func (mr *MockClientMockRecorder) BlockedServices() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BlockedServices", reflect.TypeOf((*MockClient)(nil).BlockedServices))
} }
// BlockedServicesSchedule mocks base method. // BlockedServicesSchedule mocks base method.
@@ -170,52 +161,52 @@ func (mr *MockClientMockRecorder) DNSConfig() *gomock.Call {
} }
// DeleteClient mocks base method. // DeleteClient mocks base method.
func (m *MockClient) DeleteClient(arg0 *model.Client) error { func (m *MockClient) DeleteClient(client *model.Client) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteClient", arg0) ret := m.ctrl.Call(m, "DeleteClient", client)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)
return ret0 return ret0
} }
// DeleteClient indicates an expected call of DeleteClient. // DeleteClient indicates an expected call of DeleteClient.
func (mr *MockClientMockRecorder) DeleteClient(arg0 interface{}) *gomock.Call { func (mr *MockClientMockRecorder) DeleteClient(client any) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteClient", reflect.TypeOf((*MockClient)(nil).DeleteClient), arg0) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteClient", reflect.TypeOf((*MockClient)(nil).DeleteClient), client)
} }
// DeleteDHCPStaticLease mocks base method. // DeleteDHCPStaticLease mocks base method.
func (m *MockClient) DeleteDHCPStaticLease(arg0 model.DhcpStaticLease) error { func (m *MockClient) DeleteDHCPStaticLease(lease model.DhcpStaticLease) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteDHCPStaticLease", arg0) ret := m.ctrl.Call(m, "DeleteDHCPStaticLease", lease)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)
return ret0 return ret0
} }
// DeleteDHCPStaticLease indicates an expected call of DeleteDHCPStaticLease. // DeleteDHCPStaticLease indicates an expected call of DeleteDHCPStaticLease.
func (mr *MockClientMockRecorder) DeleteDHCPStaticLease(arg0 interface{}) *gomock.Call { func (mr *MockClientMockRecorder) DeleteDHCPStaticLease(lease any) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteDHCPStaticLease", reflect.TypeOf((*MockClient)(nil).DeleteDHCPStaticLease), arg0) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteDHCPStaticLease", reflect.TypeOf((*MockClient)(nil).DeleteDHCPStaticLease), lease)
} }
// DeleteFilter mocks base method. // DeleteFilter mocks base method.
func (m *MockClient) DeleteFilter(arg0 bool, arg1 model.Filter) error { func (m *MockClient) DeleteFilter(whitelist bool, f model.Filter) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteFilter", arg0, arg1) ret := m.ctrl.Call(m, "DeleteFilter", whitelist, f)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)
return ret0 return ret0
} }
// DeleteFilter indicates an expected call of DeleteFilter. // DeleteFilter indicates an expected call of DeleteFilter.
func (mr *MockClientMockRecorder) DeleteFilter(arg0, arg1 interface{}) *gomock.Call { func (mr *MockClientMockRecorder) DeleteFilter(whitelist, f any) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteFilter", reflect.TypeOf((*MockClient)(nil).DeleteFilter), arg0, arg1) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteFilter", reflect.TypeOf((*MockClient)(nil).DeleteFilter), whitelist, f)
} }
// DeleteRewriteEntries mocks base method. // DeleteRewriteEntries mocks base method.
func (m *MockClient) DeleteRewriteEntries(arg0 ...model.RewriteEntry) error { func (m *MockClient) DeleteRewriteEntries(e ...model.RewriteEntry) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
varargs := []interface{}{} varargs := []any{}
for _, a := range arg0 { for _, a := range e {
varargs = append(varargs, a) varargs = append(varargs, a)
} }
ret := m.ctrl.Call(m, "DeleteRewriteEntries", varargs...) ret := m.ctrl.Call(m, "DeleteRewriteEntries", varargs...)
@@ -224,9 +215,9 @@ func (m *MockClient) DeleteRewriteEntries(arg0 ...model.RewriteEntry) error {
} }
// DeleteRewriteEntries indicates an expected call of DeleteRewriteEntries. // DeleteRewriteEntries indicates an expected call of DeleteRewriteEntries.
func (mr *MockClientMockRecorder) DeleteRewriteEntries(arg0 ...interface{}) *gomock.Call { func (mr *MockClientMockRecorder) DeleteRewriteEntries(e ...any) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteRewriteEntries", reflect.TypeOf((*MockClient)(nil).DeleteRewriteEntries), arg0...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteRewriteEntries", reflect.TypeOf((*MockClient)(nil).DeleteRewriteEntries), e...)
} }
// DhcpConfig mocks base method. // DhcpConfig mocks base method.
@@ -303,11 +294,26 @@ func (mr *MockClientMockRecorder) ProfileInfo() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ProfileInfo", reflect.TypeOf((*MockClient)(nil).ProfileInfo)) 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. // QueryLogConfig mocks base method.
func (m *MockClient) QueryLogConfig() (*model.QueryLogConfig, error) { func (m *MockClient) QueryLogConfig() (*model.QueryLogConfigWithIgnored, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "QueryLogConfig") ret := m.ctrl.Call(m, "QueryLogConfig")
ret0, _ := ret[0].(*model.QueryLogConfig) ret0, _ := ret[0].(*model.QueryLogConfigWithIgnored)
ret1, _ := ret[1].(error) ret1, _ := ret[1].(error)
return ret0, ret1 return ret0, ret1
} }
@@ -319,17 +325,17 @@ func (mr *MockClientMockRecorder) QueryLogConfig() *gomock.Call {
} }
// RefreshFilters mocks base method. // RefreshFilters mocks base method.
func (m *MockClient) RefreshFilters(arg0 bool) error { func (m *MockClient) RefreshFilters(whitelist bool) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "RefreshFilters", arg0) ret := m.ctrl.Call(m, "RefreshFilters", whitelist)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)
return ret0 return ret0
} }
// RefreshFilters indicates an expected call of RefreshFilters. // RefreshFilters indicates an expected call of RefreshFilters.
func (mr *MockClientMockRecorder) RefreshFilters(arg0 interface{}) *gomock.Call { func (mr *MockClientMockRecorder) RefreshFilters(whitelist any) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RefreshFilters", reflect.TypeOf((*MockClient)(nil).RefreshFilters), arg0) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RefreshFilters", reflect.TypeOf((*MockClient)(nil).RefreshFilters), whitelist)
} }
// RewriteList mocks base method. // RewriteList mocks base method.
@@ -378,143 +384,129 @@ func (mr *MockClientMockRecorder) SafeSearchConfig() *gomock.Call {
} }
// SetAccessList mocks base method. // SetAccessList mocks base method.
func (m *MockClient) SetAccessList(arg0 *model.AccessList) error { func (m *MockClient) SetAccessList(accessList *model.AccessList) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetAccessList", arg0) ret := m.ctrl.Call(m, "SetAccessList", accessList)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)
return ret0 return ret0
} }
// SetAccessList indicates an expected call of SetAccessList. // SetAccessList indicates an expected call of SetAccessList.
func (mr *MockClientMockRecorder) SetAccessList(arg0 interface{}) *gomock.Call { func (mr *MockClientMockRecorder) SetAccessList(accessList any) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetAccessList", reflect.TypeOf((*MockClient)(nil).SetAccessList), arg0) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetAccessList", reflect.TypeOf((*MockClient)(nil).SetAccessList), accessList)
}
// SetBlockedServices mocks base method.
func (m *MockClient) SetBlockedServices(arg0 *[]string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetBlockedServices", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// SetBlockedServices indicates an expected call of SetBlockedServices.
func (mr *MockClientMockRecorder) SetBlockedServices(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBlockedServices", reflect.TypeOf((*MockClient)(nil).SetBlockedServices), arg0)
} }
// SetBlockedServicesSchedule mocks base method. // SetBlockedServicesSchedule mocks base method.
func (m *MockClient) SetBlockedServicesSchedule(arg0 *model.BlockedServicesSchedule) error { func (m *MockClient) SetBlockedServicesSchedule(schedule *model.BlockedServicesSchedule) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetBlockedServicesSchedule", arg0) ret := m.ctrl.Call(m, "SetBlockedServicesSchedule", schedule)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)
return ret0 return ret0
} }
// SetBlockedServicesSchedule indicates an expected call of SetBlockedServicesSchedule. // SetBlockedServicesSchedule indicates an expected call of SetBlockedServicesSchedule.
func (mr *MockClientMockRecorder) SetBlockedServicesSchedule(arg0 interface{}) *gomock.Call { func (mr *MockClientMockRecorder) SetBlockedServicesSchedule(schedule any) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBlockedServicesSchedule", reflect.TypeOf((*MockClient)(nil).SetBlockedServicesSchedule), arg0) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBlockedServicesSchedule", reflect.TypeOf((*MockClient)(nil).SetBlockedServicesSchedule), schedule)
} }
// SetCustomRules mocks base method. // SetCustomRules mocks base method.
func (m *MockClient) SetCustomRules(arg0 *[]string) error { func (m *MockClient) SetCustomRules(rules *[]string) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetCustomRules", arg0) ret := m.ctrl.Call(m, "SetCustomRules", rules)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)
return ret0 return ret0
} }
// SetCustomRules indicates an expected call of SetCustomRules. // SetCustomRules indicates an expected call of SetCustomRules.
func (mr *MockClientMockRecorder) SetCustomRules(arg0 interface{}) *gomock.Call { func (mr *MockClientMockRecorder) SetCustomRules(rules any) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetCustomRules", reflect.TypeOf((*MockClient)(nil).SetCustomRules), arg0) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetCustomRules", reflect.TypeOf((*MockClient)(nil).SetCustomRules), rules)
} }
// SetDNSConfig mocks base method. // SetDNSConfig mocks base method.
func (m *MockClient) SetDNSConfig(arg0 *model.DNSConfig) error { func (m *MockClient) SetDNSConfig(config *model.DNSConfig) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetDNSConfig", arg0) ret := m.ctrl.Call(m, "SetDNSConfig", config)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)
return ret0 return ret0
} }
// SetDNSConfig indicates an expected call of SetDNSConfig. // SetDNSConfig indicates an expected call of SetDNSConfig.
func (mr *MockClientMockRecorder) SetDNSConfig(arg0 interface{}) *gomock.Call { func (mr *MockClientMockRecorder) SetDNSConfig(config any) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDNSConfig", reflect.TypeOf((*MockClient)(nil).SetDNSConfig), arg0) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDNSConfig", reflect.TypeOf((*MockClient)(nil).SetDNSConfig), config)
} }
// SetDhcpConfig mocks base method. // SetDhcpConfig mocks base method.
func (m *MockClient) SetDhcpConfig(arg0 *model.DhcpStatus) error { func (m *MockClient) SetDhcpConfig(status *model.DhcpStatus) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetDhcpConfig", arg0) ret := m.ctrl.Call(m, "SetDhcpConfig", status)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)
return ret0 return ret0
} }
// SetDhcpConfig indicates an expected call of SetDhcpConfig. // SetDhcpConfig indicates an expected call of SetDhcpConfig.
func (mr *MockClientMockRecorder) SetDhcpConfig(arg0 interface{}) *gomock.Call { func (mr *MockClientMockRecorder) SetDhcpConfig(status any) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDhcpConfig", reflect.TypeOf((*MockClient)(nil).SetDhcpConfig), arg0) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDhcpConfig", reflect.TypeOf((*MockClient)(nil).SetDhcpConfig), status)
} }
// SetProfileInfo mocks base method. // SetProfileInfo mocks base method.
func (m *MockClient) SetProfileInfo(arg0 *model.ProfileInfo) error { func (m *MockClient) SetProfileInfo(settings *model.ProfileInfo) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetProfileInfo", arg0) ret := m.ctrl.Call(m, "SetProfileInfo", settings)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)
return ret0 return ret0
} }
// SetProfileInfo indicates an expected call of SetProfileInfo. // SetProfileInfo indicates an expected call of SetProfileInfo.
func (mr *MockClientMockRecorder) SetProfileInfo(arg0 interface{}) *gomock.Call { func (mr *MockClientMockRecorder) SetProfileInfo(settings any) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetProfileInfo", reflect.TypeOf((*MockClient)(nil).SetProfileInfo), arg0) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetProfileInfo", reflect.TypeOf((*MockClient)(nil).SetProfileInfo), settings)
} }
// SetQueryLogConfig mocks base method. // SetQueryLogConfig mocks base method.
func (m *MockClient) SetQueryLogConfig(arg0 *model.QueryLogConfig) error { func (m *MockClient) SetQueryLogConfig(ql *model.QueryLogConfigWithIgnored) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetQueryLogConfig", arg0) ret := m.ctrl.Call(m, "SetQueryLogConfig", ql)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)
return ret0 return ret0
} }
// SetQueryLogConfig indicates an expected call of SetQueryLogConfig. // SetQueryLogConfig indicates an expected call of SetQueryLogConfig.
func (mr *MockClientMockRecorder) SetQueryLogConfig(arg0 interface{}) *gomock.Call { func (mr *MockClientMockRecorder) SetQueryLogConfig(ql any) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetQueryLogConfig", reflect.TypeOf((*MockClient)(nil).SetQueryLogConfig), arg0) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetQueryLogConfig", reflect.TypeOf((*MockClient)(nil).SetQueryLogConfig), ql)
} }
// SetSafeSearchConfig mocks base method. // SetSafeSearchConfig mocks base method.
func (m *MockClient) SetSafeSearchConfig(arg0 *model.SafeSearchConfig) error { func (m *MockClient) SetSafeSearchConfig(settings *model.SafeSearchConfig) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetSafeSearchConfig", arg0) ret := m.ctrl.Call(m, "SetSafeSearchConfig", settings)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)
return ret0 return ret0
} }
// SetSafeSearchConfig indicates an expected call of SetSafeSearchConfig. // SetSafeSearchConfig indicates an expected call of SetSafeSearchConfig.
func (mr *MockClientMockRecorder) SetSafeSearchConfig(arg0 interface{}) *gomock.Call { func (mr *MockClientMockRecorder) SetSafeSearchConfig(settings any) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSafeSearchConfig", reflect.TypeOf((*MockClient)(nil).SetSafeSearchConfig), arg0) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSafeSearchConfig", reflect.TypeOf((*MockClient)(nil).SetSafeSearchConfig), settings)
} }
// SetStatsConfig mocks base method. // SetStatsConfig mocks base method.
func (m *MockClient) SetStatsConfig(arg0 *model.StatsConfig) error { func (m *MockClient) SetStatsConfig(sc *model.PutStatsConfigUpdateRequest) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetStatsConfig", arg0) ret := m.ctrl.Call(m, "SetStatsConfig", sc)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)
return ret0 return ret0
} }
// SetStatsConfig indicates an expected call of SetStatsConfig. // SetStatsConfig indicates an expected call of SetStatsConfig.
func (mr *MockClientMockRecorder) SetStatsConfig(arg0 interface{}) *gomock.Call { func (mr *MockClientMockRecorder) SetStatsConfig(sc any) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetStatsConfig", reflect.TypeOf((*MockClient)(nil).SetStatsConfig), arg0) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetStatsConfig", reflect.TypeOf((*MockClient)(nil).SetStatsConfig), sc)
} }
// Setup mocks base method. // Setup mocks base method.
@@ -531,11 +523,26 @@ func (mr *MockClientMockRecorder) Setup() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Setup", reflect.TypeOf((*MockClient)(nil).Setup)) 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. // StatsConfig mocks base method.
func (m *MockClient) StatsConfig() (*model.StatsConfig, error) { func (m *MockClient) StatsConfig() (*model.PutStatsConfigUpdateRequest, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "StatsConfig") ret := m.ctrl.Call(m, "StatsConfig")
ret0, _ := ret[0].(*model.StatsConfig) ret0, _ := ret[0].(*model.PutStatsConfigUpdateRequest)
ret1, _ := ret[1].(error) ret1, _ := ret[1].(error)
return ret0, ret1 return ret0, ret1
} }
@@ -562,85 +569,85 @@ func (mr *MockClientMockRecorder) Status() *gomock.Call {
} }
// ToggleFiltering mocks base method. // ToggleFiltering mocks base method.
func (m *MockClient) ToggleFiltering(arg0 bool, arg1 int) error { func (m *MockClient) ToggleFiltering(enabled bool, interval int) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ToggleFiltering", arg0, arg1) ret := m.ctrl.Call(m, "ToggleFiltering", enabled, interval)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)
return ret0 return ret0
} }
// ToggleFiltering indicates an expected call of ToggleFiltering. // ToggleFiltering indicates an expected call of ToggleFiltering.
func (mr *MockClientMockRecorder) ToggleFiltering(arg0, arg1 interface{}) *gomock.Call { func (mr *MockClientMockRecorder) ToggleFiltering(enabled, interval any) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleFiltering", reflect.TypeOf((*MockClient)(nil).ToggleFiltering), arg0, arg1) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleFiltering", reflect.TypeOf((*MockClient)(nil).ToggleFiltering), enabled, interval)
} }
// ToggleParental mocks base method. // ToggleParental mocks base method.
func (m *MockClient) ToggleParental(arg0 bool) error { func (m *MockClient) ToggleParental(enable bool) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ToggleParental", arg0) ret := m.ctrl.Call(m, "ToggleParental", enable)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)
return ret0 return ret0
} }
// ToggleParental indicates an expected call of ToggleParental. // ToggleParental indicates an expected call of ToggleParental.
func (mr *MockClientMockRecorder) ToggleParental(arg0 interface{}) *gomock.Call { func (mr *MockClientMockRecorder) ToggleParental(enable any) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleParental", reflect.TypeOf((*MockClient)(nil).ToggleParental), arg0) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleParental", reflect.TypeOf((*MockClient)(nil).ToggleParental), enable)
} }
// ToggleProtection mocks base method. // ToggleProtection mocks base method.
func (m *MockClient) ToggleProtection(arg0 bool) error { func (m *MockClient) ToggleProtection(enable bool) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ToggleProtection", arg0) ret := m.ctrl.Call(m, "ToggleProtection", enable)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)
return ret0 return ret0
} }
// ToggleProtection indicates an expected call of ToggleProtection. // ToggleProtection indicates an expected call of ToggleProtection.
func (mr *MockClientMockRecorder) ToggleProtection(arg0 interface{}) *gomock.Call { func (mr *MockClientMockRecorder) ToggleProtection(enable any) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleProtection", reflect.TypeOf((*MockClient)(nil).ToggleProtection), arg0) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleProtection", reflect.TypeOf((*MockClient)(nil).ToggleProtection), enable)
} }
// ToggleSafeBrowsing mocks base method. // ToggleSafeBrowsing mocks base method.
func (m *MockClient) ToggleSafeBrowsing(arg0 bool) error { func (m *MockClient) ToggleSafeBrowsing(enable bool) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ToggleSafeBrowsing", arg0) ret := m.ctrl.Call(m, "ToggleSafeBrowsing", enable)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)
return ret0 return ret0
} }
// ToggleSafeBrowsing indicates an expected call of ToggleSafeBrowsing. // ToggleSafeBrowsing indicates an expected call of ToggleSafeBrowsing.
func (mr *MockClientMockRecorder) ToggleSafeBrowsing(arg0 interface{}) *gomock.Call { func (mr *MockClientMockRecorder) ToggleSafeBrowsing(enable any) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleSafeBrowsing", reflect.TypeOf((*MockClient)(nil).ToggleSafeBrowsing), arg0) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleSafeBrowsing", reflect.TypeOf((*MockClient)(nil).ToggleSafeBrowsing), enable)
} }
// UpdateClient mocks base method. // UpdateClient mocks base method.
func (m *MockClient) UpdateClient(arg0 *model.Client) error { func (m *MockClient) UpdateClient(client *model.Client) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateClient", arg0) ret := m.ctrl.Call(m, "UpdateClient", client)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)
return ret0 return ret0
} }
// UpdateClient indicates an expected call of UpdateClient. // UpdateClient indicates an expected call of UpdateClient.
func (mr *MockClientMockRecorder) UpdateClient(arg0 interface{}) *gomock.Call { func (mr *MockClientMockRecorder) UpdateClient(client any) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateClient", reflect.TypeOf((*MockClient)(nil).UpdateClient), arg0) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateClient", reflect.TypeOf((*MockClient)(nil).UpdateClient), client)
} }
// UpdateFilter mocks base method. // UpdateFilter mocks base method.
func (m *MockClient) UpdateFilter(arg0 bool, arg1 model.Filter) error { func (m *MockClient) UpdateFilter(whitelist bool, f model.Filter) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateFilter", arg0, arg1) ret := m.ctrl.Call(m, "UpdateFilter", whitelist, f)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)
return ret0 return ret0
} }
// UpdateFilter indicates an expected call of UpdateFilter. // UpdateFilter indicates an expected call of UpdateFilter.
func (mr *MockClientMockRecorder) UpdateFilter(arg0, arg1 interface{}) *gomock.Call { func (mr *MockClientMockRecorder) UpdateFilter(whitelist, f any) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateFilter", reflect.TypeOf((*MockClient)(nil).UpdateFilter), arg0, arg1) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateFilter", reflect.TypeOf((*MockClient)(nil).UpdateFilter), whitelist, f)
} }

View File

@@ -1,5 +1,10 @@
// Code generated by MockGen. DO NOT EDIT. // Code generated by MockGen. DO NOT EDIT.
// Source: github.com/bakito/adguardhome-sync/pkg/config (interfaces: Flags) // Source: github.com/bakito/adguardhome-sync/pkg/config (interfaces: Flags)
//
// Generated by this command:
//
// mockgen -package client -destination pkg/mocks/flags/mock.go github.com/bakito/adguardhome-sync/pkg/config Flags
//
// Package client is a generated GoMock package. // Package client is a generated GoMock package.
package client package client
@@ -7,13 +12,14 @@ package client
import ( import (
reflect "reflect" reflect "reflect"
gomock "github.com/golang/mock/gomock" gomock "go.uber.org/mock/gomock"
) )
// MockFlags is a mock of Flags interface. // MockFlags is a mock of Flags interface.
type MockFlags struct { type MockFlags struct {
ctrl *gomock.Controller ctrl *gomock.Controller
recorder *MockFlagsMockRecorder recorder *MockFlagsMockRecorder
isgomock struct{}
} }
// MockFlagsMockRecorder is the mock recorder for MockFlags. // MockFlagsMockRecorder is the mock recorder for MockFlags.
@@ -34,60 +40,60 @@ func (m *MockFlags) EXPECT() *MockFlagsMockRecorder {
} }
// Changed mocks base method. // Changed mocks base method.
func (m *MockFlags) Changed(arg0 string) bool { func (m *MockFlags) Changed(name string) bool {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Changed", arg0) ret := m.ctrl.Call(m, "Changed", name)
ret0, _ := ret[0].(bool) ret0, _ := ret[0].(bool)
return ret0 return ret0
} }
// Changed indicates an expected call of Changed. // Changed indicates an expected call of Changed.
func (mr *MockFlagsMockRecorder) Changed(arg0 interface{}) *gomock.Call { func (mr *MockFlagsMockRecorder) Changed(name any) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Changed", reflect.TypeOf((*MockFlags)(nil).Changed), arg0) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Changed", reflect.TypeOf((*MockFlags)(nil).Changed), name)
} }
// GetBool mocks base method. // GetBool mocks base method.
func (m *MockFlags) GetBool(arg0 string) (bool, error) { func (m *MockFlags) GetBool(name string) (bool, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetBool", arg0) ret := m.ctrl.Call(m, "GetBool", name)
ret0, _ := ret[0].(bool) ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(error) ret1, _ := ret[1].(error)
return ret0, ret1 return ret0, ret1
} }
// GetBool indicates an expected call of GetBool. // GetBool indicates an expected call of GetBool.
func (mr *MockFlagsMockRecorder) GetBool(arg0 interface{}) *gomock.Call { func (mr *MockFlagsMockRecorder) GetBool(name any) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBool", reflect.TypeOf((*MockFlags)(nil).GetBool), arg0) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBool", reflect.TypeOf((*MockFlags)(nil).GetBool), name)
} }
// GetInt mocks base method. // GetInt mocks base method.
func (m *MockFlags) GetInt(arg0 string) (int, error) { func (m *MockFlags) GetInt(name string) (int, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetInt", arg0) ret := m.ctrl.Call(m, "GetInt", name)
ret0, _ := ret[0].(int) ret0, _ := ret[0].(int)
ret1, _ := ret[1].(error) ret1, _ := ret[1].(error)
return ret0, ret1 return ret0, ret1
} }
// GetInt indicates an expected call of GetInt. // GetInt indicates an expected call of GetInt.
func (mr *MockFlagsMockRecorder) GetInt(arg0 interface{}) *gomock.Call { func (mr *MockFlagsMockRecorder) GetInt(name any) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInt", reflect.TypeOf((*MockFlags)(nil).GetInt), arg0) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInt", reflect.TypeOf((*MockFlags)(nil).GetInt), name)
} }
// GetString mocks base method. // GetString mocks base method.
func (m *MockFlags) GetString(arg0 string) (string, error) { func (m *MockFlags) GetString(name string) (string, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetString", arg0) ret := m.ctrl.Call(m, "GetString", name)
ret0, _ := ret[0].(string) ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error) ret1, _ := ret[1].(error)
return ret0, ret1 return ret0, ret1
} }
// GetString indicates an expected call of GetString. // GetString indicates an expected call of GetString.
func (mr *MockFlagsMockRecorder) GetString(arg0 interface{}) *gomock.Call { func (mr *MockFlagsMockRecorder) GetString(name any) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetString", reflect.TypeOf((*MockFlags)(nil).GetString), arg0) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetString", reflect.TypeOf((*MockFlags)(nil).GetString), name)
} }

View File

@@ -1,17 +1,18 @@
package sync package sync
import ( import (
"go.uber.org/zap"
"github.com/bakito/adguardhome-sync/pkg/client" "github.com/bakito/adguardhome-sync/pkg/client"
"github.com/bakito/adguardhome-sync/pkg/client/model" "github.com/bakito/adguardhome-sync/pkg/client/model"
"github.com/bakito/adguardhome-sync/pkg/utils" "github.com/bakito/adguardhome-sync/pkg/utils"
"go.uber.org/zap"
) )
var ( var (
actionProfileInfo = func(ac *actionContext) error { actionProfileInfo = func(ac *actionContext) error {
if pro, err := ac.client.ProfileInfo(); err != nil { if pro, err := ac.client.ProfileInfo(); err != nil {
return err return err
} else if merged := pro.ShouldSyncFor(ac.origin.profileInfo); merged != nil { } else if merged := pro.ShouldSyncFor(ac.origin.profileInfo, ac.cfg.Features.Theme); merged != nil {
return ac.client.SetProfileInfo(merged) return ac.client.SetProfileInfo(merged)
} }
return nil return nil
@@ -63,7 +64,7 @@ var (
if err != nil { if err != nil {
return err return err
} }
if ac.origin.statsConfig.Interval != sc.Interval { if !sc.Equals(ac.origin.statsConfig) {
return ac.client.SetStatsConfig(ac.origin.statsConfig) return ac.client.SetStatsConfig(ac.origin.statsConfig)
} }
return nil return nil
@@ -94,10 +95,10 @@ var (
return err return err
} }
if err = syncFilterType(ac.rl, ac.origin.filters.Filters, rf.Filters, false, ac.client, ac.continueOnError); err != nil { if err = syncFilterType(ac.rl, ac.origin.filters.Filters, rf.Filters, false, ac.client, ac.cfg.ContinueOnError); err != nil {
return err return err
} }
if err = syncFilterType(ac.rl, ac.origin.filters.WhitelistFilters, rf.WhitelistFilters, true, ac.client, ac.continueOnError); err != nil { if err = syncFilterType(ac.rl, ac.origin.filters.WhitelistFilters, rf.WhitelistFilters, true, ac.client, ac.cfg.ContinueOnError); err != nil {
return err return err
} }
@@ -105,23 +106,13 @@ var (
return ac.client.SetCustomRules(ac.origin.filters.UserRules) return ac.client.SetCustomRules(ac.origin.filters.UserRules)
} }
if ac.origin.filters.Enabled != rf.Enabled || ac.origin.filters.Interval != rf.Interval { 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 ac.client.ToggleFiltering(*ac.origin.filters.Enabled, *ac.origin.filters.Interval)
} }
return nil return nil
} }
actionBlockedServices = func(ac *actionContext) error {
rs, err := ac.client.BlockedServices()
if err != nil {
return err
}
if !model.EqualsStringSlice(ac.origin.blockedServices, rs, true) {
return ac.client.SetBlockedServices(ac.origin.blockedServices)
}
return nil
}
actionBlockedServicesSchedule = func(ac *actionContext) error { actionBlockedServicesSchedule = func(ac *actionContext) error {
rbss, err := ac.client.BlockedServicesSchedule() rbss, err := ac.client.BlockedServicesSchedule()
if err != nil { if err != nil {
@@ -144,7 +135,7 @@ var (
for _, client := range r { for _, client := range r {
if err := ac.client.DeleteClient(client); err != nil { if err := ac.client.DeleteClient(client); err != nil {
ac.rl.With("client-name", client.Name, "error", err).Error("error deleting client setting") ac.rl.With("client-name", client.Name, "error", err).Error("error deleting client setting")
if !ac.continueOnError { if !ac.cfg.ContinueOnError {
return err return err
} }
} }
@@ -153,7 +144,7 @@ var (
for _, client := range a { for _, client := range a {
if err := ac.client.AddClient(client); err != nil { if err := ac.client.AddClient(client); err != nil {
ac.rl.With("client-name", client.Name, "error", err).Error("error adding client setting") ac.rl.With("client-name", client.Name, "error", err).Error("error adding client setting")
if !ac.continueOnError { if !ac.cfg.ContinueOnError {
return err return err
} }
} }
@@ -162,7 +153,7 @@ var (
for _, client := range u { for _, client := range u {
if err := ac.client.UpdateClient(client); err != nil { if err := ac.client.UpdateClient(client); err != nil {
ac.rl.With("client-name", client.Name, "error", err).Error("error updating client setting") ac.rl.With("client-name", client.Name, "error", err).Error("error updating client setting")
if !ac.continueOnError { if !ac.cfg.ContinueOnError {
return err return err
} }
} }
@@ -186,6 +177,9 @@ var (
if err != nil { if err != nil {
return err return err
} }
// dc.Sanitize(ac.rl)
if !dc.Equals(ac.origin.dnsConfig) { if !dc.Equals(ac.origin.dnsConfig) {
if err = ac.client.SetDNSConfig(ac.origin.dnsConfig); err != nil { if err = ac.client.SetDNSConfig(ac.origin.dnsConfig); err != nil {
return err return err
@@ -209,7 +203,7 @@ var (
origClone.Enabled = ac.replica.DHCPServerEnabled origClone.Enabled = ac.replica.DHCPServerEnabled
} }
if !sc.Equals(origClone) { if !sc.CleanAndEquals(origClone) {
return ac.client.SetDhcpConfig(origClone) return ac.client.SetDhcpConfig(origClone)
} }
} }
@@ -226,7 +220,7 @@ var (
for _, lease := range r { for _, lease := range r {
if err := ac.client.DeleteDHCPStaticLease(lease); err != nil { if err := ac.client.DeleteDHCPStaticLease(lease); err != nil {
ac.rl.With("hostname", lease.Hostname, "error", err).Error("error deleting dhcp static lease") ac.rl.With("hostname", lease.Hostname, "error", err).Error("error deleting dhcp static lease")
if !ac.continueOnError { if !ac.cfg.ContinueOnError {
return err return err
} }
} }
@@ -235,7 +229,7 @@ var (
for _, lease := range a { for _, lease := range a {
if err := ac.client.AddDHCPStaticLease(lease); err != nil { if err := ac.client.AddDHCPStaticLease(lease); err != nil {
ac.rl.With("hostname", lease.Hostname, "error", err).Error("error adding dhcp static lease") ac.rl.With("hostname", lease.Hostname, "error", err).Error("error adding dhcp static lease")
if !ac.continueOnError { if !ac.cfg.ContinueOnError {
return err return err
} }
} }
@@ -244,7 +238,14 @@ var (
} }
) )
func syncFilterType(rl *zap.SugaredLogger, of *[]model.Filter, rFilters *[]model.Filter, whitelist bool, replica client.Client, continueOnError bool) error { 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) fa, fu, fd := model.MergeFilters(rFilters, of)
for _, f := range fd { for _, f := range fd {

View File

@@ -1,10 +1,11 @@
package sync package sync
import ( import (
"go.uber.org/zap"
"github.com/bakito/adguardhome-sync/pkg/client" "github.com/bakito/adguardhome-sync/pkg/client"
"github.com/bakito/adguardhome-sync/pkg/client/model" "github.com/bakito/adguardhome-sync/pkg/client/model"
"github.com/bakito/adguardhome-sync/pkg/types" "github.com/bakito/adguardhome-sync/pkg/types"
"go.uber.org/zap"
) )
func setupActions(cfg *types.Config) (actions []syncAction) { func setupActions(cfg *types.Config) (actions []syncAction) {
@@ -17,6 +18,11 @@ func setupActions(cfg *types.Config) (actions []syncAction) {
action("safe browsing", actionSafeBrowsing), action("safe browsing", actionSafeBrowsing),
) )
} }
if cfg.Features.DNS.ServerConfig {
actions = append(actions,
action("DNS server config", actionDNSServerConfig),
)
}
if cfg.Features.QueryLogConfig { if cfg.Features.QueryLogConfig {
actions = append(actions, actions = append(actions,
action("query log config", actionQueryLogConfig), action("query log config", actionQueryLogConfig),
@@ -39,7 +45,6 @@ func setupActions(cfg *types.Config) (actions []syncAction) {
} }
if cfg.Features.Services { if cfg.Features.Services {
actions = append(actions, actions = append(actions,
action("blocked services", actionBlockedServices),
action("blocked services schedule", actionBlockedServicesSchedule), action("blocked services schedule", actionBlockedServicesSchedule),
) )
} }
@@ -53,12 +58,6 @@ func setupActions(cfg *types.Config) (actions []syncAction) {
action("DNS access lists", actionDNSAccessLists), action("DNS access lists", actionDNSAccessLists),
) )
} }
if cfg.Features.DNS.ServerConfig {
actions = append(actions,
action("DNS server config", actionDNSServerConfig),
)
}
if cfg.Features.DHCP.ServerConfig { if cfg.Features.DHCP.ServerConfig {
actions = append(actions, actions = append(actions,
action("DHCP server config", actionDHCPServerConfig), action("DHCP server config", actionDHCPServerConfig),
@@ -78,12 +77,12 @@ type syncAction interface {
} }
type actionContext struct { type actionContext struct {
rl *zap.SugaredLogger rl *zap.SugaredLogger
origin *origin origin *origin
client client.Client client client.Client
replicaStatus *model.ServerStatus replicaStatus *model.ServerStatus
continueOnError bool replica types.AdGuardInstance
replica types.AdGuardInstance cfg *types.Config
} }
type defaultAction struct { type defaultAction struct {

View File

@@ -14,9 +14,11 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/bakito/adguardhome-sync/pkg/log"
"github.com/bakito/adguardhome-sync/version"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/bakito/adguardhome-sync/pkg/log"
"github.com/bakito/adguardhome-sync/pkg/metrics"
"github.com/bakito/adguardhome-sync/version"
) )
var ( var (
@@ -32,11 +34,38 @@ func (w *worker) handleSync(c *gin.Context) {
} }
func (w *worker) handleRoot(c *gin.Context) { func (w *worker) handleRoot(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", map[string]interface{}{ total, dns, blocked, malware, adult := metrics.StatsGraph()
c.HTML(http.StatusOK, "index.html", map[string]any{
"DarkMode": w.cfg.API.DarkMode, "DarkMode": w.cfg.API.DarkMode,
"Metrics": w.cfg.API.Metrics.Enabled,
"Version": version.Version, "Version": version.Version,
"Build": version.Build, "Build": version.Build,
"SyncStatus": w.status(), "SyncStatus": w.status(),
"Stats": map[string]any{
"Labels": getLast24Hours(),
"DNS": dns,
"Blocked": blocked,
"BlockedPercentage": fmt.Sprintf(
"%.2f",
(float64(*total.NumBlockedFiltering)*100.0)/float64(*total.NumDnsQueries),
),
"Malware": malware,
"MalwarePercentage": fmt.Sprintf(
"%.2f",
(float64(*total.NumReplacedSafebrowsing)*100.0)/float64(*total.NumDnsQueries),
),
"Adult": adult,
"AdultPercentage": fmt.Sprintf(
"%.2f",
(float64(*total.NumReplacedParental)*100.0)/float64(*total.NumDnsQueries),
),
"TotalDNS": total.NumDnsQueries,
"TotalBlocked": total.NumBlockedFiltering,
"TotalMalware": total.NumReplacedSafebrowsing,
"TotalAdult": total.NumReplacedParental,
},
}, },
) )
} }
@@ -49,12 +78,22 @@ func (w *worker) handleLogs(c *gin.Context) {
c.Data(http.StatusOK, "text/plain", []byte(strings.Join(log.Logs(), ""))) 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) { func (w *worker) handleStatus(c *gin.Context) {
c.JSON(http.StatusOK, w.status()) c.JSON(http.StatusOK, w.status())
} }
func (w *worker) listenAndServe() { func (w *worker) listenAndServe() {
l.With("port", w.cfg.API.Port).Info("Starting API server") 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()) ctx, cancel := context.WithCancel(context.Background())
@@ -74,12 +113,25 @@ func (w *worker) listenAndServe() {
r.SetHTMLTemplate(template.Must(template.New("index.html").Parse(string(index)))) r.SetHTMLTemplate(template.Must(template.New("index.html").Parse(string(index))))
r.POST("/api/v1/sync", w.handleSync) r.POST("/api/v1/sync", w.handleSync)
r.GET("/api/v1/logs", w.handleLogs) r.GET("/api/v1/logs", w.handleLogs)
r.POST("/api/v1/clear-logs", w.handleClearLogs)
r.GET("/api/v1/status", w.handleStatus) r.GET("/api/v1/status", w.handleStatus)
r.GET("/favicon.ico", w.handleFavicon) r.GET("/favicon.ico", w.handleFavicon)
r.GET("/", w.handleRoot) r.GET("/", w.handleRoot)
if w.cfg.API.Metrics.Enabled {
r.GET("/metrics", metrics.Handler())
go w.startScraping()
}
go func() { go func() {
if err := httpServer.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) { 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") l.With("error", err).Fatalf("HTTP server ListenAndServe")
} }
}() }()
@@ -101,7 +153,7 @@ func (w *worker) listenAndServe() {
l.Fatal("os.Kill - terminating...") l.Fatal("os.Kill - terminating...")
}() }()
gracefullCtx, cancelShutdown := context.WithTimeout(context.Background(), 5*time.Second) gracefulCtx, cancelShutdown := context.WithTimeout(context.Background(), 5*time.Second)
defer cancelShutdown() defer cancelShutdown()
if w.cron != nil { if w.cron != nil {
@@ -109,7 +161,7 @@ func (w *worker) listenAndServe() {
w.cron.Stop() w.cron.Stop()
} }
if err := httpServer.Shutdown(gracefullCtx); err != nil { if err := httpServer.Shutdown(gracefulCtx); err != nil {
l.With("error", err).Error("Shutdown error") l.With("error", err).Error("Shutdown error")
defer os.Exit(1) defer os.Exit(1)
} else { } else {
@@ -118,13 +170,12 @@ func (w *worker) listenAndServe() {
// manually cancel context if not using httpServer.RegisterOnShutdown(cancel) // manually cancel context if not using httpServer.RegisterOnShutdown(cancel)
cancel() cancel()
defer os.Exit(0)
} }
type syncStatus struct { type syncStatus struct {
Origin replicaStatus `json:"origin"` SyncRunning bool `json:"syncRunning"`
Replicas []replicaStatus `json:"replicas"` Origin replicaStatus `json:"origin"`
Replicas []replicaStatus `json:"replicas"`
} }
type replicaStatus struct { type replicaStatus struct {
@@ -134,3 +185,26 @@ type replicaStatus struct {
Error string `json:"error,omitempty"` Error string `json:"error,omitempty"`
ProtectionEnabled *bool `json:"protection_enabled"` 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

@@ -1,15 +1,15 @@
<html lang="en"> <html lang="en">
<head> <head>
<title>AdGuardHome sync</title> <title>AdGuardHome sync</title>
<script type="text/javascript" src="https://code.jquery.com/jquery-3.6.0.min.js"> <script type="text/javascript" src="https://code.jquery.com/jquery-3.7.1.min.js">
</script> </script>
{{- if .DarkMode }} {{- if .DarkMode }}
<link rel="stylesheet" href="https://bootswatch.com/5/darkly/bootstrap.min.css" <link rel="stylesheet" href="https://bootswatch.com/5/darkly/bootstrap.min.css"
crossorigin="anonymous"> crossorigin="anonymous">
{{- else }} {{- else }}
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css" rel="stylesheet" <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-KyZXEAg3QhqLMpG8r+8fhAXLRk2vvoC2f3B09zVXn8CA5QIVfZOJ3BCsw2P0p/We" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
crossOrigin="anonymous"> crossorigin="anonymous">
{{- end }} {{- end }}
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function () { $(document).ready(function () {
@@ -30,6 +30,12 @@
} }
); );
}); });
$("#clearLogs").click(function () {
$.post("api/v1/clear-logs", {}, function () {
$('#logs').html("");
}
);
});
$("#sync").click(function () { $("#sync").click(function () {
$.post("api/v1/sync", {}, function (data) { $.post("api/v1/sync", {}, function (data) {
}); });
@@ -39,6 +45,43 @@
}); });
</script> </script>
<link rel="shortcut icon" href="favicon.ico"> <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> </head>
<body> <body>
<div class="container-fluid px-4"> <div class="container-fluid px-4">
@@ -48,15 +91,60 @@
<p class="h6">{{ .Version }} ({{ .Build }})</p> <p class="h6">{{ .Version }} ({{ .Build }})</p>
</p> </p>
</div> </div>
<div class="row"> {{- 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="col">
<div class="btn-group" role="group"> <div class="btn-group" role="group">
<input class="btn btn-success" type="button" id="sync" value="Synchronize"/> <button type="button" class="btn btn-success" id="sync">Synchronize</button>
<input class="btn btn-secondary" type="button" id="showLogs" value="Update Logs"/> <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> </div>
<div class="col col-md-auto"> <div class="col col-md-auto">
<div class="float-right"> <div class="btn-group float-right" role="group">
<a href="{{ .SyncStatus.Origin.URL }}" target="_blank" class="btn btn-{{ .SyncStatus.Origin.Status }}" <a href="{{ .SyncStatus.Origin.URL }}" target="_blank" class="btn btn-{{ .SyncStatus.Origin.Status }}"
type="button" id="origin" type="button" id="origin"
{{ if .SyncStatus.Origin.Error }} title="{{ .SyncStatus.Origin.Error }}" {{ end }}>Origin {{ .SyncStatus.Origin.Host }}</a> {{ if .SyncStatus.Origin.Error }} title="{{ .SyncStatus.Origin.Error }}" {{ end }}>Origin {{ .SyncStatus.Origin.Host }}</a>
@@ -69,10 +157,97 @@
</div> </div>
</div> </div>
<div class="row mt-3"> <div class="row mt-3">
<div class="col-12"> <div class="col-12 col-md-12">
<pre class="p-3 border"><code id="logs"></code></pre> <div class="stat-card">
<pre class="p-3 border"><code id="logs"></code></pre>
</div>
</div> </div>
</div> </div>
</div> </div>
<!-- openssl dgst -sha384 -binary popper.min.js | openssl base64 -A -->
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js"
integrity="sha384-IQsoLXl5PILFhosVNubq5LC7Qb9DXgDA9i+tQ8Zj3iwWAwPtgFTxbJ8NT4GN1R8p" crossorigin="anonymous">
</script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.min.js"
integrity="sha384-0pUGZvbkm6XF6gxjEnlmuGrJXVbNuzT9qBBavbLwCsOGabYfZo0T0to5eqruptLy" crossorigin="anonymous">
</script>
{{- if .Metrics }}
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js"ŝ
integrity="sha384-vsrfeLOOY6KuIYKDlmVH5UiBmgIdB1oEf7p01YgWHuqmOHfZr374+odEv96n9tNC" crossorigin="anonymous">
</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> </body>
</html> </html>

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

@@ -0,0 +1,51 @@
package sync
import (
"time"
"github.com/bakito/adguardhome-sync/pkg/metrics"
"github.com/bakito/adguardhome-sync/pkg/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
}

View File

@@ -7,27 +7,29 @@ import (
"sort" "sort"
"time" "time"
"github.com/robfig/cron/v3"
"go.uber.org/zap"
"github.com/bakito/adguardhome-sync/pkg/client" "github.com/bakito/adguardhome-sync/pkg/client"
"github.com/bakito/adguardhome-sync/pkg/client/model" "github.com/bakito/adguardhome-sync/pkg/client/model"
"github.com/bakito/adguardhome-sync/pkg/log" "github.com/bakito/adguardhome-sync/pkg/log"
"github.com/bakito/adguardhome-sync/pkg/metrics"
"github.com/bakito/adguardhome-sync/pkg/types" "github.com/bakito/adguardhome-sync/pkg/types"
"github.com/bakito/adguardhome-sync/pkg/utils" "github.com/bakito/adguardhome-sync/pkg/utils"
"github.com/bakito/adguardhome-sync/pkg/versions" "github.com/bakito/adguardhome-sync/pkg/versions"
"github.com/bakito/adguardhome-sync/version" "github.com/bakito/adguardhome-sync/version"
"github.com/robfig/cron/v3"
"go.uber.org/zap"
) )
var l = log.GetLogger("sync") var l = log.GetLogger("sync")
// Sync config from origin to replica // Sync config from origin to replica.
func Sync(cfg *types.Config) error { func Sync(cfg *types.Config) error {
if cfg.Origin.URL == "" { if cfg.Origin.URL == "" {
return fmt.Errorf("origin URL is required") return errors.New("origin URL is required")
} }
if len(cfg.UniqueReplicas()) == 0 { if len(cfg.UniqueReplicas()) == 0 {
return fmt.Errorf("no replicas configured") return errors.New("no replicas configured")
} }
l.With( l.With(
@@ -41,10 +43,8 @@ func Sync(cfg *types.Config) error {
cfg.Origin.AutoSetup = false cfg.Origin.AutoSetup = false
w := &worker{ w := &worker{
cfg: cfg, cfg: cfg,
createClient: func(ai types.AdGuardInstance) (client.Client, error) { createClient: client.New,
return client.New(ai)
},
} }
if cfg.Cron != "" { if cfg.Cron != "" {
w.cron = cron.New() w.cron = cron.New()
@@ -66,16 +66,12 @@ func Sync(cfg *types.Config) error {
if cfg.API.Port != 0 { if cfg.API.Port != 0 {
w.cron.Start() w.cron.Start()
} else { } else {
runOnStartAsync(cfg, w)
w.cron.Run() w.cron.Run()
} }
} }
if cfg.API.Port != 0 { if cfg.API.Port != 0 {
if cfg.RunOnStart { runOnStartAsync(cfg, w)
go func() {
l.Info("Running sync on startup")
w.sync()
}()
}
w.listenAndServe() w.listenAndServe()
} else if cfg.RunOnStart { } else if cfg.RunOnStart {
l.Info("Running sync on startup") l.Info("Running sync on startup")
@@ -85,6 +81,15 @@ func Sync(cfg *types.Config) error {
return nil 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 { type worker struct {
cfg *types.Config cfg *types.Config
running bool running bool
@@ -95,7 +100,7 @@ type worker struct {
func (w *worker) status() *syncStatus { func (w *worker) status() *syncStatus {
syncStatus := &syncStatus{ syncStatus := &syncStatus{
Origin: w.getStatus(w.cfg.Origin), Origin: w.getStatus(*w.cfg.Origin),
} }
for _, replica := range w.cfg.Replicas { for _, replica := range w.cfg.Replicas {
@@ -110,18 +115,20 @@ func (w *worker) status() *syncStatus {
return syncStatus.Replicas[i].Host < syncStatus.Replicas[j].Host return syncStatus.Replicas[i].Host < syncStatus.Replicas[j].Host
}) })
syncStatus.SyncRunning = w.running
return syncStatus return syncStatus
} }
func (w *worker) getStatus(inst types.AdGuardInstance) (st replicaStatus) { func (w *worker) getStatus(inst types.AdGuardInstance) replicaStatus {
st = replicaStatus{Host: inst.WebHost, URL: inst.WebURL} st := replicaStatus{Host: inst.WebHost, URL: inst.WebURL}
oc, err := w.createClient(inst) oc, err := w.createClient(inst)
if err != nil { if err != nil {
l.With("error", err, "url", w.cfg.Origin.URL).Error("Error creating origin client") l.With("error", err, "url", w.cfg.Origin.URL).Error("Error creating origin client")
st.Status = "danger" st.Status = "danger"
st.Error = err.Error() st.Error = err.Error()
return return st
} }
sl := l.With("from", inst.WebHost) sl := l.With("from", inst.WebHost)
status, err := oc.Status() status, err := oc.Status()
@@ -129,16 +136,16 @@ func (w *worker) getStatus(inst types.AdGuardInstance) (st replicaStatus) {
if errors.Is(err, client.ErrSetupNeeded) { if errors.Is(err, client.ErrSetupNeeded) {
st.Status = "warning" st.Status = "warning"
st.Error = err.Error() st.Error = err.Error()
return return st
} }
sl.With("error", err).Error("Error getting origin status") sl.With("error", err).Error("Error getting origin status")
st.Status = "danger" st.Status = "danger"
st.Error = err.Error() st.Error = err.Error()
return return st
} }
st.Status = "success" st.Status = "success"
st.ProtectionEnabled = utils.Ptr(status.ProtectionEnabled) st.ProtectionEnabled = utils.Ptr(status.ProtectionEnabled)
return return st
} }
func (w *worker) sync() { func (w *worker) sync() {
@@ -147,9 +154,11 @@ func (w *worker) sync() {
return return
} }
w.running = true w.running = true
defer func() { w.running = false }() defer func() {
w.running = false
}()
oc, err := w.createClient(w.cfg.Origin) oc, err := w.createClient(*w.cfg.Origin)
if err != nil { if err != nil {
l.With("error", err, "url", w.cfg.Origin.URL).Error("Error creating origin client") l.With("error", err, "url", w.cfg.Origin.URL).Error("Error creating origin client")
return return
@@ -165,7 +174,8 @@ func (w *worker) sync() {
} }
if versions.IsNewerThan(versions.MinAgh, o.status.Version) { 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) sl.With("error", err, "version", o.status.Version).
Errorf("Origin AdGuard Home version must be >= %s", versions.MinAgh)
return return
} }
@@ -199,12 +209,6 @@ func (w *worker) sync() {
return return
} }
o.blockedServices, err = oc.BlockedServices()
if err != nil {
sl.With("error", err).Error("Error getting origin blocked services")
return
}
o.blockedServicesSchedule, err = oc.BlockedServicesSchedule() o.blockedServicesSchedule, err = oc.BlockedServicesSchedule()
if err != nil { if err != nil {
sl.With("error", err).Error("Error getting origin blocked services schedule") sl.With("error", err).Error("Error getting origin blocked services schedule")
@@ -269,45 +273,65 @@ func (w *worker) syncTo(l *zap.SugaredLogger, o *origin, replica types.AdGuardIn
rl := l.With("to", rc.Host()) rl := l.With("to", rc.Host())
rl.Info("Start sync") rl.Info("Start sync")
start := time.Now()
withError := false
delta := time.Since(start).Seconds()
defer func() {
metrics.UpdateResult(rc.Host(), !withError, delta)
doneLog := rl.With("duration", fmt.Sprintf("%vs", delta))
if withError {
doneLog.Error("Sync done")
} else {
doneLog.Info("Sync done")
}
}()
replicaStatus, err := w.statusWithSetup(rl, replica, rc) replicaStatus, err := w.statusWithSetup(rl, replica, rc)
if err != nil { if err != nil {
rl.With("error", err).Error("Error getting replica status") rl.With("error", err).Error("Error getting replica status")
withError = true
return return
} }
rl.With("version", replicaStatus.Version).Info("Connected to replica") rl.With("version", replicaStatus.Version).Info("Connected to replica")
if versions.IsNewerThan(versions.MinAgh, replicaStatus.Version) { if versions.IsNewerThan(versions.MinAgh, replicaStatus.Version) {
rl.With("error", err, "version", replicaStatus.Version).Errorf("Replica AdGuard Home version must be >= %s", versions.MinAgh) rl.With("error", err, "version", replicaStatus.Version).
Errorf("Replica AdGuard Home version must be >= %s", versions.MinAgh)
withError = true
return return
} }
if o.status.Version != replicaStatus.Version { if o.status.Version != replicaStatus.Version {
rl.With("originVersion", o.status.Version, "replicaVersion", replicaStatus.Version).Warn("Versions do not match") rl.With("originVersion", o.status.Version, "replicaVersion", replicaStatus.Version).
Warn("Versions do not match")
} }
ac := &actionContext{ ac := &actionContext{
continueOnError: w.cfg.ContinueOnError, cfg: w.cfg,
rl: rl, rl: rl,
origin: o, origin: o,
replicaStatus: replicaStatus, replicaStatus: replicaStatus,
client: rc, client: rc,
replica: replica, replica: replica,
} }
for _, action := range w.actions { for _, action := range w.actions {
if err := action.sync(ac); err != nil { if err := action.sync(ac); err != nil {
rl.With("error", err).Errorf("Error syncing %s", action.name()) rl.With("error", err).Errorf("Error syncing %s", action.name())
withError = true
if !w.cfg.ContinueOnError { if !w.cfg.ContinueOnError {
return return
} }
} }
} }
rl.Info("Sync done")
} }
func (w *worker) statusWithSetup(rl *zap.SugaredLogger, replica types.AdGuardInstance, rc client.Client) (*model.ServerStatus, error) { func (w *worker) statusWithSetup(
rl *zap.SugaredLogger,
replica types.AdGuardInstance,
rc client.Client,
) (*model.ServerStatus, error) {
rs, err := rc.Status() rs, err := rc.Status()
if err != nil { if err != nil {
if replica.AutoSetup && errors.Is(err, client.ErrSetupNeeded) { if replica.AutoSetup && errors.Is(err, client.ErrSetupNeeded) {
@@ -325,12 +349,11 @@ func (w *worker) statusWithSetup(rl *zap.SugaredLogger, replica types.AdGuardIns
type origin struct { type origin struct {
status *model.ServerStatus status *model.ServerStatus
rewrites *model.RewriteEntries rewrites *model.RewriteEntries
blockedServices *model.BlockedServicesArray
blockedServicesSchedule *model.BlockedServicesSchedule blockedServicesSchedule *model.BlockedServicesSchedule
filters *model.FilterStatus filters *model.FilterStatus
clients *model.Clients clients *model.Clients
queryLogConfig *model.QueryLogConfig queryLogConfig *model.QueryLogConfigWithIgnored
statsConfig *model.StatsConfig statsConfig *model.GetStatsConfigResponse
accessList *model.AccessList accessList *model.AccessList
dnsConfig *model.DNSConfig dnsConfig *model.DNSConfig
dhcpServerConfig *model.DhcpStatus dhcpServerConfig *model.DhcpStatus

View File

@@ -3,16 +3,17 @@ package sync
import ( import (
"errors" "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/pkg/client" "github.com/bakito/adguardhome-sync/pkg/client"
"github.com/bakito/adguardhome-sync/pkg/client/model" "github.com/bakito/adguardhome-sync/pkg/client/model"
clientmock "github.com/bakito/adguardhome-sync/pkg/mocks/client" clientmock "github.com/bakito/adguardhome-sync/pkg/mocks/client"
"github.com/bakito/adguardhome-sync/pkg/types" "github.com/bakito/adguardhome-sync/pkg/types"
"github.com/bakito/adguardhome-sync/pkg/utils" "github.com/bakito/adguardhome-sync/pkg/utils"
"github.com/bakito/adguardhome-sync/pkg/versions" "github.com/bakito/adguardhome-sync/pkg/versions"
gm "github.com/golang/mock/gomock"
"github.com/google/uuid"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
) )
var _ = Describe("Sync", func() { var _ = Describe("Sync", func() {
@@ -48,6 +49,7 @@ var _ = Describe("Sync", func() {
GeneralSettings: true, GeneralSettings: true,
StatsConfig: true, StatsConfig: true,
QueryLogConfig: true, QueryLogConfig: true,
Theme: true,
}, },
Replicas: []types.AdGuardInstance{ Replicas: []types.AdGuardInstance{
{}, {},
@@ -57,8 +59,8 @@ var _ = Describe("Sync", func() {
te = errors.New(uuid.NewString()) te = errors.New(uuid.NewString())
ac = &actionContext{ ac = &actionContext{
continueOnError: false, cfg: w.cfg,
rl: l, rl: l,
origin: &origin{ origin: &origin{
profileInfo: &model.ProfileInfo{ profileInfo: &model.ProfileInfo{
Name: "origin", Name: "origin",
@@ -67,8 +69,8 @@ var _ = Describe("Sync", func() {
}, },
status: &model.ServerStatus{}, status: &model.ServerStatus{},
safeSearch: &model.SafeSearchConfig{}, safeSearch: &model.SafeSearchConfig{},
queryLogConfig: &model.QueryLogConfig{}, queryLogConfig: &model.QueryLogConfigWithIgnored{},
statsConfig: &model.StatsConfig{}, statsConfig: &model.PutStatsConfigUpdateRequest{},
}, },
replicaStatus: &model.ServerStatus{}, replicaStatus: &model.ServerStatus{},
client: cl, client: cl,
@@ -258,16 +260,32 @@ var _ = Describe("Sync", func() {
err := actionProfileInfo(ac) err := actionProfileInfo(ac)
Ω(err).ShouldNot(HaveOccurred()) Ω(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() { It("should not sync profileInfo if language is not set", func() {
ac.origin.profileInfo.Language = "" ac.origin.profileInfo.Language = ""
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{Name: "replica", Language: "en", Theme: "auto"}, nil) cl.EXPECT().
ProfileInfo().
Return(&model.ProfileInfo{Name: "replica", Language: "en", Theme: "auto"}, nil)
cl.EXPECT().SetProfileInfo(ac.origin.profileInfo).Times(0) cl.EXPECT().SetProfileInfo(ac.origin.profileInfo).Times(0)
err := actionProfileInfo(ac) err := actionProfileInfo(ac)
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
}) })
It("should not sync profileInfo if theme is not set", func() { It("should not sync profileInfo if theme is not set", func() {
ac.origin.profileInfo.Theme = "" ac.origin.profileInfo.Theme = ""
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{Name: "replica", Language: "en", Theme: "auto"}, nil) cl.EXPECT().
ProfileInfo().
Return(&model.ProfileInfo{Name: "replica", Language: "en", Theme: "auto"}, nil)
cl.EXPECT().SetProfileInfo(ac.origin.profileInfo).Times(0) cl.EXPECT().SetProfileInfo(ac.origin.profileInfo).Times(0)
err := actionProfileInfo(ac) err := actionProfileInfo(ac)
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
@@ -289,9 +307,9 @@ var _ = Describe("Sync", func() {
}) })
}) })
Context("actionQueryLogConfig", func() { Context("actionQueryLogConfig", func() {
var qlc *model.QueryLogConfig var qlc *model.QueryLogConfigWithIgnored
BeforeEach(func() { BeforeEach(func() {
qlc = &model.QueryLogConfig{} qlc = &model.QueryLogConfigWithIgnored{}
}) })
It("should have no changes", func() { It("should have no changes", func() {
cl.EXPECT().QueryLogConfig().Return(qlc, nil) cl.EXPECT().QueryLogConfig().Return(qlc, nil)
@@ -302,15 +320,16 @@ var _ = Describe("Sync", func() {
var interval model.QueryLogConfigInterval = 123 var interval model.QueryLogConfigInterval = 123
ac.origin.queryLogConfig.Interval = &interval ac.origin.queryLogConfig.Interval = &interval
cl.EXPECT().QueryLogConfig().Return(qlc, nil) cl.EXPECT().QueryLogConfig().Return(qlc, nil)
cl.EXPECT().SetQueryLogConfig(&model.QueryLogConfig{AnonymizeClientIp: nil, Interval: &interval, Enabled: nil}) cl.EXPECT().
SetQueryLogConfig(&model.QueryLogConfigWithIgnored{QueryLogConfig: model.QueryLogConfig{AnonymizeClientIp: nil, Interval: &interval, Enabled: nil}})
err := actionQueryLogConfig(ac) err := actionQueryLogConfig(ac)
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
}) })
}) })
Context("syncConfigs", func() { Context("syncConfigs", func() {
var sc *model.StatsConfig var sc *model.PutStatsConfigUpdateRequest
BeforeEach(func() { BeforeEach(func() {
sc = &model.StatsConfig{} sc = &model.PutStatsConfigUpdateRequest{}
}) })
It("should have no changes", func() { It("should have no changes", func() {
cl.EXPECT().StatsConfig().Return(sc, nil) cl.EXPECT().StatsConfig().Return(sc, nil)
@@ -318,10 +337,10 @@ var _ = Describe("Sync", func() {
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
}) })
It("should have StatsConfig changes", func() { It("should have StatsConfig changes", func() {
var interval model.StatsConfigInterval = 123 var interval float32 = 123
ac.origin.statsConfig.Interval = &interval ac.origin.statsConfig.Interval = interval
cl.EXPECT().StatsConfig().Return(sc, nil) cl.EXPECT().StatsConfig().Return(sc, nil)
cl.EXPECT().SetStatsConfig(&model.StatsConfig{Interval: &interval}) cl.EXPECT().SetStatsConfig(&model.PutStatsConfigUpdateRequest{Interval: interval})
err := actionStatsConfig(ac) err := actionStatsConfig(ac)
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
}) })
@@ -359,26 +378,6 @@ var _ = Describe("Sync", func() {
Ω(st).Should(BeNil()) Ω(st).Should(BeNil())
}) })
}) })
Context("actionBlockedServices", func() {
var rbs *model.BlockedServicesArray
BeforeEach(func() {
ac.origin.blockedServices = &model.BlockedServicesArray{"foo"}
rbs = &model.BlockedServicesArray{"foo"}
})
It("should have no changes", func() {
cl.EXPECT().BlockedServices().Return(rbs, nil)
err := actionBlockedServices(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have blockedServices changes", func() {
ac.origin.blockedServices = &model.BlockedServicesArray{"bar"}
cl.EXPECT().BlockedServices().Return(rbs, nil)
cl.EXPECT().SetBlockedServices(ac.origin.blockedServices)
err := actionBlockedServices(ac)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("actionBlockedServicesSchedule", func() { Context("actionBlockedServicesSchedule", func() {
var rbss *model.BlockedServicesSchedule var rbss *model.BlockedServicesSchedule
BeforeEach(func() { BeforeEach(func() {
@@ -441,7 +440,9 @@ var _ = Describe("Sync", func() {
Ω(err).ShouldNot(HaveOccurred()) Ω(err).ShouldNot(HaveOccurred())
}) })
It("should update a filter", func() { It("should update a filter", func() {
ac.origin.filters.Filters = utils.Ptr([]model.Filter{{Name: "foo", Url: "https://foo.bar", Enabled: true}}) 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"}}) rf.Filters = utils.Ptr([]model.Filter{{Name: "foo", Url: "https://foo.bar"}})
cl.EXPECT().Filtering().Return(rf, nil) cl.EXPECT().Filtering().Return(rf, nil)
cl.EXPECT().UpdateFilter(false, model.Filter{Name: "foo", Url: "https://foo.bar", Enabled: true}) cl.EXPECT().UpdateFilter(false, model.Filter{Name: "foo", Url: "https://foo.bar", Enabled: true})
@@ -451,19 +452,25 @@ var _ = Describe("Sync", func() {
}) })
It("should abort after failed added filter", func() { It("should abort after failed added filter", func() {
ac.continueOnError = false ac.cfg.ContinueOnError = false
ac.origin.filters.Filters = utils.Ptr([]model.Filter{{Name: "foo", Url: "https://foo.bar"}}) ac.origin.filters.Filters = utils.Ptr([]model.Filter{{Name: "foo", Url: "https://foo.bar"}})
cl.EXPECT().Filtering().Return(rf, nil) 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: "foo", Url: "https://foo.bar"}).
Return(errors.New("test failure"))
err := actionFilters(ac) err := actionFilters(ac)
Ω(err).Should(HaveOccurred()) Ω(err).Should(HaveOccurred())
}) })
It("should continue after failed added filter", func() { It("should continue after failed added filter", func() {
ac.continueOnError = true ac.cfg.ContinueOnError = true
ac.origin.filters.Filters = utils.Ptr([]model.Filter{{Name: "foo", Url: "https://foo.bar"}, {Name: "bar", Url: "https://bar.foo"}}) 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().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: "foo", Url: "https://foo.bar"}).
Return(errors.New("test failure"))
cl.EXPECT().AddFilter(false, model.Filter{Name: "bar", Url: "https://bar.foo"}) cl.EXPECT().AddFilter(false, model.Filter{Name: "bar", Url: "https://bar.foo"})
cl.EXPECT().RefreshFilters(gm.Any()) cl.EXPECT().RefreshFilters(gm.Any())
err := actionFilters(ac) err := actionFilters(ac)
@@ -569,7 +576,7 @@ var _ = Describe("Sync", func() {
Context("sync", func() { Context("sync", func() {
BeforeEach(func() { BeforeEach(func() {
w.cfg = &types.Config{ w.cfg = &types.Config{
Origin: types.AdGuardInstance{}, Origin: &types.AdGuardInstance{},
Replica: &types.AdGuardInstance{URL: "foo"}, Replica: &types.AdGuardInstance{URL: "foo"},
Features: types.Features{ Features: types.Features{
DHCP: types.DHCP{ DHCP: types.DHCP{
@@ -592,19 +599,18 @@ var _ = Describe("Sync", func() {
}) })
It("should have no changes", func() { It("should have no changes", func() {
// origin // origin
cl.EXPECT().Host() cl.EXPECT().Host().Times(2)
cl.EXPECT().Status().Return(&model.ServerStatus{Version: versions.MinAgh}, nil) cl.EXPECT().Status().Return(&model.ServerStatus{Version: versions.MinAgh}, nil)
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{}, nil) cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{}, nil)
cl.EXPECT().Parental() cl.EXPECT().Parental()
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil) cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil)
cl.EXPECT().SafeBrowsing() cl.EXPECT().SafeBrowsing()
cl.EXPECT().RewriteList().Return(&model.RewriteEntries{}, nil) cl.EXPECT().RewriteList().Return(&model.RewriteEntries{}, nil)
cl.EXPECT().BlockedServices()
cl.EXPECT().BlockedServicesSchedule() cl.EXPECT().BlockedServicesSchedule()
cl.EXPECT().Filtering().Return(&model.FilterStatus{}, nil) cl.EXPECT().Filtering().Return(&model.FilterStatus{}, nil)
cl.EXPECT().Clients().Return(&model.Clients{}, nil) cl.EXPECT().Clients().Return(&model.Clients{}, nil)
cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfig{}, nil) cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfigWithIgnored{}, nil)
cl.EXPECT().StatsConfig().Return(&model.StatsConfig{}, nil) cl.EXPECT().StatsConfig().Return(&model.PutStatsConfigUpdateRequest{}, nil)
cl.EXPECT().AccessList().Return(&model.AccessList{}, nil) cl.EXPECT().AccessList().Return(&model.AccessList{}, nil)
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil) cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
cl.EXPECT().DhcpConfig().Return(&model.DhcpStatus{}, nil) cl.EXPECT().DhcpConfig().Return(&model.DhcpStatus{}, nil)
@@ -616,13 +622,12 @@ var _ = Describe("Sync", func() {
cl.EXPECT().Parental() cl.EXPECT().Parental()
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil) cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil)
cl.EXPECT().SafeBrowsing() cl.EXPECT().SafeBrowsing()
cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfig{}, nil) cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfigWithIgnored{}, nil)
cl.EXPECT().StatsConfig().Return(&model.StatsConfig{}, nil) cl.EXPECT().StatsConfig().Return(&model.PutStatsConfigUpdateRequest{}, nil)
cl.EXPECT().RewriteList().Return(&model.RewriteEntries{}, nil) cl.EXPECT().RewriteList().Return(&model.RewriteEntries{}, nil)
cl.EXPECT().AddRewriteEntries() cl.EXPECT().AddRewriteEntries()
cl.EXPECT().DeleteRewriteEntries() cl.EXPECT().DeleteRewriteEntries()
cl.EXPECT().Filtering().Return(&model.FilterStatus{}, nil) cl.EXPECT().Filtering().Return(&model.FilterStatus{}, nil)
cl.EXPECT().BlockedServices()
cl.EXPECT().BlockedServicesSchedule() cl.EXPECT().BlockedServicesSchedule()
cl.EXPECT().Clients().Return(&model.Clients{}, nil) cl.EXPECT().Clients().Return(&model.Clients{}, nil)
cl.EXPECT().AccessList().Return(&model.AccessList{}, nil) cl.EXPECT().AccessList().Return(&model.AccessList{}, nil)
@@ -634,19 +639,18 @@ var _ = Describe("Sync", func() {
w.cfg.Features.DHCP.ServerConfig = false w.cfg.Features.DHCP.ServerConfig = false
w.cfg.Features.DHCP.StaticLeases = false w.cfg.Features.DHCP.StaticLeases = false
// origin // origin
cl.EXPECT().Host() cl.EXPECT().Host().Times(2)
cl.EXPECT().Status().Return(&model.ServerStatus{Version: versions.MinAgh}, nil) cl.EXPECT().Status().Return(&model.ServerStatus{Version: versions.MinAgh}, nil)
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{}, nil) cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{}, nil)
cl.EXPECT().Parental() cl.EXPECT().Parental()
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil) cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil)
cl.EXPECT().SafeBrowsing() cl.EXPECT().SafeBrowsing()
cl.EXPECT().RewriteList().Return(&model.RewriteEntries{}, nil) cl.EXPECT().RewriteList().Return(&model.RewriteEntries{}, nil)
cl.EXPECT().BlockedServices()
cl.EXPECT().BlockedServicesSchedule() cl.EXPECT().BlockedServicesSchedule()
cl.EXPECT().Filtering().Return(&model.FilterStatus{}, nil) cl.EXPECT().Filtering().Return(&model.FilterStatus{}, nil)
cl.EXPECT().Clients().Return(&model.Clients{}, nil) cl.EXPECT().Clients().Return(&model.Clients{}, nil)
cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfig{}, nil) cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfigWithIgnored{}, nil)
cl.EXPECT().StatsConfig().Return(&model.StatsConfig{}, nil) cl.EXPECT().StatsConfig().Return(&model.PutStatsConfigUpdateRequest{}, nil)
cl.EXPECT().AccessList().Return(&model.AccessList{}, nil) cl.EXPECT().AccessList().Return(&model.AccessList{}, nil)
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil) cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
@@ -657,13 +661,12 @@ var _ = Describe("Sync", func() {
cl.EXPECT().Parental() cl.EXPECT().Parental()
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil) cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil)
cl.EXPECT().SafeBrowsing() cl.EXPECT().SafeBrowsing()
cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfig{}, nil) cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfigWithIgnored{}, nil)
cl.EXPECT().StatsConfig().Return(&model.StatsConfig{}, nil) cl.EXPECT().StatsConfig().Return(&model.PutStatsConfigUpdateRequest{}, nil)
cl.EXPECT().RewriteList().Return(&model.RewriteEntries{}, nil) cl.EXPECT().RewriteList().Return(&model.RewriteEntries{}, nil)
cl.EXPECT().AddRewriteEntries() cl.EXPECT().AddRewriteEntries()
cl.EXPECT().DeleteRewriteEntries() cl.EXPECT().DeleteRewriteEntries()
cl.EXPECT().Filtering().Return(&model.FilterStatus{}, nil) cl.EXPECT().Filtering().Return(&model.FilterStatus{}, nil)
cl.EXPECT().BlockedServices()
cl.EXPECT().BlockedServicesSchedule() cl.EXPECT().BlockedServicesSchedule()
cl.EXPECT().Clients().Return(&model.Clients{}, nil) cl.EXPECT().Clients().Return(&model.Clients{}, nil)
cl.EXPECT().AccessList().Return(&model.AccessList{}, nil) cl.EXPECT().AccessList().Return(&model.AccessList{}, nil)
@@ -685,18 +688,17 @@ var _ = Describe("Sync", func() {
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil) cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil)
cl.EXPECT().SafeBrowsing() cl.EXPECT().SafeBrowsing()
cl.EXPECT().RewriteList().Return(&model.RewriteEntries{}, nil) cl.EXPECT().RewriteList().Return(&model.RewriteEntries{}, nil)
cl.EXPECT().BlockedServices()
cl.EXPECT().BlockedServicesSchedule() cl.EXPECT().BlockedServicesSchedule()
cl.EXPECT().Filtering().Return(&model.FilterStatus{}, nil) cl.EXPECT().Filtering().Return(&model.FilterStatus{}, nil)
cl.EXPECT().Clients().Return(&model.Clients{}, nil) cl.EXPECT().Clients().Return(&model.Clients{}, nil)
cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfig{}, nil) cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfigWithIgnored{}, nil)
cl.EXPECT().StatsConfig().Return(&model.StatsConfig{}, nil) cl.EXPECT().StatsConfig().Return(&model.PutStatsConfigUpdateRequest{}, nil)
cl.EXPECT().AccessList().Return(&model.AccessList{}, nil) cl.EXPECT().AccessList().Return(&model.AccessList{}, nil)
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil) cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
cl.EXPECT().DhcpConfig().Return(&model.DhcpStatus{}, nil) cl.EXPECT().DhcpConfig().Return(&model.DhcpStatus{}, nil)
// replica // replica
cl.EXPECT().Host() cl.EXPECT().Host().Times(2)
cl.EXPECT().Status().Return(&model.ServerStatus{Version: "v0.106.9"}, nil) cl.EXPECT().Status().Return(&model.ServerStatus{Version: "v0.106.9"}, nil)
w.sync() w.sync()
}) })

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

View File

@@ -1,58 +0,0 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
// Code generated by deepcopy-gen. DO NOT EDIT.
package types
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AdGuardInstance) DeepCopyInto(out *AdGuardInstance) {
*out = *in
if in.DHCPServerEnabled != nil {
in, out := &in.DHCPServerEnabled, &out.DHCPServerEnabled
*out = new(bool)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdGuardInstance.
func (in *AdGuardInstance) DeepCopy() *AdGuardInstance {
if in == nil {
return nil
}
out := new(AdGuardInstance)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Config) DeepCopyInto(out *Config) {
*out = *in
in.Origin.DeepCopyInto(&out.Origin)
if in.Replica != nil {
in, out := &in.Replica, &out.Replica
*out = new(AdGuardInstance)
(*in).DeepCopyInto(*out)
}
if in.Replicas != nil {
in, out := &in.Replicas, &out.Replicas
*out = make([]AdGuardInstance, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
out.API = in.API
out.Features = in.Features
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Config.
func (in *Config) DeepCopy() *Config {
if in == nil {
return nil
}
out := new(Config)
in.DeepCopyInto(out)
return out
}

View File

@@ -21,35 +21,37 @@ func NewFeatures(enabled bool) Features {
ClientSettings: enabled, ClientSettings: enabled,
Services: enabled, Services: enabled,
Filters: enabled, Filters: enabled,
Theme: enabled,
} }
} }
// Features feature flags // Features feature flags.
type Features struct { type Features struct {
DNS DNS `json:"dns" yaml:"dns"` DNS DNS `json:"dns" yaml:"dns"`
DHCP DHCP `json:"dhcp" yaml:"dhcp"` DHCP DHCP `json:"dhcp" yaml:"dhcp"`
GeneralSettings bool `json:"generalSettings" yaml:"generalSettings" env:"FEATURES_GENERAL_SETTINGS"` GeneralSettings bool `json:"generalSettings" yaml:"generalSettings" documentation:"Sync general settings" env:"FEATURES_GENERAL_SETTINGS"`
QueryLogConfig bool `json:"queryLogConfig" yaml:"queryLogConfig" env:"FEATURES_QUERY_LOG_CONFIG"` QueryLogConfig bool `json:"queryLogConfig" yaml:"queryLogConfig" documentation:"Sync query log config" env:"FEATURES_QUERY_LOG_CONFIG"`
StatsConfig bool `json:"statsConfig" yaml:"statsConfig" env:"FEATURES_STATS_CONFIG"` StatsConfig bool `json:"statsConfig" yaml:"statsConfig" documentation:"Sync stats config" env:"FEATURES_STATS_CONFIG"`
ClientSettings bool `json:"clientSettings" yaml:"clientSettings" env:"FEATURES_CLIENT_SETTINGS"` ClientSettings bool `json:"clientSettings" yaml:"clientSettings" documentation:"Sync client settings" env:"FEATURES_CLIENT_SETTINGS"`
Services bool `json:"services" yaml:"services" env:"FEATURES_SERVICES"` Services bool `json:"services" yaml:"services" documentation:"Sync services" env:"FEATURES_SERVICES"`
Filters bool `json:"filters" yaml:"filters" env:"FEATURES_FILTERS"` Filters bool `json:"filters" yaml:"filters" documentation:"Sync filters" env:"FEATURES_FILTERS"`
Theme bool `json:"theme" yaml:"theme" documentation:"Sync the weg UI theme" env:"FEATURES_THEME"`
} }
// DHCP features // DHCP features.
type DHCP struct { type DHCP struct {
ServerConfig bool `json:"serverConfig" yaml:"serverConfig" env:"FEATURES_DHCP_SERVER_CONFIG"` ServerConfig bool `documentation:"Sync DHCP server config" env:"FEATURES_DHCP_SERVER_CONFIG" json:"serverConfig" yaml:"serverConfig"`
StaticLeases bool `json:"staticLeases" yaml:"staticLeases" env:"FEATURES_DHCP_STATIC_LEASES"` StaticLeases bool `documentation:"Sync DHCP static leases" env:"FEATURES_DHCP_STATIC_LEASES" json:"staticLeases" yaml:"staticLeases"`
} }
// DNS features // DNS features.
type DNS struct { type DNS struct {
AccessLists bool `json:"accessLists" yaml:"accessLists" env:"FEATURES_DNS_ACCESS_LISTS"` AccessLists bool `documentation:"Sync DNS access lists" env:"FEATURES_DNS_ACCESS_LISTS" json:"accessLists" yaml:"accessLists"`
ServerConfig bool `json:"serverConfig" yaml:"serverConfig" env:"FEATURES_DNS_SERVER_CONFIG"` ServerConfig bool `documentation:"Sync DNS server config" env:"FEATURES_DNS_SERVER_CONFIG" json:"serverConfig" yaml:"serverConfig"`
Rewrites bool `json:"rewrites" yaml:"rewrites" env:"FEATURES_DNS_REWRITES"` Rewrites bool `documentation:"Sync DNS rewrites" env:"FEATURES_DNS_REWRITES" json:"rewrites" yaml:"rewrites"`
} }
// LogDisabled log all disabled features // LogDisabled log all disabled features.
func (f *Features) LogDisabled(l *zap.SugaredLogger) { func (f *Features) LogDisabled(l *zap.SugaredLogger) {
features := f.collectDisabled() features := f.collectDisabled()

View File

@@ -1,47 +1,87 @@
// Package types
// +kubebuilder:object:generate=true
package types package types
import ( import (
"fmt" "fmt"
"net/url" "net/url"
"path/filepath"
"strings" "strings"
"time"
"go.uber.org/zap" "go.uber.org/zap"
) )
const ( const (
// DefaultAPIPath default api path // DefaultAPIPath default api path.
DefaultAPIPath = "/control" DefaultAPIPath = "/control"
) )
// Config application configuration struct // Config application configuration struct
// +k8s:deepcopy-gen=true // +k8s:deepcopy-gen=true
type Config struct { type Config struct {
Origin AdGuardInstance `json:"origin" yaml:"origin" env:"ORIGIN"` // Origin adguardhome instance
Replica *AdGuardInstance `json:"replica,omitempty" yaml:"replica,omitempty" env:"REPLICA"` Origin *AdGuardInstance `json:"origin" yaml:"origin"`
Replicas []AdGuardInstance `json:"replicas,omitempty" yaml:"replicas,omitempty"` // One single replica adguardhome instance
Cron string `json:"cron,omitempty" yaml:"cron,omitempty" env:"CRON"` Replica *AdGuardInstance `json:"replica,omitempty" yaml:"replica,omitempty"`
RunOnStart bool `json:"runOnStart,omitempty" yaml:"runOnStart,omitempty" env:"RUN_ON_START"` // Multiple replica instances
PrintConfigOnly bool `json:"printConfigOnly,omitempty" yaml:"printConfigOnly,omitempty" env:"PRINT_CONFIG_ONLY"` Replicas []AdGuardInstance `json:"replicas,omitempty" yaml:"replicas,omitempty" faker:"slice_len=2"`
ContinueOnError bool `json:"continueOnError,omitempty" yaml:"continueOnError,omitempty" env:"CONTINUE_ON_ERROR"` Cron string `json:"cron,omitempty" yaml:"cron,omitempty" documentation:"Cron expression for the sync interval" env:"CRON"`
API API `json:"api,omitempty" yaml:"api,omitempty" env:"API"` RunOnStart bool `json:"runOnStart,omitempty" yaml:"runOnStart,omitempty" documentation:"Run the sung on startup" env:"RUN_ON_START"`
Features Features `json:"features,omitempty" yaml:"features,omitempty" env:"FEATURES_"` 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 // API configuration.
type API struct { type API struct {
Port int `json:"port,omitempty" yaml:"port,omitempty" env:"API_PORT"` Port int `documentation:"API port" env:"API_PORT" json:"port,omitempty" yaml:"port,omitempty"`
Username string `json:"username,omitempty" yaml:"username,omitempty" env:"API_USERNAME"` Username string `documentation:"API username" env:"API_USERNAME" json:"username,omitempty" yaml:"username,omitempty"`
Password string `json:"password,omitempty" yaml:"password,omitempty" env:"API_PASSWORD"` Password string `documentation:"API password" env:"API_PASSWORD" json:"password,omitempty" yaml:"password,omitempty"`
DarkMode bool `json:"darkMode,omitempty" yaml:"darkMode,omitempty" env:"API_DARK_MODE"` 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"`
} }
// Mask maks username and password // 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() { func (a *API) Mask() {
a.Username = mask(a.Username) a.Username = mask(a.Username)
a.Password = mask(a.Password) a.Password = mask(a.Password)
} }
// UniqueReplicas get unique replication instances // UniqueReplicas get unique replication instances.
func (cfg *Config) UniqueReplicas() []AdGuardInstance { func (cfg *Config) UniqueReplicas() []AdGuardInstance {
dedup := make(map[string]AdGuardInstance) dedup := make(map[string]AdGuardInstance)
if cfg.Replica != nil && cfg.Replica.URL != "" { if cfg.Replica != nil && cfg.Replica.URL != "" {
@@ -66,7 +106,7 @@ func (cfg *Config) UniqueReplicas() []AdGuardInstance {
return r return r
} }
// Log the current config // Log the current config.
func (cfg *Config) Log(l *zap.SugaredLogger) { func (cfg *Config) Log(l *zap.SugaredLogger) {
c := cfg.mask() c := cfg.mask()
l.With("config", c).Debug("Using config") l.With("config", c).Debug("Using config")
@@ -105,27 +145,27 @@ func (cfg *Config) Init() error {
// AdGuardInstance AdguardHome config instance // AdGuardInstance AdguardHome config instance
// +k8s:deepcopy-gen=true // +k8s:deepcopy-gen=true
type AdGuardInstance struct { type AdGuardInstance struct {
URL string `json:"url" yaml:"url" env:"URL"` URL string `documentation:"URL of adguardhome instance" env:"URL" faker:"url" json:"url" yaml:"url"`
WebURL string `json:"webURL" yaml:"webURL" env:"WEB_URL"` WebURL string `documentation:"Web URL of adguardhome instance" env:"WEB_URL" faker:"url" json:"webURL" yaml:"webURL"`
APIPath string `json:"apiPath,omitempty" yaml:"apiPath,omitempty" env:"API_PATH"` APIPath string `documentation:"API Path" env:"API_PATH" json:"apiPath,omitempty" yaml:"apiPath,omitempty"`
Username string `json:"username,omitempty" yaml:"username,omitempty" env:"USERNAME"` Username string `documentation:"Adguardhome username" env:"USERNAME" json:"username,omitempty" yaml:"username,omitempty"`
Password string `json:"password,omitempty" yaml:"password,omitempty" env:"PASSWORD"` Password string `documentation:"Adguardhome password" env:"PASSWORD" json:"password,omitempty" yaml:"password,omitempty"`
Cookie string `json:"cookie,omitempty" yaml:"cookie,omitempty" env:"COOKIE"` Cookie string `documentation:"Adguardhome cookie" env:"COOKIE" json:"cookie,omitempty" yaml:"cookie,omitempty"`
InsecureSkipVerify bool `json:"insecureSkipVerify" yaml:"insecureSkipVerify" env:"INSECURE_SKIP_VERIFY"` InsecureSkipVerify bool `documentation:"Skip TLS verification" env:"INSECURE_SKIP_VERIFY" json:"insecureSkipVerify" yaml:"insecureSkipVerify"`
AutoSetup bool `json:"autoSetup" yaml:"autoSetup" env:"AUTO_SETUP"` AutoSetup bool `documentation:"Automatically setup the instance if it is not initialized" env:"AUTO_SETUP" json:"autoSetup" yaml:"autoSetup"`
InterfaceName string `json:"interfaceName,omitempty" yaml:"interfaceName,omitempty" env:"INTERFACE_NAME"` InterfaceName string `documentation:"Network interface name" env:"INTERFACE_NAME" json:"interfaceName,omitempty" yaml:"interfaceName,omitempty"`
DHCPServerEnabled *bool `json:"dhcpServerEnabled,omitempty" yaml:"dhcpServerEnabled,omitempty" env:"DHCP_SERVER_ENABLED"` DHCPServerEnabled *bool `documentation:"Enable DHCP server" env:"DHCP_SERVER_ENABLED" json:"dhcpServerEnabled,omitempty" yaml:"dhcpServerEnabled,omitempty"`
Host string `json:"-" yaml:"-"` Host string `json:"-" yaml:"-"`
WebHost string `json:"-" yaml:"-"` WebHost string `json:"-" yaml:"-"`
} }
// Key AdGuardInstance key // Key AdGuardInstance key.
func (i *AdGuardInstance) Key() string { func (i *AdGuardInstance) Key() string {
return fmt.Sprintf("%s#%s", i.URL, i.APIPath) return fmt.Sprintf("%s#%s", i.URL, i.APIPath)
} }
// Mask maks username and password // Mask maks username and password.
func (i *AdGuardInstance) Mask() { func (i *AdGuardInstance) Mask() {
i.Username = mask(i.Username) i.Username = mask(i.Username)
i.Password = mask(i.Password) i.Password = mask(i.Password)
@@ -152,19 +192,19 @@ func (i *AdGuardInstance) Init() error {
} }
func mask(s string) string { func mask(s string) string {
if s == "" { if len(s) < 3 {
return "***" return strings.Repeat("*", len(s))
} }
mask := strings.Repeat("*", len(s)-2) mask := strings.Repeat("*", len(s)-2)
return fmt.Sprintf("%v%s%v", string(s[0]), mask, string(s[len(s)-1])) return fmt.Sprintf("%v%s%v", string(s[0]), mask, string(s[len(s)-1]))
} }
// Protection API struct // Protection API struct.
type Protection struct { type Protection struct {
ProtectionEnabled bool `json:"protection_enabled"` ProtectionEnabled bool `json:"protection_enabled"`
} }
// InstallConfig AdguardHome install config // InstallConfig AdguardHome install config.
type InstallConfig struct { type InstallConfig struct {
Web InstallPort `json:"web"` Web InstallPort `json:"web"`
DNS InstallPort `json:"dns"` DNS InstallPort `json:"dns"`
@@ -172,7 +212,7 @@ type InstallConfig struct {
Password string `json:"password"` Password string `json:"password"`
} }
// InstallPort AdguardHome install config port // InstallPort AdguardHome install config port.
type InstallPort struct { type InstallPort struct {
IP string `json:"ip"` IP string `json:"ip"`
Port int `json:"port"` Port int `json:"port"`

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

@@ -1,6 +1,8 @@
package types package types
import ( import (
"strings"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )
@@ -39,6 +41,7 @@ var _ = Describe("Types", func() {
Context("Config", func() { Context("Config", func() {
Context("init", func() { Context("init", func() {
cfg := Config{ cfg := Config{
Origin: &AdGuardInstance{},
Replicas: []AdGuardInstance{ Replicas: []AdGuardInstance{
{URL: "https://localhost:3000"}, {URL: "https://localhost:3000"},
}, },
@@ -53,6 +56,7 @@ var _ = Describe("Types", func() {
Context("UniqueReplicas", func() { Context("UniqueReplicas", func() {
It("should return unique replicas in the array", func() { It("should return unique replicas in the array", func() {
cfg := Config{ cfg := Config{
Origin: &AdGuardInstance{},
Replicas: []AdGuardInstance{ Replicas: []AdGuardInstance{
{URL: "a"}, {URL: "a"},
{URL: "a", APIPath: DefaultAPIPath}, {URL: "a", APIPath: DefaultAPIPath},
@@ -68,6 +72,7 @@ var _ = Describe("Types", func() {
Context("mask", func() { Context("mask", func() {
It("should mask all names and passwords", func() { It("should mask all names and passwords", func() {
cfg := Config{ cfg := Config{
Origin: &AdGuardInstance{},
Replicas: []AdGuardInstance{ Replicas: []AdGuardInstance{
{URL: "a", Username: "user", Password: "pass"}, {URL: "a", Username: "user", Password: "pass"},
}, },
@@ -83,6 +88,15 @@ var _ = Describe("Types", func() {
Ω(masked.API.Password).Should(Equal("p**s")) Ω(masked.API.Password).Should(Equal("p**s"))
}) })
}) })
DescribeTable("mask should work correctly",
func(value, expected string) {
Ω(mask(value)).Should(Equal(expected))
},
Entry(`Empty password`, "", ""),
Entry(`1 char password`, "a", "*"),
Entry(`2 char password`, "ab", "**"),
Entry(`3 char password`, "abc", "a*c"),
)
}) })
Context("Feature", func() { Context("Feature", func() {
Context("LogDisabled", func() { Context("LogDisabled", func() {
@@ -96,4 +110,43 @@ var _ = Describe("Types", func() {
}) })
}) })
}) })
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,202 @@
//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.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
}

View File

@@ -1,15 +1,18 @@
package utils package utils
import "encoding/json" import (
"bytes"
"encoding/json"
)
func Clone[I interface{}](in I, out I) I { func Clone[I any](in, out I) I {
b, _ := json.Marshal(in) b, _ := json.Marshal(in)
_ = json.Unmarshal(b, out) _ = json.Unmarshal(b, out)
return out return out
} }
func JsonEquals(a interface{}, b interface{}) bool { func JSONEquals(a, b any) bool {
ja, _ := json.Marshal(a) ja, _ := json.Marshal(a)
jb, _ := json.Marshal(b) jb, _ := json.Marshal(b)
return string(ja) == string(jb) return bytes.Equal(ja, jb)
} }

View File

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

View File

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

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/pkg/versions"
) )
var _ = Describe("Versions", func() { var _ = Describe("Versions", func() {

28
renovate.json Normal file
View File

@@ -0,0 +1,28 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"customManagers": [
{
"customType": "regex",
"datasourceTemplate": "go",
"description": "Update toolbox tools in .toolbox.mk",
"fileMatch": [
"^\\.toolbox\\.mk$"
],
"matchStrings": [
"# renovate: packageName=(?<packageName>.+?)\\s+.+?_VERSION \\?= (?<currentValue>.+?)\\s"
]
},
{
"customType": "regex",
"datasourceTemplate": "github-releases",
"description": "Update github _VERSION Makefile",
"fileMatch": [
"^Makefile$"
],
"matchStrings": [
"# renovate: packageName=(?<packageName>.+?)\\s+.+?_VERSION \\?= (?<currentValue>.+?)\\s"
]
}
],
"dependencyDashboard": true
}

View File

@@ -0,0 +1,31 @@
<!-- PLEASE COPY THE FOLLOWING OUTPUT AS IS INTO THE GITHUB ISSUE (Don't forget to mask your usernames, passwords, IPs and other sensitive information when using this in an issue ) -->
### Runtime
AdguardHome-Sync Version: %s
Build: %s
OperatingSystem: %s
Architecture: %s
OriginVersion: v0.0.1
ReplicaVersions:
- Replica 1: v0.0.2
### AdGuardHome sync aggregated config
```yaml
origin:
url: https://ha.xxxx.net:3000
webURL: ""
insecureSkipVerify: false
autoSetup: false
```
### Environment Variables
```ini
BAR=bar
FOO=foo
```
<!-- END OF GITHUB ISSUE CONTENT -->

View File

@@ -0,0 +1,41 @@
<!-- PLEASE COPY THE FOLLOWING OUTPUT AS IS INTO THE GITHUB ISSUE (Don't forget to mask your usernames, passwords, IPs and other sensitive information when using this in an issue ) -->
### Runtime
AdguardHome-Sync Version: %s
Build: %s
OperatingSystem: %s
Architecture: %s
OriginVersion: v0.0.1
ReplicaVersions:
- Replica 1: v0.0.2
### AdGuardHome sync aggregated config
```yaml
origin:
url: https://ha.xxxx.net:3000
webURL: ""
insecureSkipVerify: false
autoSetup: false
```
### AdGuardHome sync unmodified config file
Config file path: config.yaml
```yaml
origin:
url: https://ha.xxxx.net:3000
```
### Environment Variables
```ini
BAR=bar
FOO=foo
```
<!-- END OF GITHUB ISSUE CONTENT -->

View File

@@ -15,6 +15,7 @@ replica:
insecureSkipVerify: false insecureSkipVerify: false
autoSetup: false autoSetup: false
interfaceName: eth3 interfaceName: eth3
dhcpServerEnabled: false
cron: '*/15 * * * *' cron: '*/15 * * * *'
runOnStart: true runOnStart: true
printConfigOnly: true printConfigOnly: true
@@ -34,3 +35,4 @@ features:
clientSettings: true clientSettings: true
services: true services: true
filters: true filters: true
theme: true

View File

@@ -15,6 +15,7 @@ replicas:
insecureSkipVerify: false insecureSkipVerify: false
autoSetup: false autoSetup: false
interfaceName: eth3 interfaceName: eth3
dhcpServerEnabled: false
cron: '*/15 * * * *' cron: '*/15 * * * *'
runOnStart: true runOnStart: true
printConfigOnly: true printConfigOnly: true
@@ -34,3 +35,4 @@ features:
clientSettings: true clientSettings: true
services: true services: true
filters: true filters: true
theme: true

View File

@@ -15,6 +15,7 @@ replica:
insecureSkipVerify: false insecureSkipVerify: false
autoSetup: false autoSetup: false
interfaceName: eth3 interfaceName: eth3
dhcpServerEnabled: false
replicas: replicas:
- url: https://replicas-file:443 - url: https://replicas-file:443
webURL: https://replicas-file:443 webURL: https://replicas-file:443
@@ -24,6 +25,7 @@ replicas:
insecureSkipVerify: false insecureSkipVerify: false
autoSetup: false autoSetup: false
interfaceName: eth3 interfaceName: eth3
dhcpServerEnabled: false
cron: '*/15 * * * *' cron: '*/15 * * * *'
runOnStart: true runOnStart: true
printConfigOnly: true printConfigOnly: true
@@ -43,3 +45,4 @@ features:
clientSettings: true clientSettings: true
services: true services: true
filters: true filters: true
theme: true

View File

@@ -7,3 +7,4 @@ if [[ $(helm list --no-headers -n agh-e2e | grep agh-e2e | wc -l) == "1" ]]; the
helm delete agh-e2e -n agh-e2e --wait helm delete agh-e2e -n agh-e2e --wait
fi fi
helm install agh-e2e testdata/e2e -n agh-e2e --create-namespace --set mode=${1} helm install agh-e2e testdata/e2e -n agh-e2e --create-namespace --set mode=${1}

View File

@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
set -e set -e
echo "## Pod adguardhome-origin logs" >> $GITHUB_STEP_SUMMARY echo "## Pod adguardhome-origin ${1} logs" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY
kubectl logs adguardhome-origin >> $GITHUB_STEP_SUMMARY kubectl logs adguardhome-origin >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY

View File

@@ -4,15 +4,18 @@ set -e
for pod in $(kubectl get pods -l bakito.net/adguardhome-sync=replica -o name); do for pod in $(kubectl get pods -l bakito.net/adguardhome-sync=replica -o name); do
echo "## Pod ${pod} logs" >> $GITHUB_STEP_SUMMARY echo "## Pod ${pod} logs" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY
LOGS=$(kubectl logs ${pod}) K8S_LOGS=$(kubectl logs ${pod})
# ignore certain errors # ignore certain errors
LOGS=$(echo -e "${LOGS}" | grep -v -e "error.* deleting filter .* no such file or directory" ) LOGS=$(echo -e "${K8S_LOGS}" |
# https://github.com/AdguardTeam/AdGuardHome/issues/4944 grep -v -e "error.* deleting filter .* no such file or directory" |
LOGS=$(echo -e "${LOGS}" | grep -v -e "error.* creating dhcpv4 srv") grep -v -e '\[error\] storage: recovered from panic: runtime' # https://github.com/AdguardTeam/AdGuardHome/issues/7315
echo -e "${LOGS}" >> $GITHUB_STEP_SUMMARY )
ERRORS=$(echo -e "${LOGS}"} | grep '\[error\]' | wc -l)
echo '```' >> $GITHUB_STEP_SUMMARY
echo "Found ${ERRORS} error(s) in ${pod} log" >> $GITHUB_STEP_SUMMARY
echo "----------------------------------------------" >> $GITHUB_STEP_SUMMARY
echo -e "${K8S_LOGS}" >> $GITHUB_STEP_SUMMARY
ERRORS=$(echo -e "${LOGS}"} | grep '\[error\]' | wc -l)
TOTAL_ERRORS=$(echo -e "${K8S_LOGS}"} | grep '\[error\]' | wc -l)
IGNORED_ERRORS=$(echo "${TOTAL_ERRORS} - ${ERRORS}" | bc)
echo '```' >> $GITHUB_STEP_SUMMARY
echo "Found ${ERRORS} error(s) (${IGNORED_ERRORS} ignored) in ${pod} log" >> $GITHUB_STEP_SUMMARY
echo "----------------------------------------------" >> $GITHUB_STEP_SUMMARY
done done

11
testdata/e2e/bin/show-sync-metrics.sh vendored Executable file
View File

@@ -0,0 +1,11 @@
#!/bin/bash
set -e
echo "wait another scrape interval (30s)"
sleep 30
echo "## Pod adguardhome-sync metrics" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
curl ${1}://localhost:9090/metrics -s -k >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY

3
testdata/e2e/bin/wait-for-start.sh vendored Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/bash
kubectl wait --for=jsonpath='{.status.phase}'=Running pod/adguardhome-sync --timeout=1m

View File

@@ -1,8 +1,12 @@
#!/bin/bash #!/bin/bash
kubectl wait --for=jsonpath='{.status.phase}'=Succeeded pod/adguardhome-sync --timeout=1m kubectl port-forward pod/adguardhome-sync 9090:9090 &
RESULT=$?
if [[ "${RESULT}" != "0" ]]; then for i in {1..6}; do
kubectl logs adguardhome-sync sleep 10
fi RUNNING=$(curl ${1}://localhost:9090/api/v1/status -s -k | jq -r .syncRunning)
exit ${RESULT} echo "SyncRunning = ${RUNNING}"
if [[ "${RUNNING}" == "false" ]]; then
exit 0
fi
done

View File

@@ -18,19 +18,18 @@ dns:
port: 53 port: 53
anonymize_client_ip: false anonymize_client_ip: false
ratelimit: 20 ratelimit: 20
ratelimit_whitelist: [ ] ratelimit_whitelist: []
refuse_any: true refuse_any: true
upstream_dns: upstream_dns:
- https://dns10.quad9.net/dns-query - 8.8.8.8
upstream_dns_file: "" upstream_dns_file: ""
bootstrap_dns: bootstrap_dns:
- 1.1.1.1:53 - 1.1.1.1:53
fallback_dns: [ ] fallback_dns: []
all_servers: false upstream_mode: load_balance
fastest_addr: false
fastest_timeout: 1s fastest_timeout: 1s
allowed_clients: [ ] allowed_clients: []
disallowed_clients: [ ] disallowed_clients: []
blocked_hosts: blocked_hosts:
- version.bind - version.bind
- id.server - id.server
@@ -42,7 +41,7 @@ dns:
cache_ttl_min: 0 cache_ttl_min: 0
cache_ttl_max: 0 cache_ttl_max: 0
cache_optimistic: true cache_optimistic: true
bogus_nxdomain: [ ] bogus_nxdomain: []
aaaa_disabled: false aaaa_disabled: false
enable_dnssec: false enable_dnssec: false
edns_client_subnet: edns_client_subnet:
@@ -51,17 +50,19 @@ dns:
use_custom: false use_custom: false
max_goroutines: 300 max_goroutines: 300
handle_ddr: true handle_ddr: true
ipset: [ ] ipset: []
ipset_file: "" ipset_file: ""
bootstrap_prefer_ipv6: false bootstrap_prefer_ipv6: false
upstream_timeout: 10s upstream_timeout: 10s
private_networks: [ ] private_networks: []
use_private_ptr_resolvers: true use_private_ptr_resolvers: true
local_ptr_upstreams: [ ] local_ptr_upstreams: []
use_dns64: false use_dns64: false
dns64_prefixes: [ ] dns64_prefixes: []
serve_http3: false serve_http3: false
use_http3_upstreams: false use_http3_upstreams: false
serve_plain_dns: true
hostsfile_enabled: true
tls: tls:
enabled: false enabled: false
server_name: "" server_name: ""
@@ -78,13 +79,15 @@ tls:
private_key_path: "" private_key_path: ""
strict_sni_check: false strict_sni_check: false
querylog: querylog:
ignored: [ ] dir_path: ""
ignored: []
interval: 6h interval: 6h
size_memory: 1000 size_memory: 1000
enabled: true enabled: true
file_enabled: true file_enabled: true
statistics: statistics:
ignored: [ ] dir_path: ""
ignored: []
interval: 24h interval: 24h
enabled: true enabled: true
filters: filters:
@@ -96,7 +99,7 @@ filters:
url: https://adaway.org/hosts.txt url: https://adaway.org/hosts.txt
name: AdAway Default Blocklist name: AdAway Default Blocklist
id: 2 id: 2
whitelist_filters: [ ] whitelist_filters: []
user_rules: user_rules:
- '||metrics2.data.hicloud.com^$important' - '||metrics2.data.hicloud.com^$important'
- '||www.curiouscorrespondence.com^$important' - '||www.curiouscorrespondence.com^$important'
@@ -113,7 +116,7 @@ dhcp:
range_end: 1.2.3.56 range_end: 1.2.3.56
lease_duration: 86400 lease_duration: 86400
icmp_timeout_msec: 1000 icmp_timeout_msec: 1000
options: [ ] options: []
dhcpv6: dhcpv6:
range_start: "" range_start: ""
lease_duration: 86400 lease_duration: 86400
@@ -150,7 +153,7 @@ filtering:
blocking_mode: default blocking_mode: default
parental_block_host: family-block.dns.adguard.com parental_block_host: family-block.dns.adguard.com
safebrowsing_block_host: standard-block.dns.adguard.com safebrowsing_block_host: standard-block.dns.adguard.com
rewrites: [ ] rewrites: []
safebrowsing_cache_size: 1048576 safebrowsing_cache_size: 1048576
safesearch_cache_size: 1048576 safesearch_cache_size: 1048576
parental_cache_size: 1048576 parental_cache_size: 1048576
@@ -170,8 +173,6 @@ clients:
hosts: true hosts: true
persistent: persistent:
- name: Device 1 - name: Device 1
tags:
- device_1
ids: ids:
- 2.2.2.2 - 2.2.2.2
blocked_services: blocked_services:
@@ -183,7 +184,7 @@ clients:
- qq - qq
- vk - vk
- ok - ok
upstreams: [ ] upstreams: []
use_global_settings: true use_global_settings: true
filtering_enabled: false filtering_enabled: false
parental_enabled: false parental_enabled: false
@@ -192,6 +193,7 @@ clients:
ignore_querylog: false ignore_querylog: false
ignore_statistics: false ignore_statistics: false
log: log:
enabled: true
file: "" file: ""
max_backups: 0 max_backups: 0
max_size: 100 max_size: 100
@@ -203,4 +205,4 @@ os:
group: "" group: ""
user: "" user: ""
rlimit_nofile: 0 rlimit_nofile: 0
schema_version: 27 schema_version: 28

View File

@@ -0,0 +1,93 @@
{{- if eq .Values.mode "env" }}
apiVersion: v1
kind: ConfigMap
metadata:
name: certs
namespace: {{ .Release.Namespace }}
data:
my.crt: |
-----BEGIN CERTIFICATE-----
MIIE7zCCAtcCFFthw5GxzLaZC/pTTlbwju6vYr9pMA0GCSqGSIb3DQEBCwUAMDMx
CzAJBgNVBAYTAkNIMRMwEQYDVQQIDApTb21lLVN0YXRlMQ8wDQYDVQQKDAZiYWtp
dG8wIBcNMjQwNDA0MDUxOTMyWhgPMjEyNDAzMTEwNTE5MzJaMDMxCzAJBgNVBAYT
AkNIMRMwEQYDVQQIDApTb21lLVN0YXRlMQ8wDQYDVQQKDAZiYWtpdG8wggIiMA0G
CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCenHJ0SunHqdtBdG8d5Uy8y5KESOPE
GCpcjNetSR53binlV3QjVckrA+6tHQBR3yXpSgHWU424fR3eTdHsbS9/8/pAnV0V
CAaUntbCr5CJ8ww2zRlGSz6IlxkwJkqx4crmYUcTm6XNcEm5kSoBmFR1s8TiQAQ8
CnJyt4m3S5jSKnexAMi2l2LP4F5P5MwLHdpl2sfRYydWOoJty9nSYuRxarv7G7e3
N102voL3W2F9Dk8QbKF1Kzxcfmh+4twNz/YfUxxB3tggrCGHfMu4EcvMrFCkKhNv
sFcG9aZ0gNj4x54vCOMK+LuVd8JZQMcGBdcxZUq+q4nyj4dxFDKSUjZ+nVjdbuab
dk72dVbv/r2j6aGFuZDwPeYrKOyGCUlzyPvtX3qDjrebvalkrua0ApJLTiUntQK4
E+8P658GUzwqhKi6COEmgJgZO1+xIhO68OzPS6qj8OvgOw97VJqWsnTkDeu5Xd3E
qW37lk62EQ75FCLaWAmyBcxTDlIWNfwvSlI57Iyw0ec9ziN4JP+6pX3QTcj69Kvc
ERExUNGWcyQVYiCFigBqL/UtGO73PA4kb8P6Zef0oTx7siw4ysImvQ/RHvbArXPE
iqxg/afjJz/w7fTTwk9DRW12hiWj7+ojZMcoYCj1PcOlmZvchwZRdN8Lp37wm11I
EYxdoBQVRWk0vwIDAQABMA0GCSqGSIb3DQEBCwUAA4ICAQB6H+z2IEtiBTOIFnYJ
++CvZWw0CLPSpuFTwpDRIVUbrfhpFoXMwb6ungaqrGNXv3H4HQXl2EwXwZomsy2K
/pjk/0rHFdleEtJ4beSv44ToGCL+RqeU38sGuxCDQBCxIqGcbkErkqEYJqaSEV9I
elKjBA5PEy+WO/P+J5UYU6aY+59C+ZHrZ8HzoPUGrZ6ESu9Wm/9iOpUn4DWFzOOu
E/14QQqpHzHRvpbfOb/dsnEYv7JoiOaXhOedKmx5k+bLhNCXOJiqyqphNp0Io3Fr
8i6IxfvOo/6nw9sQ02E6PjNyC5bRq7TUrqffTcCRq7EYhJoK43V7nzLpUBItBF7K
FszFnvBxvgPtUdbhs6O8U8pqoPw1vCnoJLnerGfN1fEclV7Sbf3d1hCYXtHzmpZM
qflEkJIPSgkr8dyi2JjSQR5AJUtB5/VAZwBATNKNYINfnEVzyle+64iVaKteRt9e
yeLoy/ktTZSlQbb7/qg4NtQx9SA5PlYVMLj20S5HwStGQhLIb5lrwzkHNUZmkDWr
LUX1ufOfhNpCH2Egn078pyEn88B5iRTga2auCk3siOs9H3wmbT2r57E7ddX3l9Sf
MzO/8HyfNoa2RUTnJHdyduAT7VytyJSSRmfeapw9ITngZHGmpPhisKhxb6IoZm0J
j10kyQuFfNcZKlNa3D7XFvqzWQ==
-----END CERTIFICATE-----
my.key: |
-----BEGIN PRIVATE KEY-----
MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCenHJ0SunHqdtB
dG8d5Uy8y5KESOPEGCpcjNetSR53binlV3QjVckrA+6tHQBR3yXpSgHWU424fR3e
TdHsbS9/8/pAnV0VCAaUntbCr5CJ8ww2zRlGSz6IlxkwJkqx4crmYUcTm6XNcEm5
kSoBmFR1s8TiQAQ8CnJyt4m3S5jSKnexAMi2l2LP4F5P5MwLHdpl2sfRYydWOoJt
y9nSYuRxarv7G7e3N102voL3W2F9Dk8QbKF1Kzxcfmh+4twNz/YfUxxB3tggrCGH
fMu4EcvMrFCkKhNvsFcG9aZ0gNj4x54vCOMK+LuVd8JZQMcGBdcxZUq+q4nyj4dx
FDKSUjZ+nVjdbuabdk72dVbv/r2j6aGFuZDwPeYrKOyGCUlzyPvtX3qDjrebvalk
rua0ApJLTiUntQK4E+8P658GUzwqhKi6COEmgJgZO1+xIhO68OzPS6qj8OvgOw97
VJqWsnTkDeu5Xd3EqW37lk62EQ75FCLaWAmyBcxTDlIWNfwvSlI57Iyw0ec9ziN4
JP+6pX3QTcj69KvcERExUNGWcyQVYiCFigBqL/UtGO73PA4kb8P6Zef0oTx7siw4
ysImvQ/RHvbArXPEiqxg/afjJz/w7fTTwk9DRW12hiWj7+ojZMcoYCj1PcOlmZvc
hwZRdN8Lp37wm11IEYxdoBQVRWk0vwIDAQABAoICAEx3eTh2m1F+oq1iQGXF0+Kb
NEZhS6mQyu92mU46F52Vd05RhLS6WXNLAIjmF+7gqYrYep1FB+ifLUSs+N1GYGWM
DqSTGTqX9XA2SaxvbrwK+GL9K0e34+x/CA4uD8nFZQf/cwBRhDRQg8KaaQl+0o0y
P2OiYEg/8yA6OwMqx4DfJ3gmvB1HS8STU3SqBfMAD/gV60qXxnGsYJAlfJyQv7is
L/dmTAJhByfq3gH5xLzBJr3w2UA/OWkQKjmuDk/8aBh+/XsGP+U0hy+mKyLRNZM1
qeUTQe6RMcuxp2+4ZKI/vpPHcYorE2iCZaiY8bqGG1J9lnTpB2bw6mfKSH1BdHKB
BCpnvMebe56hfNzQgbE5N4XZUHE4fdFRSDe0cLyagN+5BAekaA10E5cW5/7bfs4P
VFtJgcj3wSRsnYQ4UZoA/9LE6IeQXuHauUFf9lGvy26oW8FkuTysZk6w5uxyJOew
qM0/r7DaGZ6umAsINgLMV/2+ksVDGdOpcHmd7S6Gi26vFKIPdIXWO6BtVej6Mkb9
vUKEEnJbx+gdiehAO63WAg9KR77bHwTXFPYGvza3mB1SXrZCGjjfBnI5yQP6VDwG
uRa0fY/thbsS8QdHc6lmMJNKJnCyes3sG99qcpOX0HBWRpGt9YzjHMK16uQbMPn1
0ApKa/1j/izsQtMkai81AoIBAQDLi/WbQY5jrKesRjSN7AOGND0Ge/NHF7hPIKW6
Ql+BKiu3AiADemiXfnpeBS8OtkqxFWNFT6Ob2uv5pF2aRzP1un8mTaSipQu5iEOz
tejyiWGMCzKaGPksB/d2spDxaDTs1KIyM+YVP2ynx8IGwet2mRcWZ4ixyQxgaj2o
vZeCesxJZSPlt+D0NoPlthwDx4B7D49tU288WEvhPspcAjW3udE4WV4dnmFTZFtD
ZmJ9GrW4rQsti6D2f+hnkWi17F+qGR0B7B4yyE66W8y3wzMfq0/tZzBpAH+azwjk
qkHIbePrWTpwLwbAK4L4cKENs+yivAldfUj4kgVUhVLCscCrAoIBAQDHfBKKF/DK
BsGvWSxo5QahJza2px1walQeE0AifkDYkLI+fyYnArOTUKKAC1/SxI21PWlPPJHz
bt2lhF+I3+DFJTDLsUrBUN923rTpt1WpQVvBNzUFVz6JDlYr6VUOVOX0KY+LVE20
iPmZLrutgO3JRMMFWOaT0ptV1dVx/Eyd4BiT+NfK06D4ZSHGZ6QAsuUmjc+M5VfM
nRbuDlpMgOcBQYQSAfrA+LT6jZorv0mOO6rigNWzIP8FlsJxf1A5o5zL9CYAOOk/
lRWI7K4LSP4TTcTW8DnXqVH7q569d2QA/RS2B1EUEdpNuT2aKIAsNNrhA3JCX6S9
UZm9KOBwYuQ9AoIBAQCbD8ZNPkXA/RjHDryemXud01HiDK8qK5HHBfH60PF8rqma
w02sGKZxMnL6CSzuIkUIXmi/tonHA6HdDjAYhcG5oxeWEHQpS16BOqOI1j3d9naP
f0BPUFMSDgehLytoHKClAt+FKzBOY4Dc2Dqhdz1vnfSOptTly2lYUdcjIzu2tOHH
z/rm14vRv23/oxn4bxUbqqDzAiqtZ/52W6VBLpXJnw8ZxEsEeVFffAZidC73a0+g
noLzcXlwD8T2kTmZzbabGIKWok/nE92V7rUoENZze8hp7MBeXXjYcHwv5twyWjTV
Z6YzLEASSZN+vB6VF8pftqvTwsvCQUs6Nk7z7wH9AoIBAA6EH9E+tr3syfFZmtqz
N81ITjnyZTkF88MQgY1BBLT9qorTs9II50pkBr8slLeAqBM1OdGTRceiHKzrugv6
xp9x+mAIMblpiilbQWz0c15SrDueKdSOqbVNfsXJP/BAC0++KnzoEJN/mDImbW/N
vv/zagGcm4LMQ5N2cQbPZj/iy8cQx5sx1TfeHBwU9KE8Y2Jv1VeaZM417DI8hyOk
CatUuiiZTkb2kizdWwet7stT2jaLS4Gyd/xPIS0jJ5JaLpHE3XMMsSR4U83X8z5M
/HgpI5bEemEQKDAZJ/7/jh5oTDaGx8afGfSn8yyhn9oXqonPN2RPE2zXYEmcjOCA
wb0CggEBAI9ztuEtsucOVKa52Itzvk8sZn2MF1kpUPHGF6/VW6a/s45wvvFFW3PE
BjqmgJX8s2ZLgOgWE/wEmEpHX+lxmebYJ3bmOsx5eGfA1eZBypWCBYBSfezf/1zD
3AD65+gwO4GhnSN4zsrlyVC6SsWEaeu+sxexHt15YJI9SkwCkvZTYoLqe9nMcAjY
h41p/i6bhl9ODZPd1+hL631EFWguyMVSVTDgIG141OIYKz5hVQZ08ddrwHB2gPf6
JSdU2DFOD6tNCfvTYAMvUfkoIt3g9RDJ5dTdR9J/3efm0ZIutcYcHiASIcubPU9x
r8Ww1tZIoBx/s50i7X1JFyuXp0TWQsI=
-----END PRIVATE KEY-----
{{- end }}

View File

@@ -5,7 +5,13 @@ metadata:
name: sync-conf name: sync-conf
namespace: {{ .Release.Namespace }} namespace: {{ .Release.Namespace }}
data: data:
API_PORT: '0' API_PORT: '9090'
API_METRICS_ENABLED: 'true'
API_METRICS_SCRAPE_INTERVAL: '30s'
API_TLS_CERT_DIR: '/certs'
API_TLS_CERT_NAME: 'my.crt'
API_TLS_KEY_NAME: 'my.key'
LOG_FORMAT: 'json'
ORIGIN_URL: 'http://service-origin.{{ $.Release.Namespace }}.svc.cluster.local:3000' ORIGIN_URL: 'http://service-origin.{{ $.Release.Namespace }}.svc.cluster.local:3000'
ORIGIN_PASSWORD: 'password' ORIGIN_PASSWORD: 'password'
ORIGIN_USERNAME: 'username' ORIGIN_USERNAME: 'username'

View File

@@ -11,7 +11,10 @@ data:
username: username username: username
password: password password: password
api: api:
port: 0 port: 9090
metrics:
enabled: true
scrapeInterval: 30s
replicas: replicas:
{{- range $i,$version := .Values.replica.versions }} {{- range $i,$version := .Values.replica.versions }}
- url: 'http://service-replica-{{ $version | toString | replace "." "-" }}.{{ $.Release.Namespace }}.svc.cluster.local:3000' - url: 'http://service-replica-{{ $version | toString | replace "." "-" }}.{{ $.Release.Namespace }}.svc.cluster.local:3000'

View File

@@ -26,5 +26,12 @@ spec:
envFrom: envFrom:
- configMapRef: - configMapRef:
name: sync-conf name: sync-conf
volumeMounts:
- name: certs
mountPath: /certs
restartPolicy: Never restartPolicy: Never
volumes:
- name: certs
configMap:
name: certs
{{- end }} {{- end }}

View File

@@ -8,4 +8,4 @@ mode: env
kubectl: kubectl:
repository: bitnami/kubectl repository: bitnami/kubectl
tag: 1.27 tag: "1.30"

8
testdata/querylog_config.json vendored Normal file
View File

@@ -0,0 +1,8 @@
{
"enabled": true,
"interval": 90,
"anonymize_client_ip": false,
"ignored": [
"foo.bar"
]
}

View File

@@ -1,5 +0,0 @@
{
"enabled": true,
"interval": 90,
"anonymize_client_ip": false
}

16
testdata/test-layout.kdl vendored Normal file
View File

@@ -0,0 +1,16 @@
layout {
pane split_direction="vertical" {
pane name="main"
pane {
pane {
command "make"
args "start-replica"
}
pane {
command "make"
args "start-replica2"
}
}
}
}

8
tools.go Normal file
View File

@@ -0,0 +1,8 @@
//go:build tools
// +build tools
package tools
import (
_ "github.com/onsi/ginkgo/v2/ginkgo"
)

View File

@@ -1,8 +1,8 @@
package version package version
var ( var (
// Version the module version // Version the module version.
Version = "devel" Version = "devel"
// Build the build time // Build the build time.
Build = "N/A" Build = "N/A"
) )