Compare commits

...

53 Commits

Author SHA1 Message Date
Marc Brugger
3c58a8f091 Replace deprecated API endpoints (#326)
* extend query log config
* replace deprecated services
* replace more deprecated services
* implement equals for stats config
2024-04-02 20:31:47 +02:00
dependabot[bot]
4a62b80e75 Bump github.com/golangci/golangci-lint from 1.57.1 to 1.57.2 (#325)
Bumps [github.com/golangci/golangci-lint](https://github.com/golangci/golangci-lint) from 1.57.1 to 1.57.2.
- [Release notes](https://github.com/golangci/golangci-lint/releases)
- [Changelog](https://github.com/golangci/golangci-lint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/golangci/golangci-lint/compare/v1.57.1...v1.57.2)

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

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


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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

* go-jose.v2 v2.6.3

---------

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

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

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

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


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

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

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

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

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

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

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

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

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

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

* ignore goseg G402

---------

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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


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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

* rename type tags

* replace env lib

* move to config module

* read flags

* show e2e logs on error

* extract env

* replace deprecated env var

* increment index

* check replica numbers do not start with 0

* remove test suite

* error handling

* refactor flags

* flags test

* file test

* file test

* config tests

* extend tests

* test mixed mode

* simplify

* simplify

* test mask

* correct uniqe replicas

* Update types_test.go

* e2e test with file mode
2024-01-14 13:29:36 +01:00
bakito
906dfc680a extend bug issue template wiht 'applied-config' 2024-01-14 09:07:54 +01:00
Marc Brugger
583197f1c8 add deprecated env var table 2024-01-10 20:12:08 +01:00
65 changed files with 4618 additions and 833 deletions

View File

@@ -37,6 +37,13 @@ body:
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:

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,6 +40,12 @@ 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@v3

View File

@@ -11,10 +11,18 @@ on:
jobs:
e2e:
runs-on: ubuntu-latest
strategy:
matrix:
build:
- mode: env
- mode: file
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup jq
uses: dcarbone/install-jq-action@v2
- name: Install kind with registry
uses: bakito/kind-with-registry-action@main
@@ -22,7 +30,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
@@ -31,5 +39,7 @@ jobs:
run: ./testdata/e2e/bin/show-replica-logs.sh
- name: Show Sync Logs
run: ./testdata/e2e/bin/show-sync-logs.sh
- name: Show Sync Metrics
run: ./testdata/e2e/bin/show-sync-metrics.sh
- name: Read latest replica config
run: ./testdata/e2e/bin/read-latest-replica-config.sh

View File

@@ -19,7 +19,7 @@ jobs:
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

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

@@ -0,0 +1,17 @@
name: Mark stale issues and pull requests
on:
schedule:
- cron: "0 0 * * *"
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'This issue has been inactive for 60 days. If the issue is still relevant please comment to re-activate the issue. If no action is taken within 7 days, the issue will be marked closed.'
stale-pr-message: 'This pull request has been inactive for 60 days. If the pull request is still relevant please comment to re-activate the pull request. If no action is taken within 7 days, the pull request will be marked closed.'

5
.gitignore vendored
View File

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

View File

@@ -3,34 +3,35 @@ run:
linters:
enable:
- asciicheck
- bodyclose
- dogsled
- durationcheck
- errcheck
- errorlint
- exportloopref
- gci
- gofmt
- gofumpt
- goimports
- gosec
- gosimple
- govet
- importas
- ineffassign
- megacheck
- misspell
- nakedret
- nolintlint
- staticcheck
- unconvert
- unparam
- unused
- asciicheck
- bodyclose
- dogsled
- durationcheck
- errcheck
- errorlint
- gci
- gofmt
- gofumpt
- goimports
- gosec
- gosimple
- govet
- importas
- ineffassign
- megacheck
- misspell
- nakedret
- nolintlint
- staticcheck
- unconvert
- unparam
- unused
linters-settings:
gosec:
# Exclude generated files
exclude-generated: true
excludes:
- G601 # not applicable in go 1.22 anymore
gofmt:
# simplify code: gofmt with `-s` option, true by default
simplify: true

View File

@@ -30,7 +30,7 @@ builds:
hooks:
post:
# don't upx windows binaries as they make trouble with virus scanners
- bash -c 'if [[ "{{ .Path }}" != *.exe ]]; then upx {{ .Path }}; fi'
- bash -c 'if [[ "{{ .Path }}" != *.exe ]] && [[ "{{ .Path }}" != *darwin* ]]; then upx {{ .Path }}; fi'
checksum:
name_template: 'checksums.txt'
snapshot:

View File

@@ -1,4 +1,4 @@
FROM golang:1.21-bullseye as builder
FROM golang:1.22-bullseye as builder
WORKDIR /go/src/app

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); \
@@ -29,7 +30,7 @@ release: semver goreleaser
$(GORELEASER) --clean
test-release: goreleaser
$(GORELEASER) --skip-publish --snapshot --clean
$(GORELEASER) --skip=publish --snapshot --clean
## toolbox - start
## Current working directory
@@ -40,64 +41,56 @@ $(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.23.0
DEEPCOPY_GEN_VERSION ?= v0.29.0
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:
@@ -131,10 +124,10 @@ kind-test:
model: oapi-codegen
@mkdir -p tmp
go run openapi/main.go v0.107.43
go run openapi/main.go v0.107.46
$(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.43
go run openapi/main.go v0.107.46
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,9 +7,11 @@
Synchronize [AdGuardHome](https://github.com/AdguardTeam/AdGuardHome) config to replica instances.
## FAQ
## FAQ & Deprecations
Please check the wiki for [FAQ](https://github.com/bakito/adguardhome-sync/wiki/FAQ).
Please check the wiki
for [FAQ](https://github.com/bakito/adguardhome-sync/wiki/FAQ)
and [Deprecations](https://github.com/bakito/adguardhome-sync/wiki/Deprecations).
## Current sync features
@@ -59,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
@@ -87,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
@@ -150,23 +153,26 @@ services:
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_API_PATH: "/some/path/control"
# REPLICA1_WEB_URL: "https://some-other.url" # used in the web interface (default: <replica-url>
# REPLICA1_AUTO_SETUP: true # if true, AdGuardHome is automatically initialized.
# REPLICA1_INTERFACE_NAME: 'ens18' # use custom dhcp interface name
# REPLICA1_DHCP_SERVER_ENABLED: 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
# API_DARK_MODE: true
# API_USERNAME: admin
# API_PASSWORD: secret
# Configure sync features; by default all features are enabled.
# FEATURES_GENERAL_SETTINGS: true
@@ -208,16 +214,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
# webURL: "https://some-other.url" # used in the web interface (default: <origin-url>
# replicas instances (optional, if more than one)
# replicas instances
replicas:
# url of the replica instance
- url: http://192.168.1.3
@@ -241,6 +238,12 @@ api:
# enable api dark mode
darkMode: true
# enable metrics on path '/metrics' (api port must be != 0)
# metrics:
# enabled: true
# scrapeInterval: 30s
# queryLogLimit: 10000
# Configure sync features; by default all features are enabled.
features:
generalSettings: true
@@ -260,7 +263,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)
@@ -268,3 +271,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`

135
Taskfile.yml Normal file
View File

@@ -0,0 +1,135 @@
version: '3'
env:
AGH_MODEL_VERSION: v0.107.43
GOBIN: '{{.USER_WORKING_DIR}}/bin'
tasks:
install-go-tool:
label: "Install {{ .TOOL_NAME }}"
cmds:
- go install {{ .TOOL_MODULE }}
status:
- test -f {{.GOBIN}}/{{.TOOL_NAME}}
deepcopy-gen:
desc: Install deepcopy-gen
cmd:
task: install-go-tool
vars:
TOOL_NAME: deepcopy-gen
TOOL_MODULE: k8s.io/code-generator/cmd/deepcopy-gen
ginkgo:
cmd:
task: install-go-tool
vars:
TOOL_NAME: ginkgo
TOOL_MODULE: github.com/onsi/ginkgo/v2/ginkgo
goreleaser:
cmd:
task: install-go-tool
vars:
TOOL_NAME: goreleaser
TOOL_MODULE: github.com/goreleaser/goreleaser
golangci-lint:
cmd:
task: install-go-tool
vars:
TOOL_NAME: golangci-lint
TOOL_MODULE: github.com/golangci/golangci-lint/cmd/golangci-lint
mockgen:
cmd:
task: install-go-tool
vars:
TOOL_NAME: mockgen
TOOL_MODULE: go.uber.org/mock/mockgen
oapi-codegen:
cmd:
task: install-go-tool
vars:
TOOL_NAME: oapi-codegen
TOOL_MODULE: github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen
semver:
cmd:
task: install-go-tool
vars:
TOOL_NAME: semver
TOOL_MODULE: github.com/bakito/semver
lint:
deps:
- golangci-lint
cmds:
- '{{.GOBIN}}/golangci-lint run --fix'
tidy:
desc: Run go mod tidy
cmd: go mod tidy
generate:
deps:
- deepcopy-gen
cmds:
- mkdir -p ./tmp
- touch ./tmp/deepcopy-gen-boilerplate.go.txt
- '{{.GOBIN}}/deepcopy-gen -h ./tmp/deepcopy-gen-boilerplate.go.txt -i ./pkg/types'
mocks:
deps:
- mockgen
cmds:
- '{{.GOBIN}}/mockgen -package client -destination pkg/mocks/client/mock.go github.com/bakito/adguardhome-sync/pkg/client Client'
- '{{.GOBIN}}/mockgen -package client -destination pkg/mocks/flags/mock.go github.com/bakito/adguardhome-sync/pkg/config Flags'
test:
cmds:
- task: generate
- task: lint
- task: test-ci
test-ci:
deps:
- ginkgo
- tidy
- mocks
cmds:
- '{{.GOBIN}}/ginkgo --cover --coverprofile coverage.out.tmp ./...'
- cat coverage.out.tmp | grep -v "_generated.go" > coverage.out
- go tool cover -func=coverage.out
release:
deps:
- semver
- goreleaser
cmds:
- git tag -s $$version -m"Release $({{.GOBIN}}/semver)
- '{{.GOBIN}}/goreleaser --clean'
test-release:
deps:
- goreleaser
- semver
cmds:
- '{{.GOBIN}}/goreleaser --skip=publish --snapshot --clean'
model:
deps:
- oapi-codegen
cmds:
- mkdir -p tmp
- go run openapi/main.go {{.AGH_MODEL_VERSION}}
- '{{.GOBIN}}/oapi-codegen -package model -generate types,client -config .oapi-codegen.yaml tmp/schema.yaml > pkg/client/model/model_generated.go'
model-diff:
deps:
- oapi-codegen
cmds:
- go run openapi/main.go {{.AGH_MODEL_VERSION}}
- go run openapi/main.go
- diff tmp/schema.yaml tmp/schema-master.yaml

View File

@@ -3,65 +3,15 @@ package cmd
import (
"fmt"
"os"
"regexp"
"strconv"
"strings"
"github.com/bakito/adguardhome-sync/pkg/log"
"github.com/bakito/adguardhome-sync/pkg/types"
"github.com/bakito/adguardhome-sync/pkg/utils"
"github.com/bakito/adguardhome-sync/version"
"github.com/mitchellh/go-homedir"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
const (
configCron = "CRON"
configRunOnStart = "RUN_ON_START"
configPrintConfigOnly = "PRINT_CONFIG_ONLY"
configContinueOnError = "CONTINUE_ON_ERROR"
configAPIPort = "API.PORT"
configAPIUsername = "API.USERNAME"
configAPIPassword = "API.PASSWORD"
configAPIDarkMode = "API.DARK_MODE"
configFeatureDHCPServerConfig = "FEATURES.DHCP.SERVER_CONFIG"
configFeatureDHCPStaticLeases = "FEATURES.DHCP.STATIC_LEASES"
configFeatureDNServerConfig = "FEATURES.DNS.SERVER_CONFIG"
configFeatureDNSPAccessLists = "FEATURES.DNS.ACCESS_LISTS"
configFeatureDNSRewrites = "FEATURES.DNS.rewrites"
configFeatureGeneralSettings = "FEATURES.GENERAL_SETTINGS"
configFeatureQueryLogConfig = "FEATURES.QUERY_LOG_CONFIG"
configFeatureStatsConfig = "FEATURES.STATS_CONFIG"
configFeatureClientSettings = "FEATURES.CLIENT_SETTINGS"
configFeatureServices = "FEATURES.SERVICES"
configFeatureFilters = "FEATURES.FILTERS"
configOriginURL = "ORIGIN.URL"
configOriginWebURL = "ORIGIN.WEB_URL"
configOriginAPIPath = "ORIGIN.API_PATH"
configOriginUsername = "ORIGIN.USERNAME"
configOriginPassword = "ORIGIN.PASSWORD"
configOriginCookie = "ORIGIN.COOKIE"
configOriginInsecureSkipVerify = "ORIGIN.INSECURE_SKIP_VERIFY"
configReplicaURL = "REPLICA.URL"
configReplicaWebURL = "REPLICA.WEB_URL"
configReplicaAPIPath = "REPLICA.API_PATH"
configReplicaUsername = "REPLICA.USERNAME"
configReplicaPassword = "REPLICA.PASSWORD"
configReplicaCookie = "REPLICA.COOKIE"
configReplicaInsecureSkipVerify = "REPLICA.INSECURE_SKIP_VERIFY"
configReplicaAutoSetup = "REPLICA.AUTO_SETUP"
configReplicaInterfaceName = "REPLICA.INTERFACE_NAME"
)
var (
cfgFile string
logger = log.GetLogger("root")
envReplicasURLPattern = regexp.MustCompile(`^REPLICA(\d+)_URL=(.*)`)
cfgFile string
logger = log.GetLogger("root")
)
// rootCmd represents the base command when called without any subcommands
@@ -81,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.
@@ -93,168 +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() (*types.Config, error) {
cfg := &types.Config{}
if err := viper.Unmarshal(cfg); err != nil {
return nil, err
}
if cfg.Replica != nil &&
cfg.Replica.URL == "" &&
cfg.Replica.Username == "" {
cfg.Replica = nil
}
if len(cfg.Replicas) == 0 {
cfg.Replicas = append(cfg.Replicas, collectEnvReplicas()...)
}
handleDeprecatedEnvVars(cfg)
return cfg, nil
}
func handleDeprecatedEnvVars(cfg *types.Config) {
value := checkDeprecatedEnvVar("RUNONSTART", "RUN_ON_START")
if value != "" {
cfg.RunOnStart, _ = strconv.ParseBool(value)
}
value = checkDeprecatedEnvVar("API_DARKMODE", "API_DARK_MODE")
if value != "" {
cfg.API.DarkMode, _ = strconv.ParseBool(value)
}
value = checkDeprecatedEnvVar("FEATURES_GENERALSETTINGS", "FEATURES_GENERAL_SETTINGS")
if value != "" {
cfg.Features.GeneralSettings, _ = strconv.ParseBool(value)
}
value = checkDeprecatedEnvVar("FEATURES_QUERYLOGCONFIG", "FEATURES_QUERY_LOG_CONFIG")
if value != "" {
cfg.Features.QueryLogConfig, _ = strconv.ParseBool(value)
}
value = checkDeprecatedEnvVar("FEATURES_STATSCONFIG", "FEATURES_STATS_CONFIG")
if value != "" {
cfg.Features.StatsConfig, _ = strconv.ParseBool(value)
}
value = checkDeprecatedEnvVar("FEATURES_CLIENTSETTINGS", "FEATURES_CLIENT_SETTINGS")
if value != "" {
cfg.Features.ClientSettings, _ = strconv.ParseBool(value)
}
value = checkDeprecatedEnvVar("FEATURES_DHCP_SERVERCONFIG", "FEATURES_DHCP_SERVER_CONFIG")
if value != "" {
cfg.Features.DHCP.ServerConfig, _ = strconv.ParseBool(value)
}
value = checkDeprecatedEnvVar("FEATURES_DHCP_STATICLEASES", "FEATURES_DHCP_STATIC_LEASES")
if value != "" {
cfg.Features.DHCP.StaticLeases, _ = strconv.ParseBool(value)
}
value = checkDeprecatedEnvVar("FEATURES_DNS_ACCESSLISTS", "FEATURES_DNS_ACCESS_LISTS")
if value != "" {
cfg.Features.DNS.AccessLists, _ = strconv.ParseBool(value)
}
value = checkDeprecatedEnvVar("FEATURES_DNS_SERVERCONFIG", "FEATURES_DNS_SERVER_CONFIG")
if value != "" {
cfg.Features.DNS.ServerConfig, _ = strconv.ParseBool(value)
}
if cfg.Replica != nil {
value = checkDeprecatedEnvVar("REPLICA_WEBURL", "REPLICA_WEB_URL")
if value != "" {
cfg.Replica.WebURL = value
}
value = checkDeprecatedEnvVar("REPLICA_AUTOSETUP", "REPLICA_AUTO_SETUP")
if value != "" {
cfg.Replica.AutoSetup, _ = strconv.ParseBool(value)
}
value = checkDeprecatedEnvVar("REPLICA_INTERFACENAME", "REPLICA_INTERFACE_NAME")
if value != "" {
cfg.Replica.InterfaceName = value
}
value = checkDeprecatedEnvVar("REPLICA_DHCPSERVERENABLED", "REPLICA_DHCP_SERVER_ENABLED")
if value != "" {
if b, err := strconv.ParseBool(value); err != nil {
cfg.Replica.DHCPServerEnabled = utils.Ptr(b)
}
}
}
}
func checkDeprecatedEnvVar(oldName string, newName string) string {
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
}
return old
}
func checkDeprecatedReplicaEnvVar(oldPattern, newPattern, replicaID string) string {
return checkDeprecatedEnvVar(fmt.Sprintf(oldPattern, replicaID), fmt.Sprintf(newPattern, replicaID))
}
// Manually collect replicas from env.
func collectEnvReplicas() []types.AdGuardInstance {
var replicas []types.AdGuardInstance
for _, v := range os.Environ() {
if envReplicasURLPattern.MatchString(v) {
sm := envReplicasURLPattern.FindStringSubmatch(v)
index := sm[1]
re := types.AdGuardInstance{
URL: sm[2],
WebURL: os.Getenv(fmt.Sprintf("REPLICA%s_WEB_URL", index)),
APIPath: checkDeprecatedReplicaEnvVar("REPLICA%s_APIPATH", "REPLICA%s_API_PATH", index),
Username: os.Getenv(fmt.Sprintf("REPLICA%s_USERNAME", index)),
Password: os.Getenv(fmt.Sprintf("REPLICA%s_PASSWORD", index)),
Cookie: os.Getenv(fmt.Sprintf("REPLICA%s_COOKIE", index)),
InsecureSkipVerify: strings.EqualFold(checkDeprecatedReplicaEnvVar("REPLICA%s_INSECURESKIPVERIFY", "REPLICA%s_INSECURE_SKIP_VERIFY", index), "true"),
AutoSetup: strings.EqualFold(checkDeprecatedReplicaEnvVar("REPLICA%s_AUTOSETUP", "REPLICA%s_AUTO_SETUP", index), "true"),
InterfaceName: checkDeprecatedReplicaEnvVar("REPLICA%s_INTERFACENAME", "REPLICA%s_INTERFACE_NAME", index),
}
dhcpEnabled := checkDeprecatedReplicaEnvVar("REPLICA%s_DHCPSERVERENABLED", "REPLICA%s_DHCP_SERVER_ENABLED", index)
if strings.EqualFold(dhcpEnabled, "true") {
re.DHCPServerEnabled = utils.Ptr(true)
} else if strings.EqualFold(dhcpEnabled, "false") {
re.DHCPServerEnabled = utils.Ptr(false)
}
if re.APIPath == "" {
re.APIPath = "/control"
}
replicas = append(replicas, re)
}
}
return replicas
}

View File

@@ -1,10 +1,10 @@
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"
)
@@ -15,7 +15,7 @@ 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()
cfg, err := config.Get(cfgFile, cmd.Flags())
if err != nil {
logger.Error(err)
return err
@@ -43,82 +43,47 @@ var doCmd = &cobra.Command{
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().Bool("printConfigOnly", false, "Prints the configuration only and exists. "+
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.")
_ = viper.BindPFlag(configPrintConfigOnly, doCmd.PersistentFlags().Lookup("printConfigOnly"))
doCmd.PersistentFlags().Bool("continueOnError", false, "If enabled, the synchronisation task "+
doCmd.PersistentFlags().Bool(config.FlagContinueOnError, false, "If enabled, the synchronisation task "+
"will not fail on single errors, but will log the errors and continue.")
_ = viper.BindPFlag(configContinueOnError, doCmd.PersistentFlags().Lookup("continueOnError"))
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().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-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().Bool(config.FlagFeatureDhcpServerConfig, true, "Enable DHCP server config feature")
doCmd.PersistentFlags().Bool(config.FlagFeatureDhcpStaticLeases, true, "Enable DHCP server static leases feature")
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.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("origin-url", "", "Origin instance url")
_ = viper.BindPFlag(configOriginURL, doCmd.PersistentFlags().Lookup("origin-url"))
doCmd.PersistentFlags().String("origin-web-url", "", "Origin instance web url used in the web interface (default: <origin-url>)")
_ = viper.BindPFlag(configOriginWebURL, doCmd.PersistentFlags().Lookup("origin-web-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.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("replica-url", "", "Replica instance url")
_ = viper.BindPFlag(configReplicaURL, doCmd.PersistentFlags().Lookup("replica-url"))
doCmd.PersistentFlags().String("replica-web-url", "", "Replica instance web url used in the web interface (default: <replica-url>)")
_ = viper.BindPFlag(configReplicaWebURL, doCmd.PersistentFlags().Lookup("replica-web-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().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")
}

402
go.mod
View File

@@ -1,80 +1,432 @@
module github.com/bakito/adguardhome-sync
go 1.21
go 1.22
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.9.1
github.com/golang/mock v1.6.0
github.com/google/uuid v1.5.0
github.com/go-resty/resty/v2 v2.12.0
github.com/golangci/golangci-lint v1.57.2
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.1.1
github.com/onsi/ginkgo/v2 v2.13.2
github.com/onsi/gomega v1.30.0
github.com/onsi/ginkgo/v2 v2.17.1
github.com/onsi/gomega v1.32.0
github.com/prometheus/client_golang v1.19.0
github.com/robfig/cron/v3 v3.0.1
github.com/spf13/cobra v1.8.0
github.com/spf13/viper v1.18.2
go.uber.org/zap v1.26.0
golang.org/x/mod v0.14.0
go.uber.org/mock v0.4.0
go.uber.org/zap v1.27.0
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc
golang.org/x/mod v0.16.0
gopkg.in/yaml.v3 v3.0.1
k8s.io/apimachinery v0.29.0
k8s.io/apimachinery v0.29.3
k8s.io/code-generator v0.29.3
)
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.14 // 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.2.0 // 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.4 // 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/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.1 // 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.7.1 // indirect
github.com/cavaliergopher/cpio v1.0.1 // indirect
github.com/ccojocar/zxcvbn-go v1.0.2 // 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.1 // indirect
github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589 // indirect
github.com/ckaznocha/intrange v0.1.1 // 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.3 // 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.5.0 // 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.9+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/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.5 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-logr/logr v1.3.0 // indirect
github.com/go-critic/go-critic v0.11.2 // 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.4.1 // 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.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.2.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.4 // indirect
github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect
github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e // indirect
github.com/golangci/misspell v0.4.1 // indirect
github.com/golangci/plugin-module-register v0.1.1 // indirect
github.com/golangci/revgrep v0.5.2 // indirect
github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed // 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.1 // 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.3 // 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/julz/importas v0.1.0 // indirect
github.com/karamaru-alpha/copyloopvar v1.0.10 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/kisielk/errcheck v1.7.0 // indirect
github.com/kkHAIKE/contextcheck v1.1.5 // 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.10 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/kyoh86/exportloopref v0.1.11 // indirect
github.com/ldez/gomoddirectives v0.2.4 // 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/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/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.1 // indirect
github.com/sagikazarmark/locafero v0.4.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.16.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.2.0 // 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_model v0.5.0 // indirect
github.com/prometheus/common v0.48.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/quasilyte/go-ruleguard v0.4.2 // 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.1 // 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/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect
github.com/sashamelentyev/interfacebloat v1.1.0 // indirect
github.com/sashamelentyev/usestdlibvars v1.25.0 // indirect
github.com/secure-systems-lab/go-securesystemslib v0.7.0 // indirect
github.com/securego/gosec/v2 v2.19.0 // 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/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/sourcegraph/go-diff v0.7.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.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.2 // indirect
github.com/stretchr/testify v1.9.0 // 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.3 // 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.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.9.0 // indirect
go-simpler.org/sloglint v0.5.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
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/net v0.19.0 // indirect
golang.org/x/sys v0.16.0 // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f // indirect
golang.org/x/net v0.22.0 // indirect
golang.org/x/oauth2 v0.16.0 // indirect
golang.org/x/sync v0.6.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/term v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.16.0 // indirect
google.golang.org/protobuf v1.32.0 // indirect
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.19.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.33.0 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/go-jose/go-jose.v2 v2.6.3 // 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
honnef.co/go/tools v0.4.7 // 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/unparam v0.0.0-20240104100049-c549a3470d14 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // 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
)

1551
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -53,10 +53,8 @@ func New(config types.AdGuardInstance) (Client, error) {
u.Path = path.Clean(u.Path)
cl := resty.New().SetBaseURL(u.String()).SetDisableWarn(true)
if config.InsecureSkipVerify {
// #nosec G402 has to be explicitly enabled
cl.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
}
// #nosec G402 has to be explicitly enabled
cl.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: config.InsecureSkipVerify})
cookieParts := strings.Split(config.Cookie, "=")
if len(cookieParts) == 2 {
@@ -90,6 +88,8 @@ func New(config types.AdGuardInstance) (Client, error) {
type Client interface {
Host() string
Status() (*model.ServerStatus, error)
Stats() (*model.Stats, error)
QueryLog(limit int) (*model.QueryLog, error)
ToggleProtection(enable bool) error
RewriteList() (*model.RewriteEntries, error)
AddRewriteEntries(e ...model.RewriteEntry) error
@@ -109,18 +109,16 @@ type Client interface {
SetSafeSearchConfig(settings *model.SafeSearchConfig) error
ProfileInfo() (*model.ProfileInfo, error)
SetProfileInfo(settings *model.ProfileInfo) error
BlockedServices() (*model.BlockedServicesArray, error)
BlockedServicesSchedule() (*model.BlockedServicesSchedule, error)
SetBlockedServices(services *model.BlockedServicesArray) error
SetBlockedServicesSchedule(schedule *model.BlockedServicesSchedule) error
Clients() (*model.Clients, error)
AddClient(client *model.Client) error
UpdateClient(client *model.Client) error
DeleteClient(client *model.Client) error
QueryLogConfig() (*model.QueryLogConfig, error)
SetQueryLogConfig(*model.QueryLogConfig) error
StatsConfig() (*model.StatsConfig, error)
SetStatsConfig(sc *model.StatsConfig) error
QueryLogConfig() (*model.QueryLogConfigWithIgnored, error)
SetQueryLogConfig(*model.QueryLogConfigWithIgnored) error
StatsConfig() (*model.GetStatsConfigResponse, error)
SetStatsConfig(sc *model.PutStatsConfigUpdateRequest) error
Setup() error
AccessList() (*model.AccessList, error)
SetAccessList(*model.AccessList) error
@@ -160,6 +158,18 @@ func (cl *client) Status() (*model.ServerStatus, error) {
return status, err
}
func (cl *client) Stats() (*model.Stats, error) {
stats := &model.Stats{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(stats), "stats")
return stats, err
}
func (cl *client) QueryLog(limit int) (*model.QueryLog, error) {
ql := &model.QueryLog{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(ql), fmt.Sprintf(`querylog?limit=%d&response_status="all"`, limit))
return ql, err
}
func (cl *client) RewriteList() (*model.RewriteEntries, error) {
rewrites := &model.RewriteEntries{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(&rewrites), "/rewrite/list")
@@ -167,8 +177,7 @@ func (cl *client) RewriteList() (*model.RewriteEntries, error) {
}
func (cl *client) AddRewriteEntries(entries ...model.RewriteEntry) error {
for i := range entries {
e := entries[i]
for _, e := range entries {
cl.log.With("domain", e.Domain, "answer", e.Answer).Info("Add DNS rewrite entry")
err := cl.doPost(cl.client.R().EnableTrace().SetBody(&e), "/rewrite/add")
if err != nil {
@@ -179,8 +188,7 @@ func (cl *client) AddRewriteEntries(entries ...model.RewriteEntry) error {
}
func (cl *client) DeleteRewriteEntries(entries ...model.RewriteEntry) error {
for i := range entries {
e := entries[i]
for _, e := range entries {
cl.log.With("domain", e.Domain, "answer", e.Answer).Info("Delete DNS rewrite entry")
err := cl.doPost(cl.client.R().EnableTrace().SetBody(&e), "/rewrite/delete")
if err != nil {
@@ -261,7 +269,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")
}
@@ -273,17 +285,6 @@ func (cl *client) ToggleFiltering(enabled bool, interval int) error {
}), "/filtering/config")
}
func (cl *client) BlockedServices() (*model.BlockedServicesArray, error) {
svcs := &model.BlockedServicesArray{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(svcs), "/blocked_services/list")
return svcs, err
}
func (cl *client) SetBlockedServices(services *model.BlockedServicesArray) error {
cl.log.With("services", model.ArrayString(services)).Info("Set blocked services")
return cl.doPost(cl.client.R().EnableTrace().SetBody(services), "/blocked_services/set")
}
func (cl *client) BlockedServicesSchedule() (*model.BlockedServicesSchedule, error) {
sched := &model.BlockedServicesSchedule{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(sched), "/blocked_services/get")
@@ -316,26 +317,26 @@ func (cl *client) DeleteClient(client *model.Client) error {
return cl.doPost(cl.client.R().EnableTrace().SetBody(client), "/clients/delete")
}
func (cl *client) QueryLogConfig() (*model.QueryLogConfig, error) {
qlc := &model.QueryLogConfig{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(qlc), "/querylog_info")
func (cl *client) QueryLogConfig() (*model.QueryLogConfigWithIgnored, error) {
qlc := &model.QueryLogConfigWithIgnored{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(qlc), "/querylog/config")
return qlc, err
}
func (cl *client) SetQueryLogConfig(qlc *model.QueryLogConfig) error {
func (cl *client) SetQueryLogConfig(qlc *model.QueryLogConfigWithIgnored) error {
cl.log.With("enabled", *qlc.Enabled, "interval", *qlc.Interval, "anonymizeClientIP", *qlc.AnonymizeClientIp).Info("Set query log config")
return cl.doPost(cl.client.R().EnableTrace().SetBody(qlc), "/querylog_config")
return cl.doPut(cl.client.R().EnableTrace().SetBody(qlc), "/querylog/config/update")
}
func (cl *client) StatsConfig() (*model.StatsConfig, error) {
stats := &model.StatsConfig{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(stats), "/stats_info")
func (cl *client) StatsConfig() (*model.GetStatsConfigResponse, error) {
stats := &model.GetStatsConfigResponse{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(stats), "/stats/config")
return stats, err
}
func (cl *client) SetStatsConfig(sc *model.StatsConfig) error {
cl.log.With("interval", *sc.Interval).Info("Set stats config")
return cl.doPost(cl.client.R().EnableTrace().SetBody(sc), "/stats_config")
func (cl *client) SetStatsConfig(sc *model.PutStatsConfigUpdateRequest) error {
cl.log.With("interval", sc.Interval).Info("Set stats config")
return cl.doPut(cl.client.R().EnableTrace().SetBody(sc), "/stats/config/update")
}
func (cl *client) Setup() error {

View File

@@ -97,6 +97,20 @@ var _ = Describe("Client", func() {
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())
})
})
Context("Status", func() {
@@ -225,20 +239,6 @@ var _ = Describe("Client", func() {
})
})
Context("BlockedServices", func() {
It("should read BlockedServices", func() {
ts, cl = ClientGet("blockedservices-list.json", "/blocked_services/list")
s, err := cl.BlockedServices()
Ω(err).ShouldNot(HaveOccurred())
Ω(*s).Should(HaveLen(2))
})
It("should set BlockedServices", func() {
ts, cl = ClientPost("/blocked_services/set", `["bar","foo"]`)
err := cl.SetBlockedServices(&model.BlockedServicesArray{"foo", "bar"})
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("BlockedServicesSchedule", func() {
It("should read BlockedServicesSchedule", func() {
ts, cl = ClientGet("blockedservicesschedule-get.json", "/blocked_services/get")
@@ -294,7 +294,7 @@ var _ = Describe("Client", func() {
Context("QueryLogConfig", func() {
It("should read QueryLogConfig", func() {
ts, cl = ClientGet("querylog_info.json", "/querylog_info")
ts, cl = ClientGet("querylog_config.json", "/querylog/config")
qlc, err := cl.QueryLogConfig()
Ω(err).ShouldNot(HaveOccurred())
Ω(qlc.Enabled).ShouldNot(BeNil())
@@ -303,26 +303,33 @@ var _ = Describe("Client", func() {
Ω(*qlc.Interval).Should(Equal(model.QueryLogConfigInterval(90)))
})
It("should set QueryLogConfig", func() {
ts, cl = ClientPost("/querylog_config", `{"anonymize_client_ip":true,"enabled":true,"interval":123}`)
ts, cl = ClientPut("/querylog/config/update", `{"anonymize_client_ip":true,"enabled":true,"interval":123,"ignored":["foo.bar"]}`)
var interval model.QueryLogConfigInterval = 123
err := cl.SetQueryLogConfig(&model.QueryLogConfig{AnonymizeClientIp: utils.Ptr(true), Interval: &interval, Enabled: utils.Ptr(true)})
err := cl.SetQueryLogConfig(&model.QueryLogConfigWithIgnored{
QueryLogConfig: model.QueryLogConfig{
AnonymizeClientIp: utils.Ptr(true),
Interval: &interval,
Enabled: utils.Ptr(true),
},
Ignored: []string{"foo.bar"},
})
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("StatsConfig", func() {
It("should read StatsConfig", func() {
ts, cl = ClientGet("stats_info.json", "/stats_info")
ts, cl = ClientGet("stats_info.json", "/stats/config")
sc, err := cl.StatsConfig()
Ω(err).ShouldNot(HaveOccurred())
Ω(sc.Interval).ShouldNot(BeNil())
Ω(*sc.Interval).Should(Equal(model.StatsConfigInterval(1)))
Ω(sc.Interval).Should(Equal(float32(1)))
})
It("should set StatsConfig", func() {
ts, cl = ClientPost("/stats_config", `{"interval":123}`)
ts, cl = ClientPost("/stats/config/update", `{"enabled":false,"ignored":null,"interval":123}`)
var interval model.StatsConfigInterval = 123
err := cl.SetStatsConfig(&model.StatsConfig{Interval: &interval})
var interval float32 = 123
err := cl.SetStatsConfig(&model.PutStatsConfigUpdateRequest{Interval: interval})
Ω(err).ShouldNot(HaveOccurred())
})
})
@@ -347,8 +354,8 @@ var _ = Describe("Client", func() {
Context("doPost", func() {
It("should return an error on status code != 200", func() {
var interval model.StatsConfigInterval = 123
err := cl.SetStatsConfig(&model.StatsConfig{Interval: &interval})
var interval float32 = 123
err := cl.SetStatsConfig(&model.PutStatsConfigUpdateRequest{Interval: interval})
Ω(err).Should(HaveOccurred())
Ω(err.Error()).Should(Equal("401 Unauthorized"))
})

View File

@@ -33,12 +33,11 @@ func New(config types.AdGuardInstance) (Client, error) {
}
u.Path = path.Clean(u.Path)
httpClient := &http.Client{}
if config.InsecureSkipVerify {
// #nosec G402 has to be explicitly enabled
httpClient.Transport = &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
httpClient := &http.Client{
Transport: &http.Transport{
// #nosec G402 has to be explicitly enabled
TLSClientConfig: &tls.Config{InsecureSkipVerify: config.InsecureSkipVerify},
},
}
aghClient, err := model.NewClient(u.String(), func(client *model.AdguardHomeClient) error {

View File

@@ -7,6 +7,7 @@ import (
"github.com/bakito/adguardhome-sync/pkg/utils"
"github.com/jinzhu/copier"
"go.uber.org/zap"
)
// Clone the config
@@ -16,6 +17,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 +43,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
@@ -170,8 +190,7 @@ func (clients *Clients) Merge(other *Clients) ([]*Client, []*Client, []*Client)
current := make(map[string]*Client)
if clients.Clients != nil {
cc := *clients.Clients
for i := range cc {
client := cc[i]
for _, client := range cc {
current[*client.Name] = &client
}
}
@@ -179,8 +198,7 @@ func (clients *Clients) Merge(other *Clients) ([]*Client, []*Client, []*Client)
expected := make(map[string]*Client)
if other.Clients != nil {
oc := *other.Clients
for i := range oc {
client := oc[i]
for _, client := range oc {
expected[*client.Name] = &client
}
}
@@ -273,15 +291,13 @@ func MergeFilters(this *[]Filter, other *[]Filter) ([]Filter, []Filter, []Filter
var updates []Filter
var removes []Filter
if this != nil {
for i := range *this {
fi := (*this)[i]
for _, fi := range *this {
current[fi.Url] = &fi
}
}
if other != nil {
for i := range *other {
rr := (*other)[i]
for _, rr := range *other {
if c, ok := current[rr.Url]; ok {
if !c.Equals(&rr) {
updates = append(updates, rr)
@@ -305,11 +321,16 @@ func (f *Filter) Equals(o *Filter) bool {
return f.Enabled == o.Enabled && f.Url == o.Url && f.Name == o.Name
}
type QueryLogConfigWithIgnored struct {
QueryLogConfig
// Ignored List of host names, which should not be written to log
Ignored []string `json:"ignored,omitempty"`
}
// Equals QueryLogConfig equal check
func (qlc *QueryLogConfig) Equals(o *QueryLogConfig) bool {
return ptrEquals(qlc.Enabled, o.Enabled) &&
ptrEquals(qlc.AnonymizeClientIp, o.AnonymizeClientIp) &&
qlc.Interval.Equals(o.Interval)
func (qlc *QueryLogConfigWithIgnored) Equals(o *QueryLogConfigWithIgnored) bool {
return utils.JsonEquals(qlc, o)
}
// Equals QueryLogConfigInterval equal check
@@ -386,3 +407,17 @@ func ArrayString(a *[]string) string {
sort.Strings(sorted)
return fmt.Sprintf("[%s]", strings.Join(sorted, ","))
}
func (c *DNSConfig) Sanitize(l *zap.SugaredLogger) {
// disable UsePrivatePtrResolvers if not configured
if c.UsePrivatePtrResolvers != nil && *c.UsePrivatePtrResolvers &&
(c.LocalPtrUpstreams == nil || len(*c.LocalPtrUpstreams) == 0) {
l.Warn("disabling replica 'Use private reverse DNS resolvers' as no 'Private reverse DNS servers' are configured on origin")
c.UsePrivatePtrResolvers = utils.Ptr(false)
}
}
// Equals GetStatsConfigResponse equal check
func (sc *GetStatsConfigResponse) Equals(o *GetStatsConfigResponse) bool {
return utils.JsonEquals(sc, o)
}

View File

@@ -1,6 +1,6 @@
// Package model provides primitives to interact with the openapi HTTP API.
//
// Code generated by github.com/deepmap/oapi-codegen/v2 version v2.0.0 DO NOT EDIT.
// Code generated by github.com/deepmap/oapi-codegen/v2 version v2.1.0 DO NOT EDIT.
package model
import (
@@ -37,6 +37,14 @@ const (
Refused DNSConfigBlockingMode = "refused"
)
// Defines values for DNSConfigUpstreamMode.
const (
MapConstDeprecatedTrueDescriptionUseloadBalanceInstead DNSConfigUpstreamMode = "map[const: deprecated:true description:Use `load_balance` instead.]"
MapConstFastestAddr DNSConfigUpstreamMode = "map[const:fastest_addr]"
MapConstLoadBalance DNSConfigUpstreamMode = "map[const:load_balance]"
MapConstParallel DNSConfigUpstreamMode = "map[const:parallel]"
)
// Defines values for DhcpSearchResultOtherServerFound.
const (
DhcpSearchResultOtherServerFoundError DhcpSearchResultOtherServerFound = "error"
@@ -306,7 +314,7 @@ type Client struct {
// changed.
//
// This behaviour can be changed in the future versions.
UpstreamsCacheSize *int `json:"upstreams_cache_size,omitempty"`
UpstreamsCacheSize *int `json:"upstreams_cache_size,omitempty"`
UseGlobalBlockedServices *bool `json:"use_global_blocked_services,omitempty"`
UseGlobalSettings *bool `json:"use_global_settings,omitempty"`
}
@@ -431,15 +439,20 @@ type DNSConfig struct {
ResolveClients *bool `json:"resolve_clients,omitempty"`
// UpstreamDns Upstream servers, port is optional after colon. Empty value will reset it to default values.
UpstreamDns *[]string `json:"upstream_dns,omitempty"`
UpstreamDnsFile *string `json:"upstream_dns_file,omitempty"`
UpstreamMode *interface{} `json:"upstream_mode,omitempty"`
UsePrivatePtrResolvers *bool `json:"use_private_ptr_resolvers,omitempty"`
UpstreamDns *[]string `json:"upstream_dns,omitempty"`
UpstreamDnsFile *string `json:"upstream_dns_file,omitempty"`
// UpstreamMode Upstream modes enumeration.
UpstreamMode *DNSConfigUpstreamMode `json:"upstream_mode,omitempty"`
UsePrivatePtrResolvers *bool `json:"use_private_ptr_resolvers,omitempty"`
}
// DNSConfigBlockingMode defines model for DNSConfig.BlockingMode.
type DNSConfigBlockingMode string
// DNSConfigUpstreamMode Upstream modes enumeration.
type DNSConfigUpstreamMode string
// DayRange The single interval within a day. It begins at the `start` and ends before the `end`.
type DayRange struct {
// End The number of milliseconds elapsed from the start of a day. It is expected to be rounded to minutes. The maximum value is `86400000` (24 hours).
@@ -1046,6 +1059,9 @@ type TlsConfig struct {
// PrivateKeySaved Set to true if the user has previously saved a private key as a string. This is used so that the server and the client don't have to send the private key between each other every time, which might lead to security issues.
PrivateKeySaved *bool `json:"private_key_saved,omitempty"`
// ServePlainDns Set to true if plain DNS is allowed for incoming requests.
ServePlainDns *bool `json:"serve_plain_dns,omitempty"`
// ServerName server_name is the hostname of your HTTPS/TLS server
ServerName *string `json:"server_name,omitempty"`

View File

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

View File

@@ -115,12 +115,12 @@ var _ = Describe("Types", func() {
Context("QueryLogConfig", func() {
Context("Equal", func() {
var (
a *model.QueryLogConfig
b *model.QueryLogConfig
a *model.QueryLogConfigWithIgnored
b *model.QueryLogConfigWithIgnored
)
BeforeEach(func() {
a = &model.QueryLogConfig{}
b = &model.QueryLogConfig{}
a = &model.QueryLogConfigWithIgnored{}
b = &model.QueryLogConfigWithIgnored{}
})
It("should be equal", func() {
a.Enabled = utils.Ptr(true)
@@ -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

@@ -1,9 +1,10 @@
package cmd
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"
@@ -41,22 +42,21 @@ var deprecatedEnvVars = []string{
"REPLICA1_DHCPSERVERENABLED",
}
var _ = Describe("Run", func() {
var _ = Describe("Config", func() {
Context("deprecated", func() {
BeforeEach(func() {
for _, envVar := range deprecatedEnvVars {
Ω(os.Setenv(envVar, "false")).ShouldNot(HaveOccurred())
}
initConfig()
})
AfterEach(func() {
for _, envVar := range deprecatedEnvVars {
Ω(os.Unsetenv(envVar)).ShouldNot(HaveOccurred())
}
})
Context("getConfig", func() {
Context("Get", func() {
It("features should be false", func() {
cfg, err := getConfig()
cfg, err := config.Get("", nil)
Ω(err).ShouldNot(HaveOccurred())
verifyFeatures(cfg, false)
})
@@ -67,21 +67,20 @@ var _ = Describe("Run", func() {
for _, envVar := range envVars {
Ω(os.Unsetenv(envVar)).ShouldNot(HaveOccurred())
}
initConfig()
})
AfterEach(func() {
for _, envVar := range envVars {
Ω(os.Unsetenv(envVar)).ShouldNot(HaveOccurred())
}
})
Context("getConfig", func() {
Context("Get", func() {
It("features should be true by default", func() {
cfg, err := getConfig()
cfg, err := config.Get("", nil)
Ω(err).ShouldNot(HaveOccurred())
verifyFeatures(cfg, true)
})
It("features should be true by default", func() {
cfg, err := getConfig()
cfg, err := config.Get("", nil)
Ω(err).ShouldNot(HaveOccurred())
verifyFeatures(cfg, true)
})
@@ -89,7 +88,7 @@ var _ = Describe("Run", func() {
for _, envVar := range envVars {
Ω(os.Setenv(envVar, "false")).ShouldNot(HaveOccurred())
}
cfg, err := getConfig()
cfg, err := config.Get("", nil)
Ω(err).ShouldNot(HaveOccurred())
verifyFeatures(cfg, false)
})
@@ -97,7 +96,7 @@ var _ = Describe("Run", 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 := getConfig()
cfg, err := config.Get("", nil)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].InterfaceName).Should(Equal("eth0"))
})
@@ -106,7 +105,7 @@ var _ = Describe("Run", 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 := getConfig()
cfg, err := config.Get("", nil)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].DHCPServerEnabled).ShouldNot(BeNil())
Ω(*cfg.Replicas[0].DHCPServerEnabled).Should(BeTrue())
@@ -114,7 +113,7 @@ var _ = Describe("Run", func() {
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 := getConfig()
cfg, err := config.Get("", nil)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].DHCPServerEnabled).ShouldNot(BeNil())
Ω(*cfg.Replicas[0].DHCPServerEnabled).Should(BeFalse())

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.yaml"), 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.yaml")))
})
})
})

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

14
pkg/metrics/handler.go Normal file
View File

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

245
pkg/metrics/metrics.go Normal file
View File

@@ -0,0 +1,245 @@
package metrics
import (
"github.com/bakito/adguardhome-sync/pkg/client/model"
"github.com/bakito/adguardhome-sync/pkg/log"
"github.com/prometheus/client_golang/prometheus"
"golang.org/x/exp/constraints"
)
var (
l = log.GetLogger("metrics")
// avgProcessingTime - Average processing time for a DNS query
avgProcessingTime = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "avg_processing_time",
Namespace: "adguard",
Help: "This represent the average processing time for a DNS query in s",
},
[]string{"hostname"},
)
// dnsQueries - Number of DNS queries
dnsQueries = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "num_dns_queries",
Namespace: "adguard",
Help: "Number of DNS queries",
},
[]string{"hostname"},
)
// blockedFiltering - Number of DNS queries blocked
blockedFiltering = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "num_blocked_filtering",
Namespace: "adguard",
Help: "This represent the number of domains blocked",
},
[]string{"hostname"},
)
// parentalFiltering - Number of DNS queries replaced by parental control
parentalFiltering = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "num_replaced_parental",
Namespace: "adguard",
Help: "This represent the number of domains blocked (parental)",
},
[]string{"hostname"},
)
// safeBrowsingFiltering - Number of DNS queries replaced by safe browsing
safeBrowsingFiltering = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "num_replaced_safebrowsing",
Namespace: "adguard",
Help: "This represent the number of domains blocked (safe browsing)",
},
[]string{"hostname"},
)
// safeSearchFiltering - Number of DNS queries replaced by safe search
safeSearchFiltering = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "num_replaced_safesearch",
Namespace: "adguard",
Help: "This represent the number of domains blocked (safe search)",
},
[]string{"hostname"},
)
// topQueries - The number of top queries
topQueries = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "top_queried_domains",
Namespace: "adguard",
Help: "This represent the top queried domains",
},
[]string{"hostname", "domain"},
)
// topBlocked - The number of top domains blocked
topBlocked = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "top_blocked_domains",
Namespace: "adguard",
Help: "This represent the top bloacked domains",
},
[]string{"hostname", "domain"},
)
// topClients - The number of top clients
topClients = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "top_clients",
Namespace: "adguard",
Help: "This represent the top clients",
},
[]string{"hostname", "client"},
)
// queryTypes - The type of DNS Queries (A, AAAA...)
queryTypes = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "query_types",
Namespace: "adguard",
Help: "This represent the DNS query types",
},
[]string{"hostname", "type"},
)
// running - If Adguard is running
running = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "running",
Namespace: "adguard",
Help: "This represent if Adguard is running",
},
[]string{"hostname"},
)
// protectionEnabled - If Adguard protection is enabled
protectionEnabled = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "protection_enabled",
Namespace: "adguard",
Help: "This represent if Adguard Protection is enabled",
},
[]string{"hostname"},
)
)
// Init initializes all Prometheus metrics made available by AdGuard exporter.
func Init() {
initMetric("avg_processing_time", avgProcessingTime)
initMetric("num_dns_queries", dnsQueries)
initMetric("num_blocked_filtering", blockedFiltering)
initMetric("num_replaced_parental", parentalFiltering)
initMetric("num_replaced_safebrowsing", safeBrowsingFiltering)
initMetric("num_replaced_safesearch", safeSearchFiltering)
initMetric("top_queried_domains", topQueries)
initMetric("top_blocked_domains", topBlocked)
initMetric("top_clients", topClients)
initMetric("query_types", queryTypes)
initMetric("running", running)
initMetric("protection_enabled", protectionEnabled)
}
func initMetric(name string, metric *prometheus.GaugeVec) {
prometheus.MustRegister(metric)
l.With("name", name).Info("New Prometheus metric registered")
}
func Update(ims ...InstanceMetrics) {
for _, im := range ims {
update(im)
}
l.Debug("updated")
}
func update(im InstanceMetrics) {
// Status
var isRunning int = 0
if im.Status.Running {
isRunning = 1
}
running.WithLabelValues(im.HostName).Set(float64(isRunning))
var isProtected int = 0
if im.Status.ProtectionEnabled {
isProtected = 1
}
protectionEnabled.WithLabelValues(im.HostName).Set(float64(isProtected))
// Stats
avgProcessingTime.WithLabelValues(im.HostName).Set(safeMetric(im.Stats.AvgProcessingTime))
dnsQueries.WithLabelValues(im.HostName).Set(safeMetric(im.Stats.NumDnsQueries))
blockedFiltering.WithLabelValues(im.HostName).Set(safeMetric(im.Stats.NumBlockedFiltering))
parentalFiltering.WithLabelValues(im.HostName).Set(safeMetric(im.Stats.NumReplacedParental))
safeBrowsingFiltering.WithLabelValues(im.HostName).Set(safeMetric(im.Stats.NumReplacedSafebrowsing))
safeSearchFiltering.WithLabelValues(im.HostName).Set(safeMetric(im.Stats.NumReplacedSafesearch))
if im.Stats.TopQueriedDomains != nil {
for _, tq := range *im.Stats.TopQueriedDomains {
for domain, value := range tq.AdditionalProperties {
topQueries.WithLabelValues(im.HostName, domain).Set(float64(value))
}
}
}
if im.Stats.TopBlockedDomains != nil {
for _, tb := range *im.Stats.TopBlockedDomains {
for domain, value := range tb.AdditionalProperties {
topBlocked.WithLabelValues(im.HostName, domain).Set(float64(value))
}
}
}
if im.Stats.TopClients != nil {
for _, tc := range *im.Stats.TopClients {
for source, value := range tc.AdditionalProperties {
topClients.WithLabelValues(im.HostName, source).Set(float64(value))
}
}
}
// LogQuery
m := make(map[string]int)
if im.QueryLog != nil && im.QueryLog.Data != nil {
logdata := *im.QueryLog.Data
for _, ld := range logdata {
if ld.Answer != nil {
dnsanswer := *ld.Answer
if len(dnsanswer) > 0 {
for _, dnsa := range dnsanswer {
dnsType := *dnsa.Type
m[dnsType] += 1
}
}
}
}
}
for key, value := range m {
queryTypes.WithLabelValues(im.HostName, key).Set(float64(value))
}
}
type InstanceMetrics struct {
HostName string
Status *model.ServerStatus
Stats *model.Stats
QueryLog *model.QueryLog
}
func safeMetric[T Number](v *T) float64 {
if v == nil {
return 0
}
return float64(*v)
}
type Number interface {
constraints.Float | constraints.Integer
}

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.
@@ -58,7 +63,7 @@ func (m *MockClient) AddClient(arg0 *model.Client) error {
}
// AddClient indicates an expected call of AddClient.
func (mr *MockClientMockRecorder) AddClient(arg0 interface{}) *gomock.Call {
func (mr *MockClientMockRecorder) AddClient(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddClient", reflect.TypeOf((*MockClient)(nil).AddClient), arg0)
}
@@ -72,7 +77,7 @@ func (m *MockClient) AddDHCPStaticLease(arg0 model.DhcpStaticLease) error {
}
// AddDHCPStaticLease indicates an expected call of AddDHCPStaticLease.
func (mr *MockClientMockRecorder) AddDHCPStaticLease(arg0 interface{}) *gomock.Call {
func (mr *MockClientMockRecorder) AddDHCPStaticLease(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddDHCPStaticLease", reflect.TypeOf((*MockClient)(nil).AddDHCPStaticLease), arg0)
}
@@ -86,7 +91,7 @@ func (m *MockClient) AddFilter(arg0 bool, arg1 model.Filter) error {
}
// AddFilter indicates an expected call of AddFilter.
func (mr *MockClientMockRecorder) AddFilter(arg0, arg1 interface{}) *gomock.Call {
func (mr *MockClientMockRecorder) AddFilter(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddFilter", reflect.TypeOf((*MockClient)(nil).AddFilter), arg0, arg1)
}
@@ -94,7 +99,7 @@ func (mr *MockClientMockRecorder) AddFilter(arg0, arg1 interface{}) *gomock.Call
// 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)
}
@@ -104,26 +109,11 @@ 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...)
}
// BlockedServices mocks base method.
func (m *MockClient) BlockedServices() (*[]string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "BlockedServices")
ret0, _ := ret[0].(*[]string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// BlockedServices indicates an expected call of BlockedServices.
func (mr *MockClientMockRecorder) BlockedServices() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BlockedServices", reflect.TypeOf((*MockClient)(nil).BlockedServices))
}
// BlockedServicesSchedule mocks base method.
func (m *MockClient) BlockedServicesSchedule() (*model.BlockedServicesSchedule, error) {
m.ctrl.T.Helper()
@@ -178,7 +168,7 @@ func (m *MockClient) DeleteClient(arg0 *model.Client) error {
}
// DeleteClient indicates an expected call of DeleteClient.
func (mr *MockClientMockRecorder) DeleteClient(arg0 interface{}) *gomock.Call {
func (mr *MockClientMockRecorder) DeleteClient(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteClient", reflect.TypeOf((*MockClient)(nil).DeleteClient), arg0)
}
@@ -192,7 +182,7 @@ func (m *MockClient) DeleteDHCPStaticLease(arg0 model.DhcpStaticLease) error {
}
// DeleteDHCPStaticLease indicates an expected call of DeleteDHCPStaticLease.
func (mr *MockClientMockRecorder) DeleteDHCPStaticLease(arg0 interface{}) *gomock.Call {
func (mr *MockClientMockRecorder) DeleteDHCPStaticLease(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteDHCPStaticLease", reflect.TypeOf((*MockClient)(nil).DeleteDHCPStaticLease), arg0)
}
@@ -206,7 +196,7 @@ func (m *MockClient) DeleteFilter(arg0 bool, arg1 model.Filter) error {
}
// DeleteFilter indicates an expected call of DeleteFilter.
func (mr *MockClientMockRecorder) DeleteFilter(arg0, arg1 interface{}) *gomock.Call {
func (mr *MockClientMockRecorder) DeleteFilter(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteFilter", reflect.TypeOf((*MockClient)(nil).DeleteFilter), arg0, arg1)
}
@@ -214,7 +204,7 @@ func (mr *MockClientMockRecorder) DeleteFilter(arg0, arg1 interface{}) *gomock.C
// 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)
}
@@ -224,7 +214,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...)
}
@@ -303,11 +293,26 @@ func (mr *MockClientMockRecorder) ProfileInfo() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ProfileInfo", reflect.TypeOf((*MockClient)(nil).ProfileInfo))
}
// QueryLog mocks base method.
func (m *MockClient) QueryLog(arg0 int) (*model.QueryLog, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "QueryLog", arg0)
ret0, _ := ret[0].(*model.QueryLog)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// QueryLog indicates an expected call of QueryLog.
func (mr *MockClientMockRecorder) QueryLog(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueryLog", reflect.TypeOf((*MockClient)(nil).QueryLog), arg0)
}
// QueryLogConfig mocks base method.
func (m *MockClient) QueryLogConfig() (*model.QueryLogConfig, error) {
func (m *MockClient) QueryLogConfig() (*model.QueryLogConfigWithIgnored, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "QueryLogConfig")
ret0, _ := ret[0].(*model.QueryLogConfig)
ret0, _ := ret[0].(*model.QueryLogConfigWithIgnored)
ret1, _ := ret[1].(error)
return ret0, ret1
}
@@ -327,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)
}
@@ -386,25 +391,11 @@ 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)
}
// SetBlockedServices mocks base method.
func (m *MockClient) SetBlockedServices(arg0 *[]string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetBlockedServices", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// SetBlockedServices indicates an expected call of SetBlockedServices.
func (mr *MockClientMockRecorder) SetBlockedServices(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBlockedServices", reflect.TypeOf((*MockClient)(nil).SetBlockedServices), arg0)
}
// SetBlockedServicesSchedule mocks base method.
func (m *MockClient) SetBlockedServicesSchedule(arg0 *model.BlockedServicesSchedule) error {
m.ctrl.T.Helper()
@@ -414,7 +405,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)
}
@@ -428,7 +419,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)
}
@@ -442,7 +433,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)
}
@@ -456,7 +447,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)
}
@@ -470,13 +461,13 @@ 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)
}
// SetQueryLogConfig mocks base method.
func (m *MockClient) SetQueryLogConfig(arg0 *model.QueryLogConfig) error {
func (m *MockClient) SetQueryLogConfig(arg0 *model.QueryLogConfigWithIgnored) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetQueryLogConfig", arg0)
ret0, _ := ret[0].(error)
@@ -484,7 +475,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)
}
@@ -498,13 +489,13 @@ 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)
}
// SetStatsConfig mocks base method.
func (m *MockClient) SetStatsConfig(arg0 *model.StatsConfig) error {
func (m *MockClient) SetStatsConfig(arg0 *model.GetStatsConfigResponse) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetStatsConfig", arg0)
ret0, _ := ret[0].(error)
@@ -512,7 +503,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)
}
@@ -531,11 +522,26 @@ func (mr *MockClientMockRecorder) Setup() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Setup", reflect.TypeOf((*MockClient)(nil).Setup))
}
// Stats mocks base method.
func (m *MockClient) Stats() (*model.Stats, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Stats")
ret0, _ := ret[0].(*model.Stats)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Stats indicates an expected call of Stats.
func (mr *MockClientMockRecorder) Stats() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stats", reflect.TypeOf((*MockClient)(nil).Stats))
}
// StatsConfig mocks base method.
func (m *MockClient) StatsConfig() (*model.StatsConfig, error) {
func (m *MockClient) StatsConfig() (*model.GetStatsConfigResponse, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "StatsConfig")
ret0, _ := ret[0].(*model.StatsConfig)
ret0, _ := ret[0].(*model.GetStatsConfigResponse)
ret1, _ := ret[1].(error)
return ret0, ret1
}
@@ -570,7 +576,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)
}
@@ -584,7 +590,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)
}
@@ -598,7 +604,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)
}
@@ -612,7 +618,7 @@ 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)
}
@@ -626,7 +632,7 @@ func (m *MockClient) UpdateClient(arg0 *model.Client) error {
}
// UpdateClient indicates an expected call of UpdateClient.
func (mr *MockClientMockRecorder) UpdateClient(arg0 interface{}) *gomock.Call {
func (mr *MockClientMockRecorder) UpdateClient(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateClient", reflect.TypeOf((*MockClient)(nil).UpdateClient), arg0)
}
@@ -640,7 +646,7 @@ func (m *MockClient) UpdateFilter(arg0 bool, arg1 model.Filter) error {
}
// UpdateFilter indicates an expected call of UpdateFilter.
func (mr *MockClientMockRecorder) UpdateFilter(arg0, arg1 interface{}) *gomock.Call {
func (mr *MockClientMockRecorder) UpdateFilter(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
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)
}

View File

@@ -63,7 +63,7 @@ var (
if err != nil {
return err
}
if ac.origin.statsConfig.Interval != sc.Interval {
if !sc.Equals(ac.origin.statsConfig) {
return ac.client.SetStatsConfig(ac.origin.statsConfig)
}
return nil
@@ -111,17 +111,6 @@ var (
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 {
@@ -186,6 +175,9 @@ var (
if err != nil {
return err
}
dc.Sanitize(ac.rl)
if !dc.Equals(ac.origin.dnsConfig) {
if err = ac.client.SetDNSConfig(ac.origin.dnsConfig); err != nil {
return err
@@ -209,7 +201,7 @@ var (
origClone.Enabled = ac.replica.DHCPServerEnabled
}
if !sc.Equals(origClone) {
if !sc.CleanAndEquals(origClone) {
return ac.client.SetDhcpConfig(origClone)
}
}

View File

@@ -39,7 +39,6 @@ func setupActions(cfg *types.Config) (actions []syncAction) {
}
if cfg.Features.Services {
actions = append(actions,
action("blocked services", actionBlockedServices),
action("blocked services schedule", actionBlockedServicesSchedule),
)
}

View File

@@ -15,6 +15,7 @@ import (
"time"
"github.com/bakito/adguardhome-sync/pkg/log"
"github.com/bakito/adguardhome-sync/pkg/metrics"
"github.com/bakito/adguardhome-sync/version"
"github.com/gin-gonic/gin"
)
@@ -77,6 +78,11 @@ func (w *worker) listenAndServe() {
r.GET("/api/v1/status", w.handleStatus)
r.GET("/favicon.ico", w.handleFavicon)
r.GET("/", w.handleRoot)
if w.cfg.API.Metrics.Enabled {
r.GET("/metrics", metrics.Handler())
go w.startScraping()
}
go func() {
if err := httpServer.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
@@ -101,7 +107,7 @@ func (w *worker) listenAndServe() {
l.Fatal("os.Kill - terminating...")
}()
gracefullCtx, cancelShutdown := context.WithTimeout(context.Background(), 5*time.Second)
gracefulCtx, cancelShutdown := context.WithTimeout(context.Background(), 5*time.Second)
defer cancelShutdown()
if w.cron != nil {
@@ -109,7 +115,7 @@ func (w *worker) listenAndServe() {
w.cron.Stop()
}
if err := httpServer.Shutdown(gracefullCtx); err != nil {
if err := httpServer.Shutdown(gracefulCtx); err != nil {
l.With("error", err).Error("Shutdown error")
defer os.Exit(1)
} else {
@@ -123,8 +129,9 @@ func (w *worker) listenAndServe() {
}
type syncStatus struct {
Origin replicaStatus `json:"origin"`
Replicas []replicaStatus `json:"replicas"`
SyncRunning bool `json:"syncRunning"`
Origin replicaStatus `json:"origin"`
Replicas []replicaStatus `json:"replicas"`
}
type replicaStatus struct {

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

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

View File

@@ -110,6 +110,8 @@ func (w *worker) status() *syncStatus {
return syncStatus.Replicas[i].Host < syncStatus.Replicas[j].Host
})
syncStatus.SyncRunning = w.running
return syncStatus
}
@@ -199,12 +201,6 @@ func (w *worker) sync() {
return
}
o.blockedServices, err = oc.BlockedServices()
if err != nil {
sl.With("error", err).Error("Error getting origin blocked services")
return
}
o.blockedServicesSchedule, err = oc.BlockedServicesSchedule()
if err != nil {
sl.With("error", err).Error("Error getting origin blocked services schedule")
@@ -325,12 +321,11 @@ func (w *worker) statusWithSetup(rl *zap.SugaredLogger, replica types.AdGuardIns
type origin struct {
status *model.ServerStatus
rewrites *model.RewriteEntries
blockedServices *model.BlockedServicesArray
blockedServicesSchedule *model.BlockedServicesSchedule
filters *model.FilterStatus
clients *model.Clients
queryLogConfig *model.QueryLogConfig
statsConfig *model.StatsConfig
queryLogConfig *model.QueryLogConfigWithIgnored
statsConfig *model.GetStatsConfigResponse
accessList *model.AccessList
dnsConfig *model.DNSConfig
dhcpServerConfig *model.DhcpStatus

View File

@@ -9,10 +9,10 @@ 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 _ = Describe("Sync", func() {
@@ -67,8 +67,8 @@ var _ = Describe("Sync", func() {
},
status: &model.ServerStatus{},
safeSearch: &model.SafeSearchConfig{},
queryLogConfig: &model.QueryLogConfig{},
statsConfig: &model.StatsConfig{},
queryLogConfig: &model.QueryLogConfigWithIgnored{},
statsConfig: &model.PutStatsConfigUpdateRequest{},
},
replicaStatus: &model.ServerStatus{},
client: cl,
@@ -289,9 +289,9 @@ var _ = Describe("Sync", func() {
})
})
Context("actionQueryLogConfig", func() {
var qlc *model.QueryLogConfig
var qlc *model.QueryLogConfigWithIgnored
BeforeEach(func() {
qlc = &model.QueryLogConfig{}
qlc = &model.QueryLogConfigWithIgnored{}
})
It("should have no changes", func() {
cl.EXPECT().QueryLogConfig().Return(qlc, nil)
@@ -302,15 +302,15 @@ var _ = Describe("Sync", func() {
var interval model.QueryLogConfigInterval = 123
ac.origin.queryLogConfig.Interval = &interval
cl.EXPECT().QueryLogConfig().Return(qlc, nil)
cl.EXPECT().SetQueryLogConfig(&model.QueryLogConfig{AnonymizeClientIp: nil, Interval: &interval, Enabled: nil})
cl.EXPECT().SetQueryLogConfig(&model.QueryLogConfigWithIgnored{QueryLogConfig: model.QueryLogConfig{AnonymizeClientIp: nil, Interval: &interval, Enabled: nil}})
err := actionQueryLogConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("syncConfigs", func() {
var sc *model.StatsConfig
var sc *model.PutStatsConfigUpdateRequest
BeforeEach(func() {
sc = &model.StatsConfig{}
sc = &model.PutStatsConfigUpdateRequest{}
})
It("should have no changes", func() {
cl.EXPECT().StatsConfig().Return(sc, nil)
@@ -318,10 +318,10 @@ var _ = Describe("Sync", func() {
Ω(err).ShouldNot(HaveOccurred())
})
It("should have StatsConfig changes", func() {
var interval model.StatsConfigInterval = 123
ac.origin.statsConfig.Interval = &interval
var interval float32 = 123
ac.origin.statsConfig.Interval = interval
cl.EXPECT().StatsConfig().Return(sc, nil)
cl.EXPECT().SetStatsConfig(&model.StatsConfig{Interval: &interval})
cl.EXPECT().SetStatsConfig(&model.PutStatsConfigUpdateRequest{Interval: interval})
err := actionStatsConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
@@ -359,26 +359,6 @@ var _ = Describe("Sync", func() {
Ω(st).Should(BeNil())
})
})
Context("actionBlockedServices", func() {
var rbs *model.BlockedServicesArray
BeforeEach(func() {
ac.origin.blockedServices = &model.BlockedServicesArray{"foo"}
rbs = &model.BlockedServicesArray{"foo"}
})
It("should have no changes", func() {
cl.EXPECT().BlockedServices().Return(rbs, nil)
err := actionBlockedServices(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have blockedServices changes", func() {
ac.origin.blockedServices = &model.BlockedServicesArray{"bar"}
cl.EXPECT().BlockedServices().Return(rbs, nil)
cl.EXPECT().SetBlockedServices(ac.origin.blockedServices)
err := actionBlockedServices(ac)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("actionBlockedServicesSchedule", func() {
var rbss *model.BlockedServicesSchedule
BeforeEach(func() {
@@ -514,12 +494,14 @@ var _ = Describe("Sync", func() {
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"),
}}
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
})
@@ -554,6 +536,14 @@ var _ = Describe("Sync", func() {
err := actionDHCPServerConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should not sync empty IPv4", func() {
ac.replica.DHCPServerEnabled = utils.Ptr(false)
ac.origin.dhcpServerConfig.V4 = &model.DhcpConfigV4{
GatewayIp: utils.Ptr(""),
}
err := actionDHCPServerConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("sync", func() {
@@ -589,12 +579,11 @@ var _ = Describe("Sync", func() {
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil)
cl.EXPECT().SafeBrowsing()
cl.EXPECT().RewriteList().Return(&model.RewriteEntries{}, nil)
cl.EXPECT().BlockedServices()
cl.EXPECT().BlockedServicesSchedule()
cl.EXPECT().Filtering().Return(&model.FilterStatus{}, nil)
cl.EXPECT().Clients().Return(&model.Clients{}, nil)
cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfig{}, nil)
cl.EXPECT().StatsConfig().Return(&model.StatsConfig{}, nil)
cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfigWithIgnored{}, nil)
cl.EXPECT().StatsConfig().Return(&model.PutStatsConfigUpdateRequest{}, nil)
cl.EXPECT().AccessList().Return(&model.AccessList{}, nil)
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
cl.EXPECT().DhcpConfig().Return(&model.DhcpStatus{}, nil)
@@ -606,13 +595,12 @@ var _ = Describe("Sync", func() {
cl.EXPECT().Parental()
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil)
cl.EXPECT().SafeBrowsing()
cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfig{}, nil)
cl.EXPECT().StatsConfig().Return(&model.StatsConfig{}, nil)
cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfigWithIgnored{}, nil)
cl.EXPECT().StatsConfig().Return(&model.PutStatsConfigUpdateRequest{}, nil)
cl.EXPECT().RewriteList().Return(&model.RewriteEntries{}, nil)
cl.EXPECT().AddRewriteEntries()
cl.EXPECT().DeleteRewriteEntries()
cl.EXPECT().Filtering().Return(&model.FilterStatus{}, nil)
cl.EXPECT().BlockedServices()
cl.EXPECT().BlockedServicesSchedule()
cl.EXPECT().Clients().Return(&model.Clients{}, nil)
cl.EXPECT().AccessList().Return(&model.AccessList{}, nil)
@@ -631,12 +619,11 @@ var _ = Describe("Sync", func() {
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil)
cl.EXPECT().SafeBrowsing()
cl.EXPECT().RewriteList().Return(&model.RewriteEntries{}, nil)
cl.EXPECT().BlockedServices()
cl.EXPECT().BlockedServicesSchedule()
cl.EXPECT().Filtering().Return(&model.FilterStatus{}, nil)
cl.EXPECT().Clients().Return(&model.Clients{}, nil)
cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfig{}, nil)
cl.EXPECT().StatsConfig().Return(&model.StatsConfig{}, nil)
cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfigWithIgnored{}, nil)
cl.EXPECT().StatsConfig().Return(&model.PutStatsConfigUpdateRequest{}, nil)
cl.EXPECT().AccessList().Return(&model.AccessList{}, nil)
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
@@ -647,13 +634,12 @@ var _ = Describe("Sync", func() {
cl.EXPECT().Parental()
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil)
cl.EXPECT().SafeBrowsing()
cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfig{}, nil)
cl.EXPECT().StatsConfig().Return(&model.StatsConfig{}, nil)
cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfigWithIgnored{}, nil)
cl.EXPECT().StatsConfig().Return(&model.PutStatsConfigUpdateRequest{}, nil)
cl.EXPECT().RewriteList().Return(&model.RewriteEntries{}, nil)
cl.EXPECT().AddRewriteEntries()
cl.EXPECT().DeleteRewriteEntries()
cl.EXPECT().Filtering().Return(&model.FilterStatus{}, nil)
cl.EXPECT().BlockedServices()
cl.EXPECT().BlockedServicesSchedule()
cl.EXPECT().Clients().Return(&model.Clients{}, nil)
cl.EXPECT().AccessList().Return(&model.AccessList{}, nil)
@@ -675,12 +661,11 @@ var _ = Describe("Sync", func() {
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil)
cl.EXPECT().SafeBrowsing()
cl.EXPECT().RewriteList().Return(&model.RewriteEntries{}, nil)
cl.EXPECT().BlockedServices()
cl.EXPECT().BlockedServicesSchedule()
cl.EXPECT().Filtering().Return(&model.FilterStatus{}, nil)
cl.EXPECT().Clients().Return(&model.Clients{}, nil)
cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfig{}, nil)
cl.EXPECT().StatsConfig().Return(&model.StatsConfig{}, nil)
cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfigWithIgnored{}, nil)
cl.EXPECT().StatsConfig().Return(&model.PutStatsConfigUpdateRequest{}, nil)
cl.EXPECT().AccessList().Return(&model.AccessList{}, nil)
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
cl.EXPECT().DhcpConfig().Return(&model.DhcpStatus{}, nil)

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" mapstructure:"DNS"`
DHCP DHCP `json:"dhcp" yaml:"dhcp" mapstructure:"DHCP"`
GeneralSettings bool `json:"generalSettings" yaml:"generalSettings" mapstructure:"GENERAL_SETTINGS"`
QueryLogConfig bool `json:"queryLogConfig" yaml:"queryLogConfig" mapstructure:"QUERY_LOG_CONFIG"`
StatsConfig bool `json:"statsConfig" yaml:"statsConfig" mapstructure:"STATS_CONFIG"`
ClientSettings bool `json:"clientSettings" yaml:"clientSettings" mapstructure:"CLIENT_SETTINGS"`
Services bool `json:"services" yaml:"services" mapstructure:"SERVICES"`
Filters bool `json:"filters" yaml:"filters" mapstructure:"FILTERS"`
DNS DNS `json:"dns" yaml:"dns"`
DHCP DHCP `json:"dhcp" yaml:"dhcp"`
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" mapstructure:"SERVER_CONFIG"`
StaticLeases bool `json:"staticLeases" yaml:"staticLeases" mapstructure:"STATIC_LEASES"`
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" mapstructure:"ACCESS_LISTS"`
ServerConfig bool `json:"serverConfig" yaml:"serverConfig" mapstructure:"SERVER_CONFIG"`
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

@@ -3,6 +3,8 @@ package types
import (
"fmt"
"net/url"
"strings"
"time"
"go.uber.org/zap"
)
@@ -15,32 +17,52 @@ const (
// Config application configuration struct
// +k8s:deepcopy-gen=true
type Config struct {
Origin AdGuardInstance `json:"origin" yaml:"origin" mapstructure:"ORIGIN"`
Replica *AdGuardInstance `json:"replica,omitempty" yaml:"replica,omitempty" mapstructure:"REPLICA"`
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" mapstructure:"CRON"`
RunOnStart bool `json:"runOnStart,omitempty" yaml:"runOnStart,omitempty" mapstructure:"RUN_ON_START"`
PrintConfigOnly bool `json:"printConfigOnly,omitempty" yaml:"printConfigOnly,omitempty" mapstructure:"PRINT_CONFIG_ONLY"`
ContinueOnError bool `json:"continueOnError,omitempty" yaml:"continueOnError,omitempty" mapstructure:"CONTINUE_ON_ERROR"`
API API `json:"api,omitempty" yaml:"api,omitempty" mapstructure:"API"`
Features Features `json:"features,omitempty" yaml:"features,omitempty" mapstructure:"FEATURES"`
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" mapstructure:"PORT"`
Username string `json:"username,omitempty" yaml:"username,omitempty" mapstructure:"USERNAME"`
Password string `json:"password,omitempty" yaml:"password,omitempty" mapstructure:"PASSWORD"`
DarkMode bool `json:"darkMode,omitempty" yaml:"darkMode,omitempty" mapstructure:"DARK_MODE"`
Port int `json:"port,omitempty" yaml:"port,omitempty" env:"API_PORT"`
Username string `json:"username,omitempty" yaml:"username,omitempty" env:"API_USERNAME"`
Password string `json:"password,omitempty" yaml:"password,omitempty" env:"API_PASSWORD"`
DarkMode bool `json:"darkMode,omitempty" yaml:"darkMode,omitempty" env:"API_DARK_MODE"`
Metrics Metrics `json:"metrics,omitempty" yaml:"metrics,omitempty" env:"API_METRICS"`
}
// Metrics configuration
type Metrics struct {
Enabled bool `json:"enabled,omitempty" yaml:"enabled,omitempty" env:"API_METRICS_ENABLED"`
ScrapeInterval time.Duration `json:"scrapeInterval,omitempty" yaml:"scrapeInterval,omitempty" env:"API_METRICS_SCRAPE_INTERVAL"`
QueryLogLimit int `json:"queryLogLimit,omitempty" yaml:"queryLogLimit,omitempty" env:"API_METRICS_QUERY_LOG_LIMIT"`
}
// 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
}
@@ -48,9 +70,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
@@ -58,6 +77,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 {
@@ -70,7 +94,8 @@ 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 {
@@ -89,16 +114,16 @@ func (cfg *Config) Init() error {
// AdGuardInstance AdguardHome config instance
// +k8s:deepcopy-gen=true
type AdGuardInstance struct {
URL string `json:"url" yaml:"url" mapstructure:"URL"`
WebURL string `json:"webURL" yaml:"webURL" mapstructure:"WEB_URL"`
APIPath string `json:"apiPath,omitempty" yaml:"apiPath,omitempty" mapstructure:"API_PATH"`
Username string `json:"username,omitempty" yaml:"username,omitempty" mapstructure:"USERNAME"`
Password string `json:"password,omitempty" yaml:"password,omitempty" mapstructure:"PASSWORD"`
Cookie string `json:"cookie,omitempty" yaml:"cookie,omitempty" mapstructure:"COOKIE"`
InsecureSkipVerify bool `json:"insecureSkipVerify" yaml:"insecureSkipVerify" mapstructure:"INSECURE_SKIP_VERIFY"`
AutoSetup bool `json:"autoSetup" yaml:"autoSetup" mapstructure:"AUTO_SETUP"`
InterfaceName string `json:"interfaceName,omitempty" yaml:"interfaceName,omitempty" mapstructure:"INTERFACE_NAME"`
DHCPServerEnabled *bool `json:"dhcpServerEnabled,omitempty" yaml:"dhcpServerEnabled,omitempty" mapstructure:"DHCP_SERVER_ENABLED"`
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:"-"`
@@ -139,7 +164,8 @@ 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

@@ -1,38 +1,99 @@
package types_test
package types
import (
"github.com/bakito/adguardhome-sync/pkg/types"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("AdGuardInstance", func() {
var inst types.AdGuardInstance
var _ = Describe("Types", func() {
Context("AdGuardInstance", func() {
var inst AdGuardInstance
BeforeEach(func() {
inst = types.AdGuardInstance{}
})
Context("Init", func() {
BeforeEach(func() {
inst.URL = "https://localhost:3000"
inst = AdGuardInstance{}
})
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"))
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"))
})
})
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()
})
Context("Config", func() {
Context("init", func() {
cfg := Config{
Replicas: []AdGuardInstance{
{URL: "https://localhost:3000"},
},
}
err := cfg.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"))
Ω(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())
})
})
})
})

1
testdata/.gitignore vendored
View File

@@ -1 +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}

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

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

View File

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

View File

@@ -0,0 +1,21 @@
{{- if eq .Values.mode "env" }}
apiVersion: v1
kind: ConfigMap
metadata:
name: sync-conf
namespace: {{ .Release.Namespace }}
data:
API_PORT: '9090'
API_METRICS_ENABLED: 'true'
API_METRICS_SCRAPE_INTERVAL: '30s'
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,25 @@
{{- 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: 9090
metrics:
enabled: true
scrapeInterval: 30s
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: 'debug'
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

@@ -3,3 +3,9 @@ replica:
- v0.107.40
- v0.107.43
- latest
mode: env
kubectl:
repository: bitnami/kubectl
tag: 1.27

8
testdata/querylog_config.json vendored Normal file
View File

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

View File

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

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