Compare commits

...

65 Commits

Author SHA1 Message Date
dependabot[bot]
ebcb2b84ae Bump github.com/goreleaser/goreleaser from 1.23.0 to 1.24.0 (#299)
Bumps [github.com/goreleaser/goreleaser](https://github.com/goreleaser/goreleaser) from 1.23.0 to 1.24.0.
- [Release notes](https://github.com/goreleaser/goreleaser/releases)
- [Changelog](https://github.com/goreleaser/goreleaser/blob/main/.goreleaser.yaml)
- [Commits](https://github.com/goreleaser/goreleaser/compare/v1.23.0...v1.24.0)

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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


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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

* rename type tags

* replace env lib

* move to config module

* read flags

* show e2e logs on error

* extract env

* replace deprecated env var

* increment index

* check replica numbers do not start with 0

* remove test suite

* error handling

* refactor flags

* flags test

* file test

* file test

* config tests

* extend tests

* test mixed mode

* simplify

* simplify

* test mask

* correct uniqe replicas

* Update types_test.go

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

* dns rewrites and filters

* continue on filter error

* servides

* client settings

* dns

* dhcp

* remove deprecated env var

* fix client tests

* tests

* copy replica config

* map continue on error

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-20 07:10:45 +01:00
bakito
37ee52aa8f add links to dashboard 2023-11-13 23:45:42 +01:00
64 changed files with 4870 additions and 1488 deletions

View File

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

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

@@ -0,0 +1,67 @@
name: Bug report
description: Create a report to help us improve
labels: ['bug']
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this issue report! If you have usage questions, please try the [FAQ](https://github.com/bakito/adguardhome-sync/wiki/FAQ) first.
- type: textarea
id: what-happened
attributes:
label: What happened?
description: Also tell us, what did you expect to happen?
validations:
required: true
- type: textarea
id: adguardhome-sync-version
attributes:
label: AdguardHome-Sync Version
description: What version of adguardhome-sync was running when you discovered this issue?
validations:
required: true
- type: textarea
id: adguardhome-version
attributes:
label: AdguardHome Version
description: What version of adguardhome was running when you discovered this issue?
validations:
required: true
- type: textarea
id: config
attributes:
label: Configuration
description: |
- How did you configure adguardhome-sync?
- Please provide your configuration
render: shell
validations:
required: true
- type: textarea
id: applied-config
attributes:
label: Current Applied Configuration
description: |
- Run adguardhome-sync with environment variable `PRINT_CONFIG_ONLY=true` and provide the output here
render: shell
- type: textarea
id: logs
attributes:
label: Relevant log output
description: |
Please copy and paste any relevant **debug** log output. This will be automatically formatted into code, so no need for backticks.
Enable debug logs by defining the following environment variable `LOG_LEVEL=debug`.
Please also check adguardhome logs and paste any relevant logs/errors to this issue.
render: shell
validations:
required: true
- type: textarea
attributes:
label: Anything else?
description: |
Links? References? Anything that will provide more context about the issue you are encountering!
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
validations:
required: false

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

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

View File

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

View File

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

View File

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

View File

@@ -9,6 +9,19 @@ updates:
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
groups:
k8s:
patterns:
- "k8s.io/*"
update-types:
- "minor"
- "patch"
onsi:
patterns:
- "github.com/onsi/*"
update-types:
- "minor"
- "patch"
- package-ecosystem: "github-actions"
directory: "/"
schedule:

View File

@@ -40,9 +40,15 @@ jobs:
- name: Checkout repository
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.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -53,7 +59,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
uses: github/codeql-action/autobuild@v3
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -67,4 +73,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3

View File

@@ -11,6 +11,11 @@ on:
jobs:
e2e:
runs-on: ubuntu-latest
strategy:
matrix:
build:
- mode: env
- mode: file
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -22,7 +27,7 @@ jobs:
run: ./testdata/e2e/bin/build-image.sh
- name: Install Helm Chart
run: ./testdata/e2e/bin/install-chart.sh
run: ./testdata/e2e/bin/install-chart.sh ${{ matrix.build.mode }}
- name: Wait for sync to finish
run: ./testdata/e2e/bin/wait-for-sync.sh
- name: Show origin Logs

View File

@@ -14,12 +14,12 @@ jobs:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
uses: actions/setup-go@v5
with:
go-version-file: "go.mod"
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
uses: golangci/golangci-lint-action@v4
with:
skip-pkg-cache: true
@@ -32,7 +32,7 @@ jobs:
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
uses: actions/setup-go@v5
with:
go-version-file: "go.mod"
@@ -53,7 +53,7 @@ jobs:
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
uses: actions/setup-go@v5
with:
go-version-file: "go.mod"

View File

@@ -1,9 +1,9 @@
name: docker-image
on:
push:
branches:
- main
workflow_dispatch: # allows manual triggering
schedule:
- cron: '0 0 * * *'
release:
types:
- published
@@ -39,6 +39,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Checkout
uses: actions/checkout@v4
- name: Modify Dockerfile
run: |
sed -i -e "s|FROM scratch|FROM ${{ matrix.build.fromImage }}|g" Dockerfile
@@ -58,10 +59,14 @@ jobs:
VERSION=${{ github.event.release.tag_name }}
BUILD=${{ env.curr_date }}
- name: Check for commits in the last 24 hours
run: echo "NEW_COMMIT_COUNT=$(git log --oneline --since '24 hours ago' | wc -l)" >> $GITHUB_ENV
if: ${{ github.event.release.tag_name == '' }}
- name: Build and push main
id: docker_build_main
uses: docker/build-push-action@v5
if: ${{ github.event.release.tag_name == '' }}
if: ${{ github.event.release.tag_name == '' && env.NEW_COMMIT_COUNT > 0 }}
with:
context: .
pull: true

3
.gitignore vendored
View File

@@ -8,5 +8,6 @@ main
.adguardhome-sync.yaml
tmp
bin
config.yaml
config*.yaml
*.log
wiki

View File

@@ -3,7 +3,7 @@ FROM golang:1.21-bullseye as builder
WORKDIR /go/src/app
RUN apt-get update && \
apt-get install -y upx && \
apt-get install -y upx ca-certificates tzdata && \
apt-get upgrade -y # upgrade to get latest ca-certs
ARG VERSION=main
@@ -26,6 +26,7 @@ LABEL maintainer="bakito <github@bakito.ch>"
EXPOSE 8080
ENTRYPOINT ["/opt/go/adguardhome-sync"]
CMD ["run", "--config", "/config/adguardhome-sync.yaml"]
COPY --from=builder /go/src/app/adguardhome-sync /opt/go/adguardhome-sync
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=builder /usr/share/zoneinfo/ /usr/share/zoneinfo/
COPY --from=builder /go/src/app/adguardhome-sync /opt/go/adguardhome-sync
USER 1001

View File

@@ -15,13 +15,14 @@ generate: deepcopy-gen
test: generate lint test-ci
# Run ci tests
test-ci: mocks tidy
go test ./... -coverprofile=coverage.out.tmp
test-ci: mocks tidy ginkgo
$(GINKGO) --cover --coverprofile coverage.out.tmp ./...
cat coverage.out.tmp | grep -v "_generated.go" > coverage.out
go tool cover -func=coverage.out
mocks: mockgen
$(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
release: semver goreleaser
@version=$$($(LOCALBIN)/semver); \
@@ -40,70 +41,65 @@ $(LOCALBIN):
mkdir -p $(LOCALBIN)
## Tool Binaries
SEMVER ?= $(LOCALBIN)/semver
OAPI_CODEGEN ?= $(LOCALBIN)/oapi-codegen
MOCKGEN ?= $(LOCALBIN)/mockgen
DEEPCOPY_GEN ?= $(LOCALBIN)/deepcopy-gen
GINKGO ?= $(LOCALBIN)/ginkgo
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.22.1
DEEPCOPY_GEN_VERSION ?= v0.28.3
MOCKGEN ?= $(LOCALBIN)/mockgen
OAPI_CODEGEN ?= $(LOCALBIN)/oapi-codegen
SEMVER ?= $(LOCALBIN)/semver
## 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)
test -s $(LOCALBIN)/deepcopy-gen || GOBIN=$(LOCALBIN) go install k8s.io/code-generator/cmd/deepcopy-gen
.PHONY: ginkgo
ginkgo: $(GINKGO) ## Download ginkgo locally if necessary.
$(GINKGO): $(LOCALBIN)
test -s $(LOCALBIN)/ginkgo || GOBIN=$(LOCALBIN) go install github.com/onsi/ginkgo/v2/ginkgo
.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
.PHONY: goreleaser
goreleaser: $(GORELEASER) ## Download goreleaser locally if necessary.
$(GORELEASER): $(LOCALBIN)
test -s $(LOCALBIN)/goreleaser || GOBIN=$(LOCALBIN) go install github.com/goreleaser/goreleaser
.PHONY: mockgen
mockgen: $(MOCKGEN) ## Download mockgen locally if necessary.
$(MOCKGEN): $(LOCALBIN)
test -s $(LOCALBIN)/mockgen || GOBIN=$(LOCALBIN) go install go.uber.org/mock/mockgen
.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
.PHONY: semver
semver: $(SEMVER) ## Download semver locally if necessary.
$(SEMVER): $(LOCALBIN)
test -s $(LOCALBIN)/semver || GOBIN=$(LOCALBIN) go install github.com/bakito/semver
## Update Tools
.PHONY: update-toolbox-tools
update-toolbox-tools:
@rm -f \
$(LOCALBIN)/semver \
$(LOCALBIN)/oapi-codegen \
$(LOCALBIN)/mockgen \
$(LOCALBIN)/deepcopy-gen \
$(LOCALBIN)/ginkgo \
$(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
$(LOCALBIN)/mockgen \
$(LOCALBIN)/oapi-codegen \
$(LOCALBIN)/semver
toolbox makefile -f $(LOCALDIR)/Makefile
## toolbox - end
start-replica:
docker run --pull always --name adguardhome-replica -p 9091:3000 --rm adguard/adguardhome:latest
# docker run --pull always --name adguardhome-replica -p 9090:80 -p 9091:3000 --rm adguard/adguardhome:v0.107.13
copy-replica-config:
docker cp adguardhome-replica:/opt/adguardhome/conf/AdGuardHome.yaml tmp/AdGuardHome.yaml
start-replica2:
docker run --pull always --name adguardhome-replica2 -p 9093:3000 --rm adguard/adguardhome:latest
# docker run --pull always --name adguardhome-replica -p 9090:80 -p 9091:3000 --rm adguard/adguardhome:v0.107.13
@@ -128,10 +124,10 @@ kind-test:
model: oapi-codegen
@mkdir -p tmp
go run openapi/main.go v0.107.40
go run openapi/main.go v0.107.43
$(OAPI_CODEGEN) -package model -generate types,client -config .oapi-codegen.yaml tmp/schema.yaml > pkg/client/model/model_generated.go
model-diff:
go run openapi/main.go v0.107.40
go run openapi/main.go v0.107.43
go run openapi/main.go
diff tmp/schema.yaml tmp/schema-master.yaml

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)
[![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)
[![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,6 +7,12 @@
Synchronize [AdGuardHome](https://github.com/AdguardTeam/AdGuardHome) config to replica instances.
## FAQ & Deprecations
Please check the wiki
for [FAQ](https://github.com/bakito/adguardhome-sync/wiki/FAQ)
and [Deprecations](https://github.com/bakito/adguardhome-sync/wiki/Deprecations).
## Current sync features
- General Settings
@@ -39,7 +46,9 @@ Both the origin instance must be initially setup via the AdguardHome installatio
## Username / Password vs. Cookie
Some instances of AdGuard Home do not support basic authentication. For instance, many routers with built-in Adguard Home support do not. If this is the case, a valid cookie may be provided instead. If the router protects the AdGuard instance behind its own authentication, the cookie from an authenticated request may allow the sync to succeed.
Some instances of AdGuard Home do not support basic authentication. For instance, many routers with built-in Adguard
Home support do not. If this is the case, a valid cookie may be provided instead. If the router protects the AdGuard
instance behind its own authentication, the cookie from an authenticated request may allow the sync to succeed.
- This has been tested successfully against GL.Inet routers with AdGuard Home.
- Note: due to the short validity of cookies, this approach is likely only suitable for one-time syncs
@@ -53,9 +62,9 @@ export ORIGIN_URL=https://192.168.1.2:3000
export ORIGIN_USERNAME=username
export ORIGIN_PASSWORD=password
# export ORIGIN_COOKIE=Origin-Cookie-Name=CCCOOOKKKIIIEEE
export REPLICA_URL=http://192.168.1.3
export REPLICA_USERNAME=username
export REPLICA_PASSWORD=password
export REPLICA1_URL=http://192.168.1.3
export REPLICA1_USERNAME=username
export REPLICA1_PASSWORD=password
# export REPLICA_COOKIE=Replica-Cookie-Name=CCCOOOKKKIIIEEE
# run once
@@ -81,10 +90,10 @@ set ORIGIN_USERNAME=username
set ORIGIN_PASSWORD=password
# set ORIGIN_COOKIE=Origin-Cookie-Name=CCCOOOKKKIIIEEE
set REPLICA_URL=http://192.168.2.2:3000
set REPLICA_USERNAME=username
set REPLICA_PASSWORD=password
# set REPLICA_COOKIE=Replica-Cookie-Name=CCCOOOKKKIIIEEE
set REPLICA1_URL=http://192.168.2.2:3000
set REPLICA1_USERNAME=username
set REPLICA1_PASSWORD=password
# set REPLICA1_COOKIE=Replica-Cookie-Name=CCCOOOKKKIIIEEE
set FEATURES_DHCP=false
set FEATURES_DHCP_SERVERCONFIG=false
@@ -140,35 +149,39 @@ services:
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"
ORIGIN_PASSWORD: "password"
REPLICA_URL: "http://192.168.1.3"
REPLICA_USERNAME: "username"
REPLICA_PASSWORD: "password"
REPLICA1_URL: "http://192.168.1.4"
REPLICA1_URL: "http://192.168.1.3"
REPLICA1_USERNAME: "username"
REPLICA1_PASSWORD: "password"
REPLICA1_APIPATH: "/some/path/control"
# REPLICA1_AUTOSETUP: true # if true, AdGuardHome is automatically initialized.
# REPLICA1_INTERFACENAME: 'ens18' # use custom dhcp interface name
# REPLICA1_DHCPSERVERENABLED: true/false (optional) enables/disables the dhcp server on the replica
REPLICA2_URL: "http://192.168.1.4"
REPLICA2_USERNAME: "username"
REPLICA2_PASSWORD: "password"
REPLICA2_API_PATH: "/some/path/control"
# REPLICA2_WEB_URL: "https://some-other.url" # used in the web interface (default: <replica-url>
# REPLICA2_AUTO_SETUP: true # if true, AdGuardHome is automatically initialized.
# REPLICA2_INTERFACE_NAME: 'ens18' # use custom dhcp interface name
# REPLICA2_DHCP_SERVER_ENABLED: true/false (optional) enables/disables the dhcp server on the replica
CRON: "*/10 * * * *" # run every 10 minutes
RUNONSTART: true
# CONTINUE_ON_ERROR: false # If enabled, the synchronisation task will not fail on single errors, but will log the errors and continue
# Configure the sync API server, disabled if api port is 0
API_PORT: 8080
# Configure sync features; by default all features are enabled.
# FEATURES_GENERALSETTINGS: true
# FEATURES_QUERYLOGCONFIG: true
# FEATURES_STATSCONFIG: true
# FEATURES_CLIENTSETTINGS: true
# FEATURES_GENERAL_SETTINGS: true
# FEATURES_QUERY_LOG_CONFIG: true
# FEATURES_STATS_CONFIG: true
# FEATURES_CLIENT_SETTINGS: true
# FEATURES_SERVICES: true
# FEATURES_FILTERS: true
# FEATURES_DHCP_SERVERCONFIG: true
# FEATURES_DHCP_STATICLEASES: true
# FEATURES_DNS_SERVERCONFIG: true
# FEATURES_DNS_ACCESSLISTS: 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
@@ -186,6 +199,9 @@ cron: "*/10 * * * *"
# runs the synchronisation on startup
runOnStart: true
# If enabled, the synchronisation task will not fail on single errors, but will log the errors and continue
continueOnError: false
origin:
# url of the origin instance
url: https://192.168.1.2:3000
@@ -195,15 +211,7 @@ origin:
password: password
# cookie: Origin-Cookie-Name=CCCOOOKKKIIIEEE
# replica instance (optional, if only one)
replica:
# url of the replica instance
url: http://192.168.1.3
username: username
password: password
# cookie: Replica-Cookie-Name=CCCOOOKKKIIIEEE
# replicas instances (optional, if more than one)
# replicas instances
replicas:
# url of the replica instance
- url: http://192.168.1.3
@@ -215,6 +223,7 @@ replicas:
password: password
# cookie: Replica2-Cookie-Name=CCCOOOKKKIIIEEE
# autoSetup: true # if true, AdGuardHome is automatically initialized.
# webURL: "https://some-other.url" # used in the web interface (default: <replica-url>
# Configure the sync API server, disabled if api port is 0
api:
@@ -223,6 +232,8 @@ api:
# if username and password are defined, basic auth is applied to the sync API
username: username
password: password
# enable api dark mode
darkMode: true
# Configure sync features; by default all features are enabled.
features:
@@ -243,7 +254,7 @@ features:
## 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)
@@ -251,3 +262,8 @@ The following log levels are supported (default: info)
- info
- warn
- error
## Log Format
Default log format is `console`.
It can be changed to `json`by setting the environment variable: `LOG_FORMAT=json`

View File

@@ -3,71 +3,15 @@ package cmd
import (
"fmt"
"os"
"regexp"
"strings"
"github.com/bakito/adguardhome-sync/pkg/log"
"github.com/bakito/adguardhome-sync/pkg/types"
"github.com/bakito/adguardhome-sync/version"
"github.com/mitchellh/go-homedir"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"go.uber.org/zap"
)
const (
configCron = "cron"
configRunOnStart = "runOnStart"
configAPIPort = "api.port"
configAPIUsername = "api.username"
configAPIPassword = "api.password"
configAPIDarkMode = "api.darkMode"
configFeatureDHCPServerConfig = "features.dhcp.serverConfig"
configFeatureDHCPStaticLeases = "features.dhcp.staticLeases"
configFeatureDNServerConfig = "features.dns.serverConfig"
configFeatureDNSPAccessLists = "features.dns.accessLists"
configFeatureDNSRewrites = "features.dns.rewrites"
configFeatureGeneralSettings = "features.generalSettings"
configFeatureQueryLogConfig = "features.queryLogConfig"
configFeatureStatsConfig = "features.statsConfig"
configFeatureClientSettings = "features.clientSettings"
configFeatureServices = "features.services"
configFeatureFilters = "features.filters"
configOriginURL = "origin.url"
configOriginAPIPath = "origin.apiPath"
configOriginUsername = "origin.username"
configOriginPassword = "origin.password"
configOriginCookie = "origin.cookie"
configOriginInsecureSkipVerify = "origin.insecureSkipVerify"
configReplicaURL = "replica.url"
configReplicaAPIPath = "replica.apiPath"
configReplicaUsername = "replica.username"
configReplicaPassword = "replica.password"
configReplicaCookie = "replica.cookie"
configReplicaInsecureSkipVerify = "replica.insecureSkipVerify"
configReplicaAutoSetup = "replica.autoSetup"
configReplicaInterfaceName = "replica.interfaceName"
envReplicasUsernameFormat = "REPLICA%s_USERNAME" // #nosec G101
envReplicasPasswordFormat = "REPLICA%s_PASSWORD" // #nosec G101
envReplicasCookieFormat = "REPLICA%s_COOKIE" // #nosec G101
envReplicasAPIPathFormat = "REPLICA%s_APIPATH"
envReplicasInsecureSkipVerifyFormat = "REPLICA%s_INSECURESKIPVERIFY"
envReplicasAutoSetup = "REPLICA%s_AUTOSETUP"
envReplicasInterfaceName = "REPLICA%s_INTERFACENAME"
// Deprecated: use envReplicasInterfaceName instead
envReplicasInterfaceNameDeprecated = "REPLICA%s_INTERFACWENAME"
envDHCPServerEnabled = "REPLICA%s_DHCPSERVERENABLED"
)
var (
cfgFile string
logger = log.GetLogger("root")
envReplicasURLPattern = regexp.MustCompile(`^REPLICA(\d+)_URL=(.*)`)
)
// rootCmd represents the base command when called without any subcommands
@@ -87,8 +31,6 @@ func Execute() {
}
func init() {
cobra.OnInitialize(initConfig)
// Here you will define your flags and configuration settings.
// Cobra supports persistent flags, which, if defined here,
// will be global for your application.
@@ -99,87 +41,3 @@ func init() {
// when this action is called directly.
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
// initConfig reads in config file and ENV variables if set.
func initConfig() {
if cfgFile != "" {
// Use config file from the flag.
viper.SetConfigFile(cfgFile)
} else {
// Find home directory.
home, err := homedir.Dir()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
// Search config in home directory with name ".adguardhome-sync" (without extension).
viper.AddConfigPath(home)
viper.SetConfigName(".adguardhome-sync")
}
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_", ".", "_"))
viper.AutomaticEnv() // read in environment variables that match
// If a config file is found, read it in.
if err := viper.ReadInConfig(); err == nil {
logger.Info("Using config file:", viper.ConfigFileUsed())
} else if cfgFile != "" {
fmt.Println(err)
os.Exit(1)
}
}
func getConfig(logger *zap.SugaredLogger) (*types.Config, error) {
cfg := &types.Config{}
if err := viper.Unmarshal(cfg); err != nil {
return nil, err
}
if len(cfg.Replicas) == 0 {
cfg.Replicas = append(cfg.Replicas, collectEnvReplicas(logger)...)
}
return cfg, nil
}
// Manually collect replicas from env.
func collectEnvReplicas(logger *zap.SugaredLogger) []types.AdGuardInstance {
var replicas []types.AdGuardInstance
for _, v := range os.Environ() {
if envReplicasURLPattern.MatchString(v) {
sm := envReplicasURLPattern.FindStringSubmatch(v)
re := types.AdGuardInstance{
URL: sm[2],
Username: os.Getenv(fmt.Sprintf(envReplicasUsernameFormat, sm[1])),
Password: os.Getenv(fmt.Sprintf(envReplicasPasswordFormat, sm[1])),
Cookie: os.Getenv(fmt.Sprintf(envReplicasCookieFormat, sm[1])),
APIPath: os.Getenv(fmt.Sprintf(envReplicasAPIPathFormat, sm[1])),
InsecureSkipVerify: strings.EqualFold(os.Getenv(fmt.Sprintf(envReplicasInsecureSkipVerifyFormat, sm[1])), "true"),
AutoSetup: strings.EqualFold(os.Getenv(fmt.Sprintf(envReplicasAutoSetup, sm[1])), "true"),
InterfaceName: os.Getenv(fmt.Sprintf(envReplicasInterfaceName, sm[1])),
}
if re.InterfaceName == "" {
if in, ok := os.LookupEnv(fmt.Sprintf(envReplicasInterfaceNameDeprecated, sm[1])); ok {
logger.
With("correct", envReplicasInterfaceName, "deprecated", envReplicasInterfaceNameDeprecated).
Warn("Deprecated env variable is used, please use the correct one")
re.InterfaceName = in
}
}
if dhcpEnabled, ok := os.LookupEnv(fmt.Sprintf(envDHCPServerEnabled, sm[1])); ok {
if strings.EqualFold(dhcpEnabled, "true") {
re.DHCPServerEnabled = boolPtr(true)
} else if strings.EqualFold(dhcpEnabled, "false") {
re.DHCPServerEnabled = boolPtr(false)
}
}
replicas = append(replicas, re)
}
}
return replicas
}
func boolPtr(b bool) *bool {
return &b
}

View File

@@ -1,116 +0,0 @@
package cmd
import (
"fmt"
"os"
"github.com/bakito/adguardhome-sync/pkg/log"
"github.com/bakito/adguardhome-sync/pkg/types"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"go.uber.org/zap"
)
var envVars = []string{
"FEATURES_GENERALSETTINGS",
"FEATURES_QUERYLOGCONFIG",
"FEATURES_STATSCONFIG",
"FEATURES_CLIENTSETTINGS",
"FEATURES_SERVICES",
"FEATURES_FILTERS",
"FEATURES_DHCP_SERVERCONFIG",
"FEATURES_DHCP_STATICLEASES",
"FEATURES_DNS_SERVERCONFIG",
"FEATURES_DNS_ACCESSLISTS",
"FEATURES_DNS_REWRITES",
"REPLICA1_INTERFACENAME",
"REPLICA1_INTERFACWENAME",
"REPLICA1_DHCPSERVERENABLED",
}
var _ = Describe("Run", func() {
var logger *zap.SugaredLogger
BeforeEach(func() {
logger = log.GetLogger("root")
for _, envVar := range envVars {
Ω(os.Unsetenv(envVar)).ShouldNot(HaveOccurred())
}
initConfig()
})
AfterEach(func() {
for _, envVar := range envVars {
Ω(os.Unsetenv(envVar)).ShouldNot(HaveOccurred())
}
})
Context("getConfig", func() {
It("features should be true by default", func() {
cfg, err := getConfig(logger)
Ω(err).ShouldNot(HaveOccurred())
verifyFeatures(cfg, true)
})
It("features should be false", func() {
for _, envVar := range envVars {
Ω(os.Setenv(envVar, "false")).ShouldNot(HaveOccurred())
}
cfg, err := getConfig(logger)
Ω(err).ShouldNot(HaveOccurred())
verifyFeatures(cfg, false)
})
Context("interface name", func() {
It("should set interface name of replica 1", func() {
Ω(os.Setenv("REPLICA1_URL", "https://foo.bar")).ShouldNot(HaveOccurred())
Ω(os.Setenv(fmt.Sprintf(envReplicasInterfaceName, "1"), "eth0")).ShouldNot(HaveOccurred())
cfg, err := getConfig(logger)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].InterfaceName).Should(Equal("eth0"))
})
It("should set interface name of replica 1 from deprecated env", func() {
Ω(os.Setenv("REPLICA1_URL", "https://foo.bar")).ShouldNot(HaveOccurred())
Ω(os.Setenv(fmt.Sprintf(envReplicasInterfaceNameDeprecated, "1"), "eth0")).ShouldNot(HaveOccurred())
cfg, err := getConfig(logger)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].InterfaceName).Should(Equal("eth0"))
})
It("deprecated should not overwrite the correct", func() {
Ω(os.Setenv("REPLICA1_URL", "https://foo.bar")).ShouldNot(HaveOccurred())
Ω(os.Setenv(fmt.Sprintf(envReplicasInterfaceNameDeprecated, "1"), "eth1")).ShouldNot(HaveOccurred())
Ω(os.Setenv(fmt.Sprintf(envReplicasInterfaceName, "1"), "eth0")).ShouldNot(HaveOccurred())
cfg, err := getConfig(logger)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].InterfaceName).Should(Equal("eth0"))
})
})
Context("dhcp server", func() {
It("should enable the dhcp server of replica 1", func() {
Ω(os.Setenv("REPLICA1_URL", "https://foo.bar")).ShouldNot(HaveOccurred())
Ω(os.Setenv(fmt.Sprintf(envDHCPServerEnabled, "1"), "true")).ShouldNot(HaveOccurred())
cfg, err := getConfig(logger)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].DHCPServerEnabled).ShouldNot(BeNil())
Ω(*cfg.Replicas[0].DHCPServerEnabled).Should(BeTrue())
})
It("should disable the dhcp server of replica 1", func() {
Ω(os.Setenv("REPLICA1_URL", "https://foo.bar")).ShouldNot(HaveOccurred())
Ω(os.Setenv(fmt.Sprintf(envDHCPServerEnabled, "1"), "false")).ShouldNot(HaveOccurred())
cfg, err := getConfig(logger)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].DHCPServerEnabled).ShouldNot(BeNil())
Ω(*cfg.Replicas[0].DHCPServerEnabled).Should(BeFalse())
})
})
})
})
func verifyFeatures(cfg *types.Config, value bool) {
Ω(cfg.Features.GeneralSettings).Should(Equal(value))
Ω(cfg.Features.QueryLogConfig).Should(Equal(value))
Ω(cfg.Features.StatsConfig).Should(Equal(value))
Ω(cfg.Features.ClientSettings).Should(Equal(value))
Ω(cfg.Features.Services).Should(Equal(value))
Ω(cfg.Features.Filters).Should(Equal(value))
Ω(cfg.Features.DHCP.ServerConfig).Should(Equal(value))
Ω(cfg.Features.DHCP.StaticLeases).Should(Equal(value))
Ω(cfg.Features.DNS.ServerConfig).Should(Equal(value))
Ω(cfg.Features.DNS.AccessLists).Should(Equal(value))
Ω(cfg.Features.DNS.Rewrites).Should(Equal(value))
}

View File

@@ -1,10 +1,11 @@
package cmd
import (
"github.com/bakito/adguardhome-sync/pkg/config"
"github.com/bakito/adguardhome-sync/pkg/log"
"github.com/bakito/adguardhome-sync/pkg/sync"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"gopkg.in/yaml.v3"
)
// runCmd represents the run command
@@ -14,83 +15,75 @@ var doCmd = &cobra.Command{
Long: `Synchronizes the configuration form an origin instance to a replica`,
RunE: func(cmd *cobra.Command, args []string) error {
logger = log.GetLogger("run")
cfg, err := getConfig(logger)
cfg, err := config.Get(cfgFile, cmd.Flags())
if err != nil {
logger.Error(err)
return err
}
if err := cfg.Init(); err != nil {
logger.Error(err)
return err
}
if cfg.PrintConfigOnly {
config, err := yaml.Marshal(cfg)
if err != nil {
logger.Error(err)
return err
}
logger.Infof("Printing adguardhome-sync config (THE APPLICATION WILL NOT START IN THIS MODE): \n%s",
string(config))
return nil
}
return sync.Sync(cfg)
},
}
func init() {
rootCmd.AddCommand(doCmd)
doCmd.PersistentFlags().String("cron", "", "The cron expression to run in daemon mode")
_ = viper.BindPFlag(configCron, doCmd.PersistentFlags().Lookup("cron"))
doCmd.PersistentFlags().Bool("runOnStart", true, "Run the sync job on start.")
_ = viper.BindPFlag(configRunOnStart, doCmd.PersistentFlags().Lookup("runOnStart"))
doCmd.PersistentFlags().Int("api-port", 8080, "Sync API Port, the API endpoint will be started to enable remote triggering; if 0 port API is disabled.")
_ = viper.BindPFlag(configAPIPort, doCmd.PersistentFlags().Lookup("api-port"))
doCmd.PersistentFlags().String("api-username", "", "Sync API username")
_ = viper.BindPFlag(configAPIUsername, doCmd.PersistentFlags().Lookup("api-username"))
doCmd.PersistentFlags().String("api-password", "", "Sync API password")
_ = viper.BindPFlag(configAPIPassword, doCmd.PersistentFlags().Lookup("api-password"))
doCmd.PersistentFlags().String("api-darkMode", "", "API UI in dark mode")
_ = viper.BindPFlag(configAPIDarkMode, doCmd.PersistentFlags().Lookup("api-darkMode"))
doCmd.PersistentFlags().String(config.FlagCron, "", "The cron expression to run in daemon mode")
doCmd.PersistentFlags().Bool(config.FlagRunOnStart, true, "Run the sync job on start.")
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.")
doCmd.PersistentFlags().Bool(config.FlagContinueOnError, false, "If enabled, the synchronisation task "+
"will not fail on single errors, but will log the errors and continue.")
doCmd.PersistentFlags().Bool("feature-dhcp-server-config", true, "Enable DHCP server config feature")
_ = viper.BindPFlag(configFeatureDHCPServerConfig, doCmd.PersistentFlags().Lookup("feature-dhcp-server-config"))
doCmd.PersistentFlags().Bool("feature-dhcp-static-leases", true, "Enable DHCP server static leases feature")
_ = viper.BindPFlag(configFeatureDHCPStaticLeases, doCmd.PersistentFlags().Lookup("feature-dhcp-static-leases"))
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().String(config.FlagApiUsername, "", "Sync API username")
doCmd.PersistentFlags().String(config.FlagApiPassword, "", "Sync API password")
doCmd.PersistentFlags().String(config.FlagApiDarkMode, "", "API UI in dark mode")
doCmd.PersistentFlags().Bool("feature-dns-server-config", true, "Enable DNS server config feature")
_ = viper.BindPFlag(configFeatureDNServerConfig, doCmd.PersistentFlags().Lookup("feature-dns-server-config"))
doCmd.PersistentFlags().Bool("feature-dns-access-lists", true, "Enable DNS server access lists feature")
_ = viper.BindPFlag(configFeatureDNSPAccessLists, doCmd.PersistentFlags().Lookup("feature-dns-access-lists"))
doCmd.PersistentFlags().Bool("feature-dns-rewrites", true, "Enable DNS rewrites feature")
_ = viper.BindPFlag(configFeatureDNSRewrites, doCmd.PersistentFlags().Lookup("feature-dns-rewrites"))
doCmd.PersistentFlags().Bool("feature-general-settings", true, "Enable general settings feature")
_ = viper.BindPFlag(configFeatureGeneralSettings, doCmd.PersistentFlags().Lookup("feature-general-settings"))
_ = viper.BindPFlag("features.generalSettings", doCmd.PersistentFlags().Lookup("feature-general-settings"))
doCmd.PersistentFlags().Bool("feature-query-log-config", true, "Enable query log config feature")
_ = viper.BindPFlag(configFeatureQueryLogConfig, doCmd.PersistentFlags().Lookup("feature-query-log-config"))
doCmd.PersistentFlags().Bool("feature-stats-config", true, "Enable stats config feature")
_ = viper.BindPFlag(configFeatureStatsConfig, doCmd.PersistentFlags().Lookup("feature-stats-config"))
doCmd.PersistentFlags().Bool("feature-client-settings", true, "Enable client settings feature")
_ = viper.BindPFlag(configFeatureClientSettings, doCmd.PersistentFlags().Lookup("feature-client-settings"))
doCmd.PersistentFlags().Bool("feature-services", true, "Enable services sync feature")
_ = viper.BindPFlag(configFeatureServices, doCmd.PersistentFlags().Lookup("feature-services"))
doCmd.PersistentFlags().Bool("feature-filters", true, "Enable filters sync feature")
_ = viper.BindPFlag(configFeatureFilters, doCmd.PersistentFlags().Lookup("feature-filters"))
doCmd.PersistentFlags().Bool(config.FlagFeatureDhcpServerConfig, true, "Enable DHCP server config feature")
doCmd.PersistentFlags().Bool(config.FlagFeatureDhcpStaticLeases, true, "Enable DHCP server static leases feature")
doCmd.PersistentFlags().String("origin-url", "", "Origin instance url")
_ = viper.BindPFlag(configOriginURL, doCmd.PersistentFlags().Lookup("origin-url"))
doCmd.PersistentFlags().String("origin-api-path", "/control", "Origin instance API path")
_ = viper.BindPFlag(configOriginAPIPath, doCmd.PersistentFlags().Lookup("origin-api-path"))
doCmd.PersistentFlags().String("origin-username", "", "Origin instance username")
_ = viper.BindPFlag(configOriginUsername, doCmd.PersistentFlags().Lookup("origin-username"))
doCmd.PersistentFlags().String("origin-password", "", "Origin instance password")
_ = viper.BindPFlag(configOriginPassword, doCmd.PersistentFlags().Lookup("origin-password"))
doCmd.PersistentFlags().String("origin-cookie", "", "If Set, uses a cookie for authentication")
_ = viper.BindPFlag(configOriginCookie, doCmd.PersistentFlags().Lookup("origin-cookie"))
doCmd.PersistentFlags().Bool("origin-insecure-skip-verify", false, "Enable Origin instance InsecureSkipVerify")
_ = viper.BindPFlag(configOriginInsecureSkipVerify, doCmd.PersistentFlags().Lookup("origin-insecure-skip-verify"))
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.FlagFeatureDnsRewrites, true, "Enable DNS rewrites feature")
doCmd.PersistentFlags().String("replica-url", "", "Replica instance url")
_ = viper.BindPFlag(configReplicaURL, doCmd.PersistentFlags().Lookup("replica-url"))
doCmd.PersistentFlags().String("replica-api-path", "/control", "Replica instance API path")
_ = viper.BindPFlag(configReplicaAPIPath, doCmd.PersistentFlags().Lookup("replica-api-path"))
doCmd.PersistentFlags().String("replica-username", "", "Replica instance username")
_ = viper.BindPFlag(configReplicaUsername, doCmd.PersistentFlags().Lookup("replica-username"))
doCmd.PersistentFlags().String("replica-password", "", "Replica instance password")
_ = viper.BindPFlag(configReplicaPassword, doCmd.PersistentFlags().Lookup("replica-password"))
doCmd.PersistentFlags().String("replica-cookie", "", "If Set, uses a cookie for authentication")
_ = viper.BindPFlag(configReplicaCookie, doCmd.PersistentFlags().Lookup("replica-cookie"))
doCmd.PersistentFlags().Bool("replica-insecure-skip-verify", false, "Enable Replica instance InsecureSkipVerify")
_ = viper.BindPFlag(configReplicaInsecureSkipVerify, doCmd.PersistentFlags().Lookup("replica-insecure-skip-verify"))
doCmd.PersistentFlags().Bool("replica-auto-setup", false, "Enable automatic setup of new AdguardHome instances. This replaces the setup wizard.")
_ = viper.BindPFlag(configReplicaAutoSetup, doCmd.PersistentFlags().Lookup("replica-auto-setup"))
doCmd.PersistentFlags().String("replica-interface-name", "", "Optional change the interface name of the replica if it differs from the master")
_ = viper.BindPFlag(configReplicaInterfaceName, doCmd.PersistentFlags().Lookup("replica-interface-name"))
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.FlagFeatureStats, true, "Enable stats config feature")
doCmd.PersistentFlags().Bool(config.FlagFeatureClient, true, "Enable client settings feature")
doCmd.PersistentFlags().Bool(config.FlagFeatureServices, true, "Enable services sync feature")
doCmd.PersistentFlags().Bool(config.FlagFeatureFilters, true, "Enable filters sync feature")
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().String(config.FlagOriginApiPath, "/control", "Origin instance API path")
doCmd.PersistentFlags().String(config.FlagOriginUsername, "", "Origin instance username")
doCmd.PersistentFlags().String(config.FlagOriginPassword, "", "Origin instance password")
doCmd.PersistentFlags().String(config.FlagOriginCookie, "", "If Set, uses a cookie for authentication")
doCmd.PersistentFlags().Bool(config.FlagOriginISV, false, "Enable Origin instance InsecureSkipVerify")
doCmd.PersistentFlags().String(config.FlagReplicaURL, "", "Replica instance url")
doCmd.PersistentFlags().String(config.FlagReplicaWebURL, "", "Replica instance web url used in the web interface (default: <replica-url>)")
doCmd.PersistentFlags().String(config.FlagReplicaApiPath, "/control", "Replica instance API path")
doCmd.PersistentFlags().String(config.FlagReplicaUsername, "", "Replica instance username")
doCmd.PersistentFlags().String(config.FlagReplicaPassword, "", "Replica instance password")
doCmd.PersistentFlags().String(config.FlagReplicaCookie, "", "If Set, uses a cookie for authentication")
doCmd.PersistentFlags().Bool(config.FlagReplicaISV, false, "Enable Replica instance InsecureSkipVerify")
doCmd.PersistentFlags().Bool(config.FlagReplicaAutoSetup, false, "Enable automatic setup of new AdguardHome instances. This replaces the setup wizard.")
doCmd.PersistentFlags().String(config.FlagReplicaInterfaceName, "", "Optional change the interface name of the replica if it differs from the master")
}

429
go.mod
View File

@@ -1,80 +1,439 @@
module github.com/bakito/adguardhome-sync
go 1.20
go 1.21
require (
github.com/bakito/semver v1.1.3
github.com/caarlos0/env/v10 v10.0.0
github.com/deepmap/oapi-codegen/v2 v2.1.0
github.com/gin-gonic/gin v1.9.1
github.com/go-resty/resty/v2 v2.10.0
github.com/golang/mock v1.6.0
github.com/google/uuid v1.4.0
github.com/go-resty/resty/v2 v2.9.1
github.com/golangci/golangci-lint v1.56.1
github.com/google/uuid v1.6.0
github.com/goreleaser/goreleaser v1.24.0
github.com/jinzhu/copier v0.4.0
github.com/mitchellh/go-homedir v1.1.0
github.com/oapi-codegen/runtime v1.0.0
github.com/onsi/ginkgo/v2 v2.13.1
github.com/onsi/gomega v1.30.0
github.com/oapi-codegen/runtime v1.1.1
github.com/onsi/ginkgo/v2 v2.15.0
github.com/onsi/gomega v1.31.1
github.com/robfig/cron/v3 v3.0.1
github.com/spf13/cobra v1.8.0
github.com/spf13/viper v1.17.0
go.uber.org/mock v0.4.0
go.uber.org/zap v1.26.0
golang.org/x/mod v0.14.0
golang.org/x/mod v0.15.0
gopkg.in/yaml.v3 v3.0.1
k8s.io/apimachinery v0.28.3
k8s.io/apimachinery v0.29.1
k8s.io/code-generator v0.29.1
)
require (
4d63.com/gocheckcompilerdirectives v1.2.1 // indirect
4d63.com/gochecknoglobals v0.2.1 // indirect
cloud.google.com/go v0.110.10 // indirect
cloud.google.com/go/compute v1.23.3 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/iam v1.1.5 // indirect
cloud.google.com/go/kms v1.15.5 // indirect
cloud.google.com/go/storage v1.35.1 // indirect
code.gitea.io/sdk/gitea v0.17.1 // indirect
dario.cat/mergo v1.0.0 // indirect
github.com/4meepo/tagalign v1.3.3 // indirect
github.com/Abirdcfly/dupword v0.0.13 // indirect
github.com/AlekSi/pointer v1.2.0 // indirect
github.com/Antonboom/errname v0.1.12 // indirect
github.com/Antonboom/nilnil v0.1.7 // indirect
github.com/Antonboom/testifylint v1.1.1 // indirect
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.0 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.29 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.23 // indirect
github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 // indirect
github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 // indirect
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.0 // indirect
github.com/BurntSushi/toml v1.3.2 // indirect
github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect
github.com/GaijinEntertainment/go-exhaustruct/v3 v3.2.0 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver v1.5.0 // indirect
github.com/Masterminds/semver/v3 v3.2.1 // indirect
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/OpenPeeDeeP/depguard/v2 v2.2.0 // indirect
github.com/ProtonMail/go-crypto v1.0.0 // indirect
github.com/alecthomas/go-check-sumtype v0.1.4 // indirect
github.com/alessio/shellescape v1.4.1 // indirect
github.com/alexkohler/nakedret/v2 v2.0.2 // indirect
github.com/alexkohler/prealloc v1.0.0 // indirect
github.com/alingse/asasalint v0.0.11 // indirect
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/bytedance/sonic v1.10.0 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/ashanbrown/forbidigo v1.6.0 // indirect
github.com/ashanbrown/makezero v1.1.1 // indirect
github.com/atc0005/go-teams-notify/v2 v2.9.0 // indirect
github.com/aws/aws-sdk-go v1.50.10 // indirect
github.com/aws/aws-sdk-go-v2 v1.24.0 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 // indirect
github.com/aws/aws-sdk-go-v2/config v1.26.1 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.16.12 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10 // indirect
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.7 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.9 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.9 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.9 // indirect
github.com/aws/aws-sdk-go-v2/service/ecr v1.20.2 // indirect
github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.18.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.9 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.9 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.9 // indirect
github.com/aws/aws-sdk-go-v2/service/kms v1.27.5 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v1.47.5 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.18.5 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.26.5 // indirect
github.com/aws/smithy-go v1.19.0 // indirect
github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20231024185945-8841054dbdb8 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bkielbasa/cyclop v1.2.1 // indirect
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect
github.com/blizzy78/varnamelen v0.8.0 // indirect
github.com/bombsimon/wsl/v4 v4.2.0 // indirect
github.com/breml/bidichk v0.2.7 // indirect
github.com/breml/errchkjson v0.3.6 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/butuzov/ireturn v0.3.0 // indirect
github.com/butuzov/mirror v1.1.0 // indirect
github.com/bytedance/sonic v1.10.2 // indirect
github.com/caarlos0/ctrlc v1.2.0 // indirect
github.com/caarlos0/env/v9 v9.0.0 // indirect
github.com/caarlos0/go-reddit/v3 v3.0.1 // indirect
github.com/caarlos0/go-shellwords v1.0.12 // indirect
github.com/caarlos0/go-version v0.1.1 // indirect
github.com/caarlos0/log v0.4.4 // indirect
github.com/catenacyber/perfsprint v0.6.0 // indirect
github.com/cavaliergopher/cpio v1.0.1 // indirect
github.com/ccojocar/zxcvbn-go v1.0.1 // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/charithe/durationcheck v0.0.10 // indirect
github.com/charmbracelet/lipgloss v0.9.1 // indirect
github.com/charmbracelet/x/exp/ordered v0.0.0-20231010190216-1cb11efc897d // indirect
github.com/chavacava/garif v0.1.0 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
github.com/chenzhuoyu/iasm v0.9.0 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/chenzhuoyu/iasm v0.9.1 // indirect
github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
github.com/coreos/go-semver v0.3.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
github.com/curioswitch/go-reassign v0.2.0 // indirect
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
github.com/daixiang0/gci v0.12.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/davidmz/go-pageant v1.0.2 // indirect
github.com/denis-tingaikin/go-header v0.4.3 // indirect
github.com/dghubble/go-twitter v0.0.0-20211115160449-93a8679adecb // indirect
github.com/dghubble/oauth1 v0.7.2 // indirect
github.com/dghubble/sling v1.4.0 // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/distribution/reference v0.5.0 // indirect
github.com/docker/cli v24.0.7+incompatible // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/docker v24.0.7+incompatible // indirect
github.com/docker/docker-credential-helpers v0.8.0 // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/elliotchance/orderedmap/v2 v2.2.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/esimonov/ifshort v1.0.4 // indirect
github.com/ettle/strcase v0.2.0 // indirect
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/fatih/structtag v1.2.0 // indirect
github.com/firefart/nonamedreturns v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/fzipp/gocyclo v0.6.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/getkin/kin-openapi v0.122.0 // indirect
github.com/ghostiam/protogetter v0.3.4 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-critic/go-critic v0.11.0 // indirect
github.com/go-fed/httpsig v1.1.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.5.0 // indirect
github.com/go-git/go-git/v5 v5.11.0 // indirect
github.com/go-logr/logr v1.3.0 // indirect
github.com/go-openapi/analysis v0.21.4 // indirect
github.com/go-openapi/errors v0.20.4 // indirect
github.com/go-openapi/jsonpointer v0.20.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/loads v0.21.2 // indirect
github.com/go-openapi/runtime v0.26.0 // indirect
github.com/go-openapi/spec v0.20.9 // indirect
github.com/go-openapi/strfmt v0.21.7 // indirect
github.com/go-openapi/swag v0.22.4 // indirect
github.com/go-openapi/validate v0.22.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/validator/v10 v10.15.1 // indirect
github.com/go-playground/validator/v10 v10.16.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect
github.com/go-toolsmith/astcast v1.1.0 // indirect
github.com/go-toolsmith/astcopy v1.1.0 // indirect
github.com/go-toolsmith/astequal v1.1.0 // indirect
github.com/go-toolsmith/astfmt v1.1.0 // indirect
github.com/go-toolsmith/astp v1.1.0 // indirect
github.com/go-toolsmith/strparse v1.1.0 // indirect
github.com/go-toolsmith/typep v1.1.0 // indirect
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 // indirect
github.com/go-xmlfmt/xmlfmt v1.1.2 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/gofrs/flock v0.8.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang-jwt/jwt/v5 v5.1.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 // indirect
github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect
github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe // indirect
github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e // indirect
github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 // indirect
github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca // indirect
github.com/golangci/misspell v0.4.1 // indirect
github.com/golangci/revgrep v0.5.2 // indirect
github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/go-containerregistry v0.19.0 // indirect
github.com/google/go-github/v57 v57.0.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/google/ko v0.15.1 // indirect
github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b // indirect
github.com/google/rpmpack v0.5.0 // indirect
github.com/google/s2a-go v0.1.7 // indirect
github.com/google/safetext v0.0.0-20220905092116-b49f7bc46da2 // indirect
github.com/google/wire v0.5.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
github.com/gordonklaus/ineffassign v0.1.0 // indirect
github.com/goreleaser/chglog v0.5.0 // indirect
github.com/goreleaser/fileglob v1.3.0 // indirect
github.com/goreleaser/nfpm/v2 v2.35.3 // indirect
github.com/gorilla/websocket v1.5.1 // indirect
github.com/gostaticanalysis/analysisutil v0.7.1 // indirect
github.com/gostaticanalysis/comment v1.4.2 // indirect
github.com/gostaticanalysis/forcetypeassert v0.1.0 // indirect
github.com/gostaticanalysis/nilerr v0.1.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.4 // indirect
github.com/hashicorp/go-version v1.6.0 // indirect
github.com/hashicorp/hcl v1.0.1-vault-5 // indirect
github.com/hexops/gotextdiff v1.0.3 // indirect
github.com/huandu/xstrings v1.3.3 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/invopop/jsonschema v0.12.0 // indirect
github.com/invopop/yaml v0.2.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jgautheron/goconst v1.7.0 // indirect
github.com/jingyugao/rowserrcheck v1.1.1 // indirect
github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af // indirect
github.com/jjti/go-spancheck v0.5.2 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
github.com/julz/importas v0.1.0 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/kisielk/errcheck v1.7.0 // indirect
github.com/kisielk/gotool v1.0.0 // indirect
github.com/kkHAIKE/contextcheck v1.1.4 // indirect
github.com/klauspost/compress v1.17.5 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/kulti/thelper v0.6.3 // indirect
github.com/kunwardeep/paralleltest v1.0.9 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/kyoh86/exportloopref v0.1.11 // indirect
github.com/ldez/gomoddirectives v0.2.3 // indirect
github.com/ldez/tagliatelle v0.5.0 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/leonklingele/grouper v1.1.1 // indirect
github.com/letsencrypt/boulder v0.0.0-20231026200631-000cd05d5491 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/lufeee/execinquery v1.2.1 // indirect
github.com/macabu/inamedparam v0.1.3 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/maratori/testableexamples v1.0.0 // indirect
github.com/maratori/testpackage v1.1.1 // indirect
github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-mastodon v0.0.6 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
github.com/mbilski/exhaustivestruct v1.2.0 // indirect
github.com/mgechev/revive v1.3.7 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/moricho/tparallel v0.3.1 // indirect
github.com/muesli/mango v0.1.0 // indirect
github.com/muesli/mango-cobra v1.2.0 // indirect
github.com/muesli/mango-pflag v0.1.0 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/roff v0.1.0 // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/nakabonne/nestif v0.3.1 // indirect
github.com/nishanths/exhaustive v0.12.0 // indirect
github.com/nishanths/predeclared v0.2.2 // indirect
github.com/nunnatsa/ginkgolinter v0.15.2 // indirect
github.com/oklog/ulid v1.3.1 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0-rc5 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.1.1 // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/polyfloyd/go-errorlint v1.4.8 // indirect
github.com/prometheus/client_golang v1.17.0 // indirect
github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/common v0.45.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/quasilyte/go-ruleguard v0.4.0 // indirect
github.com/quasilyte/gogrep v0.5.0 // indirect
github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect
github.com/rivo/uniseg v0.4.2 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/ryancurrah/gomodguard v1.3.0 // indirect
github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect
github.com/sagikazarmark/locafero v0.3.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sanposhiho/wastedassign/v2 v2.0.7 // indirect
github.com/sashamelentyev/interfacebloat v1.1.0 // indirect
github.com/sashamelentyev/usestdlibvars v1.24.0 // indirect
github.com/secure-systems-lab/go-securesystemslib v0.7.0 // indirect
github.com/securego/gosec/v2 v2.18.2 // indirect
github.com/sergi/go-diff v1.2.0 // indirect
github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect
github.com/shopspring/decimal v1.2.0 // indirect
github.com/sigstore/cosign/v2 v2.2.1 // indirect
github.com/sigstore/rekor v1.3.3 // indirect
github.com/sigstore/sigstore v1.7.5 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/sivchari/containedctx v1.0.3 // indirect
github.com/sivchari/nosnakecase v1.7.0 // indirect
github.com/sivchari/tenv v1.7.1 // indirect
github.com/skeema/knownhosts v1.2.1 // indirect
github.com/slack-go/slack v0.12.3 // indirect
github.com/sonatard/noctx v0.0.2 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.10.0 // indirect
github.com/sourcegraph/go-diff v0.7.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.5.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.17.0 // indirect
github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect
github.com/stbenjam/no-sprintf-host-port v0.1.1 // indirect
github.com/stretchr/objx v0.5.0 // indirect
github.com/stretchr/testify v1.8.4 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c // indirect
github.com/tdakkota/asciicheck v0.2.0 // indirect
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
github.com/tetafro/godot v1.4.16 // indirect
github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 // indirect
github.com/timonwong/loggercheck v0.9.4 // indirect
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect
github.com/tomarrell/wrapcheck/v2 v2.8.1 // indirect
github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
github.com/ulikunitz/xz v0.5.11 // indirect
github.com/ultraware/funlen v0.1.0 // indirect
github.com/ultraware/whitespace v0.1.0 // indirect
github.com/uudashr/gocognit v1.1.2 // indirect
github.com/vbatts/tar-split v0.11.5 // indirect
github.com/withfig/autocomplete-tools/integrations/cobra v1.2.1 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/xanzy/go-gitlab v0.97.0 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xen0n/gosmopolitan v1.2.2 // indirect
github.com/yagipy/maintidx v1.0.0 // indirect
github.com/yeya24/promlinter v0.2.0 // indirect
github.com/ykadowak/zerologlint v0.1.5 // indirect
gitlab.com/bosi/decorder v0.4.1 // indirect
gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect
go-simpler.org/musttag v0.8.0 // indirect
go-simpler.org/sloglint v0.4.0 // indirect
go.mongodb.org/mongo-driver v1.12.1 // indirect
go.opencensus.io v0.24.0 // indirect
go.uber.org/automaxprocs v1.5.3 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/arch v0.4.0 // indirect
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/sys v0.14.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/tools v0.14.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
gocloud.dev v0.36.0 // indirect
golang.org/x/arch v0.7.0 // indirect
golang.org/x/crypto v0.18.0 // indirect
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc // indirect
golang.org/x/exp/typeparams v0.0.0-20231219180239-dc181d75b848 // indirect
golang.org/x/net v0.20.0 // indirect
golang.org/x/oauth2 v0.16.0 // indirect
golang.org/x/sync v0.6.0 // indirect
golang.org/x/sys v0.16.0 // indirect
golang.org/x/term v0.16.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.17.0 // indirect
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
google.golang.org/api v0.152.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto v0.0.0-20231120223509-83a465c0220f // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect
google.golang.org/grpc v1.59.0 // indirect
google.golang.org/protobuf v1.32.0 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/go-jose/go-jose.v2 v2.6.1 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/mail.v2 v2.3.1 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/klog/v2 v2.100.1 // indirect
honnef.co/go/tools v0.4.6 // indirect
k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01 // indirect
k8s.io/klog/v2 v2.110.1 // indirect
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
mvdan.cc/gofumpt v0.6.0 // indirect
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed // indirect
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b // indirect
mvdan.cc/unparam v0.0.0-20240104100049-c549a3470d14 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.3.0 // indirect
sigs.k8s.io/kind v0.20.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)

1319
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -5,6 +5,7 @@ import (
"net/http"
"github.com/go-resty/resty/v2"
"go.uber.org/zap"
)
func (cl *client) doGet(req *resty.Request, url string) error {
@@ -22,9 +23,13 @@ func (cl *client) doGet(req *resty.Request, url string) error {
return ErrSetupNeeded
}
}
rl.With("status", resp.StatusCode(), "body", string(resp.Body()), "error", err).Debug("error in do get")
return detailedError(resp, err)
}
checkAuthenticationIssue(resp, rl)
rl.With(
"status", resp.StatusCode(),
"body", string(resp.Body()),
@@ -48,6 +53,9 @@ func (cl *client) doPost(req *resty.Request, url string) error {
rl.With("status", resp.StatusCode(), "body", string(resp.Body()), "error", err).Debug("error in do post")
return detailedError(resp, err)
}
checkAuthenticationIssue(resp, rl)
rl.With(
"status", resp.StatusCode(),
"body", string(resp.Body()),
@@ -71,6 +79,9 @@ func (cl *client) doPut(req *resty.Request, url string) error {
rl.With("status", resp.StatusCode(), "body", string(resp.Body()), "error", err).Debug("error in do put")
return detailedError(resp, err)
}
checkAuthenticationIssue(resp, rl)
rl.With(
"status", resp.StatusCode(),
"body", string(resp.Body()),
@@ -81,3 +92,10 @@ func (cl *client) doPut(req *resty.Request, url string) error {
}
return nil
}
func checkAuthenticationIssue(resp *resty.Response, rl *zap.SugaredLogger) {
if resp != nil && (resp.StatusCode() == http.StatusUnauthorized || resp.StatusCode() == http.StatusForbidden) {
rl.With("status", resp.StatusCode()).Error("there seems to be an authentication issue - " +
"please check https://github.com/bakito/adguardhome-sync/wiki/FAQ")
}
}

View File

@@ -80,9 +80,9 @@ func New(config types.AdGuardInstance) (Client, error) {
}
return &client{
host: u.Host,
host: config.Host,
client: cl,
log: l.With("host", u.Host),
log: l.With("host", config.Host),
}, nil
}
@@ -96,9 +96,9 @@ type Client interface {
DeleteRewriteEntries(e ...model.RewriteEntry) error
Filtering() (*model.FilterStatus, error)
ToggleFiltering(enabled bool, interval int) error
AddFilters(whitelist bool, e ...model.Filter) error
DeleteFilters(whitelist bool, e ...model.Filter) error
UpdateFilters(whitelist bool, e ...model.Filter) error
AddFilter(whitelist bool, f model.Filter) error
DeleteFilter(whitelist bool, f model.Filter) error
UpdateFilter(whitelist bool, f model.Filter) error
RefreshFilters(whitelist bool) error
SetCustomRules(rules *[]string) error
SafeBrowsing() (bool, error)
@@ -114,9 +114,9 @@ type Client interface {
SetBlockedServices(services *model.BlockedServicesArray) error
SetBlockedServicesSchedule(schedule *model.BlockedServicesSchedule) error
Clients() (*model.Clients, error)
AddClients(client ...*model.Client) error
UpdateClients(client ...*model.Client) error
DeleteClients(client ...*model.Client) error
AddClient(client *model.Client) error
UpdateClient(client *model.Client) error
DeleteClient(client *model.Client) error
QueryLogConfig() (*model.QueryLogConfig, error)
SetQueryLogConfig(*model.QueryLogConfig) error
StatsConfig() (*model.StatsConfig, error)
@@ -128,8 +128,8 @@ type Client interface {
SetDNSConfig(*model.DNSConfig) error
DhcpConfig() (*model.DhcpStatus, error)
SetDhcpConfig(*model.DhcpStatus) error
AddDHCPStaticLeases(leases ...model.DhcpStaticLease) error
DeleteDHCPStaticLeases(leases ...model.DhcpStaticLease) error
AddDHCPStaticLease(lease model.DhcpStaticLease) error
DeleteDHCPStaticLease(lease model.DhcpStaticLease) error
}
type client struct {
@@ -169,7 +169,7 @@ func (cl *client) RewriteList() (*model.RewriteEntries, error) {
func (cl *client) AddRewriteEntries(entries ...model.RewriteEntry) error {
for i := range entries {
e := entries[i]
cl.log.With("domain", e.Domain, "answer", e.Answer).Info("Add 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")
if err != nil {
return err
@@ -181,7 +181,7 @@ func (cl *client) AddRewriteEntries(entries ...model.RewriteEntry) error {
func (cl *client) DeleteRewriteEntries(entries ...model.RewriteEntry) error {
for i := range entries {
e := entries[i]
cl.log.With("domain", e.Domain, "answer", e.Answer).Info("Delete 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")
if err != nil {
return err
@@ -229,43 +229,25 @@ func (cl *client) Filtering() (*model.FilterStatus, error) {
return f, err
}
func (cl *client) AddFilters(whitelist bool, filters ...model.Filter) error {
for _, f := range filters {
func (cl *client) AddFilter(whitelist bool, f model.Filter) error {
cl.log.With("url", f.Url, "whitelist", whitelist, "enabled", f.Enabled).Info("Add filter")
ff := &model.AddUrlRequest{Name: utils.Ptr(f.Name), Url: utils.Ptr(f.Url), Whitelist: utils.Ptr(whitelist)}
err := cl.doPost(cl.client.R().EnableTrace().SetBody(ff), "/filtering/add_url")
if err != nil {
return err
}
}
return nil
return cl.doPost(cl.client.R().EnableTrace().SetBody(ff), "/filtering/add_url")
}
func (cl *client) DeleteFilters(whitelist bool, filters ...model.Filter) error {
for _, f := range filters {
func (cl *client) DeleteFilter(whitelist bool, f model.Filter) error {
cl.log.With("url", f.Url, "whitelist", whitelist, "enabled", f.Enabled).Info("Delete filter")
ff := &model.RemoveUrlRequest{Url: utils.Ptr(f.Url), Whitelist: utils.Ptr(whitelist)}
err := cl.doPost(cl.client.R().EnableTrace().SetBody(ff), "/filtering/remove_url")
if err != nil {
return err
}
}
return nil
return cl.doPost(cl.client.R().EnableTrace().SetBody(ff), "/filtering/remove_url")
}
func (cl *client) UpdateFilters(whitelist bool, filters ...model.Filter) error {
for _, f := range filters {
func (cl *client) UpdateFilter(whitelist bool, f model.Filter) error {
cl.log.With("url", f.Url, "whitelist", whitelist, "enabled", f.Enabled).Info("Update filter")
fu := &model.FilterSetUrl{
Whitelist: utils.Ptr(whitelist), Url: utils.Ptr(f.Url),
Data: &model.FilterSetUrlData{Name: f.Name, Url: f.Url, Enabled: f.Enabled},
}
err := cl.doPost(cl.client.R().EnableTrace().SetBody(fu), "/filtering/set_url")
if err != nil {
return err
}
}
return nil
return cl.doPost(cl.client.R().EnableTrace().SetBody(fu), "/filtering/set_url")
}
func (cl *client) RefreshFilters(whitelist bool) error {
@@ -279,7 +261,11 @@ func (cl *client) ToggleProtection(enable bool) 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")
}
@@ -319,39 +305,19 @@ func (cl *client) Clients() (*model.Clients, error) {
return clients, err
}
func (cl *client) AddClients(clients ...*model.Client) error {
for i := range clients {
client := clients[i]
cl.log.With("name", *client.Name).Info("Add client")
err := cl.doPost(cl.client.R().EnableTrace().SetBody(client), "/clients/add")
if err != nil {
return err
}
}
return nil
func (cl *client) AddClient(client *model.Client) error {
cl.log.With("name", *client.Name).Info("Add client settings")
return cl.doPost(cl.client.R().EnableTrace().SetBody(client), "/clients/add")
}
func (cl *client) UpdateClients(clients ...*model.Client) error {
for _, client := range clients {
cl.log.With("name", *client.Name).Info("Update client")
err := cl.doPost(cl.client.R().EnableTrace().SetBody(&model.ClientUpdate{Name: client.Name, Data: client}), "/clients/update")
if err != nil {
return err
}
}
return nil
func (cl *client) UpdateClient(client *model.Client) error {
cl.log.With("name", *client.Name).Info("Update client settings")
return cl.doPost(cl.client.R().EnableTrace().SetBody(&model.ClientUpdate{Name: client.Name, Data: client}), "/clients/update")
}
func (cl *client) DeleteClients(clients ...*model.Client) error {
for i := range clients {
client := clients[i]
cl.log.With("name", *client.Name).Info("Delete client")
err := cl.doPost(cl.client.R().EnableTrace().SetBody(client), "/clients/delete")
if err != nil {
return err
}
}
return nil
func (cl *client) DeleteClient(client *model.Client) error {
cl.log.With("name", *client.Name).Info("Delete client settings")
return cl.doPost(cl.client.R().EnableTrace().SetBody(client), "/clients/delete")
}
func (cl *client) QueryLogConfig() (*model.QueryLogConfig, error) {
@@ -435,25 +401,21 @@ func (cl *client) SetDhcpConfig(config *model.DhcpStatus) error {
return cl.doPost(cl.client.R().EnableTrace().SetBody(config), "/dhcp/set_config")
}
func (cl *client) AddDHCPStaticLeases(leases ...model.DhcpStaticLease) error {
for _, l := range leases {
func (cl *client) AddDHCPStaticLease(l model.DhcpStaticLease) error {
cl.log.With("mac", l.Mac, "ip", l.Ip, "hostname", l.Hostname).Info("Add static dhcp lease")
err := cl.doPost(cl.client.R().EnableTrace().SetBody(l), "/dhcp/add_static_lease")
if err != nil {
return err
}
}
return nil
}
func (cl *client) DeleteDHCPStaticLeases(leases ...model.DhcpStaticLease) error {
for _, l := range leases {
func (cl *client) DeleteDHCPStaticLease(l model.DhcpStaticLease) error {
cl.log.With("mac", l.Mac, "ip", l.Ip, "hostname", l.Hostname).Info("Delete static dhcp lease")
err := cl.doPost(cl.client.R().EnableTrace().SetBody(l), "/dhcp/remove_static_lease")
if err != nil {
return err
}
}
return nil
}

View File

@@ -35,7 +35,10 @@ var _ = Describe("Client", func() {
Context("Host", func() {
It("should read the current host", func() {
cl, _ := client.New(types.AdGuardInstance{URL: "https://foo.bar:3000"})
inst := types.AdGuardInstance{URL: "https://foo.bar:3000"}
err := inst.Init()
Ω(err).ShouldNot(HaveOccurred())
cl, _ := client.New(inst)
host := cl.Host()
Ω(host).Should(Equal("foo.bar:3000"))
})
@@ -69,7 +72,9 @@ var _ = Describe("Client", func() {
`{"name":"","url":"foo","whitelist":true}`,
`{"name":"","url":"bar","whitelist":true}`,
)
err := cl.AddFilters(true, model.Filter{Url: "foo"}, model.Filter{Url: "bar"})
err := cl.AddFilter(true, model.Filter{Url: "foo"})
Ω(err).ShouldNot(HaveOccurred())
err = cl.AddFilter(true, model.Filter{Url: "bar"})
Ω(err).ShouldNot(HaveOccurred())
})
It("should update Filters", func() {
@@ -77,7 +82,9 @@ var _ = Describe("Client", func() {
`{"data":{"enabled":false,"name":"","url":"foo"},"url":"foo","whitelist":true}`,
`{"data":{"enabled":false,"name":"","url":"bar"},"url":"bar","whitelist":true}`,
)
err := cl.UpdateFilters(true, model.Filter{Url: "foo"}, model.Filter{Url: "bar"})
err := cl.UpdateFilter(true, model.Filter{Url: "foo"})
Ω(err).ShouldNot(HaveOccurred())
err = cl.UpdateFilter(true, model.Filter{Url: "bar"})
Ω(err).ShouldNot(HaveOccurred())
})
It("should delete Filters", func() {
@@ -85,7 +92,23 @@ var _ = Describe("Client", func() {
`{"url":"foo","whitelist":true}`,
`{"url":"bar","whitelist":true}`,
)
err := cl.DeleteFilters(true, model.Filter{Url: "foo"}, model.Filter{Url: "bar"})
err := cl.DeleteFilter(true, model.Filter{Url: "foo"})
Ω(err).ShouldNot(HaveOccurred())
err = cl.DeleteFilter(true, model.Filter{Url: "bar"})
Ω(err).ShouldNot(HaveOccurred())
})
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())
})
})
@@ -264,21 +287,21 @@ var _ = Describe("Client", func() {
ts, cl = ClientPost("/clients/add",
`{"ids":["id"],"name":"foo"}`,
)
err := cl.AddClients(&model.Client{Name: utils.Ptr("foo"), Ids: utils.Ptr([]string{"id"})})
err := cl.AddClient(&model.Client{Name: utils.Ptr("foo"), Ids: utils.Ptr([]string{"id"})})
Ω(err).ShouldNot(HaveOccurred())
})
It("should update Clients", func() {
ts, cl = ClientPost("/clients/update",
`{"data":{"ids":["id"],"name":"foo"},"name":"foo"}`,
)
err := cl.UpdateClients(&model.Client{Name: utils.Ptr("foo"), Ids: utils.Ptr([]string{"id"})})
err := cl.UpdateClient(&model.Client{Name: utils.Ptr("foo"), Ids: utils.Ptr([]string{"id"})})
Ω(err).ShouldNot(HaveOccurred())
})
It("should delete Clients", func() {
ts, cl = ClientPost("/clients/delete",
`{"ids":["id"],"name":"foo"}`,
)
err := cl.DeleteClients(&model.Client{Name: utils.Ptr("foo"), Ids: utils.Ptr([]string{"id"})})
err := cl.DeleteClient(&model.Client{Name: utils.Ptr("foo"), Ids: utils.Ptr([]string{"id"})})
Ω(err).ShouldNot(HaveOccurred())
})
})

View File

@@ -16,6 +16,22 @@ func (c *DhcpStatus) Clone() *DhcpStatus {
return clone
}
func (c *DhcpStatus) cleanV4V6() {
if c.V4 != nil && !c.V4.isValid() {
c.V4 = nil
}
if c.V6 != nil && !c.V6.isValid() {
c.V6 = nil
}
}
// CleanAndEquals dhcp server config equal check where V4 and V6 are cleaned in advance
func (c *DhcpStatus) CleanAndEquals(o *DhcpStatus) bool {
c.cleanV4V6()
o.cleanV4V6()
return c.Equals(o)
}
// Equals dhcp server config equal check
func (c *DhcpStatus) Equals(o *DhcpStatus) bool {
return utils.JsonEquals(c, o)
@@ -26,11 +42,14 @@ func (c *DhcpStatus) HasConfig() bool {
}
func (j DhcpConfigV4) isValid() bool {
return j.GatewayIp != nil && 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 {
return j.RangeStart != nil
return j.RangeStart != nil && *j.RangeStart != ""
}
type DhcpStaticLeases []DhcpStaticLease
@@ -349,11 +368,27 @@ func (ssc *SafeSearchConfig) Equals(o *SafeSearchConfig) bool {
}
func (pi *ProfileInfo) Equals(o *ProfileInfo) bool {
return pi.Name == o.Name &&
pi.Language == o.Language &&
return pi.Language == o.Language &&
pi.Theme == o.Theme
}
func (pi *ProfileInfo) ShouldSyncFor(o *ProfileInfo) *ProfileInfo {
if pi.Equals(o) {
return nil
}
merged := &ProfileInfo{Name: pi.Name, Language: pi.Language, Theme: pi.Theme}
if o.Language != "" {
merged.Language = o.Language
}
if o.Theme != "" {
merged.Theme = o.Theme
}
if merged.Name == "" || merged.Language == "" || merged.Theme == "" || merged.Equals(pi) {
return nil
}
return merged
}
func (bss *BlockedServicesSchedule) Equals(o *BlockedServicesSchedule) bool {
return utils.JsonEquals(bss, o)
}

View File

@@ -290,6 +290,23 @@ type Client struct {
SafesearchEnabled *bool `json:"safesearch_enabled,omitempty"`
Tags *[]string `json:"tags,omitempty"`
Upstreams *[]string `json:"upstreams,omitempty"`
// UpstreamsCacheEnabled NOTE: If `upstreams_cache_enabled` is not set in HTTP API
// `GET /clients/add` request then default value (false) will be used.
//
// If `upstreams_cache_enabled` is not set in HTTP API
// `GET /clients/update` request then the existing value will not be
// changed.
//
// This behaviour can be changed in the future versions.
UpstreamsCacheEnabled *bool `json:"upstreams_cache_enabled,omitempty"`
// UpstreamsCacheSize NOTE: If `upstreams_cache_enabled` is not set in HTTP API
// `GET /clients/update` request then the existing value will not be
// changed.
//
// This behaviour can be changed in the future versions.
UpstreamsCacheSize *int `json:"upstreams_cache_size,omitempty"`
UseGlobalBlockedServices *bool `json:"use_global_blocked_services,omitempty"`
UseGlobalSettings *bool `json:"use_global_settings,omitempty"`
}
@@ -402,6 +419,15 @@ type DNSConfig struct {
ProtectionDisabledUntil *string `json:"protection_disabled_until,omitempty"`
ProtectionEnabled *bool `json:"protection_enabled,omitempty"`
Ratelimit *int `json:"ratelimit,omitempty"`
// RatelimitSubnetSubnetLenIpv4 Length of the subnet mask for IPv4 addresses.
RatelimitSubnetSubnetLenIpv4 *int `json:"ratelimit_subnet_subnet_len_ipv4,omitempty"`
// RatelimitSubnetSubnetLenIpv6 Length of the subnet mask for IPv6 addresses.
RatelimitSubnetSubnetLenIpv6 *int `json:"ratelimit_subnet_subnet_len_ipv6,omitempty"`
// RatelimitWhitelist List of IP addresses excluded from rate limiting.
RatelimitWhitelist *[]string `json:"ratelimit_whitelist,omitempty"`
ResolveClients *bool `json:"resolve_clients,omitempty"`
// UpstreamDns Upstream servers, port is optional after colon. Empty value will reset it to default values.

View File

@@ -0,0 +1,74 @@
package model
import (
"github.com/bakito/adguardhome-sync/pkg/utils"
. "github.com/onsi/ginkgo/v2"
"github.com/onsi/gomega"
)
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("")}),
)
})
})

View File

@@ -403,7 +403,29 @@ var _ = Describe("Types", func() {
}
Ω(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{
V4: &model.DhcpConfigV4{
GatewayIp: utils.Ptr("1.2.3.4"),
@@ -416,15 +438,6 @@ var _ = Describe("Types", func() {
}
Ω(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())
})
})
})
})

95
pkg/config/config.go Normal file
View File

@@ -0,0 +1,95 @@
package config
import (
"errors"
"regexp"
"github.com/bakito/adguardhome-sync/pkg/log"
"github.com/bakito/adguardhome-sync/pkg/types"
"github.com/caarlos0/env/v10"
)
var (
envReplicasURLPattern = regexp.MustCompile(`^REPLICA(\d+)_URL=(.*)`)
logger = log.GetLogger("config")
)
func Get(configFile string, flags Flags) (*types.Config, error) {
path, err := configFilePath(configFile)
if err != nil {
return nil, err
}
cfg := initialConfig()
// read yaml config
if err := readFile(cfg, path); err != nil {
return nil, err
}
// overwrite from command flags
if err := readFlags(cfg, flags); err != nil {
return nil, err
}
// *bool field creates issues when already not nil
cfg.Origin.DHCPServerEnabled = nil // origin filed makes no sense to be set.
// keep previously set value
replicaDhcpServer := cfg.Replica.DHCPServerEnabled
cfg.Replica.DHCPServerEnabled = nil
// overwrite from env vars
if err := env.Parse(cfg); err != nil {
return nil, err
}
if err := env.ParseWithOptions(cfg.Replica, env.Options{Prefix: "REPLICA_"}); err != nil {
return nil, err
}
// if not set from env, use previous value
if cfg.Replica.DHCPServerEnabled == nil {
cfg.Replica.DHCPServerEnabled = replicaDhcpServer
}
if err := env.ParseWithOptions(&cfg.Origin, env.Options{Prefix: "ORIGIN_"}); err != nil {
return nil, err
}
if cfg.Replica != nil &&
cfg.Replica.URL == "" &&
cfg.Replica.Username == "" {
cfg.Replica = nil
}
if len(cfg.Replicas) > 0 && cfg.Replica != nil {
return nil, errors.New("mixed replica config in use. " +
"Do not use single replica and numbered (list) replica config combined")
}
handleDeprecatedEnvVars(cfg)
if cfg.Replica != nil {
cfg.Replicas = []types.AdGuardInstance{*cfg.Replica}
cfg.Replica = nil
}
cfg.Replicas, err = enrichReplicasFromEnv(cfg.Replicas)
return cfg, err
}
func initialConfig() *types.Config {
return &types.Config{
RunOnStart: true,
Origin: types.AdGuardInstance{
APIPath: "/control",
},
Replica: &types.AdGuardInstance{
APIPath: "/control",
},
API: types.API{
Port: 8080,
},
Features: types.NewFeatures(true),
}
}

View File

@@ -1,4 +1,4 @@
package cmd_test
package config_test
import (
"testing"
@@ -9,5 +9,5 @@ import (
func TestCmd(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Cmd Suite")
RunSpecs(t, "Config Suite")
}

238
pkg/config/config_test.go Normal file
View File

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

View File

@@ -0,0 +1,138 @@
package config_test
import (
"fmt"
"os"
"github.com/bakito/adguardhome-sync/pkg/config"
"github.com/bakito/adguardhome-sync/pkg/types"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var envVars = []string{
"FEATURES_GENERAL_SETTINGS",
"FEATURES_QUERY_LOG_CONFIG",
"FEATURES_STATS_CONFIG",
"FEATURES_CLIENT_SETTINGS",
"FEATURES_SERVICES",
"FEATURES_FILTERS",
"FEATURES_DHCP_SERVER_CONFIG",
"FEATURES_DHCP_STATIC_LEASES",
"FEATURES_DNS_SERVER_CONFIG",
"FEATURES_DNS_ACCESS_LISTS",
"FEATURES_DNS_REWRITES",
"REPLICA1_INTERFACE_NAME",
"REPLICA1_DHCP_SERVER_ENABLED",
}
var deprecatedEnvVars = []string{
"FEATURES_GENERALSETTINGS",
"FEATURES_QUERYLOGCONFIG",
"FEATURES_STATSCONFIG",
"FEATURES_CLIENTSETTINGS",
"FEATURES_SERVICES",
"FEATURES_FILTERS",
"FEATURES_DHCP_SERVERCONFIG",
"FEATURES_DHCP_STATICLEASES",
"FEATURES_DNS_SERVERCONFIG",
"FEATURES_DNS_ACCESSLISTS",
"FEATURES_DNS_REWRITES",
"REPLICA1_INTERFACENAME",
"REPLICA1_DHCPSERVERENABLED",
}
var _ = Describe("Config", func() {
Context("deprecated", func() {
BeforeEach(func() {
for _, envVar := range deprecatedEnvVars {
Ω(os.Setenv(envVar, "false")).ShouldNot(HaveOccurred())
}
})
AfterEach(func() {
for _, envVar := range deprecatedEnvVars {
Ω(os.Unsetenv(envVar)).ShouldNot(HaveOccurred())
}
})
Context("Get", func() {
It("features should be false", func() {
cfg, err := config.Get("", nil)
Ω(err).ShouldNot(HaveOccurred())
verifyFeatures(cfg, false)
})
})
})
Context("current", func() {
BeforeEach(func() {
for _, envVar := range envVars {
Ω(os.Unsetenv(envVar)).ShouldNot(HaveOccurred())
}
})
AfterEach(func() {
for _, envVar := range envVars {
Ω(os.Unsetenv(envVar)).ShouldNot(HaveOccurred())
}
})
Context("Get", func() {
It("features should be true by default", func() {
cfg, err := config.Get("", nil)
Ω(err).ShouldNot(HaveOccurred())
verifyFeatures(cfg, true)
})
It("features should be true by default", func() {
cfg, err := config.Get("", nil)
Ω(err).ShouldNot(HaveOccurred())
verifyFeatures(cfg, true)
})
It("features should be false", func() {
for _, envVar := range envVars {
Ω(os.Setenv(envVar, "false")).ShouldNot(HaveOccurred())
}
cfg, err := config.Get("", nil)
Ω(err).ShouldNot(HaveOccurred())
verifyFeatures(cfg, false)
})
Context("interface name", func() {
It("should set interface name of replica 1", func() {
Ω(os.Setenv("REPLICA1_URL", "https://foo.bar")).ShouldNot(HaveOccurred())
Ω(os.Setenv(fmt.Sprintf("REPLICA%s_INTERFACE_NAME", "1"), "eth0")).ShouldNot(HaveOccurred())
cfg, err := config.Get("", nil)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].InterfaceName).Should(Equal("eth0"))
})
})
Context("dhcp server", func() {
It("should enable the dhcp server of replica 1", func() {
Ω(os.Setenv("REPLICA1_URL", "https://foo.bar")).ShouldNot(HaveOccurred())
Ω(os.Setenv(fmt.Sprintf("REPLICA%s_DHCPSERVERENABLED", "1"), "true")).ShouldNot(HaveOccurred())
cfg, err := config.Get("", nil)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].DHCPServerEnabled).ShouldNot(BeNil())
Ω(*cfg.Replicas[0].DHCPServerEnabled).Should(BeTrue())
})
It("should disable the dhcp server of replica 1", func() {
Ω(os.Setenv("REPLICA1_URL", "https://foo.bar")).ShouldNot(HaveOccurred())
Ω(os.Setenv(fmt.Sprintf("REPLICA%s_DHCPSERVERENABLED", "1"), "false")).ShouldNot(HaveOccurred())
cfg, err := config.Get("", nil)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].DHCPServerEnabled).ShouldNot(BeNil())
Ω(*cfg.Replicas[0].DHCPServerEnabled).Should(BeFalse())
})
})
})
})
})
func verifyFeatures(cfg *types.Config, value bool) {
Ω(cfg.Features.GeneralSettings).Should(Equal(value))
Ω(cfg.Features.QueryLogConfig).Should(Equal(value))
Ω(cfg.Features.StatsConfig).Should(Equal(value))
Ω(cfg.Features.ClientSettings).Should(Equal(value))
Ω(cfg.Features.Services).Should(Equal(value))
Ω(cfg.Features.Filters).Should(Equal(value))
Ω(cfg.Features.DHCP.ServerConfig).Should(Equal(value))
Ω(cfg.Features.DHCP.StaticLeases).Should(Equal(value))
Ω(cfg.Features.DNS.ServerConfig).Should(Equal(value))
Ω(cfg.Features.DNS.AccessLists).Should(Equal(value))
Ω(cfg.Features.DNS.Rewrites).Should(Equal(value))
}

145
pkg/config/env.go Normal file
View File

@@ -0,0 +1,145 @@
package config
import (
"fmt"
"os"
"strconv"
"strings"
"github.com/bakito/adguardhome-sync/pkg/types"
"github.com/bakito/adguardhome-sync/pkg/utils"
"github.com/caarlos0/env/v10"
)
func handleDeprecatedEnvVars(cfg *types.Config) {
if val, ok := checkDeprecatedEnvVar("RUNONSTART", "RUN_ON_START"); ok {
cfg.RunOnStart, _ = strconv.ParseBool(val)
}
if val, ok := checkDeprecatedEnvVar("API_DARKMODE", "API_DARK_MODE"); ok {
cfg.API.DarkMode, _ = strconv.ParseBool(val)
}
if val, ok := checkDeprecatedEnvVar("FEATURES_GENERALSETTINGS", "FEATURES_GENERAL_SETTINGS"); ok {
cfg.Features.GeneralSettings, _ = strconv.ParseBool(val)
}
if val, ok := checkDeprecatedEnvVar("FEATURES_QUERYLOGCONFIG", "FEATURES_QUERY_LOG_CONFIG"); ok {
cfg.Features.QueryLogConfig, _ = strconv.ParseBool(val)
}
if val, ok := checkDeprecatedEnvVar("FEATURES_STATSCONFIG", "FEATURES_STATS_CONFIG"); ok {
cfg.Features.StatsConfig, _ = strconv.ParseBool(val)
}
if val, ok := checkDeprecatedEnvVar("FEATURES_CLIENTSETTINGS", "FEATURES_CLIENT_SETTINGS"); ok {
cfg.Features.ClientSettings, _ = strconv.ParseBool(val)
}
if val, ok := checkDeprecatedEnvVar("FEATURES_DHCP_SERVERCONFIG", "FEATURES_DHCP_SERVER_CONFIG"); ok {
cfg.Features.DHCP.ServerConfig, _ = strconv.ParseBool(val)
}
if val, ok := checkDeprecatedEnvVar("FEATURES_DHCP_STATICLEASES", "FEATURES_DHCP_STATIC_LEASES"); ok {
cfg.Features.DHCP.StaticLeases, _ = strconv.ParseBool(val)
}
if val, ok := checkDeprecatedEnvVar("FEATURES_DNS_ACCESSLISTS", "FEATURES_DNS_ACCESS_LISTS"); ok {
cfg.Features.DNS.AccessLists, _ = strconv.ParseBool(val)
}
if val, ok := checkDeprecatedEnvVar("FEATURES_DNS_SERVERCONFIG", "FEATURES_DNS_SERVER_CONFIG"); ok {
cfg.Features.DNS.ServerConfig, _ = strconv.ParseBool(val)
}
if cfg.Replica != nil {
if val, ok := checkDeprecatedEnvVar("REPLICA_WEBURL", "REPLICA_WEB_URL"); ok {
cfg.Replica.WebURL = val
}
if val, ok := checkDeprecatedEnvVar("REPLICA_AUTOSETUP", "REPLICA_AUTO_SETUP"); ok {
cfg.Replica.AutoSetup, _ = strconv.ParseBool(val)
}
if val, ok := checkDeprecatedEnvVar("REPLICA_INTERFACENAME", "REPLICA_INTERFACE_NAME"); ok {
cfg.Replica.InterfaceName = val
}
if val, ok := checkDeprecatedEnvVar("REPLICA_DHCPSERVERENABLED", "REPLICA_DHCP_SERVER_ENABLED"); ok {
if b, err := strconv.ParseBool(val); err != nil {
cfg.Replica.DHCPServerEnabled = utils.Ptr(b)
}
}
}
}
func checkDeprecatedEnvVar(oldName string, newName string) (string, bool) {
old, oldOK := os.LookupEnv(oldName)
if oldOK {
logger.With("deprecated", oldName, "replacement", newName).
Warn("Deprecated env variable is used, please use the correct one")
}
new, newOK := os.LookupEnv(newName)
if newOK {
return new, true
}
return old, oldOK
}
func checkDeprecatedReplicaEnvVar(oldPattern string, newPattern string, replicaID int) (string, bool) {
return checkDeprecatedEnvVar(fmt.Sprintf(oldPattern, replicaID), fmt.Sprintf(newPattern, replicaID))
}
// Manually collect replicas from env.
func enrichReplicasFromEnv(initialReplicas []types.AdGuardInstance) ([]types.AdGuardInstance, error) {
var replicas []types.AdGuardInstance
for _, v := range os.Environ() {
if envReplicasURLPattern.MatchString(v) {
sm := envReplicasURLPattern.FindStringSubmatch(v)
id, _ := strconv.Atoi(sm[1])
if id <= 0 {
return nil, fmt.Errorf("numbered replica env variables must have a number id >= 1, got %q", v)
}
if id > len(initialReplicas) {
replicas = append(replicas, types.AdGuardInstance{URL: sm[2]})
} else {
re := initialReplicas[id-1]
re.URL = sm[2]
replicas = append(replicas, re)
}
}
}
if len(replicas) == 0 {
replicas = initialReplicas
}
for i := range replicas {
reID := i + 1
// keep previously set value
replicaDhcpServer := replicas[i].DHCPServerEnabled
replicas[i].DHCPServerEnabled = nil
if err := env.ParseWithOptions(&replicas[i], env.Options{Prefix: fmt.Sprintf("REPLICA%d_", reID)}); err != nil {
return nil, err
}
if replicas[i].DHCPServerEnabled == nil {
replicas[i].DHCPServerEnabled = replicaDhcpServer
}
if val, ok := checkDeprecatedReplicaEnvVar("REPLICA%d_APIPATH", "REPLICA%d_API_PATH", reID); ok {
replicas[i].APIPath = val
}
if val, ok := checkDeprecatedReplicaEnvVar("REPLICA%d_INSECURESKIPVERIFY", "REPLICA%d_INSECURE_SKIP_VERIFY", reID); ok {
replicas[i].InsecureSkipVerify = strings.EqualFold(val, "true")
}
if val, ok := checkDeprecatedReplicaEnvVar("REPLICA%d_AUTOSETUP", "REPLICA%d_AUTO_SETUP", reID); ok {
replicas[i].AutoSetup = strings.EqualFold(val, "true")
}
if val, ok := checkDeprecatedReplicaEnvVar("REPLICA%d_INTERFACENAME", "REPLICA%d_INTERFACE_NAME", reID); ok {
replicas[i].InterfaceName = val
}
if dhcpEnabled, ok := checkDeprecatedReplicaEnvVar("REPLICA%d_DHCPSERVERENABLED", "REPLICA%d_DHCP_SERVER_ENABLED", reID); ok {
if strings.EqualFold(dhcpEnabled, "true") {
replicas[i].DHCPServerEnabled = utils.Ptr(true)
} else if strings.EqualFold(dhcpEnabled, "false") {
replicas[i].DHCPServerEnabled = utils.Ptr(false)
}
}
if replicas[i].APIPath == "" {
replicas[i].APIPath = "/control"
}
}
return replicas, nil
}

25
pkg/config/env_test.go Normal file
View File

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

34
pkg/config/file.go Normal file
View File

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

32
pkg/config/file_test.go Normal file
View File

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

44
pkg/config/flag-names.go Normal file
View File

@@ -0,0 +1,44 @@
package config
const (
FlagCron = "cron"
FlagRunOnStart = "runOnStart"
FlagPrintConfigOnly = "printConfigOnly"
FlagContinueOnError = "continueOnError"
FlagApiPort = "api-port"
FlagApiUsername = "api-username"
FlagApiPassword = "api-password"
FlagApiDarkMode = "api-dark-mode"
FlagFeatureDhcpServerConfig = "feature-dhcp-server-config"
FlagFeatureDhcpStaticLeases = "feature-dhcp-static-leases"
FlagFeatureDnsServerConfig = "feature-dns-server-config"
FlagFeatureDnsAccessLists = "feature-dns-access-lists"
FlagFeatureDnsRewrites = "feature-dns-rewrites"
FlagFeatureGeneral = "feature-general-settings"
FlagFeatureQueryLog = "feature-query-log-config"
FlagFeatureStats = "feature-stats-config"
FlagFeatureClient = "feature-client-settings"
FlagFeatureServices = "feature-services"
FlagFeatureFilters = "feature-filters"
FlagOriginURL = "origin-url"
FlagOriginWebURL = "origin-web-url"
FlagOriginApiPath = "origin-api-path"
FlagOriginUsername = "origin-username"
FlagOriginPassword = "origin-password"
FlagOriginCookie = "origin-cookie"
FlagOriginISV = "origin-insecure-skip-verify"
FlagReplicaURL = "replica-url"
FlagReplicaWebURL = "replica-web-url"
FlagReplicaApiPath = "replica-api-path"
FlagReplicaUsername = "replica-username"
FlagReplicaPassword = "replica-password"
FlagReplicaCookie = "replica-cookie"
FlagReplicaISV = "replica-insecure-skip-verify"
FlagReplicaAutoSetup = "replica-auto-setup"
FlagReplicaInterfaceName = "replica-interface-name"
)

283
pkg/config/flags.go Normal file
View File

@@ -0,0 +1,283 @@
package config
import (
"github.com/bakito/adguardhome-sync/pkg/types"
)
func readFlags(cfg *types.Config, flags Flags) error {
if flags == nil {
return nil
}
fr := &flagReader{
cfg: cfg,
flags: flags,
}
if err := fr.readRootFlags(); err != nil {
return err
}
if err := fr.readApiFlags(); err != nil {
return err
}
if err := fr.readFeatureFlags(); err != nil {
return err
}
if err := fr.readOriginFlags(); err != nil {
return err
}
if err := fr.readReplicaFlags(); err != nil {
return err
}
return nil
}
type flagReader struct {
cfg *types.Config
flags Flags
}
func (fr *flagReader) readReplicaFlags() error {
if err := fr.setStringFlag(FlagReplicaURL, func(cgf *types.Config, value string) {
fr.cfg.Replica.URL = value
}); err != nil {
return err
}
if err := fr.setStringFlag(FlagReplicaWebURL, func(cgf *types.Config, value string) {
fr.cfg.Replica.WebURL = value
}); err != nil {
return err
}
if err := fr.setStringFlag(FlagReplicaApiPath, func(cgf *types.Config, value string) {
fr.cfg.Replica.APIPath = value
}); err != nil {
return err
}
if err := fr.setStringFlag(FlagReplicaUsername, func(cgf *types.Config, value string) {
fr.cfg.Replica.Username = value
}); err != nil {
return err
}
if err := fr.setStringFlag(FlagReplicaPassword, func(cgf *types.Config, value string) {
fr.cfg.Replica.Password = value
}); err != nil {
return err
}
if err := fr.setStringFlag(FlagReplicaCookie, func(cgf *types.Config, value string) {
fr.cfg.Replica.Cookie = value
}); err != nil {
return err
}
if err := fr.setBoolFlag(FlagReplicaISV, func(cgf *types.Config, value bool) {
fr.cfg.Replica.InsecureSkipVerify = value
}); err != nil {
return err
}
if err := fr.setBoolFlag(FlagReplicaAutoSetup, func(cgf *types.Config, value bool) {
fr.cfg.Replica.AutoSetup = value
}); err != nil {
return err
}
if err := fr.setStringFlag(FlagReplicaInterfaceName, func(cgf *types.Config, value string) {
fr.cfg.Replica.InterfaceName = value
}); err != nil {
return err
}
return nil
}
func (fr *flagReader) readOriginFlags() error {
if err := fr.setStringFlag(FlagOriginURL, func(cgf *types.Config, value string) {
fr.cfg.Origin.URL = value
}); err != nil {
return err
}
if err := fr.setStringFlag(FlagOriginWebURL, func(cgf *types.Config, value string) {
fr.cfg.Origin.WebURL = value
}); err != nil {
return err
}
if err := fr.setStringFlag(FlagOriginApiPath, func(cgf *types.Config, value string) {
fr.cfg.Origin.APIPath = value
}); err != nil {
return err
}
if err := fr.setStringFlag(FlagOriginUsername, func(cgf *types.Config, value string) {
fr.cfg.Origin.Username = value
}); err != nil {
return err
}
if err := fr.setStringFlag(FlagOriginPassword, func(cgf *types.Config, value string) {
fr.cfg.Origin.Password = value
}); err != nil {
return err
}
if err := fr.setStringFlag(FlagOriginCookie, func(cgf *types.Config, value string) {
fr.cfg.Origin.Cookie = value
}); err != nil {
return err
}
if err := fr.setBoolFlag(FlagOriginISV, func(cgf *types.Config, value bool) {
fr.cfg.Origin.InsecureSkipVerify = value
}); err != nil {
return err
}
return nil
}
func (fr *flagReader) readFeatureFlags() error {
if err := fr.setBoolFlag(FlagFeatureDhcpServerConfig, func(cgf *types.Config, value bool) {
fr.cfg.Features.DHCP.ServerConfig = value
}); err != nil {
return err
}
if err := fr.setBoolFlag(FlagFeatureDhcpStaticLeases, func(cgf *types.Config, value bool) {
fr.cfg.Features.DHCP.StaticLeases = value
}); err != nil {
return err
}
if err := fr.setBoolFlag(FlagFeatureDnsServerConfig, func(cgf *types.Config, value bool) {
fr.cfg.Features.DNS.ServerConfig = value
}); err != nil {
return err
}
if err := fr.setBoolFlag(FlagFeatureDnsAccessLists, func(cgf *types.Config, value bool) {
fr.cfg.Features.DNS.AccessLists = value
}); err != nil {
return err
}
if err := fr.setBoolFlag(FlagFeatureDnsRewrites, func(cgf *types.Config, value bool) {
fr.cfg.Features.DNS.Rewrites = value
}); err != nil {
return err
}
if err := fr.setBoolFlag(FlagFeatureGeneral, func(cgf *types.Config, value bool) {
fr.cfg.Features.GeneralSettings = value
}); err != nil {
return err
}
if err := fr.setBoolFlag(FlagFeatureQueryLog, func(cgf *types.Config, value bool) {
fr.cfg.Features.QueryLogConfig = value
}); err != nil {
return err
}
if err := fr.setBoolFlag(FlagFeatureStats, func(cgf *types.Config, value bool) {
fr.cfg.Features.StatsConfig = value
}); err != nil {
return err
}
if err := fr.setBoolFlag(FlagFeatureClient, func(cgf *types.Config, value bool) {
fr.cfg.Features.ClientSettings = value
}); err != nil {
return err
}
if err := fr.setBoolFlag(FlagFeatureServices, func(cgf *types.Config, value bool) {
fr.cfg.Features.Services = value
}); err != nil {
return err
}
if err := fr.setBoolFlag(FlagFeatureFilters, func(cgf *types.Config, value bool) {
fr.cfg.Features.Filters = value
}); err != nil {
return err
}
return 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
}); err != nil {
return
}
if err = fr.setStringFlag(FlagApiPassword, func(cgf *types.Config, value string) {
fr.cfg.API.Password = value
}); err != nil {
return
}
if err = fr.setBoolFlag(FlagApiDarkMode, func(cgf *types.Config, value bool) {
fr.cfg.API.DarkMode = value
}); err != nil {
return
}
return
}
func (fr *flagReader) readRootFlags() (err error) {
if err = fr.setStringFlag(FlagCron, func(cgf *types.Config, value string) {
fr.cfg.Cron = value
}); err != nil {
return
}
if err = fr.setBoolFlag(FlagRunOnStart, func(cgf *types.Config, value bool) {
fr.cfg.RunOnStart = value
}); err != nil {
return
}
if err = fr.setBoolFlag(FlagPrintConfigOnly, func(cgf *types.Config, value bool) {
fr.cfg.PrintConfigOnly = value
}); err != nil {
return
}
if err = fr.setBoolFlag(FlagContinueOnError, func(cgf *types.Config, value bool) {
fr.cfg.ContinueOnError = value
}); err != nil {
return
}
return
}
type Flags interface {
Changed(name string) bool
GetString(name string) (string, error)
GetInt(name string) (int, error)
GetBool(name string) (bool, error)
}
func (fr *flagReader) setStringFlag(name string, cb callback[string]) (err error) {
if fr.flags.Changed(name) {
if value, err := fr.flags.GetString(name); err != nil {
return err
} else {
cb(fr.cfg, value)
}
}
return nil
}
func (fr *flagReader) setBoolFlag(name string, cb callback[bool]) (err error) {
if fr.flags.Changed(name) {
if value, err := fr.flags.GetBool(name); err != nil {
return err
} else {
cb(fr.cfg, value)
}
}
return nil
}
func (fr *flagReader) setIntFlag(name string, cb callback[int]) (err error) {
if fr.flags.Changed(name) {
if value, err := fr.flags.GetInt(name); err != nil {
return err
} else {
cb(fr.cfg, value)
}
}
return nil
}
type callback[T any] func(cgf *types.Config, value T)

235
pkg/config/flags_test.go Normal file
View File

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

View File

@@ -10,6 +10,7 @@ import (
const (
logHistorySize = 50
envLogLevel = "LOG_LEVEL"
envLogFormat = "LOG_FORMAT"
)
var (
@@ -31,14 +32,23 @@ func init() {
}
}
format := "console"
if fmt, ok := os.LookupEnv(envLogFormat); ok {
format = fmt
}
cfg := zap.Config{
Level: zap.NewAtomicLevelAt(level),
Development: false,
Encoding: "console",
EncoderConfig: zap.NewDevelopmentEncoderConfig(),
Encoding: format,
EncoderConfig: zap.NewProductionEncoderConfig(),
OutputPaths: []string{"stdout"},
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 {
return zapcore.NewTee(c, &logList{
enc: zapcore.NewConsoleEncoder(cfg.EncoderConfig),

View File

@@ -1,5 +1,10 @@
// Code generated by MockGen. DO NOT EDIT.
// 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
@@ -8,7 +13,7 @@ import (
reflect "reflect"
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.
@@ -49,65 +54,52 @@ func (mr *MockClientMockRecorder) AccessList() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AccessList", reflect.TypeOf((*MockClient)(nil).AccessList))
}
// AddClients mocks base method.
func (m *MockClient) AddClients(arg0 ...*model.Client) error {
// AddClient mocks base method.
func (m *MockClient) AddClient(arg0 *model.Client) error {
m.ctrl.T.Helper()
varargs := []interface{}{}
for _, a := range arg0 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "AddClients", varargs...)
ret := m.ctrl.Call(m, "AddClient", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// AddClients indicates an expected call of AddClients.
func (mr *MockClientMockRecorder) AddClients(arg0 ...interface{}) *gomock.Call {
// AddClient indicates an expected call of AddClient.
func (mr *MockClientMockRecorder) AddClient(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddClients", reflect.TypeOf((*MockClient)(nil).AddClients), arg0...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddClient", reflect.TypeOf((*MockClient)(nil).AddClient), arg0)
}
// AddDHCPStaticLeases mocks base method.
func (m *MockClient) AddDHCPStaticLeases(arg0 ...model.DhcpStaticLease) error {
// AddDHCPStaticLease mocks base method.
func (m *MockClient) AddDHCPStaticLease(arg0 model.DhcpStaticLease) error {
m.ctrl.T.Helper()
varargs := []interface{}{}
for _, a := range arg0 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "AddDHCPStaticLeases", varargs...)
ret := m.ctrl.Call(m, "AddDHCPStaticLease", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// AddDHCPStaticLeases indicates an expected call of AddDHCPStaticLeases.
func (mr *MockClientMockRecorder) AddDHCPStaticLeases(arg0 ...interface{}) *gomock.Call {
// AddDHCPStaticLease indicates an expected call of AddDHCPStaticLease.
func (mr *MockClientMockRecorder) AddDHCPStaticLease(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddDHCPStaticLeases", reflect.TypeOf((*MockClient)(nil).AddDHCPStaticLeases), arg0...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddDHCPStaticLease", reflect.TypeOf((*MockClient)(nil).AddDHCPStaticLease), arg0)
}
// AddFilters mocks base method.
func (m *MockClient) AddFilters(arg0 bool, arg1 ...model.Filter) error {
// AddFilter mocks base method.
func (m *MockClient) AddFilter(arg0 bool, arg1 model.Filter) error {
m.ctrl.T.Helper()
varargs := []interface{}{arg0}
for _, a := range arg1 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "AddFilters", varargs...)
ret := m.ctrl.Call(m, "AddFilter", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// AddFilters indicates an expected call of AddFilters.
func (mr *MockClientMockRecorder) AddFilters(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
// AddFilter indicates an expected call of AddFilter.
func (mr *MockClientMockRecorder) AddFilter(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0}, arg1...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddFilters", reflect.TypeOf((*MockClient)(nil).AddFilters), varargs...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddFilter", reflect.TypeOf((*MockClient)(nil).AddFilter), arg0, arg1)
}
// AddRewriteEntries mocks base method.
func (m *MockClient) AddRewriteEntries(arg0 ...model.RewriteEntry) error {
m.ctrl.T.Helper()
varargs := []interface{}{}
varargs := []any{}
for _, a := range arg0 {
varargs = append(varargs, a)
}
@@ -117,7 +109,7 @@ func (m *MockClient) AddRewriteEntries(arg0 ...model.RewriteEntry) error {
}
// AddRewriteEntries indicates an expected call of AddRewriteEntries.
func (mr *MockClientMockRecorder) AddRewriteEntries(arg0 ...interface{}) *gomock.Call {
func (mr *MockClientMockRecorder) AddRewriteEntries(arg0 ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRewriteEntries", reflect.TypeOf((*MockClient)(nil).AddRewriteEntries), arg0...)
}
@@ -182,65 +174,52 @@ func (mr *MockClientMockRecorder) DNSConfig() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DNSConfig", reflect.TypeOf((*MockClient)(nil).DNSConfig))
}
// DeleteClients mocks base method.
func (m *MockClient) DeleteClients(arg0 ...*model.Client) error {
// DeleteClient mocks base method.
func (m *MockClient) DeleteClient(arg0 *model.Client) error {
m.ctrl.T.Helper()
varargs := []interface{}{}
for _, a := range arg0 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "DeleteClients", varargs...)
ret := m.ctrl.Call(m, "DeleteClient", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteClients indicates an expected call of DeleteClients.
func (mr *MockClientMockRecorder) DeleteClients(arg0 ...interface{}) *gomock.Call {
// DeleteClient indicates an expected call of DeleteClient.
func (mr *MockClientMockRecorder) DeleteClient(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteClients", reflect.TypeOf((*MockClient)(nil).DeleteClients), arg0...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteClient", reflect.TypeOf((*MockClient)(nil).DeleteClient), arg0)
}
// DeleteDHCPStaticLeases mocks base method.
func (m *MockClient) DeleteDHCPStaticLeases(arg0 ...model.DhcpStaticLease) error {
// DeleteDHCPStaticLease mocks base method.
func (m *MockClient) DeleteDHCPStaticLease(arg0 model.DhcpStaticLease) error {
m.ctrl.T.Helper()
varargs := []interface{}{}
for _, a := range arg0 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "DeleteDHCPStaticLeases", varargs...)
ret := m.ctrl.Call(m, "DeleteDHCPStaticLease", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteDHCPStaticLeases indicates an expected call of DeleteDHCPStaticLeases.
func (mr *MockClientMockRecorder) DeleteDHCPStaticLeases(arg0 ...interface{}) *gomock.Call {
// DeleteDHCPStaticLease indicates an expected call of DeleteDHCPStaticLease.
func (mr *MockClientMockRecorder) DeleteDHCPStaticLease(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteDHCPStaticLeases", reflect.TypeOf((*MockClient)(nil).DeleteDHCPStaticLeases), arg0...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteDHCPStaticLease", reflect.TypeOf((*MockClient)(nil).DeleteDHCPStaticLease), arg0)
}
// DeleteFilters mocks base method.
func (m *MockClient) DeleteFilters(arg0 bool, arg1 ...model.Filter) error {
// DeleteFilter mocks base method.
func (m *MockClient) DeleteFilter(arg0 bool, arg1 model.Filter) error {
m.ctrl.T.Helper()
varargs := []interface{}{arg0}
for _, a := range arg1 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "DeleteFilters", varargs...)
ret := m.ctrl.Call(m, "DeleteFilter", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteFilters indicates an expected call of DeleteFilters.
func (mr *MockClientMockRecorder) DeleteFilters(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
// DeleteFilter indicates an expected call of DeleteFilter.
func (mr *MockClientMockRecorder) DeleteFilter(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0}, arg1...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteFilters", reflect.TypeOf((*MockClient)(nil).DeleteFilters), varargs...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteFilter", reflect.TypeOf((*MockClient)(nil).DeleteFilter), arg0, arg1)
}
// DeleteRewriteEntries mocks base method.
func (m *MockClient) DeleteRewriteEntries(arg0 ...model.RewriteEntry) error {
m.ctrl.T.Helper()
varargs := []interface{}{}
varargs := []any{}
for _, a := range arg0 {
varargs = append(varargs, a)
}
@@ -250,7 +229,7 @@ func (m *MockClient) DeleteRewriteEntries(arg0 ...model.RewriteEntry) error {
}
// DeleteRewriteEntries indicates an expected call of DeleteRewriteEntries.
func (mr *MockClientMockRecorder) DeleteRewriteEntries(arg0 ...interface{}) *gomock.Call {
func (mr *MockClientMockRecorder) DeleteRewriteEntries(arg0 ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteRewriteEntries", reflect.TypeOf((*MockClient)(nil).DeleteRewriteEntries), arg0...)
}
@@ -353,7 +332,7 @@ func (m *MockClient) RefreshFilters(arg0 bool) error {
}
// RefreshFilters indicates an expected call of RefreshFilters.
func (mr *MockClientMockRecorder) RefreshFilters(arg0 interface{}) *gomock.Call {
func (mr *MockClientMockRecorder) RefreshFilters(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RefreshFilters", reflect.TypeOf((*MockClient)(nil).RefreshFilters), arg0)
}
@@ -412,7 +391,7 @@ func (m *MockClient) SetAccessList(arg0 *model.AccessList) error {
}
// SetAccessList indicates an expected call of SetAccessList.
func (mr *MockClientMockRecorder) SetAccessList(arg0 interface{}) *gomock.Call {
func (mr *MockClientMockRecorder) SetAccessList(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetAccessList", reflect.TypeOf((*MockClient)(nil).SetAccessList), arg0)
}
@@ -426,7 +405,7 @@ func (m *MockClient) SetBlockedServices(arg0 *[]string) error {
}
// SetBlockedServices indicates an expected call of SetBlockedServices.
func (mr *MockClientMockRecorder) SetBlockedServices(arg0 interface{}) *gomock.Call {
func (mr *MockClientMockRecorder) SetBlockedServices(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBlockedServices", reflect.TypeOf((*MockClient)(nil).SetBlockedServices), arg0)
}
@@ -440,7 +419,7 @@ func (m *MockClient) SetBlockedServicesSchedule(arg0 *model.BlockedServicesSched
}
// SetBlockedServicesSchedule indicates an expected call of SetBlockedServicesSchedule.
func (mr *MockClientMockRecorder) SetBlockedServicesSchedule(arg0 interface{}) *gomock.Call {
func (mr *MockClientMockRecorder) SetBlockedServicesSchedule(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBlockedServicesSchedule", reflect.TypeOf((*MockClient)(nil).SetBlockedServicesSchedule), arg0)
}
@@ -454,7 +433,7 @@ func (m *MockClient) SetCustomRules(arg0 *[]string) error {
}
// SetCustomRules indicates an expected call of SetCustomRules.
func (mr *MockClientMockRecorder) SetCustomRules(arg0 interface{}) *gomock.Call {
func (mr *MockClientMockRecorder) SetCustomRules(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetCustomRules", reflect.TypeOf((*MockClient)(nil).SetCustomRules), arg0)
}
@@ -468,7 +447,7 @@ func (m *MockClient) SetDNSConfig(arg0 *model.DNSConfig) error {
}
// SetDNSConfig indicates an expected call of SetDNSConfig.
func (mr *MockClientMockRecorder) SetDNSConfig(arg0 interface{}) *gomock.Call {
func (mr *MockClientMockRecorder) SetDNSConfig(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDNSConfig", reflect.TypeOf((*MockClient)(nil).SetDNSConfig), arg0)
}
@@ -482,7 +461,7 @@ func (m *MockClient) SetDhcpConfig(arg0 *model.DhcpStatus) error {
}
// SetDhcpConfig indicates an expected call of SetDhcpConfig.
func (mr *MockClientMockRecorder) SetDhcpConfig(arg0 interface{}) *gomock.Call {
func (mr *MockClientMockRecorder) SetDhcpConfig(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDhcpConfig", reflect.TypeOf((*MockClient)(nil).SetDhcpConfig), arg0)
}
@@ -496,7 +475,7 @@ func (m *MockClient) SetProfileInfo(arg0 *model.ProfileInfo) error {
}
// SetProfileInfo indicates an expected call of SetProfileInfo.
func (mr *MockClientMockRecorder) SetProfileInfo(arg0 interface{}) *gomock.Call {
func (mr *MockClientMockRecorder) SetProfileInfo(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetProfileInfo", reflect.TypeOf((*MockClient)(nil).SetProfileInfo), arg0)
}
@@ -510,7 +489,7 @@ func (m *MockClient) SetQueryLogConfig(arg0 *model.QueryLogConfig) error {
}
// SetQueryLogConfig indicates an expected call of SetQueryLogConfig.
func (mr *MockClientMockRecorder) SetQueryLogConfig(arg0 interface{}) *gomock.Call {
func (mr *MockClientMockRecorder) SetQueryLogConfig(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetQueryLogConfig", reflect.TypeOf((*MockClient)(nil).SetQueryLogConfig), arg0)
}
@@ -524,7 +503,7 @@ func (m *MockClient) SetSafeSearchConfig(arg0 *model.SafeSearchConfig) error {
}
// SetSafeSearchConfig indicates an expected call of SetSafeSearchConfig.
func (mr *MockClientMockRecorder) SetSafeSearchConfig(arg0 interface{}) *gomock.Call {
func (mr *MockClientMockRecorder) SetSafeSearchConfig(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSafeSearchConfig", reflect.TypeOf((*MockClient)(nil).SetSafeSearchConfig), arg0)
}
@@ -538,7 +517,7 @@ func (m *MockClient) SetStatsConfig(arg0 *model.StatsConfig) error {
}
// SetStatsConfig indicates an expected call of SetStatsConfig.
func (mr *MockClientMockRecorder) SetStatsConfig(arg0 interface{}) *gomock.Call {
func (mr *MockClientMockRecorder) SetStatsConfig(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetStatsConfig", reflect.TypeOf((*MockClient)(nil).SetStatsConfig), arg0)
}
@@ -596,7 +575,7 @@ func (m *MockClient) ToggleFiltering(arg0 bool, arg1 int) error {
}
// ToggleFiltering indicates an expected call of ToggleFiltering.
func (mr *MockClientMockRecorder) ToggleFiltering(arg0, arg1 interface{}) *gomock.Call {
func (mr *MockClientMockRecorder) ToggleFiltering(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleFiltering", reflect.TypeOf((*MockClient)(nil).ToggleFiltering), arg0, arg1)
}
@@ -610,7 +589,7 @@ func (m *MockClient) ToggleParental(arg0 bool) error {
}
// ToggleParental indicates an expected call of ToggleParental.
func (mr *MockClientMockRecorder) ToggleParental(arg0 interface{}) *gomock.Call {
func (mr *MockClientMockRecorder) ToggleParental(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleParental", reflect.TypeOf((*MockClient)(nil).ToggleParental), arg0)
}
@@ -624,7 +603,7 @@ func (m *MockClient) ToggleProtection(arg0 bool) error {
}
// ToggleProtection indicates an expected call of ToggleProtection.
func (mr *MockClientMockRecorder) ToggleProtection(arg0 interface{}) *gomock.Call {
func (mr *MockClientMockRecorder) ToggleProtection(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleProtection", reflect.TypeOf((*MockClient)(nil).ToggleProtection), arg0)
}
@@ -638,44 +617,35 @@ func (m *MockClient) ToggleSafeBrowsing(arg0 bool) error {
}
// ToggleSafeBrowsing indicates an expected call of ToggleSafeBrowsing.
func (mr *MockClientMockRecorder) ToggleSafeBrowsing(arg0 interface{}) *gomock.Call {
func (mr *MockClientMockRecorder) ToggleSafeBrowsing(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleSafeBrowsing", reflect.TypeOf((*MockClient)(nil).ToggleSafeBrowsing), arg0)
}
// UpdateClients mocks base method.
func (m *MockClient) UpdateClients(arg0 ...*model.Client) error {
// UpdateClient mocks base method.
func (m *MockClient) UpdateClient(arg0 *model.Client) error {
m.ctrl.T.Helper()
varargs := []interface{}{}
for _, a := range arg0 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "UpdateClients", varargs...)
ret := m.ctrl.Call(m, "UpdateClient", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// UpdateClients indicates an expected call of UpdateClients.
func (mr *MockClientMockRecorder) UpdateClients(arg0 ...interface{}) *gomock.Call {
// UpdateClient indicates an expected call of UpdateClient.
func (mr *MockClientMockRecorder) UpdateClient(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateClients", reflect.TypeOf((*MockClient)(nil).UpdateClients), arg0...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateClient", reflect.TypeOf((*MockClient)(nil).UpdateClient), arg0)
}
// UpdateFilters mocks base method.
func (m *MockClient) UpdateFilters(arg0 bool, arg1 ...model.Filter) error {
// UpdateFilter mocks base method.
func (m *MockClient) UpdateFilter(arg0 bool, arg1 model.Filter) error {
m.ctrl.T.Helper()
varargs := []interface{}{arg0}
for _, a := range arg1 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "UpdateFilters", varargs...)
ret := m.ctrl.Call(m, "UpdateFilter", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// UpdateFilters indicates an expected call of UpdateFilters.
func (mr *MockClientMockRecorder) UpdateFilters(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
// UpdateFilter indicates an expected call of UpdateFilter.
func (mr *MockClientMockRecorder) UpdateFilter(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0}, arg1...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateFilters", reflect.TypeOf((*MockClient)(nil).UpdateFilters), varargs...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateFilter", reflect.TypeOf((*MockClient)(nil).UpdateFilter), arg0, arg1)
}

98
pkg/mocks/flags/mock.go Normal file
View File

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

283
pkg/sync/action-general.go Normal file
View File

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

104
pkg/sync/action.go Normal file
View File

@@ -0,0 +1,104 @@
package sync
import (
"github.com/bakito/adguardhome-sync/pkg/client"
"github.com/bakito/adguardhome-sync/pkg/client/model"
"github.com/bakito/adguardhome-sync/pkg/types"
"go.uber.org/zap"
)
func setupActions(cfg *types.Config) (actions []syncAction) {
if cfg.Features.GeneralSettings {
actions = append(actions,
action("profile info", actionProfileInfo),
action("protection", actionProtection),
action("parental", actionParental),
action("safe search config", actionSafeSearchConfig),
action("safe browsing", actionSafeBrowsing),
)
}
if cfg.Features.QueryLogConfig {
actions = append(actions,
action("query log config", actionQueryLogConfig),
)
}
if cfg.Features.StatsConfig {
actions = append(actions,
action("stats config", actionStatsConfig),
)
}
if cfg.Features.DNS.Rewrites {
actions = append(actions,
action("DNS rewrites", actionDNSRewrites),
)
}
if cfg.Features.Filters {
actions = append(actions,
action("actionFilters", actionFilters),
)
}
if cfg.Features.Services {
actions = append(actions,
action("blocked services", actionBlockedServices),
action("blocked services schedule", actionBlockedServicesSchedule),
)
}
if cfg.Features.ClientSettings {
actions = append(actions,
action("client settings", actionClientSettings),
)
}
if cfg.Features.DNS.AccessLists {
actions = append(actions,
action("DNS access lists", actionDNSAccessLists),
)
}
if cfg.Features.DNS.ServerConfig {
actions = append(actions,
action("DNS server config", actionDNSServerConfig),
)
}
if cfg.Features.DHCP.ServerConfig {
actions = append(actions,
action("DHCP server config", actionDHCPServerConfig),
)
}
if cfg.Features.DHCP.StaticLeases {
actions = append(actions,
action("DHCP static leases", actionDHCPStaticLeases),
)
}
return actions
}
type syncAction interface {
sync(ac *actionContext) error
name() string
}
type actionContext struct {
rl *zap.SugaredLogger
origin *origin
client client.Client
replicaStatus *model.ServerStatus
continueOnError bool
replica types.AdGuardInstance
}
type defaultAction struct {
myName string
doSync func(ac *actionContext) error
}
func action(name string, f func(ac *actionContext) error) syncAction {
return &defaultAction{myName: name, doSync: f}
}
func (d *defaultAction) sync(ac *actionContext) error {
return d.doSync(ac)
}
func (d *defaultAction) name() string {
return d.myName
}

View File

@@ -128,7 +128,9 @@ type syncStatus struct {
}
type replicaStatus struct {
Host string `json:"origin"`
Host string `json:"host"`
URL string `json:"url"`
Status string `json:"status"`
Error string `json:"error,omitempty"`
ProtectionEnabled *bool `json:"protection_enabled"`
}

View File

@@ -21,11 +21,11 @@
$.get("api/v1/status", {}, function (status) {
$('#origin').removeClass(function (index, className) {
return (className.match(/(^|\s)btn-\S+/g) || []).join(' ');
}).addClass("btn-" + status.origin.status);
}).addClass("btn-" + status.origin.status).attr('title', status.origin.error);
status.replicas.forEach(function (replica, i) {
$('#replica_' + i).removeClass(function (index, className) {
return (className.match(/(^|\s)btn-\S+/g) || []).join(' ');
}).addClass("btn-" + replica.status);
}).addClass("btn-" + replica.status).attr('title', replica.error);
});
}
);
@@ -57,11 +57,13 @@
</div>
<div class="col col-md-auto">
<div class="float-right">
<input class="btn btn-{{ .SyncStatus.Origin.Status }}" type="button" id="origin"
value="Origin {{ .SyncStatus.Origin.Host }}"/>
<a href="{{ .SyncStatus.Origin.URL }}" target="_blank" class="btn btn-{{ .SyncStatus.Origin.Status }}"
type="button" id="origin"
{{ if .SyncStatus.Origin.Error }} title="{{ .SyncStatus.Origin.Error }}" {{ end }}>Origin {{ .SyncStatus.Origin.Host }}</a>
{{ range $i, $r := .SyncStatus.Replicas }}
<input class="btn btn-{{ $r.Status }}" type="button" id="replica_{{ $i }}" value="Replica {{ $r.Host }}"
{{ if $r.Error }} title="{{ $r.Error }}" {{ end }}/>
<a href="{{ $r.URL }}" target="_blank" class="btn btn-{{ $r.Status }}"
type="button" id="replica_{{ $i }}"
{{ if $r.Error }} title="{{ $r.Error }}" {{ end }} >Replica {{ $r.Host }}</a>
{{ end }}
</div>
</div>

View File

@@ -90,6 +90,7 @@ type worker struct {
running bool
cron *cron.Cron
createClient func(instance types.AdGuardInstance) (client.Client, error)
actions []syncAction
}
func (w *worker) status() *syncStatus {
@@ -112,23 +113,32 @@ func (w *worker) status() *syncStatus {
return syncStatus
}
func (w *worker) getStatus(inst types.AdGuardInstance) replicaStatus {
func (w *worker) getStatus(inst types.AdGuardInstance) (st replicaStatus) {
st = replicaStatus{Host: inst.WebHost, URL: inst.WebURL}
oc, err := w.createClient(inst)
if err != nil {
l.With("error", err, "url", w.cfg.Origin.URL).Error("Error creating origin client")
return replicaStatus{Host: oc.Host(), Error: err.Error(), Status: "danger"}
st.Status = "danger"
st.Error = err.Error()
return
}
sl := l.With("from", oc.Host())
_, err = oc.Status()
sl := l.With("from", inst.WebHost)
status, err := oc.Status()
if err != nil {
if errors.Is(err, client.ErrSetupNeeded) {
return replicaStatus{Host: oc.Host(), Error: err.Error(), Status: "warning"}
st.Status = "warning"
st.Error = err.Error()
return
}
sl.With("error", err).Error("Error getting origin status")
return replicaStatus{Host: oc.Host(), Error: err.Error(), Status: "danger"}
st.Status = "danger"
st.Error = err.Error()
return
}
st := replicaStatus{Host: oc.Host(), Status: "success"}
return st
st.Status = "success"
st.ProtectionEnabled = utils.Ptr(status.ProtectionEnabled)
return
}
func (w *worker) sync() {
@@ -203,7 +213,7 @@ func (w *worker) sync() {
o.filters, err = oc.Filtering()
if err != nil {
sl.With("error", err).Error("Error getting origin filters")
sl.With("error", err).Error("Error getting origin actionFilters")
return
}
o.clients, err = oc.Clients()
@@ -242,6 +252,8 @@ func (w *worker) sync() {
}
}
w.actions = setupActions(w.cfg)
replicas := w.cfg.UniqueReplicas()
for _, replica := range replicas {
w.syncTo(sl, o, replica)
@@ -258,66 +270,37 @@ func (w *worker) syncTo(l *zap.SugaredLogger, o *origin, replica types.AdGuardIn
rl := l.With("to", rc.Host())
rl.Info("Start sync")
rs, err := w.statusWithSetup(rl, replica, rc)
replicaStatus, err := w.statusWithSetup(rl, replica, rc)
if err != nil {
rl.With("error", err).Error("Error getting replica status")
return
}
rl.With("version", rs.Version).Info("Connected to replica")
rl.With("version", replicaStatus.Version).Info("Connected to replica")
if versions.IsNewerThan(versions.MinAgh, rs.Version) {
rl.With("error", err, "version", rs.Version).Errorf("Replica AdGuard Home version must be >= %s", versions.MinAgh)
if versions.IsNewerThan(versions.MinAgh, replicaStatus.Version) {
rl.With("error", err, "version", replicaStatus.Version).Errorf("Replica AdGuard Home version must be >= %s", versions.MinAgh)
return
}
if o.status.Version != rs.Version {
rl.With("originVersion", o.status.Version, "replicaVersion", rs.Version).Warn("Versions do not match")
if o.status.Version != replicaStatus.Version {
rl.With("originVersion", o.status.Version, "replicaVersion", replicaStatus.Version).Warn("Versions do not match")
}
err = w.syncGeneralSettings(o, rs, rc)
if err != nil {
rl.With("error", err).Error("Error syncing general settings")
ac := &actionContext{
continueOnError: w.cfg.ContinueOnError,
rl: rl,
origin: o,
replicaStatus: replicaStatus,
client: rc,
replica: replica,
}
for _, action := range w.actions {
if err := action.sync(ac); err != nil {
rl.With("error", err).Errorf("Error syncing %s", action.name())
if !w.cfg.ContinueOnError {
return
}
err = w.syncConfigs(o, rc)
if err != nil {
rl.With("error", err).Error("Error syncing configs")
return
}
err = w.syncRewrites(rl, o.rewrites, rc)
if err != nil {
rl.With("error", err).Error("Error syncing rewrites")
return
}
err = w.syncFilters(o.filters, rc)
if err != nil {
rl.With("error", err).Error("Error syncing filters")
return
}
err = w.syncServices(o.blockedServices, o.blockedServicesSchedule, rc)
if err != nil {
rl.With("error", err).Error("Error syncing blockedServices")
return
}
if err = w.syncClients(o.clients, rc); err != nil {
rl.With("error", err).Error("Error syncing clients")
return
}
if err = w.syncDNS(o.accessList, o.dnsConfig, rc); err != nil {
rl.With("error", err).Error("Error syncing dns")
return
}
if w.cfg.Features.DHCP.ServerConfig || w.cfg.Features.DHCP.StaticLeases {
if err = w.syncDHCPServer(o.dhcpServerConfig, rc, replica); err != nil {
rl.With("error", err).Error("Error syncing dhcp")
return
}
}
@@ -339,260 +322,6 @@ func (w *worker) statusWithSetup(rl *zap.SugaredLogger, replica types.AdGuardIns
return rs, err
}
func (w *worker) syncServices(os *model.BlockedServicesArray, obss *model.BlockedServicesSchedule, replica client.Client) error {
if w.cfg.Features.Services {
rs, err := replica.BlockedServices()
if err != nil {
return err
}
if !model.EqualsStringSlice(os, rs, true) {
if err := replica.SetBlockedServices(os); err != nil {
return err
}
}
rbss, err := replica.BlockedServicesSchedule()
if err != nil {
return err
}
if !obss.Equals(rbss) {
if err := replica.SetBlockedServicesSchedule(obss); err != nil {
return err
}
}
}
return nil
}
func (w *worker) syncFilters(of *model.FilterStatus, replica client.Client) error {
if w.cfg.Features.Filters {
rf, err := replica.Filtering()
if err != nil {
return err
}
if err = w.syncFilterType(of.Filters, rf.Filters, false, replica); err != nil {
return err
}
if err = w.syncFilterType(of.WhitelistFilters, rf.WhitelistFilters, true, replica); err != nil {
return err
}
if utils.PtrToString(of.UserRules) != utils.PtrToString(rf.UserRules) {
return replica.SetCustomRules(of.UserRules)
}
if of.Enabled != rf.Enabled || of.Interval != rf.Interval {
if err = replica.ToggleFiltering(*of.Enabled, *of.Interval); err != nil {
return err
}
}
}
return nil
}
func (w *worker) syncFilterType(of *[]model.Filter, rFilters *[]model.Filter, whitelist bool, replica client.Client) error {
fa, fu, fd := model.MergeFilters(rFilters, of)
if err := replica.DeleteFilters(whitelist, fd...); err != nil {
return err
}
if err := replica.AddFilters(whitelist, fa...); err != nil {
return err
}
if err := replica.UpdateFilters(whitelist, fu...); err != nil {
return err
}
if len(fa) > 0 || len(fu) > 0 {
if err := replica.RefreshFilters(whitelist); err != nil {
return err
}
}
return nil
}
func (w *worker) syncRewrites(rl *zap.SugaredLogger, or *model.RewriteEntries, replica client.Client) error {
if w.cfg.Features.DNS.Rewrites {
replicaRewrites, err := replica.RewriteList()
if err != nil {
return err
}
a, r, d := replicaRewrites.Merge(or)
if err = replica.DeleteRewriteEntries(r...); err != nil {
return err
}
if err = replica.AddRewriteEntries(a...); err != nil {
return err
}
for _, dupl := range d {
rl.With("domain", dupl.Domain, "answer", dupl.Answer).Warn("Skipping duplicated rewrite from source")
}
}
return nil
}
func (w *worker) syncClients(oc *model.Clients, replica client.Client) error {
if w.cfg.Features.ClientSettings {
rc, err := replica.Clients()
if err != nil {
return err
}
a, u, r := rc.Merge(oc)
if err = replica.DeleteClients(r...); err != nil {
return err
}
if err = replica.AddClients(a...); err != nil {
return err
}
if err = replica.UpdateClients(u...); err != nil {
return err
}
}
return nil
}
func (w *worker) syncGeneralSettings(o *origin, rs *model.ServerStatus, replica client.Client) error {
if w.cfg.Features.GeneralSettings {
if pro, err := replica.ProfileInfo(); err != nil {
return err
} else if !o.profileInfo.Equals(pro) {
if err = replica.SetProfileInfo(o.profileInfo); err != nil {
return err
}
}
if o.status.ProtectionEnabled != rs.ProtectionEnabled {
if err := replica.ToggleProtection(o.status.ProtectionEnabled); err != nil {
return err
}
}
if rp, err := replica.Parental(); err != nil {
return err
} else if o.parental != rp {
if err = replica.ToggleParental(o.parental); err != nil {
return err
}
}
if ssc, err := replica.SafeSearchConfig(); err != nil {
return err
} else if !o.safeSearch.Equals(ssc) {
if err = replica.SetSafeSearchConfig(o.safeSearch); err != nil {
return err
}
}
if rs, err := replica.SafeBrowsing(); err != nil {
return err
} else if o.safeBrowsing != rs {
if err = replica.ToggleSafeBrowsing(o.safeBrowsing); err != nil {
return err
}
}
}
return nil
}
func (w *worker) syncConfigs(o *origin, rc client.Client) error {
if w.cfg.Features.QueryLogConfig {
qlc, err := rc.QueryLogConfig()
if err != nil {
return err
}
if !o.queryLogConfig.Equals(qlc) {
if err = rc.SetQueryLogConfig(o.queryLogConfig); err != nil {
return err
}
}
}
if w.cfg.Features.StatsConfig {
sc, err := rc.StatsConfig()
if err != nil {
return err
}
if o.statsConfig.Interval != sc.Interval {
if err = rc.SetStatsConfig(o.statsConfig); err != nil {
return err
}
}
}
return nil
}
func (w *worker) syncDNS(oal *model.AccessList, odc *model.DNSConfig, rc client.Client) error {
if w.cfg.Features.DNS.AccessLists {
al, err := rc.AccessList()
if err != nil {
return err
}
if !al.Equals(oal) {
if err = rc.SetAccessList(oal); err != nil {
return err
}
}
}
if w.cfg.Features.DNS.ServerConfig {
dc, err := rc.DNSConfig()
if err != nil {
return err
}
if !dc.Equals(odc) {
if err = rc.SetDNSConfig(odc); err != nil {
return err
}
}
}
return nil
}
func (w *worker) syncDHCPServer(osc *model.DhcpStatus, rc client.Client, replica types.AdGuardInstance) error {
if !w.cfg.Features.DHCP.ServerConfig && !w.cfg.Features.DHCP.StaticLeases {
return nil
}
sc, err := rc.DhcpConfig()
if w.cfg.Features.DHCP.ServerConfig && osc.HasConfig() {
if err != nil {
return err
}
origClone := osc.Clone()
if replica.InterfaceName != "" {
// overwrite interface name
origClone.InterfaceName = utils.Ptr(replica.InterfaceName)
}
if replica.DHCPServerEnabled != nil {
// overwrite dhcp enabled
origClone.Enabled = replica.DHCPServerEnabled
}
if !sc.Equals(origClone) {
if err = rc.SetDhcpConfig(origClone); err != nil {
return err
}
}
}
if w.cfg.Features.DHCP.StaticLeases {
a, r := model.MergeDhcpStaticLeases(sc.StaticLeases, osc.StaticLeases)
if err = rc.DeleteDHCPStaticLeases(r...); err != nil {
return err
}
if err = rc.AddDHCPStaticLeases(a...); err != nil {
return err
}
}
return nil
}
type origin struct {
status *model.ServerStatus
rewrites *model.RewriteEntries

View File

@@ -9,20 +9,19 @@ import (
"github.com/bakito/adguardhome-sync/pkg/types"
"github.com/bakito/adguardhome-sync/pkg/utils"
"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"
gm "go.uber.org/mock/gomock"
)
var boolTrue = true
var _ = Describe("Sync", func() {
var (
mockCtrl *gm.Controller
cl *clientmock.MockClient
w *worker
te error
ac *actionContext
)
BeforeEach(func() {
@@ -50,16 +49,38 @@ var _ = Describe("Sync", func() {
StatsConfig: true,
QueryLogConfig: true,
},
Replicas: []types.AdGuardInstance{
{},
},
},
}
te = errors.New(uuid.NewString())
ac = &actionContext{
continueOnError: false,
rl: l,
origin: &origin{
profileInfo: &model.ProfileInfo{
Name: "origin",
Language: "en",
Theme: "auto",
},
status: &model.ServerStatus{},
safeSearch: &model.SafeSearchConfig{},
queryLogConfig: &model.QueryLogConfig{},
statsConfig: &model.StatsConfig{},
},
replicaStatus: &model.ServerStatus{},
client: cl,
replica: w.cfg.Replicas[0],
}
})
AfterEach(func() {
defer mockCtrl.Finish()
})
Context("worker", func() {
Context("syncRewrites", func() {
Context("actionDNSRewrites", func() {
var (
domain string
answer string
@@ -70,254 +91,238 @@ var _ = Describe("Sync", func() {
BeforeEach(func() {
domain = uuid.NewString()
answer = uuid.NewString()
reO = []model.RewriteEntry{{Domain: utils.Ptr(domain), Answer: utils.Ptr(answer)}}
reR = []model.RewriteEntry{{Domain: utils.Ptr(domain), Answer: utils.Ptr(answer)}}
reO = model.RewriteEntries{{Domain: utils.Ptr(domain), Answer: utils.Ptr(answer)}}
reR = model.RewriteEntries{{Domain: utils.Ptr(domain), Answer: utils.Ptr(answer)}}
})
It("should have no changes (empty slices)", func() {
ac.origin.rewrites = &reO
cl.EXPECT().RewriteList().Return(&reR, nil)
cl.EXPECT().AddRewriteEntries()
cl.EXPECT().DeleteRewriteEntries()
err := w.syncRewrites(l, &reO, cl)
err := actionDNSRewrites(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should add one rewrite entry", func() {
reR = []model.RewriteEntry{}
ac.origin.rewrites = &reO
cl.EXPECT().RewriteList().Return(&reR, nil)
cl.EXPECT().AddRewriteEntries(reO[0])
cl.EXPECT().DeleteRewriteEntries()
err := w.syncRewrites(l, &reO, cl)
err := actionDNSRewrites(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should remove one rewrite entry", func() {
reO = []model.RewriteEntry{}
ac.origin.rewrites = &reO
cl.EXPECT().RewriteList().Return(&reR, nil)
cl.EXPECT().AddRewriteEntries()
cl.EXPECT().DeleteRewriteEntries(reR[0])
err := w.syncRewrites(l, &reO, cl)
err := actionDNSRewrites(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should remove one rewrite entry", func() {
reO = []model.RewriteEntry{}
ac.origin.rewrites = &reO
cl.EXPECT().RewriteList().Return(&reR, nil)
cl.EXPECT().AddRewriteEntries()
cl.EXPECT().DeleteRewriteEntries(reR[0])
err := w.syncRewrites(l, &reO, cl)
err := actionDNSRewrites(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should return error when error on RewriteList()", func() {
ac.origin.rewrites = &reO
cl.EXPECT().RewriteList().Return(nil, te)
err := w.syncRewrites(l, &reO, cl)
err := actionDNSRewrites(ac)
Ω(err).Should(HaveOccurred())
})
It("should return error when error on AddRewriteEntries()", func() {
ac.origin.rewrites = &reO
cl.EXPECT().RewriteList().Return(&reR, nil)
cl.EXPECT().DeleteRewriteEntries()
cl.EXPECT().AddRewriteEntries().Return(te)
err := w.syncRewrites(l, &reO, cl)
err := actionDNSRewrites(ac)
Ω(err).Should(HaveOccurred())
})
It("should return error when error on DeleteRewriteEntries()", func() {
ac.origin.rewrites = &reO
cl.EXPECT().RewriteList().Return(&reR, nil)
cl.EXPECT().DeleteRewriteEntries().Return(te)
err := w.syncRewrites(l, &reO, cl)
err := actionDNSRewrites(ac)
Ω(err).Should(HaveOccurred())
})
})
Context("syncClients", func() {
Context("actionClientSettings", func() {
var (
clO *model.Clients
clR *model.Clients
name string
)
BeforeEach(func() {
name = uuid.NewString()
clO = &model.Clients{Clients: &model.ClientsArray{{Name: utils.Ptr(name)}}}
ac.origin.clients = &model.Clients{Clients: &model.ClientsArray{{Name: utils.Ptr(name)}}}
clR = &model.Clients{Clients: &model.ClientsArray{{Name: utils.Ptr(name)}}}
})
It("should have no changes (empty slices)", func() {
cl.EXPECT().Clients().Return(clR, nil)
cl.EXPECT().AddClients()
cl.EXPECT().UpdateClients()
cl.EXPECT().DeleteClients()
err := w.syncClients(clO, cl)
err := actionClientSettings(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should add one client", func() {
clR.Clients = &model.ClientsArray{}
cl.EXPECT().Clients().Return(clR, nil)
cl.EXPECT().AddClients(&(*clO.Clients)[0])
cl.EXPECT().UpdateClients()
cl.EXPECT().DeleteClients()
err := w.syncClients(clO, cl)
cl.EXPECT().AddClient(&(*ac.origin.clients.Clients)[0])
err := actionClientSettings(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should update one client", func() {
(*clR.Clients)[0].FilteringEnabled = utils.Ptr(true)
cl.EXPECT().Clients().Return(clR, nil)
cl.EXPECT().AddClients()
cl.EXPECT().UpdateClients(&(*clO.Clients)[0])
cl.EXPECT().DeleteClients()
err := w.syncClients(clO, cl)
cl.EXPECT().UpdateClient(&(*ac.origin.clients.Clients)[0])
err := actionClientSettings(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should delete one client", func() {
clO.Clients = &model.ClientsArray{}
ac.origin.clients.Clients = &model.ClientsArray{}
cl.EXPECT().Clients().Return(clR, nil)
cl.EXPECT().AddClients()
cl.EXPECT().UpdateClients()
cl.EXPECT().DeleteClients(&(*clR.Clients)[0])
err := w.syncClients(clO, cl)
cl.EXPECT().DeleteClient(&(*clR.Clients)[0])
err := actionClientSettings(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should return error when error on Clients()", func() {
cl.EXPECT().Clients().Return(nil, te)
err := w.syncClients(clO, cl)
Ω(err).Should(HaveOccurred())
})
It("should return error when error on AddClients()", func() {
cl.EXPECT().Clients().Return(clR, nil)
cl.EXPECT().DeleteClients()
cl.EXPECT().AddClients().Return(te)
err := w.syncClients(clO, cl)
Ω(err).Should(HaveOccurred())
})
It("should return error when error on UpdateClients()", func() {
cl.EXPECT().Clients().Return(clR, nil)
cl.EXPECT().DeleteClients()
cl.EXPECT().AddClients()
cl.EXPECT().UpdateClients().Return(te)
err := w.syncClients(clO, cl)
Ω(err).Should(HaveOccurred())
})
It("should return error when error on DeleteClients()", func() {
cl.EXPECT().Clients().Return(clR, nil)
cl.EXPECT().DeleteClients().Return(te)
err := w.syncClients(clO, cl)
err := actionClientSettings(ac)
Ω(err).Should(HaveOccurred())
})
})
Context("syncGeneralSettings", func() {
var (
o *origin
rs *model.ServerStatus
)
BeforeEach(func() {
o = &origin{
profileInfo: &model.ProfileInfo{
Name: "test",
Language: "en",
},
status: &model.ServerStatus{},
safeSearch: &model.SafeSearchConfig{},
}
rs = &model.ServerStatus{}
})
Context("actionParental", func() {
It("should have no changes", func() {
cl.EXPECT().Parental()
cl.EXPECT().ProfileInfo().Return(o.profileInfo, nil)
cl.EXPECT().SafeSearchConfig().Return(o.safeSearch, nil)
cl.EXPECT().SafeBrowsing()
err := w.syncGeneralSettings(o, rs, cl)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have protection enabled changes", func() {
o.status.ProtectionEnabled = true
cl.EXPECT().ToggleProtection(true)
cl.EXPECT().Parental()
cl.EXPECT().ProfileInfo().Return(o.profileInfo, nil)
cl.EXPECT().SafeSearchConfig().Return(o.safeSearch, nil)
cl.EXPECT().SafeBrowsing()
err := w.syncGeneralSettings(o, rs, cl)
err := actionParental(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have parental enabled changes", func() {
o.parental = true
ac.origin.parental = true
cl.EXPECT().Parental()
cl.EXPECT().ToggleParental(true)
cl.EXPECT().ProfileInfo().Return(o.profileInfo, nil)
cl.EXPECT().SafeSearchConfig().Return(o.safeSearch, nil)
cl.EXPECT().SafeBrowsing()
err := w.syncGeneralSettings(o, rs, cl)
err := actionParental(ac)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("actionProtection", func() {
It("should have no changes", func() {
err := actionProtection(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have protection enabled changes", func() {
ac.origin.status.ProtectionEnabled = true
cl.EXPECT().ToggleProtection(true)
err := actionProtection(ac)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("actionSafeSearchConfig", func() {
It("should have no changes", func() {
cl.EXPECT().SafeSearchConfig().Return(ac.origin.safeSearch, nil)
err := actionSafeSearchConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have safeSearch enabled changes", func() {
o.safeSearch = &model.SafeSearchConfig{Enabled: utils.Ptr(true)}
cl.EXPECT().Parental()
ac.origin.safeSearch = &model.SafeSearchConfig{Enabled: utils.Ptr(true)}
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil)
cl.EXPECT().ProfileInfo().Return(o.profileInfo, nil)
cl.EXPECT().SetSafeSearchConfig(o.safeSearch)
cl.EXPECT().SafeBrowsing()
err := w.syncGeneralSettings(o, rs, cl)
cl.EXPECT().SetSafeSearchConfig(ac.origin.safeSearch)
err := actionSafeSearchConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have Duckduckgo safeSearch enabled changed", func() {
o.safeSearch = &model.SafeSearchConfig{Duckduckgo: utils.Ptr(true)}
cl.EXPECT().Parental()
cl.EXPECT().ProfileInfo().Return(o.profileInfo, nil)
ac.origin.safeSearch = &model.SafeSearchConfig{Duckduckgo: utils.Ptr(true)}
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{Google: utils.Ptr(true)}, nil)
cl.EXPECT().SafeBrowsing()
cl.EXPECT().SetSafeSearchConfig(o.safeSearch)
err := w.syncGeneralSettings(o, rs, cl)
cl.EXPECT().SetSafeSearchConfig(ac.origin.safeSearch)
err := actionSafeSearchConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("actionProfileInfo", func() {
It("should have no changes", func() {
cl.EXPECT().ProfileInfo().Return(ac.origin.profileInfo, nil)
err := actionProfileInfo(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have profileInfo language changed", func() {
o.profileInfo.Language = "de"
cl.EXPECT().Parental()
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{Language: "en"}, nil)
cl.EXPECT().SafeSearchConfig().Return(o.safeSearch, nil)
cl.EXPECT().SafeBrowsing()
cl.EXPECT().SetProfileInfo(o.profileInfo)
err := w.syncGeneralSettings(o, rs, cl)
ac.origin.profileInfo.Language = "de"
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{Name: "replica", Language: "en"}, nil)
cl.EXPECT().SetProfileInfo(&model.ProfileInfo{
Language: "de",
Name: "replica",
Theme: "auto",
})
err := actionProfileInfo(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should not sync profileInfo if language is not set", func() {
ac.origin.profileInfo.Language = ""
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{Name: "replica", Language: "en", Theme: "auto"}, nil)
cl.EXPECT().SetProfileInfo(ac.origin.profileInfo).Times(0)
err := actionProfileInfo(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should not sync profileInfo if theme is not set", func() {
ac.origin.profileInfo.Theme = ""
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{Name: "replica", Language: "en", Theme: "auto"}, nil)
cl.EXPECT().SetProfileInfo(ac.origin.profileInfo).Times(0)
err := actionProfileInfo(ac)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("actionSafeBrowsing", func() {
It("should have no changes", func() {
cl.EXPECT().SafeBrowsing()
err := actionSafeBrowsing(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have safeBrowsing enabled changes", func() {
o.safeBrowsing = true
cl.EXPECT().Parental()
cl.EXPECT().ProfileInfo().Return(o.profileInfo, nil)
cl.EXPECT().SafeSearchConfig().Return(o.safeSearch, nil)
ac.origin.safeBrowsing = true
cl.EXPECT().SafeBrowsing()
cl.EXPECT().ToggleSafeBrowsing(true)
err := w.syncGeneralSettings(o, rs, cl)
err := actionSafeBrowsing(ac)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("syncConfigs", func() {
var (
o *origin
qlc *model.QueryLogConfig
sc *model.StatsConfig
)
Context("actionQueryLogConfig", func() {
var qlc *model.QueryLogConfig
BeforeEach(func() {
o = &origin{
queryLogConfig: &model.QueryLogConfig{},
statsConfig: &model.StatsConfig{},
}
qlc = &model.QueryLogConfig{}
sc = &model.StatsConfig{}
})
It("should have no changes", func() {
cl.EXPECT().QueryLogConfig().Return(qlc, nil)
cl.EXPECT().StatsConfig().Return(sc, nil)
err := w.syncConfigs(o, cl)
err := actionQueryLogConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have QueryLogConfig changes", func() {
var interval model.QueryLogConfigInterval = 123
o.queryLogConfig.Interval = &interval
ac.origin.queryLogConfig.Interval = &interval
cl.EXPECT().QueryLogConfig().Return(qlc, nil)
cl.EXPECT().SetQueryLogConfig(&model.QueryLogConfig{AnonymizeClientIp: nil, Interval: &interval, Enabled: nil})
err := actionQueryLogConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("syncConfigs", func() {
var sc *model.StatsConfig
BeforeEach(func() {
sc = &model.StatsConfig{}
})
It("should have no changes", func() {
cl.EXPECT().StatsConfig().Return(sc, nil)
err := w.syncConfigs(o, cl)
err := actionStatsConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have StatsConfig changes", func() {
var interval model.StatsConfigInterval = 123
o.statsConfig.Interval = &interval
cl.EXPECT().QueryLogConfig().Return(qlc, nil)
ac.origin.statsConfig.Interval = &interval
cl.EXPECT().StatsConfig().Return(sc, nil)
cl.EXPECT().SetStatsConfig(&model.StatsConfig{Interval: &interval})
err := w.syncConfigs(o, cl)
err := actionStatsConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
})
@@ -354,161 +359,209 @@ var _ = Describe("Sync", func() {
Ω(st).Should(BeNil())
})
})
Context("syncServices", func() {
var (
obs *model.BlockedServicesArray
rbs *model.BlockedServicesArray
obss *model.BlockedServicesSchedule
)
Context("actionBlockedServices", func() {
var rbs *model.BlockedServicesArray
BeforeEach(func() {
obs = &model.BlockedServicesArray{"foo"}
ac.origin.blockedServices = &model.BlockedServicesArray{"foo"}
rbs = &model.BlockedServicesArray{"foo"}
obss = &model.BlockedServicesSchedule{}
})
It("should have no changes", func() {
cl.EXPECT().BlockedServices().Return(rbs, nil)
cl.EXPECT().BlockedServicesSchedule().Return(obss, nil)
err := w.syncServices(obs, obss, cl)
err := actionBlockedServices(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have blockedServices changes", func() {
obs = &model.BlockedServicesArray{"bar"}
ac.origin.blockedServices = &model.BlockedServicesArray{"bar"}
cl.EXPECT().BlockedServices().Return(rbs, nil)
cl.EXPECT().BlockedServicesSchedule().Return(obss, nil)
cl.EXPECT().SetBlockedServices(obs)
err := w.syncServices(obs, obss, cl)
cl.EXPECT().SetBlockedServices(ac.origin.blockedServices)
err := actionBlockedServices(ac)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("actionBlockedServicesSchedule", func() {
var rbss *model.BlockedServicesSchedule
BeforeEach(func() {
ac.origin.blockedServicesSchedule = &model.BlockedServicesSchedule{}
rbss = &model.BlockedServicesSchedule{}
})
It("should have no changes", func() {
cl.EXPECT().BlockedServicesSchedule().Return(ac.origin.blockedServicesSchedule, nil)
err := actionBlockedServicesSchedule(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have blockedServices schedule changes", func() {
ac.origin.blockedServicesSchedule = &model.BlockedServicesSchedule{Ids: utils.Ptr([]string{"bar"})}
cl.EXPECT().BlockedServicesSchedule().Return(rbss, nil)
cl.EXPECT().SetBlockedServicesSchedule(ac.origin.blockedServicesSchedule)
err := actionBlockedServicesSchedule(ac)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("syncFilters", func() {
var (
of *model.FilterStatus
rf *model.FilterStatus
)
var rf *model.FilterStatus
BeforeEach(func() {
of = &model.FilterStatus{}
ac.origin.filters = &model.FilterStatus{}
rf = &model.FilterStatus{}
})
It("should have no changes", func() {
cl.EXPECT().Filtering().Return(rf, nil)
cl.EXPECT().AddFilters(false)
cl.EXPECT().UpdateFilters(false)
cl.EXPECT().DeleteFilters(false)
cl.EXPECT().AddFilters(true)
cl.EXPECT().UpdateFilters(true)
cl.EXPECT().DeleteFilters(true)
err := w.syncFilters(of, cl)
err := actionFilters(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have changes user roles", func() {
of.UserRules = utils.Ptr([]string{"foo"})
ac.origin.filters.UserRules = utils.Ptr([]string{"foo"})
cl.EXPECT().Filtering().Return(rf, nil)
cl.EXPECT().AddFilters(false)
cl.EXPECT().UpdateFilters(false)
cl.EXPECT().DeleteFilters(false)
cl.EXPECT().AddFilters(true)
cl.EXPECT().UpdateFilters(true)
cl.EXPECT().DeleteFilters(true)
cl.EXPECT().SetCustomRules(of.UserRules)
err := w.syncFilters(of, cl)
cl.EXPECT().SetCustomRules(ac.origin.filters.UserRules)
err := actionFilters(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have changed filtering config", func() {
of.Enabled = utils.Ptr(true)
of.Interval = utils.Ptr(123)
ac.origin.filters.Enabled = utils.Ptr(true)
ac.origin.filters.Interval = utils.Ptr(123)
cl.EXPECT().Filtering().Return(rf, nil)
cl.EXPECT().AddFilters(false)
cl.EXPECT().UpdateFilters(false)
cl.EXPECT().DeleteFilters(false)
cl.EXPECT().AddFilters(true)
cl.EXPECT().UpdateFilters(true)
cl.EXPECT().DeleteFilters(true)
cl.EXPECT().ToggleFiltering(*of.Enabled, *of.Interval)
err := w.syncFilters(of, cl)
cl.EXPECT().ToggleFiltering(*ac.origin.filters.Enabled, *ac.origin.filters.Interval)
err := actionFilters(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should add a filter", func() {
ac.origin.filters.Filters = utils.Ptr([]model.Filter{{Name: "foo", Url: "https://foo.bar"}})
cl.EXPECT().Filtering().Return(rf, nil)
cl.EXPECT().AddFilter(false, model.Filter{Name: "foo", Url: "https://foo.bar"})
cl.EXPECT().RefreshFilters(gm.Any())
err := actionFilters(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should delete a filter", func() {
rf.Filters = utils.Ptr([]model.Filter{{Name: "foo", Url: "https://foo.bar"}})
cl.EXPECT().Filtering().Return(rf, nil)
cl.EXPECT().DeleteFilter(false, model.Filter{Name: "foo", Url: "https://foo.bar"})
err := actionFilters(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should update a filter", func() {
ac.origin.filters.Filters = utils.Ptr([]model.Filter{{Name: "foo", Url: "https://foo.bar", Enabled: true}})
rf.Filters = utils.Ptr([]model.Filter{{Name: "foo", Url: "https://foo.bar"}})
cl.EXPECT().Filtering().Return(rf, nil)
cl.EXPECT().UpdateFilter(false, model.Filter{Name: "foo", Url: "https://foo.bar", Enabled: true})
cl.EXPECT().RefreshFilters(gm.Any())
err := actionFilters(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should abort after failed added filter", func() {
ac.continueOnError = false
ac.origin.filters.Filters = utils.Ptr([]model.Filter{{Name: "foo", Url: "https://foo.bar"}})
cl.EXPECT().Filtering().Return(rf, nil)
cl.EXPECT().AddFilter(false, model.Filter{Name: "foo", Url: "https://foo.bar"}).Return(errors.New("test failure"))
err := actionFilters(ac)
Ω(err).Should(HaveOccurred())
})
It("should continue after failed added filter", func() {
ac.continueOnError = true
ac.origin.filters.Filters = utils.Ptr([]model.Filter{{Name: "foo", Url: "https://foo.bar"}, {Name: "bar", Url: "https://bar.foo"}})
cl.EXPECT().Filtering().Return(rf, nil)
cl.EXPECT().AddFilter(false, model.Filter{Name: "foo", Url: "https://foo.bar"}).Return(errors.New("test failure"))
cl.EXPECT().AddFilter(false, model.Filter{Name: "bar", Url: "https://bar.foo"})
cl.EXPECT().RefreshFilters(gm.Any())
err := actionFilters(ac)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("syncDNS", func() {
var (
oal *model.AccessList
ral *model.AccessList
odc *model.DNSConfig
rdc *model.DNSConfig
)
Context("actionDNSAccessLists", func() {
var ral *model.AccessList
BeforeEach(func() {
oal = &model.AccessList{}
ac.origin.accessList = &model.AccessList{}
ral = &model.AccessList{}
odc = &model.DNSConfig{}
rdc = &model.DNSConfig{}
})
It("should have no changes", func() {
cl.EXPECT().AccessList().Return(ral, nil)
cl.EXPECT().DNSConfig().Return(rdc, nil)
err := w.syncDNS(oal, odc, cl)
err := actionDNSAccessLists(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have access list changes", func() {
ral.BlockedHosts = utils.Ptr([]string{"foo"})
cl.EXPECT().AccessList().Return(ral, nil)
cl.EXPECT().DNSConfig().Return(rdc, nil)
cl.EXPECT().SetAccessList(oal)
err := w.syncDNS(oal, odc, cl)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have dns config changes", func() {
rdc.BootstrapDns = utils.Ptr([]string{"foo"})
cl.EXPECT().AccessList().Return(ral, nil)
cl.EXPECT().DNSConfig().Return(rdc, nil)
cl.EXPECT().SetDNSConfig(odc)
err := w.syncDNS(oal, odc, cl)
cl.EXPECT().SetAccessList(ac.origin.accessList)
err := actionDNSAccessLists(ac)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("syncDHCPServer", func() {
var (
osc *model.DhcpStatus
rsc *model.DhcpStatus
)
Context("actionDNSServerConfig", func() {
var rdc *model.DNSConfig
BeforeEach(func() {
osc = &model.DhcpStatus{V4: &model.DhcpConfigV4{
ac.origin.dnsConfig = &model.DNSConfig{}
rdc = &model.DNSConfig{}
})
It("should have no changes", func() {
cl.EXPECT().DNSConfig().Return(rdc, nil)
err := actionDNSServerConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have dns config changes", func() {
rdc.BootstrapDns = utils.Ptr([]string{"foo"})
cl.EXPECT().DNSConfig().Return(rdc, nil)
cl.EXPECT().SetDNSConfig(ac.origin.dnsConfig)
err := actionDNSServerConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("actionDHCPServerConfig", func() {
var rsc *model.DhcpStatus
BeforeEach(func() {
ac.origin.dhcpServerConfig = &model.DhcpStatus{
V4: &model.DhcpConfigV4{
GatewayIp: utils.Ptr("1.2.3.4"),
RangeStart: utils.Ptr("1.2.3.5"),
RangeEnd: utils.Ptr("1.2.3.6"),
SubnetMask: utils.Ptr("255.255.255.0"),
}}
},
}
rsc = &model.DhcpStatus{}
w.cfg.Features.DHCP.StaticLeases = false
})
It("should have no changes", func() {
rsc.V4 = osc.V4
rsc.V4 = ac.origin.dhcpServerConfig.V4
cl.EXPECT().DhcpConfig().Return(rsc, nil)
err := w.syncDHCPServer(osc, cl, types.AdGuardInstance{})
err := actionDHCPServerConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have changes", func() {
rsc.Enabled = utils.Ptr(true)
cl.EXPECT().DhcpConfig().Return(rsc, nil)
cl.EXPECT().SetDhcpConfig(osc)
err := w.syncDHCPServer(osc, cl, types.AdGuardInstance{})
cl.EXPECT().SetDhcpConfig(ac.origin.dhcpServerConfig)
err := actionDHCPServerConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should use replica interface name", func() {
ac.replica.InterfaceName = "foo"
cl.EXPECT().DhcpConfig().Return(rsc, nil)
oscClone := osc.Clone()
oscClone := ac.origin.dhcpServerConfig.Clone()
oscClone.InterfaceName = utils.Ptr("foo")
cl.EXPECT().SetDhcpConfig(oscClone)
err := w.syncDHCPServer(osc, cl, types.AdGuardInstance{InterfaceName: "foo"})
err := actionDHCPServerConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should enable the target dhcp server", func() {
ac.replica.DHCPServerEnabled = utils.Ptr(true)
cl.EXPECT().DhcpConfig().Return(rsc, nil)
oscClone := osc.Clone()
oscClone := ac.origin.dhcpServerConfig.Clone()
oscClone.Enabled = utils.Ptr(true)
cl.EXPECT().SetDhcpConfig(oscClone)
err := w.syncDHCPServer(osc, cl, types.AdGuardInstance{DHCPServerEnabled: &boolTrue})
err := actionDHCPServerConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should not sync empty IPv4", func() {
ac.replica.DHCPServerEnabled = utils.Ptr(false)
ac.origin.dhcpServerConfig.V4 = &model.DhcpConfigV4{
GatewayIp: utils.Ptr(""),
}
err := actionDHCPServerConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
})
@@ -569,23 +622,12 @@ var _ = Describe("Sync", func() {
cl.EXPECT().AddRewriteEntries()
cl.EXPECT().DeleteRewriteEntries()
cl.EXPECT().Filtering().Return(&model.FilterStatus{}, nil)
cl.EXPECT().AddFilters(false)
cl.EXPECT().UpdateFilters(false)
cl.EXPECT().DeleteFilters(false)
cl.EXPECT().AddFilters(true)
cl.EXPECT().UpdateFilters(true)
cl.EXPECT().DeleteFilters(true)
cl.EXPECT().BlockedServices()
cl.EXPECT().BlockedServicesSchedule()
cl.EXPECT().Clients().Return(&model.Clients{}, nil)
cl.EXPECT().AddClients()
cl.EXPECT().UpdateClients()
cl.EXPECT().DeleteClients()
cl.EXPECT().AccessList().Return(&model.AccessList{}, nil)
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
cl.EXPECT().DhcpConfig().Return(&model.DhcpStatus{}, nil)
cl.EXPECT().AddDHCPStaticLeases().Return(nil)
cl.EXPECT().DeleteDHCPStaticLeases().Return(nil)
w.sync()
})
It("should not sync DHCP", func() {
@@ -621,18 +663,9 @@ var _ = Describe("Sync", func() {
cl.EXPECT().AddRewriteEntries()
cl.EXPECT().DeleteRewriteEntries()
cl.EXPECT().Filtering().Return(&model.FilterStatus{}, nil)
cl.EXPECT().AddFilters(false)
cl.EXPECT().UpdateFilters(false)
cl.EXPECT().DeleteFilters(false)
cl.EXPECT().AddFilters(true)
cl.EXPECT().UpdateFilters(true)
cl.EXPECT().DeleteFilters(true)
cl.EXPECT().BlockedServices()
cl.EXPECT().BlockedServicesSchedule()
cl.EXPECT().Clients().Return(&model.Clients{}, nil)
cl.EXPECT().AddClients()
cl.EXPECT().UpdateClients()
cl.EXPECT().DeleteClients()
cl.EXPECT().AccessList().Return(&model.AccessList{}, nil)
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
w.sync()

View File

@@ -4,33 +4,61 @@ import (
"go.uber.org/zap"
)
func NewFeatures(enabled bool) Features {
return Features{
DNS: DNS{
AccessLists: enabled,
ServerConfig: enabled,
Rewrites: enabled,
},
DHCP: DHCP{
ServerConfig: enabled,
StaticLeases: enabled,
},
GeneralSettings: enabled,
QueryLogConfig: enabled,
StatsConfig: enabled,
ClientSettings: enabled,
Services: enabled,
Filters: enabled,
}
}
// Features feature flags
type Features struct {
DNS DNS `json:"dns" yaml:"dns"`
DHCP DHCP `json:"dhcp" yaml:"dhcp"`
GeneralSettings bool `json:"generalSettings" yaml:"generalSettings"`
QueryLogConfig bool `json:"queryLogConfig" yaml:"queryLogConfig"`
StatsConfig bool `json:"statsConfig" yaml:"statsConfig"`
ClientSettings bool `json:"clientSettings" yaml:"clientSettings"`
Services bool `json:"services" yaml:"services"`
Filters bool `json:"filters" yaml:"filters"`
GeneralSettings bool `json:"generalSettings" yaml:"generalSettings" env:"FEATURES_GENERAL_SETTINGS"`
QueryLogConfig bool `json:"queryLogConfig" yaml:"queryLogConfig" env:"FEATURES_QUERY_LOG_CONFIG"`
StatsConfig bool `json:"statsConfig" yaml:"statsConfig" env:"FEATURES_STATS_CONFIG"`
ClientSettings bool `json:"clientSettings" yaml:"clientSettings" env:"FEATURES_CLIENT_SETTINGS"`
Services bool `json:"services" yaml:"services" env:"FEATURES_SERVICES"`
Filters bool `json:"filters" yaml:"filters" env:"FEATURES_FILTERS"`
}
// DHCP features
type DHCP struct {
ServerConfig bool `json:"serverConfig" yaml:"serverConfig"`
StaticLeases bool `json:"staticLeases" yaml:"staticLeases"`
ServerConfig bool `json:"serverConfig" yaml:"serverConfig" env:"FEATURES_DHCP_SERVER_CONFIG"`
StaticLeases bool `json:"staticLeases" yaml:"staticLeases" env:"FEATURES_DHCP_STATIC_LEASES"`
}
// DNS features
type DNS struct {
AccessLists bool `json:"accessLists" yaml:"accessLists"`
ServerConfig bool `json:"serverConfig" yaml:"serverConfig"`
Rewrites bool `json:"rewrites" yaml:"rewrites"`
AccessLists bool `json:"accessLists" yaml:"accessLists" env:"FEATURES_DNS_ACCESS_LISTS"`
ServerConfig bool `json:"serverConfig" yaml:"serverConfig" env:"FEATURES_DNS_SERVER_CONFIG"`
Rewrites bool `json:"rewrites" yaml:"rewrites" env:"FEATURES_DNS_REWRITES"`
}
// LogDisabled log all disabled features
func (f *Features) LogDisabled(l *zap.SugaredLogger) {
features := f.collectDisabled()
if len(features) > 0 {
l.With("features", features).Info("Disabled features")
}
}
func (f *Features) collectDisabled() []string {
var features []string
if !f.DHCP.ServerConfig {
features = append(features, "DHCP.ServerConfig")
@@ -65,8 +93,5 @@ func (f *Features) LogDisabled(l *zap.SugaredLogger) {
if !f.Filters {
features = append(features, "Filters")
}
if len(features) > 0 {
l.With("features", features).Info("Disabled features")
}
return features
}

View File

@@ -2,6 +2,8 @@ package types
import (
"fmt"
"net/url"
"strings"
"go.uber.org/zap"
)
@@ -14,30 +16,44 @@ const (
// Config application configuration struct
// +k8s:deepcopy-gen=true
type Config struct {
Origin AdGuardInstance `json:"origin" yaml:"origin"`
Replica *AdGuardInstance `json:"replica,omitempty" yaml:"replica,omitempty"`
Origin AdGuardInstance `json:"origin" yaml:"origin" env:"ORIGIN"`
Replica *AdGuardInstance `json:"replica,omitempty" yaml:"replica,omitempty" env:"REPLICA"`
Replicas []AdGuardInstance `json:"replicas,omitempty" yaml:"replicas,omitempty"`
Cron string `json:"cron,omitempty" yaml:"cron,omitempty"`
RunOnStart bool `json:"runOnStart,omitempty" yaml:"runOnStart,omitempty"`
API API `json:"api,omitempty" yaml:"api,omitempty"`
Features Features `json:"features,omitempty" yaml:"features,omitempty"`
Cron string `json:"cron,omitempty" yaml:"cron,omitempty" env:"CRON"`
RunOnStart bool `json:"runOnStart,omitempty" yaml:"runOnStart,omitempty" env:"RUN_ON_START"`
PrintConfigOnly bool `json:"printConfigOnly,omitempty" yaml:"printConfigOnly,omitempty" env:"PRINT_CONFIG_ONLY"`
ContinueOnError bool `json:"continueOnError,omitempty" yaml:"continueOnError,omitempty" env:"CONTINUE_ON_ERROR"`
API API `json:"api,omitempty" yaml:"api,omitempty" env:"API"`
Features Features `json:"features,omitempty" yaml:"features,omitempty" env:"FEATURES_"`
}
// API configuration
type API struct {
Port int `json:"port,omitempty" yaml:"port,omitempty"`
Username string `json:"username,omitempty" yaml:"username,omitempty"`
Password string `json:"password,omitempty" yaml:"password,omitempty"`
DarkMode bool `json:"darkMode,omitempty" yaml:"darkMode,omitempty"`
Port int `json:"port,omitempty" yaml:"port,omitempty" env:"API_PORT"`
Username string `json:"username,omitempty" yaml:"username,omitempty" env:"API_USERNAME"`
Password string `json:"password,omitempty" yaml:"password,omitempty" env:"API_PASSWORD"`
DarkMode bool `json:"darkMode,omitempty" yaml:"darkMode,omitempty" env:"API_DARK_MODE"`
}
// Mask maks username and password
func (a *API) Mask() {
a.Username = mask(a.Username)
a.Password = mask(a.Password)
}
// UniqueReplicas get unique replication instances
func (cfg *Config) UniqueReplicas() []AdGuardInstance {
dedup := make(map[string]AdGuardInstance)
if cfg.Replica != nil && cfg.Replica.URL != "" {
if cfg.Replica.APIPath == "" {
cfg.Replica.APIPath = DefaultAPIPath
}
dedup[cfg.Replica.Key()] = *cfg.Replica
}
for _, replica := range cfg.Replicas {
if replica.APIPath == "" {
replica.APIPath = DefaultAPIPath
}
if replica.URL != "" {
dedup[replica.Key()] = replica
}
@@ -45,9 +61,6 @@ func (cfg *Config) UniqueReplicas() []AdGuardInstance {
var r []AdGuardInstance
for _, replica := range dedup {
if replica.APIPath == "" {
replica.APIPath = DefaultAPIPath
}
r = append(r, replica)
}
return r
@@ -55,6 +68,11 @@ func (cfg *Config) UniqueReplicas() []AdGuardInstance {
// Log the current config
func (cfg *Config) Log(l *zap.SugaredLogger) {
c := cfg.mask()
l.With("config", c).Debug("Using config")
}
func (cfg *Config) mask() *Config {
c := cfg.DeepCopy()
c.Origin.Mask()
if c.Replica != nil {
@@ -67,21 +85,39 @@ func (cfg *Config) Log(l *zap.SugaredLogger) {
for i := range c.Replicas {
c.Replicas[i].Mask()
}
l.With("config", c).Debug("Using config")
c.API.Mask()
return c
}
func (cfg *Config) Init() error {
if err := cfg.Origin.Init(); err != nil {
return err
}
for i := range cfg.Replicas {
replica := &cfg.Replicas[i]
if err := replica.Init(); err != nil {
return err
}
}
return nil
}
// AdGuardInstance AdguardHome config instance
// +k8s:deepcopy-gen=true
type AdGuardInstance struct {
URL string `json:"url" yaml:"url"`
APIPath string `json:"apiPath,omitempty" yaml:"apiPath,omitempty"`
Username string `json:"username,omitempty" yaml:"username,omitempty"`
Password string `json:"password,omitempty" yaml:"password,omitempty"`
Cookie string `json:"cookie,omitempty" yaml:"cookie,omitempty"`
InsecureSkipVerify bool `json:"insecureSkipVerify" yaml:"insecureSkipVerify"`
AutoSetup bool `json:"autoSetup" yaml:"autoSetup"`
InterfaceName string `json:"interfaceName" yaml:"interfaceName"`
DHCPServerEnabled *bool `json:"dhcpServerEnabled,omitempty" yaml:"dhcpServerEnabled,omitempty"`
URL string `json:"url" yaml:"url" env:"URL"`
WebURL string `json:"webURL" yaml:"webURL" env:"WEB_URL"`
APIPath string `json:"apiPath,omitempty" yaml:"apiPath,omitempty" env:"API_PATH"`
Username string `json:"username,omitempty" yaml:"username,omitempty" env:"USERNAME"`
Password string `json:"password,omitempty" yaml:"password,omitempty" env:"PASSWORD"`
Cookie string `json:"cookie,omitempty" yaml:"cookie,omitempty" env:"COOKIE"`
InsecureSkipVerify bool `json:"insecureSkipVerify" yaml:"insecureSkipVerify" env:"INSECURE_SKIP_VERIFY"`
AutoSetup bool `json:"autoSetup" yaml:"autoSetup" env:"AUTO_SETUP"`
InterfaceName string `json:"interfaceName,omitempty" yaml:"interfaceName,omitempty" env:"INTERFACE_NAME"`
DHCPServerEnabled *bool `json:"dhcpServerEnabled,omitempty" yaml:"dhcpServerEnabled,omitempty" env:"DHCP_SERVER_ENABLED"`
Host string `json:"-" yaml:"-"`
WebHost string `json:"-" yaml:"-"`
}
// Key AdGuardInstance key
@@ -95,11 +131,32 @@ func (i *AdGuardInstance) Mask() {
i.Password = mask(i.Password)
}
func (i *AdGuardInstance) Init() error {
u, err := url.Parse(i.URL)
if err != nil {
return err
}
i.Host = u.Host
if i.WebURL == "" {
i.WebHost = i.Host
i.WebURL = i.URL
} else {
u, err := url.Parse(i.WebURL)
if err != nil {
return err
}
i.WebHost = u.Host
}
return nil
}
func mask(s string) string {
if s == "" {
return "***"
}
return fmt.Sprintf("%v***%v", string(s[0]), string(s[len(s)-1]))
mask := strings.Repeat("*", len(s)-2)
return fmt.Sprintf("%v%s%v", string(s[0]), mask, string(s[len(s)-1]))
}
// Protection API struct

View File

@@ -0,0 +1,13 @@
package types_test
import (
"testing"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
func TestSync(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Types Suite")
}

99
pkg/types/types_test.go Normal file
View File

@@ -0,0 +1,99 @@
package types
import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("Types", func() {
Context("AdGuardInstance", func() {
var inst AdGuardInstance
BeforeEach(func() {
inst = AdGuardInstance{}
})
Context("Instance Init", func() {
BeforeEach(func() {
inst.URL = "https://localhost:3000"
})
It("should correctly set Host and WebHost if only URL is set", func() {
err := inst.Init()
Ω(err).ShouldNot(HaveOccurred())
Ω(inst.Host).Should(Equal("localhost:3000"))
Ω(inst.WebHost).Should(Equal("localhost:3000"))
Ω(inst.URL).Should(Equal("https://localhost:3000"))
Ω(inst.WebURL).Should(Equal("https://localhost:3000"))
})
It("should correctly set Host and WebHost if URL and WebURL are set", func() {
inst.WebURL = "https://127.0.0.1:4000"
err := inst.Init()
Ω(err).ShouldNot(HaveOccurred())
Ω(inst.Host).Should(Equal("localhost:3000"))
Ω(inst.WebHost).Should(Equal("127.0.0.1:4000"))
Ω(inst.WebURL).Should(Equal(inst.WebURL))
Ω(inst.URL).Should(Equal("https://localhost:3000"))
Ω(inst.WebURL).Should(Equal("https://127.0.0.1:4000"))
})
})
})
Context("Config", func() {
Context("init", func() {
cfg := Config{
Replicas: []AdGuardInstance{
{URL: "https://localhost:3000"},
},
}
err := cfg.Init()
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].Host).Should(Equal("localhost:3000"))
Ω(cfg.Replicas[0].WebHost).Should(Equal("localhost:3000"))
Ω(cfg.Replicas[0].URL).Should(Equal("https://localhost:3000"))
Ω(cfg.Replicas[0].WebURL).Should(Equal("https://localhost:3000"))
})
Context("UniqueReplicas", func() {
It("should return unique replicas in the array", func() {
cfg := Config{
Replicas: []AdGuardInstance{
{URL: "a"},
{URL: "a", APIPath: DefaultAPIPath},
{URL: "a", APIPath: "foo"},
{URL: "b", APIPath: DefaultAPIPath},
},
Replica: &AdGuardInstance{URL: "b"},
}
replicas := cfg.UniqueReplicas()
Ω(replicas).Should(HaveLen(3))
})
})
Context("mask", func() {
It("should mask all names and passwords", func() {
cfg := Config{
Replicas: []AdGuardInstance{
{URL: "a", Username: "user", Password: "pass"},
},
Replica: &AdGuardInstance{URL: "a", Username: "user", Password: "pass"},
API: API{Username: "user", Password: "pass"},
}
masked := cfg.mask()
Ω(masked.Replicas[0].Username).Should(Equal("u**r"))
Ω(masked.Replicas[0].Password).Should(Equal("p**s"))
Ω(masked.Replica.Username).Should(Equal("u**r"))
Ω(masked.Replica.Password).Should(Equal("p**s"))
Ω(masked.API.Username).Should(Equal("u**r"))
Ω(masked.API.Password).Should(Equal("p**s"))
})
})
})
Context("Feature", func() {
Context("LogDisabled", func() {
It("should log all features", func() {
f := NewFeatures(false)
Ω(f.collectDisabled()).Should(HaveLen(11))
})
It("should log no features", func() {
f := NewFeatures(true)
Ω(f.collectDisabled()).Should(BeEmpty())
})
})
})
})

2
testdata/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
docker-compose.yaml
!config_test_*.yaml

37
testdata/config_test_replica.yaml vendored Normal file
View File

@@ -0,0 +1,37 @@
origin:
url: https://origin-file:443
webURL: https://origin-file:443
apiPath: /control
username: foo
password: '*********'
insecureSkipVerify: true
autoSetup: false
replica:
url: https://replica-file:443
webURL: https://replica-file:443
apiPath: /control
username: bar
password: '*********'
insecureSkipVerify: false
autoSetup: false
interfaceName: eth3
dhcpServerEnabled: false
cron: '*/15 * * * *'
runOnStart: true
printConfigOnly: true
api:
port: 9090
features:
dns:
accessLists: true
serverConfig: false
rewrites: true
dhcp:
serverConfig: true
staticLeases: true
generalSettings: true
queryLogConfig: true
statsConfig: true
clientSettings: true
services: true
filters: true

37
testdata/config_test_replicas.yaml vendored Normal file
View File

@@ -0,0 +1,37 @@
origin:
url: https://origin-file:443
webURL: https://origin-file:443
apiPath: /control
username: foo
password: '*********'
insecureSkipVerify: true
autoSetup: false
replicas:
- url: https://replica-file:443
webURL: https://replica-file:443
apiPath: /control
username: bar
password: '*********'
insecureSkipVerify: false
autoSetup: false
interfaceName: eth3
dhcpServerEnabled: false
cron: '*/15 * * * *'
runOnStart: true
printConfigOnly: true
api:
port: 9090
features:
dns:
accessLists: true
serverConfig: false
rewrites: true
dhcp:
serverConfig: true
staticLeases: true
generalSettings: true
queryLogConfig: true
statsConfig: true
clientSettings: true
services: true
filters: true

View File

@@ -0,0 +1,47 @@
origin:
url: https://origin-file:443
webURL: https://origin-file:443
apiPath: /control
username: foo
password: '*********'
insecureSkipVerify: true
autoSetup: false
replica:
url: https://replica-file:443
webURL: https://replica-file:443
apiPath: /control
username: bar
password: '*********'
insecureSkipVerify: false
autoSetup: false
interfaceName: eth3
dhcpServerEnabled: false
replicas:
- url: https://replicas-file:443
webURL: https://replicas-file:443
apiPath: /control
username: bar
password: '*********'
insecureSkipVerify: false
autoSetup: false
interfaceName: eth3
dhcpServerEnabled: false
cron: '*/15 * * * *'
runOnStart: true
printConfigOnly: true
api:
port: 9090
features:
dns:
accessLists: true
serverConfig: false
rewrites: true
dhcp:
serverConfig: true
staticLeases: true
generalSettings: true
queryLogConfig: true
statsConfig: true
clientSettings: true
services: true
filters: true

View File

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

View File

@@ -1,4 +1,8 @@
#!/bin/bash
set -e
kubectl wait --for=jsonpath='{.status.phase}'=Succeeded pod/adguardhome-sync --timeout=1m
RESULT=$?
if [[ "${RESULT}" != "0" ]]; then
kubectl logs adguardhome-sync
fi
exit ${RESULT}

View File

@@ -0,0 +1,19 @@
{{- if eq .Values.mode "env" }}
apiVersion: v1
kind: ConfigMap
metadata:
name: sync-conf
namespace: {{ .Release.Namespace }}
data:
API_PORT: '0'
LOG_FORMAT: json
ORIGIN_URL: 'http://service-origin.{{ $.Release.Namespace }}.svc.cluster.local:3000'
ORIGIN_PASSWORD: 'password'
ORIGIN_USERNAME: 'username'
{{- range $i,$version := .Values.replica.versions }}
REPLICA{{ add 1 $i }}_AUTO_SETUP: 'true'
REPLICA{{ add 1 $i }}_URL: 'http://service-replica-{{ $version | toString | replace "." "-" }}.{{ $.Release.Namespace }}.svc.cluster.local:3000'
REPLICA{{ add 1 $i }}_PASSWORD: 'password'
REPLICA{{ add 1 $i }}_USERNAME: 'username'
{{- end }}
{{- end }}

View File

@@ -0,0 +1,22 @@
{{- if eq .Values.mode "file" }}
apiVersion: v1
kind: ConfigMap
metadata:
name: sync-conf
namespace: {{ .Release.Namespace }}
data:
config.yaml: |
origin:
url: 'http://service-origin.{{ $.Release.Namespace }}.svc.cluster.local:3000'
username: username
password: password
api:
port: 0
replicas:
{{- range $i,$version := .Values.replica.versions }}
- url: 'http://service-replica-{{ $version | toString | replace "." "-" }}.{{ $.Release.Namespace }}.svc.cluster.local:3000'
username: username
password: password
autoSetup: true
{{- end }}
{{- end }}

View File

@@ -1,17 +0,0 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: sync-conf
namespace: {{ .Release.Namespace }}
data:
API_PORT: '0'
LOG_LEVEL: 'info'
ORIGIN_URL: 'http://service-origin.{{ $.Release.Namespace }}.svc.cluster.local:3000'
ORIGIN_PASSWORD: 'password'
ORIGIN_USERNAME: 'username'
{{ range $i,$version := .Values.replica.versions }}
REPLICA{{ $i }}_AUTOSETUP: 'true'
REPLICA{{ $i }}_URL: 'http://service-replica-{{ $version | toString | replace "." "-" }}.{{ $.Release.Namespace }}.svc.cluster.local:3000'
REPLICA{{ $i }}_PASSWORD: 'password'
REPLICA{{ $i }}_USERNAME: 'username'
{{- end }}

View File

@@ -1,3 +1,4 @@
{{- if eq .Values.mode "env" }}
apiVersion: v1
kind: Pod
metadata:
@@ -7,7 +8,7 @@ spec:
serviceAccountName: agh-e2e
initContainers:
- name: wait-for-others
image: bitnami/kubectl:1.24
image: {{ .Values.kubectl.repository }}:{{ .Values.kubectl.tag }}
command:
- /bin/bash
- -c
@@ -19,7 +20,11 @@ spec:
command:
- /opt/go/adguardhome-sync
- run
env:
- name: LOG_LEVEL
value: 'debug'
envFrom:
- configMapRef:
name: sync-conf
restartPolicy: Never
{{- end }}

View File

@@ -0,0 +1,36 @@
{{- if eq .Values.mode "file" }}
apiVersion: v1
kind: Pod
metadata:
name: adguardhome-sync
namespace: {{ $.Release.Namespace }}
spec:
serviceAccountName: agh-e2e
initContainers:
- name: wait-for-others
image: {{ .Values.kubectl.repository }}:{{ .Values.kubectl.tag }}
command:
- /bin/bash
- -c
- |
{{- .Files.Get "bin/wait-for-agh-pods.sh" | nindent 10}}
containers:
- name: adguardhome-sync
image: localhost:5001/adguardhome-sync:e2e
command:
- /opt/go/adguardhome-sync
- run
- '--config'
- /etc/go/adguardhome-sync/config.yaml
env:
- name: LOG_LEVEL
value: 'debug'
volumeMounts:
- name: config
mountPath: /etc/go/adguardhome-sync/
volumes:
- name: config
configMap:
name: sync-conf
restartPolicy: Never
{{- end }}

View File

@@ -1,4 +1,11 @@
replica:
versions:
- v0.107.40
- v0.107.43
- latest
mode: env
kubectl:
repository: bitnami/kubectl
tag: 1.27

14
tools.go Normal file
View File

@@ -0,0 +1,14 @@
//go:build tools
// +build tools
package tools
import (
_ "github.com/bakito/semver"
_ "github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen"
_ "github.com/golangci/golangci-lint/cmd/golangci-lint"
_ "github.com/goreleaser/goreleaser"
_ "github.com/onsi/ginkgo/v2/ginkgo"
_ "go.uber.org/mock/mockgen"
_ "k8s.io/code-generator/cmd/deepcopy-gen"
)