Compare commits

...

202 Commits

Author SHA1 Message Date
Marc Brugger
3139ad605f Refactor sync into separate action functions (#268)
* sync-actions

* dns rewrites and filters

* continue on filter error

* servides

* client settings

* dns

* dhcp

* remove deprecated env var

* fix client tests

* tests

* copy replica config

* map continue on error

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* Update .golangci.yml

---------

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* Update Dockerfile

---------

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

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

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

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

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

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

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

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

* skip-pkg-cache: true

* Update .golangci.yml

---------

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* add test

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-20 08:32:03 +01:00
bakito
116fdf5c11 update makefile 2023-02-07 22:56:29 +01:00
bakito
49f301589d fix dhcp clone function #149 2023-02-07 21:44:31 +01:00
bakito
6d08d42626 log post body #149 2023-02-07 21:27:17 +01:00
bakito
3c7243fcba go 1.20 docker base image 2023-02-07 19:49:27 +01:00
dependabot[bot]
f6bb8757a4 Bump github.com/onsi/ginkgo/v2 from 2.7.1 to 2.8.0 (#147)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.7.1 to 2.8.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.7.1...v2.8.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-06 07:51:13 +01:00
dependabot[bot]
2cd21c8331 Bump docker/build-push-action from 3 to 4 (#148)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 3 to 4.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v3...v4)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-06 07:51:01 +01:00
dependabot[bot]
edac7a3b4d Bump github.com/onsi/ginkgo/v2 from 2.7.0 to 2.7.1 (#145)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.7.0 to 2.7.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.7.0...v2.7.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-30 07:18:09 +01:00
dependabot[bot]
64b73ba7cf Bump github.com/onsi/gomega from 1.25.0 to 1.26.0 (#146)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.25.0 to 1.26.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.25.0...v1.26.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-30 07:11:55 +01:00
dependabot[bot]
e41e5dbaa9 Bump github.com/spf13/viper from 1.14.0 to 1.15.0 (#143)
Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.14.0 to 1.15.0.
- [Release notes](https://github.com/spf13/viper/releases)
- [Commits](https://github.com/spf13/viper/compare/v1.14.0...v1.15.0)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-22 21:31:24 +01:00
dependabot[bot]
00c77a77c9 Bump github.com/onsi/gomega from 1.24.2 to 1.25.0 (#142)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.24.2 to 1.25.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.24.2...v1.25.0)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-22 21:28:06 +01:00
dependabot[bot]
378aa54500 Bump github.com/onsi/ginkgo/v2 from 2.6.1 to 2.7.0 (#141)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.6.1 to 2.7.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.6.1...v2.7.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>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-16 07:26:22 +01:00
bakito
3bc7874412 add debug log hint to bug template 2023-01-13 18:22:27 +01:00
bakito
a4864b2b7a use toolbox to update Makefile 2022-12-28 17:45:08 +01:00
bakito
2b27ce88fe go 1.19 2022-12-28 17:36:15 +01:00
Marc Brugger
c32694ff5c Create FUNDING.yml - fixes #135 2022-12-27 22:40:47 +01:00
bakito
97ae38c3fa do not sync DHCP if disabled #137 2022-12-27 19:32:24 +01:00
bakito
9edec9cf04 allow different version patterns - fixes #139 2022-12-27 18:50:50 +01:00
bakito
dcaad44315 use composite kind with registry action 2022-12-26 11:15:39 +01:00
dependabot[bot]
ddc8e9f31d Bump github.com/gin-gonic/gin from 1.8.1 to 1.8.2 (#138)
Bumps [github.com/gin-gonic/gin](https://github.com/gin-gonic/gin) from 1.8.1 to 1.8.2.
- [Release notes](https://github.com/gin-gonic/gin/releases)
- [Changelog](https://github.com/gin-gonic/gin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/gin-gonic/gin/compare/v1.8.1...v1.8.2)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-26 10:23:14 +01:00
Marc Brugger
6b07040ad7 move e2e test in regular build (#136)
* move e2e test in regular build

* write to summary
2022-12-25 11:49:37 +01:00
Marc Brugger
ec3209bdb3 Update kind action 2022-12-23 23:13:26 +01:00
dependabot[bot]
883a270f56 Bump github.com/onsi/gomega from 1.24.1 to 1.24.2 (#131)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.24.1 to 1.24.2.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.24.1...v1.24.2)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-19 07:04:15 +01:00
dependabot[bot]
cbcc85dc93 Bump helm/kind-action from 1.4.0 to 1.5.0 (#133)
Bumps [helm/kind-action](https://github.com/helm/kind-action) from 1.4.0 to 1.5.0.
- [Release notes](https://github.com/helm/kind-action/releases)
- [Commits](https://github.com/helm/kind-action/compare/v1.4.0...v1.5.0)

---
updated-dependencies:
- dependency-name: helm/kind-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-19 07:02:45 +01:00
dependabot[bot]
aecd921c82 Bump goreleaser/goreleaser-action from 3 to 4 (#134)
Bumps [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action) from 3 to 4.
- [Release notes](https://github.com/goreleaser/goreleaser-action/releases)
- [Commits](https://github.com/goreleaser/goreleaser-action/compare/v3...v4)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-19 07:02:35 +01:00
dependabot[bot]
549fcde1e7 Bump github.com/onsi/ginkgo/v2 from 2.5.1 to 2.6.0 (#129)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.5.1 to 2.6.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.5.1...v2.6.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>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-12 08:09:22 +01:00
dependabot[bot]
25430ebb10 Bump go.uber.org/zap from 1.23.0 to 1.24.0 (#128)
Bumps [go.uber.org/zap](https://github.com/uber-go/zap) from 1.23.0 to 1.24.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.23.0...v1.24.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>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-05 08:25:27 +01:00
dependabot[bot]
e216c051d4 Bump github.com/onsi/ginkgo/v2 from 2.5.0 to 2.5.1 (#125)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.5.0 to 2.5.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.5.0...v2.5.1)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-21 07:12:14 +01:00
Marc Brugger
debeff618d Update README.md 2022-11-20 18:49:36 +01:00
bakito
30706e5a30 #124 don't upx windows binaries as they make trouble with virus scanners 2022-11-18 18:50:42 +01:00
dependabot[bot]
5957bd0fde Bump github.com/onsi/gomega from 1.24.0 to 1.24.1 (#123)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.24.0 to 1.24.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.24.0...v1.24.1)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-14 07:18:11 +01:00
bakito
425dfc5e50 force content type json #121 2022-11-07 18:37:55 +01:00
bakito
6436dd9998 log content type #121 2022-11-07 18:05:59 +01:00
dependabot[bot]
e75600c878 Bump github.com/spf13/viper from 1.13.0 to 1.14.0 (#119)
Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.13.0 to 1.14.0.
- [Release notes](https://github.com/spf13/viper/releases)
- [Commits](https://github.com/spf13/viper/compare/v1.13.0...v1.14.0)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-07 07:11:04 +01:00
dependabot[bot]
7545af2c15 Bump github.com/onsi/gomega from 1.23.0 to 1.24.0 (#120)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.23.0 to 1.24.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.23.0...v1.24.0)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-07 07:10:48 +01:00
dependabot[bot]
5c65877bb3 Bump github.com/spf13/cobra from 1.6.0 to 1.6.1 (#118)
Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.6.0 to 1.6.1.
- [Release notes](https://github.com/spf13/cobra/releases)
- [Commits](https://github.com/spf13/cobra/compare/v1.6.0...v1.6.1)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-31 07:11:59 +01:00
dependabot[bot]
09acb664c5 Bump github.com/onsi/gomega from 1.22.1 to 1.23.0 (#117)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.22.1 to 1.23.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.22.1...v1.23.0)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-31 07:11:45 +01:00
dependabot[bot]
77d2dd96e1 Bump github.com/onsi/ginkgo/v2 from 2.3.1 to 2.4.0 (#116)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.3.1 to 2.4.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.3.1...v2.4.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>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-24 06:37:32 +02:00
Techroy23
2443ca3b0a Update README.md (#114)
change username and password and private ip.
2022-10-18 00:33:51 +02:00
Techroy23
c3f78c7b63 Update README.md (#113) 2022-10-16 22:03:02 +02:00
Marc Brugger
db1a3b2d47 Lint action needs go 2022-10-16 08:39:20 +02:00
Marc Brugger
65fb377f8e Update go.yml 2022-10-15 22:09:35 +02:00
Marc Brugger
15035f5199 Use go version from go.mod 2022-10-15 22:08:13 +02:00
dependabot[bot]
337d39076c Bump github.com/onsi/gomega from 1.21.1 to 1.22.1 (#107)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.21.1 to 1.22.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.21.1...v1.22.1)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-13 09:10:51 +02:00
dependabot[bot]
3cd0463054 Bump helm/kind-action from 1.3.0 to 1.4.0 (#104)
Bumps [helm/kind-action](https://github.com/helm/kind-action) from 1.3.0 to 1.4.0.
- [Release notes](https://github.com/helm/kind-action/releases)
- [Commits](https://github.com/helm/kind-action/compare/v1.3.0...v1.4.0)

---
updated-dependencies:
- dependency-name: helm/kind-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-13 09:07:35 +02:00
dependabot[bot]
1556ce4830 Bump actions/checkout from 2 to 3 (#105)
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v3)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-13 09:07:25 +02:00
dependabot[bot]
7065f0797a Bump actions/setup-go from 2 to 3 (#106)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 2 to 3.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v2...v3)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-13 09:07:14 +02:00
dependabot[bot]
afe9894632 Bump github.com/spf13/cobra from 1.5.0 to 1.6.0 (#109)
Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.5.0 to 1.6.0.
- [Release notes](https://github.com/spf13/cobra/releases)
- [Commits](https://github.com/spf13/cobra/compare/v1.5.0...v1.6.0)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-13 09:06:56 +02:00
dependabot[bot]
35f95fd1fb Bump goreleaser/goreleaser-action from 2 to 3 (#110)
Bumps [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action) from 2 to 3.
- [Release notes](https://github.com/goreleaser/goreleaser-action/releases)
- [Commits](https://github.com/goreleaser/goreleaser-action/compare/v2...v3)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-13 09:06:38 +02:00
dependabot[bot]
a66e41b8f3 Bump github.com/onsi/ginkgo/v2 from 2.2.0 to 2.3.1 (#111)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.2.0 to 2.3.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.2.0...v2.3.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-13 09:06:26 +02:00
dependabot[bot]
29a8202ce7 Bump docker/build-push-action from 2 to 3 (#108)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 2 to 3.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v2...v3)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-13 09:00:14 +02:00
Marc Brugger
91cf1a1e6f Handle actions with dependabot 2022-10-13 08:53:48 +02:00
Marc Brugger
16ed9c28bf Update publish.yml 2022-10-12 23:50:08 +02:00
dependabot[bot]
578bc69498 Bump github.com/onsi/gomega from 1.20.2 to 1.21.1 (#103)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.20.2 to 1.21.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.20.2...v1.21.1)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-10 12:06:26 +02:00
bakito
f17f049f62 reuse e2e scripts 2022-10-07 07:32:20 +02:00
Marc Brugger
662b177acf Wait for all to finish 2022-10-06 22:08:57 +02:00
Marc Brugger
31d91adb42 start e2e tests with helm (#102)
* use helm for e2e test

* wait for pods

* update action
2022-10-06 20:58:11 +02:00
Marc Brugger
630d9c8eef Check for Succeeded 2022-10-05 19:42:48 +02:00
Marc Brugger
97d3b0f2a9 Codeql v2 2022-10-05 19:37:38 +02:00
Marc Brugger
0206e6173f Run e2e container tests in action (#101)
*
2022-10-05 18:15:07 +02:00
Marc Brugger
ff104f543d Fix API change and block inconsistent version v0.107.14 (#100)
* fix api content change from string to json #99

* block incompatible version #99

* fix tests #99

* add mote tests #99
2022-10-04 19:55:00 +02:00
dependabot[bot]
ec3b5d7956 Bump github.com/onsi/ginkgo/v2 from 2.1.6 to 2.2.0 (#98)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.1.6 to 2.2.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.1.6...v2.2.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>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-19 07:20:58 +02:00
bakito
389cf12c1f improved error feedback #97 2022-09-12 18:40:44 +02:00
dependabot[bot]
39e81fb74b Bump github.com/spf13/viper from 1.12.0 to 1.13.0 (#96)
Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.12.0 to 1.13.0.
- [Release notes](https://github.com/spf13/viper/releases)
- [Commits](https://github.com/spf13/viper/compare/v1.12.0...v1.13.0)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-12 17:53:20 +02:00
bakito
8818c584b8 do delete first before add - fixes #95 2022-09-07 22:10:11 +02:00
dependabot[bot]
f2891135f8 Bump github.com/onsi/gomega from 1.20.1 to 1.20.2 (#93)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.20.1 to 1.20.2.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.20.1...v1.20.2)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-05 18:03:45 +02:00
bakito
5a764f7fdf log next cron execution 2022-08-31 19:12:03 +02:00
dependabot[bot]
c129df4049 Bump github.com/onsi/gomega from 1.20.0 to 1.20.1 (#91)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.20.0 to 1.20.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.20.0...v1.20.1)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-08-29 12:57:45 +02:00
dependabot[bot]
b93cbda566 Bump go.uber.org/zap from 1.22.0 to 1.23.0 (#92)
Bumps [go.uber.org/zap](https://github.com/uber-go/zap) from 1.22.0 to 1.23.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.22.0...v1.23.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>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-08-29 12:55:11 +02:00
Marc Brugger
f55137852d Delete custom.md 2022-08-22 08:44:24 +02:00
dependabot[bot]
e9aa9d7420 Bump go.uber.org/zap from 1.21.0 to 1.22.0 (#89)
Bumps [go.uber.org/zap](https://github.com/uber-go/zap) from 1.21.0 to 1.22.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.21.0...v1.22.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>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-08-15 07:46:04 +02:00
bakito
113070b14e replace deprecated ioutils 2022-08-12 09:31:24 +02:00
Marc Brugger
e66f4e5a83 Update README.md 2022-08-09 21:23:40 +02:00
dependabot[bot]
3b33623a46 Bump github.com/onsi/gomega from 1.19.0 to 1.20.0 (#86)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.19.0 to 1.20.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.19.0...v1.20.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-25 07:45:59 +02:00
bakito
8d85b58ce4 add version option to cli #85 2022-07-24 18:23:17 +02:00
bakito
dddd5b2e43 evaluate equality on clone - do not sort original - fixes #84 2022-07-23 11:05:11 +02:00
bakito
ff8e2d60d0 set header read timeout 2022-07-23 10:58:33 +02:00
Remco Schrijver
8c7bd73e8e Change docker-compose example (#83) 2022-07-07 22:20:19 +02:00
dependabot[bot]
0dc24effa9 Bump github.com/spf13/cobra from 1.4.0 to 1.5.0 (#81)
Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.4.0 to 1.5.0.
- [Release notes](https://github.com/spf13/cobra/releases)
- [Commits](https://github.com/spf13/cobra/compare/v1.4.0...v1.5.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-27 06:44:35 +02:00
bakito
809d7b2ad8 log version of instance if status could be fetched 2022-06-20 18:46:23 +02:00
dependabot[bot]
1119c96d5f Bump github.com/gin-gonic/gin from 1.8.0 to 1.8.1 (#79)
Bumps [github.com/gin-gonic/gin](https://github.com/gin-gonic/gin) from 1.8.0 to 1.8.1.
- [Release notes](https://github.com/gin-gonic/gin/releases)
- [Changelog](https://github.com/gin-gonic/gin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/gin-gonic/gin/compare/v1.8.0...v1.8.1)

---
updated-dependencies:
- dependency-name: github.com/gin-gonic/gin
  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>
2022-06-13 07:27:00 +02:00
dependabot[bot]
e71add0c82 Bump github.com/gin-gonic/gin from 1.7.7 to 1.8.0 (#75)
Bumps [github.com/gin-gonic/gin](https://github.com/gin-gonic/gin) from 1.7.7 to 1.8.0.
- [Release notes](https://github.com/gin-gonic/gin/releases)
- [Changelog](https://github.com/gin-gonic/gin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/gin-gonic/gin/compare/v1.7.7...v1.8.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-06 09:43:34 +02:00
dependabot[bot]
a4095d6833 Bump github.com/spf13/viper from 1.11.0 to 1.12.0 (#74)
Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.11.0 to 1.12.0.
- [Release notes](https://github.com/spf13/viper/releases)
- [Commits](https://github.com/spf13/viper/compare/v1.11.0...v1.12.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-30 13:00:18 +02:00
bakito
5038326e36 alow defining replica dhcp interface name - fixes #66 2022-05-05 21:08:52 +02:00
Sander
c8800b9589 Add run command for docker-compose example (#71) 2022-05-05 20:25:20 +02:00
dependabot[bot]
339ea56d18 Bump github.com/onsi/ginkgo/v2 from 2.1.3 to 2.1.4 (#70)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.1.3 to 2.1.4.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.1.3...v2.1.4)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-02 07:12:31 +02:00
bakito
76f6097847 go 1.18 2022-04-19 07:51:33 +02:00
bakito
71ff6dc3c5 use matrix to build different images 2022-03-30 22:15:28 +02:00
dependabot[bot]
25966fb186 Bump github.com/onsi/gomega from 1.18.1 to 1.19.0 (#67)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.18.1 to 1.19.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.18.1...v1.19.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-28 08:07:47 +02:00
bakito
488d2acb01 build alpine image - fixes #65 2022-03-14 20:33:15 +01:00
bakito
467c7ddeb7 fix dependencies 2022-03-14 06:58:11 +01:00
bakito
bbb885e155 #63 add cache_optimistic flag 2022-03-11 17:06:40 +01:00
dependabot[bot]
7a946b2096 Bump github.com/onsi/ginkgo/v2 from 2.1.2 to 2.1.3 (#62)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.1.2 to 2.1.3.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.1.2...v2.1.3)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-21 18:35:11 +01:00
bakito
08f8a9970e extract to constant - fixes #59 2022-02-14 18:40:17 +01:00
bakito
ec458fd04a add makefile targets 2022-02-14 18:35:56 +01:00
dependabot[bot]
2703cad2be Bump github.com/onsi/ginkgo/v2 from 2.1.1 to 2.1.2 (#61)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.1.1 to 2.1.2.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.1.1...v2.1.2)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-14 18:25:27 +01:00
dependabot[bot]
79bea82c78 Bump go.uber.org/zap from 1.20.0 to 1.21.0 (#60)
Bumps [go.uber.org/zap](https://github.com/uber-go/zap) from 1.20.0 to 1.21.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.20.0...v1.21.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>
2022-02-14 18:25:17 +01:00
bakito
93e735306e add option to change redirect policy #59 2022-02-03 19:36:30 +01:00
Mike Hennessy
bf940aae0f fix: update invalid logs when DNS feature disabled (#58)
Ensure that when a DNS feature is disabled it is not logged as a DHCP feature.
2022-01-31 07:50:12 +01:00
bakito
6bb795d622 dependency updates 2022-01-31 07:49:46 +01:00
bakito
afde0d7f3a support ui dark mode 2022-01-30 19:03:09 +01:00
bakito
d6d810fd42 use ghcr image in readme 2022-01-24 20:23:04 +01:00
79 changed files with 14916 additions and 2790 deletions

3
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,3 @@
# These are supported funding model platforms
github: [bakito]

View File

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

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

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

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

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

View File

@@ -1,10 +0,0 @@
---
name: Custom issue template
about: Describe this issue template's purpose here.
title: ''
labels: ''
assignees: ''
---

View File

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

View File

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

View File

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

View File

@@ -9,3 +9,7 @@ updates:
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"

View File

@@ -38,11 +38,11 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -53,7 +53,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
uses: github/codeql-action/autobuild@v3
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -67,4 +67,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@v3

35
.github/workflows/e2e.yaml vendored Normal file
View File

@@ -0,0 +1,35 @@
name: e2e tests
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
e2e:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install kind with registry
uses: bakito/kind-with-registry-action@main
- name: Build image
run: ./testdata/e2e/bin/build-image.sh
- name: Install Helm Chart
run: ./testdata/e2e/bin/install-chart.sh
- name: Wait for sync to finish
run: ./testdata/e2e/bin/wait-for-sync.sh
- name: Show origin Logs
run: ./testdata/e2e/bin/show-origin-logs.sh
- name: Show Replica Logs
run: ./testdata/e2e/bin/show-replica-logs.sh
- name: Show Sync Logs
run: ./testdata/e2e/bin/show-sync-logs.sh
- name: Read latest replica config
run: ./testdata/e2e/bin/read-latest-replica-config.sh

View File

@@ -11,24 +11,30 @@ jobs:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: golangci-lint
uses: golangci/golangci-lint-action@v2
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
version: latest
go-version-file: "go.mod"
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
with:
skip-pkg-cache: true
test:
name: test
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.x
uses: actions/setup-go@v2
with:
go-version: ^1.17
- name: Check out code into the Go module directory
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: "go.mod"
- name: Test
run: make test-ci
@@ -43,16 +49,16 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.x
uses: actions/setup-go@v2
with:
go-version: ^1.17
- name: Check out code into the Go module directory
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: "go.mod"
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
uses: goreleaser/goreleaser-action@v5
with:
version: latest
args: --skip-publish --snapshot --rm-dist

View File

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

8
.gitignore vendored
View File

@@ -1,6 +1,12 @@
.vscode
.idea
coverage.out
.run
coverage.out*
dist
adguardhome-sync
main
.adguardhome-sync.yaml
tmp
bin
config.yaml
*.log

View File

@@ -1,12 +1,10 @@
run:
timeout: 1m
timeout: 5m
linters:
enable:
- asciicheck
- bodyclose
- deadcode
- depguard
- dogsled
- durationcheck
- errcheck
@@ -25,15 +23,10 @@ linters:
- misspell
- nakedret
- nolintlint
- revive
- staticcheck
- structcheck
- typecheck
- unconvert
- unparam
- unused
- varcheck
- wastedassign
linters-settings:
gosec:
# Exclude generated files
@@ -41,3 +34,4 @@ linters-settings:
gofmt:
# simplify code: gofmt with `-s` option, true by default
simplify: true

View File

@@ -28,11 +28,9 @@ builds:
- goos: windows
goarch: arm64
hooks:
post: upx {{ .Path }}
archives:
- replacements:
386: i386
amd64: x86_64
post:
# don't upx windows binaries as they make trouble with virus scanners
- bash -c 'if [[ "{{ .Path }}" != *.exe ]]; then upx {{ .Path }}; fi'
checksum:
name_template: 'checksums.txt'
snapshot:
@@ -44,4 +42,4 @@ changelog:
- '^docs:'
- '^test:'
release:
prerelease: auto
prerelease: auto

3
.oapi-codegen.yaml Normal file
View File

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

View File

@@ -1,14 +1,15 @@
FROM golang:1.17 as builder
FROM golang:1.21-bullseye as builder
WORKDIR /go/src/app
RUN apt-get update && apt-get install -y upx
RUN apt-get update && \
apt-get install -y upx ca-certificates tzdata && \
apt-get upgrade -y # upgrade to get latest ca-certs
ARG VERSION=main
ARG BUILD="N/A"
ENV GOPROXY=https://goproxy.io \
GO111MODULE=on \
ENV GO111MODULE=on \
CGO_ENABLED=0 \
GOOS=linux
@@ -25,6 +26,7 @@ LABEL maintainer="bakito <github@bakito.ch>"
EXPOSE 8080
ENTRYPOINT ["/opt/go/adguardhome-sync"]
CMD ["run", "--config", "/config/adguardhome-sync.yaml"]
COPY --from=builder /go/src/app/adguardhome-sync /opt/go/adguardhome-sync
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=builder /usr/share/zoneinfo/ /usr/share/zoneinfo/
COPY --from=builder /go/src/app/adguardhome-sync /opt/go/adguardhome-sync
USER 1001

142
Makefile
View File

@@ -1,36 +1,140 @@
# Run go fmt against code
fmt:
golangci-lint run --fix
# Run go lint against code
lint: golangci-lint
$(GOLANGCI_LINT) run --fix
# Run go mod tidy
tidy:
go mod tidy
generate: deepcopy-gen
@mkdir -p ./tmp
@touch ./tmp/deepcopy-gen-boilerplate.go.txt
$(DEEPCOPY_GEN) -h ./tmp/deepcopy-gen-boilerplate.go.txt -i ./pkg/types
# Run tests
test: test-ci fmt
test: generate lint test-ci
# Run ci tests
test-ci: mocks tidy
go test ./... -coverprofile=coverage.out
go test ./... -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/client/mock.go github.com/bakito/adguardhome-sync/pkg/client Client
release: semver
@version=$$(semver); \
release: semver goreleaser
@version=$$($(LOCALBIN)/semver); \
git tag -s $$version -m"Release $$version"
goreleaser --rm-dist
$(GORELEASER) --clean
test-release:
goreleaser --skip-publish --snapshot --rm-dist
test-release: goreleaser
$(GORELEASER) --skip-publish --snapshot --clean
semver:
ifeq (, $(shell which semver))
$(shell go install github.com/bakito/semver@latest)
endif
## toolbox - start
## Current working directory
LOCALDIR ?= $(shell which cygpath > /dev/null 2>&1 && cygpath -m $$(pwd) || pwd)
## Location to install dependencies to
LOCALBIN ?= $(LOCALDIR)/bin
$(LOCALBIN):
mkdir -p $(LOCALBIN)
mockgen:
ifeq (, $(shell which mockgen))
$(shell go install github.com/golang/mock/mockgen@v1.5)
endif
## Tool Binaries
SEMVER ?= $(LOCALBIN)/semver
OAPI_CODEGEN ?= $(LOCALBIN)/oapi-codegen
MOCKGEN ?= $(LOCALBIN)/mockgen
GOLANGCI_LINT ?= $(LOCALBIN)/golangci-lint
GORELEASER ?= $(LOCALBIN)/goreleaser
DEEPCOPY_GEN ?= $(LOCALBIN)/deepcopy-gen
## Tool Versions
SEMVER_VERSION ?= v1.1.3
OAPI_CODEGEN_VERSION ?= v2.0.0
MOCKGEN_VERSION ?= v1.6.0
GOLANGCI_LINT_VERSION ?= v1.55.2
GORELEASER_VERSION ?= v1.23.0
DEEPCOPY_GEN_VERSION ?= v0.29.0
## Tool Installer
.PHONY: semver
semver: $(SEMVER) ## Download semver locally if necessary.
$(SEMVER): $(LOCALBIN)
test -s $(LOCALBIN)/semver || GOBIN=$(LOCALBIN) go install github.com/bakito/semver@$(SEMVER_VERSION)
.PHONY: oapi-codegen
oapi-codegen: $(OAPI_CODEGEN) ## Download oapi-codegen locally if necessary.
$(OAPI_CODEGEN): $(LOCALBIN)
test -s $(LOCALBIN)/oapi-codegen || GOBIN=$(LOCALBIN) go install github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen@$(OAPI_CODEGEN_VERSION)
.PHONY: mockgen
mockgen: $(MOCKGEN) ## Download mockgen locally if necessary.
$(MOCKGEN): $(LOCALBIN)
test -s $(LOCALBIN)/mockgen || GOBIN=$(LOCALBIN) go install github.com/golang/mock/mockgen@$(MOCKGEN_VERSION)
.PHONY: golangci-lint
golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary.
$(GOLANGCI_LINT): $(LOCALBIN)
test -s $(LOCALBIN)/golangci-lint || GOBIN=$(LOCALBIN) go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION)
.PHONY: goreleaser
goreleaser: $(GORELEASER) ## Download goreleaser locally if necessary.
$(GORELEASER): $(LOCALBIN)
test -s $(LOCALBIN)/goreleaser || GOBIN=$(LOCALBIN) go install github.com/goreleaser/goreleaser@$(GORELEASER_VERSION)
.PHONY: deepcopy-gen
deepcopy-gen: $(DEEPCOPY_GEN) ## Download deepcopy-gen locally if necessary.
$(DEEPCOPY_GEN): $(LOCALBIN)
test -s $(LOCALBIN)/deepcopy-gen || GOBIN=$(LOCALBIN) go install k8s.io/code-generator/cmd/deepcopy-gen@$(DEEPCOPY_GEN_VERSION)
## Update Tools
.PHONY: update-toolbox-tools
update-toolbox-tools:
@rm -f \
$(LOCALBIN)/semver \
$(LOCALBIN)/oapi-codegen \
$(LOCALBIN)/mockgen \
$(LOCALBIN)/golangci-lint \
$(LOCALBIN)/goreleaser \
$(LOCALBIN)/deepcopy-gen
toolbox makefile -f $(LOCALDIR)/Makefile \
github.com/bakito/semver \
github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen \
github.com/golang/mock/mockgen \
github.com/golangci/golangci-lint/cmd/golangci-lint \
github.com/goreleaser/goreleaser \
k8s.io/code-generator/cmd/deepcopy-gen@github.com/kubernetes/code-generator
## toolbox - end
start-replica:
docker run --pull always --name adguardhome-replica -p 9091:3000 --rm adguard/adguardhome:latest
# docker run --pull always --name adguardhome-replica -p 9090:80 -p 9091:3000 --rm adguard/adguardhome:v0.107.13
copy-replica-config:
docker cp adguardhome-replica:/opt/adguardhome/conf/AdGuardHome.yaml tmp/AdGuardHome.yaml
start-replica2:
docker run --pull always --name adguardhome-replica2 -p 9093:3000 --rm adguard/adguardhome:latest
# docker run --pull always --name adguardhome-replica -p 9090:80 -p 9091:3000 --rm adguard/adguardhome:v0.107.13
check_defined = \
$(strip $(foreach 1,$1, \
$(call __check_defined,$1,$(strip $(value 2)))))
__check_defined = \
$(if $(value $1),, \
$(error Undefined $1$(if $2, ($2))))
build-image:
$(call check_defined, AGH_SYNC_VERSION)
docker build --build-arg VERSION=${AGH_SYNC_VERSION} --build-arg BUILD=$(shell date -u +'%Y-%m-%dT%H:%M:%S.%3NZ') --name adgardhome-replica -t ghcr.io/bakito/adguardhome-sync:${AGH_SYNC_VERSION} .
kind-create:
kind delete cluster
kind create cluster
kind-test:
@./testdata/e2e/bin/install-chart.sh
model: oapi-codegen
@mkdir -p tmp
go run openapi/main.go v0.107.43
$(OAPI_CODEGEN) -package model -generate types,client -config .oapi-codegen.yaml tmp/schema.yaml > pkg/client/model/model_generated.go
model-diff:
go run openapi/main.go v0.107.43
go run openapi/main.go
diff tmp/schema.yaml tmp/schema-master.yaml

137
README.md
View File

@@ -1,10 +1,14 @@
[![Go](https://github.com/bakito/adguardhome-sync/actions/workflows/go.yml/badge.svg)](https://github.com/bakito/adguardhome-sync/actions/workflows/go.yml)
[![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)](https://coveralls.io/github/bakito/adguardhome-sync?branch=main)
[![Coverage Status](https://coveralls.io/repos/github/bakito/adguardhome-sync/badge.svg?branch=main&service=github)](https://coveralls.io/github/bakito/adguardhome-sync?branch=main)
# AdGuardHome sync
Synchronize [AdGuardHome](https://github.com/AdguardTeam/AdGuardHome) config to a replica instance.
Synchronize [AdGuardHome](https://github.com/AdguardTeam/AdGuardHome) config to replica instances.
## FAQ
Please check the wiki for [FAQ](https://github.com/bakito/adguardhome-sync/wiki/FAQ).
## Current sync features
@@ -37,16 +41,60 @@ go install github.com/bakito/adguardhome-sync@latest
Both the origin instance must be initially setup via the AdguardHome installation wizard.
## Run
## Username / Password vs. Cookie
Some instances of AdGuard Home do not support basic authentication. For instance, many routers with built-in Adguard
Home support do not. If this is the case, a valid cookie may be provided instead. If the router protects the AdGuard
instance behind its own authentication, the cookie from an authenticated request may allow the sync to succeed.
- This has been tested successfully against GL.Inet routers with AdGuard Home.
- Note: due to the short validity of cookies, this approach is likely only suitable for one-time syncs
## Run Linux/Mac
```bash
export LOG_LEVEL=info
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 REPLICA_COOKIE=Replica-Cookie-Name=CCCOOOKKKIIIEEE
# run once
adguardhome-sync run
# run as daemon
adguardhome-sync run --cron "*/10 * * * *"
```
## Run Windows
```bash
@ECHO OFF
@TITLE AdGuardHome-Sync
REM set LOG_LEVEL=debug
set LOG_LEVEL=info
REM set LOG_LEVEL=warn
REM set LOG_LEVEL=error
set ORIGIN_URL=http://192.168.1.2:3000
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 FEATURES_DHCP=false
set FEATURES_DHCP_SERVERCONFIG=false
set FEATURES_DHCP_STATICLEASES=false
# run once
adguardhome-sync run
@@ -63,7 +111,7 @@ docker run -d \
-p 8080:8080 \
-v /path/to/appdata/config/adguardhome-sync.yaml:/config/adguardhome-sync.yaml \
--restart unless-stopped \
quay.io/bakito/adguardhome-sync:latest
ghcr.io/bakito/adguardhome-sync:latest
```
## docker compose
@@ -75,8 +123,9 @@ docker run -d \
version: "2.1"
services:
adguardhome-sync:
image: quay.io/bakito/adguardhome-sync
image: ghcr.io/bakito/adguardhome-sync
container_name: adguardhome-sync
command: run --config /config/adguardhome-sync.yaml
volumes:
- /path/to/appdata/config/adguardhome-sync.yaml:/config/adguardhome-sync.yaml
ports:
@@ -91,35 +140,46 @@ services:
version: "2.1"
services:
adguardhome-sync:
image: quay.io/bakito/adguardhome-sync
image: ghcr.io/bakito/adguardhome-sync
container_name: adguardhome-sync
command: run
environment:
- ORIGIN_URL=https://192.168.1.2:3000
- 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_USERNAME=username
- REPLICA1_PASSWORD=password
- REPLICA1_APIPATH=/some/path/control
# - REPLICA1_AUTOSETUP=true # if true, AdGuardHome is automatically initialized.
- CRON=*/10 * * * * # run every 10 minutes
- RUNONSTART=true
LOG_LEVEL: "info"
ORIGIN_URL: "https://192.168.1.2:3000"
# ORIGIN_WEBURL: "https://some-other.url" # used in the web interface (default: <origin-url>
ORIGIN_USERNAME: "username"
ORIGIN_PASSWORD: "password"
REPLICA_URL: "http://192.168.1.3"
REPLICA_USERNAME: "username"
REPLICA_PASSWORD: "password"
REPLICA1_URL: "http://192.168.1.4"
REPLICA1_USERNAME: "username"
REPLICA1_PASSWORD: "password"
REPLICA1_APIPATH: "/some/path/control"
# REPLICA1_WEBURL: "https://some-other.url" # used in the web interface (default: <replica-url>
# REPLICA1_AUTOSETUP: true # if true, AdGuardHome is automatically initialized.
# REPLICA1_INTERFACENAME: 'ens18' # use custom dhcp interface name
# REPLICA1_DHCPSERVERENABLED: true/false (optional) enables/disables the dhcp server on the replica
CRON: "*/10 * * * *" # run every 10 minutes
RUNONSTART: true
# CONTINUEONERROR: false # If enabled, the synchronisation task will not fail on single errors, but will log the errors and continue
# Configure the sync API server, disabled if api port is 0
API_PORT: 8080
# Configure sync features; by default all features are enabled.
# - FEATURES_GENERALSETTINGS=true
# - FEATURES_QUERYLOGCONFIG=true
# - FEATURES_STATSCONFIG=true
# - FEATURES_CLIENTSETTINGS=true
# - FEATURES_SERVICES=true
# - FEATURES_FILTERS=true
# - FEATURES_DHCP_SERVERCONFIG=true
# - FEATURES_DHCP_STATICLEASES=true
# - FEATURES_DNS_SERVERCONFIG=true
# - FEATURES_DNS_ACCESSLISTS=true
# - FEATURES_DNS_REWRITES=true
# FEATURES_GENERALSETTINGS: true
# FEATURES_QUERYLOGCONFIG: true
# FEATURES_STATSCONFIG: true
# FEATURES_CLIENTSETTINGS: true
# FEATURES_SERVICES: true
# FEATURES_FILTERS: true
# FEATURES_DHCP_SERVERCONFIG: true
# FEATURES_DHCP_STATICLEASES: true
# FEATURES_DNS_SERVERCONFIG: true
# FEATURES_DNS_ACCESSLISTS: true
# FEATURES_DNS_REWRITES: true
ports:
- 8080:8080
restart: unless-stopped
@@ -136,6 +196,9 @@ cron: "*/10 * * * *"
# runs the synchronisation on startup
runOnStart: true
# If enabled, the synchronisation task will not fail on single errors, but will log the errors and continue
continueOnError: false
origin:
# url of the origin instance
url: https://192.168.1.2:3000
@@ -143,6 +206,7 @@ origin:
# insecureSkipVerify: true # disable tls check
username: username
password: password
# cookie: Origin-Cookie-Name=CCCOOOKKKIIIEEE
# replica instance (optional, if only one)
replica:
@@ -150,6 +214,8 @@ replica:
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:
@@ -157,18 +223,23 @@ replicas:
- url: http://192.168.1.3
username: username
password: password
# cookie: Replica1-Cookie-Name=CCCOOOKKKIIIEEE
- url: http://192.168.1.4
username: username
password: password
# autoSetup: true # if true, AdGuardHome is automatically initialized.
# cookie: Replica2-Cookie-Name=CCCOOOKKKIIIEEE
# autoSetup: true # if true, AdGuardHome is automatically initialized.
# webURL: "https://some-other.url" # used in the web interface (default: <replica-url>
# Configure the sync API server, disabled if api port is 0
api:
# Port, default 8080
port: 8080
# if username and password are defined, basic auth is applied to the sync API
# if username and password are defined, basic auth is applied to the sync API
username: username
password: password
# enable api dark mode
darkMode: true
# Configure sync features; by default all features are enabled.
features:
@@ -196,4 +267,4 @@ The following log levels are supported (default: info)
- debug
- info
- warn
- error
- error

View File

@@ -8,18 +8,23 @@ import (
"github.com/bakito/adguardhome-sync/pkg/log"
"github.com/bakito/adguardhome-sync/pkg/types"
"github.com/bakito/adguardhome-sync/version"
"github.com/mitchellh/go-homedir"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
const (
configCron = "cron"
configRunOnStart = "runOnStart"
configCron = "cron"
configRunOnStart = "runOnStart"
configPrintConfigOnly = "PRINT_CONFIG_ONLY"
configContinueOnError = "CONTINUE_ON_ERROR"
configLogLevel = "LOG_LEVEL"
configAPIPort = "api.port"
configAPIUsername = "api.username"
configAPIPassword = "api.password"
configAPIDarkMode = "api.darkMode"
configFeatureDHCPServerConfig = "features.dhcp.serverConfig"
configFeatureDHCPStaticLeases = "features.dhcp.staticLeases"
@@ -34,23 +39,32 @@ const (
configFeatureFilters = "features.filters"
configOriginURL = "origin.url"
configOriginWebURL = "origin.webURL"
configOriginAPIPath = "origin.apiPath"
configOriginUsername = "origin.username"
configOriginPassword = "origin.password"
configOriginCookie = "origin.cookie"
configOriginInsecureSkipVerify = "origin.insecureSkipVerify"
configReplicaURL = "replica.url"
configReplicaWebURL = "replica.webURL"
configReplicaAPIPath = "replica.apiPath"
configReplicaUsername = "replica.username"
configReplicaPassword = "replica.password"
configReplicaCookie = "replica.cookie"
configReplicaInsecureSkipVerify = "replica.insecureSkipVerify"
configReplicaAutoSetup = "replica.autoSetup"
configReplicaInterfaceName = "replica.interfaceName"
envReplicasUsernameFormat = "REPLICA%s_USERNAME" // #nosec G101
envReplicasPasswordFormat = "REPLICA%s_PASSWORD" // #nosec G101
envReplicasCookieFormat = "REPLICA%s_COOKIE" // #nosec G101
envReplicasAPIPathFormat = "REPLICA%s_APIPATH"
envReplicasInsecureSkipVerifyFormat = "REPLICA%s_INSECURESKIPVERIFY"
envReplicasAutoSetup = "REPLICA%s_AUTOSETUP"
envReplicasInterfaceName = "REPLICA%s_INTERFACENAME"
envDHCPServerEnabled = "REPLICA%s_DHCPSERVERENABLED"
envWebURL = "REPLICA%s_WEBURL"
)
var (
@@ -61,8 +75,9 @@ var (
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "adguardhome-sync",
Short: "Synchronize config from one AdGuardHome instance to another",
Use: "adguardhome-sync",
Short: "Synchronize config from one AdGuardHome instance to another",
Version: version.Version,
}
// Execute adds all child commands to the root command and sets flags appropriately.
@@ -122,10 +137,16 @@ func getConfig() (*types.Config, error) {
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()...)
}
return cfg, nil
}
@@ -137,11 +158,25 @@ func collectEnvReplicas() []types.AdGuardInstance {
sm := envReplicasURLPattern.FindStringSubmatch(v)
re := types.AdGuardInstance{
URL: sm[2],
WebURL: os.Getenv(fmt.Sprintf(envWebURL, sm[1])),
Username: os.Getenv(fmt.Sprintf(envReplicasUsernameFormat, sm[1])),
Password: os.Getenv(fmt.Sprintf(envReplicasPasswordFormat, sm[1])),
Cookie: os.Getenv(fmt.Sprintf(envReplicasCookieFormat, sm[1])),
APIPath: os.Getenv(fmt.Sprintf(envReplicasAPIPathFormat, sm[1])),
InsecureSkipVerify: strings.EqualFold(os.Getenv(fmt.Sprintf(envReplicasInsecureSkipVerifyFormat, sm[1])), "true"),
AutoSetup: strings.EqualFold(os.Getenv(fmt.Sprintf(envReplicasAutoSetup, sm[1])), "true"),
InterfaceName: os.Getenv(fmt.Sprintf(envReplicasInterfaceName, sm[1])),
}
if dhcpEnabled, ok := os.LookupEnv(fmt.Sprintf(envDHCPServerEnabled, sm[1])); ok {
if strings.EqualFold(dhcpEnabled, "true") {
re.DHCPServerEnabled = boolPtr(true)
} else if strings.EqualFold(dhcpEnabled, "false") {
re.DHCPServerEnabled = boolPtr(false)
}
}
if re.APIPath == "" {
re.APIPath = "/control"
}
replicas = append(replicas, re)
}
@@ -149,3 +184,7 @@ func collectEnvReplicas() []types.AdGuardInstance {
return replicas
}
func boolPtr(b bool) *bool {
return &b
}

View File

@@ -1,6 +1,7 @@
package cmd
import (
"fmt"
"os"
"github.com/bakito/adguardhome-sync/pkg/types"
@@ -20,6 +21,8 @@ var envVars = []string{
"FEATURES_DNS_SERVERCONFIG",
"FEATURES_DNS_ACCESSLISTS",
"FEATURES_DNS_REWRITES",
"REPLICA1_INTERFACENAME",
"REPLICA1_DHCPSERVERENABLED",
}
var _ = Describe("Run", func() {
@@ -29,6 +32,11 @@ var _ = Describe("Run", func() {
}
initConfig()
})
AfterEach(func() {
for _, envVar := range envVars {
Ω(os.Unsetenv(envVar)).ShouldNot(HaveOccurred())
}
})
Context("getConfig", func() {
It("features should be true by default", func() {
cfg, err := getConfig()
@@ -43,6 +51,33 @@ var _ = Describe("Run", func() {
Ω(err).ShouldNot(HaveOccurred())
verifyFeatures(cfg, false)
})
Context("interface name", func() {
It("should set interface name of replica 1", func() {
Ω(os.Setenv("REPLICA1_URL", "https://foo.bar")).ShouldNot(HaveOccurred())
Ω(os.Setenv(fmt.Sprintf(envReplicasInterfaceName, "1"), "eth0")).ShouldNot(HaveOccurred())
cfg, err := getConfig()
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].InterfaceName).Should(Equal("eth0"))
})
})
Context("dhcp server", func() {
It("should enable the dhcp server of replica 1", func() {
Ω(os.Setenv("REPLICA1_URL", "https://foo.bar")).ShouldNot(HaveOccurred())
Ω(os.Setenv(fmt.Sprintf(envDHCPServerEnabled, "1"), "true")).ShouldNot(HaveOccurred())
cfg, err := getConfig()
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].DHCPServerEnabled).ShouldNot(BeNil())
Ω(*cfg.Replicas[0].DHCPServerEnabled).Should(BeTrue())
})
It("should disable the dhcp server of replica 1", func() {
Ω(os.Setenv("REPLICA1_URL", "https://foo.bar")).ShouldNot(HaveOccurred())
Ω(os.Setenv(fmt.Sprintf(envDHCPServerEnabled, "1"), "false")).ShouldNot(HaveOccurred())
cfg, err := getConfig()
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].DHCPServerEnabled).ShouldNot(BeNil())
Ω(*cfg.Replicas[0].DHCPServerEnabled).Should(BeFalse())
})
})
})
})

View File

@@ -5,6 +5,7 @@ import (
"github.com/bakito/adguardhome-sync/pkg/sync"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"gopkg.in/yaml.v3"
)
// runCmd represents the run command
@@ -20,6 +21,22 @@ var doCmd = &cobra.Command{
return err
}
if err := cfg.Init(); err != nil {
logger.Error(err)
return err
}
if cfg.PrintConfigOnly {
config, err := yaml.Marshal(cfg)
if err != nil {
logger.Error(err)
return err
}
logger.Infof("Printing adguardhome-sync config (THE APPLICATION WILL NOT START IN THIS MODE): \n%s",
string(config))
return nil
}
return sync.Sync(cfg)
},
}
@@ -30,12 +47,24 @@ func init() {
_ = 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. "+
"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 "+
"will not fail on single errors, but will log the errors and continue.")
_ = viper.BindPFlag(configContinueOnError, doCmd.PersistentFlags().Lookup("continueOnError"))
doCmd.PersistentFlags().String("logLevel", "info", "The log level to set. One of (debug, info, warn and error)")
_ = viper.BindPFlag(configLogLevel, doCmd.PersistentFlags().Lookup("logLevel"))
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().Bool("feature-dhcp-server-config", true, "Enable DHCP server config feature")
_ = viper.BindPFlag(configFeatureDHCPServerConfig, doCmd.PersistentFlags().Lookup("feature-dhcp-server-config"))
@@ -64,25 +93,35 @@ func init() {
doCmd.PersistentFlags().String("origin-url", "", "Origin instance url")
_ = viper.BindPFlag(configOriginURL, doCmd.PersistentFlags().Lookup("origin-url"))
doCmd.PersistentFlags().String("origin-weburl", "", "Origin instance web url used in the web interface (default: <origin-url>)")
_ = viper.BindPFlag(configOriginWebURL, doCmd.PersistentFlags().Lookup("origin-weburl"))
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-insecure-skip-verify", "", "Enable Origin instance InsecureSkipVerify")
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().String("replica-url", "", "Replica instance url")
_ = viper.BindPFlag(configReplicaURL, doCmd.PersistentFlags().Lookup("replica-url"))
doCmd.PersistentFlags().String("replica-weburl", "", "Replica instance web url used in the web interface (default: <replica-url>)")
_ = viper.BindPFlag(configOriginWebURL, doCmd.PersistentFlags().Lookup("replica-weburl"))
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"))
}

90
go.mod
View File

@@ -1,38 +1,80 @@
module github.com/bakito/adguardhome-sync
go 1.17
go 1.21
require (
github.com/go-resty/resty/v2 v2.7.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.3.0
github.com/google/uuid v1.5.0
github.com/jinzhu/copier v0.4.0
github.com/mitchellh/go-homedir v1.1.0
github.com/onsi/ginkgo/v2 v2.1.0
github.com/onsi/gomega v1.18.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/robfig/cron/v3 v3.0.1
github.com/spf13/cobra v1.3.0
github.com/spf13/viper v1.10.1
go.uber.org/zap v1.20.0
golang.org/x/mod v0.5.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
gopkg.in/yaml.v3 v3.0.1
k8s.io/apimachinery v0.29.0
)
require (
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/bytedance/sonic v1.10.2 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
github.com/chenzhuoyu/iasm v0.9.1 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-logr/logr v1.3.0 // 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/goccy/go-json v0.10.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/go-cmp v0.6.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/inconshreveable/mousetrap v1.0.0 // indirect
github.com/magiconair/properties v1.8.5 // indirect
github.com/mitchellh/mapstructure v1.4.3 // indirect
github.com/pelletier/go-toml v1.9.4 // indirect
github.com/spf13/afero v1.6.0 // indirect
github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/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/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.2.0 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
golang.org/x/net v0.0.0-20211029224645-99673261e6eb // indirect
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
golang.org/x/text v0.3.7 // indirect
gopkg.in/ini.v1 v1.66.2 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/arch v0.7.0 // indirect
golang.org/x/crypto v0.17.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/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
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/klog/v2 v2.110.1 // indirect
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
)

960
go.sum

File diff suppressed because it is too large Load Diff

63
openapi/main.go Normal file
View File

@@ -0,0 +1,63 @@
package main
import (
"fmt"
"io"
"log"
"net/http"
"os"
"gopkg.in/yaml.v3"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
func main() {
version := "master"
fileName := "schema-master.yaml"
if len(os.Args) > 1 {
version = os.Args[1]
fileName = "schema.yaml"
}
log.Printf("Patching schema version %s\n", version)
resp, err := http.Get(fmt.Sprintf("https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/%s/openapi/openapi.yaml", version))
if err != nil {
log.Fatalln(err)
}
defer func() { _ = resp.Body.Close() }()
data, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatalln(err)
}
schema := make(map[string]interface{})
err = yaml.Unmarshal(data, &schema)
if err != nil {
log.Fatalln(err)
}
if requestBodies, ok, _ := unstructured.NestedMap(schema, "components", "requestBodies"); ok {
for k := range requestBodies {
_ = unstructured.SetNestedField(schema, k+"Body", "components", "requestBodies", k, "x-go-name")
}
}
if dnsInfo, ok, _ := unstructured.NestedMap(schema,
"paths", "/dns_info", "get", "responses", "200", "content", "application/json", "schema"); ok {
if allOf, ok, _ := unstructured.NestedSlice(dnsInfo, "allOf"); ok && len(allOf) == 2 {
delete(dnsInfo, "allOf")
if err := unstructured.SetNestedMap(schema, allOf[0].(map[string]interface{}),
"paths", "/dns_info", "get", "responses", "200", "content", "application/json", "schema"); err != nil {
log.Fatalln(err)
}
}
}
b, err := yaml.Marshal(&schema)
if err != nil {
log.Fatalln(err)
}
log.Printf("Writing schema file tmp/%s", fileName)
err = os.WriteFile("tmp/"+fileName, b, 0o600)
if err != nil {
log.Fatalln(err)
}
}

View File

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

View File

@@ -6,20 +6,38 @@ import (
"fmt"
"net/http"
"net/url"
"os"
"path"
"strconv"
"strings"
"github.com/bakito/adguardhome-sync/pkg/client/model"
"github.com/bakito/adguardhome-sync/pkg/log"
"github.com/bakito/adguardhome-sync/pkg/types"
"github.com/bakito/adguardhome-sync/pkg/utils"
"github.com/go-resty/resty/v2"
"go.uber.org/zap"
)
const envRedirectPolicyNoOfRedirects = "REDIRECT_POLICY_NO_OF_REDIRECTS"
var (
l = log.GetLogger("client")
// ErrSetupNeeded custom error
ErrSetupNeeded = errors.New("setup needed")
)
func detailedError(resp *resty.Response, err error) error {
e := resp.Status()
if len(resp.Body()) > 0 {
e += fmt.Sprintf("(%s)", string(resp.Body()))
}
if err != nil {
e += fmt.Sprintf(": %s", err.Error())
}
return errors.New(e)
}
// New create a new client
func New(config types.AdGuardInstance) (Client, error) {
var apiURL string
@@ -40,139 +58,118 @@ func New(config types.AdGuardInstance) (Client, error) {
cl.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
}
if config.Username != "" && config.Password != "" {
cookieParts := strings.Split(config.Cookie, "=")
if len(cookieParts) == 2 {
cl.SetCookie(&http.Cookie{
Name: cookieParts[0],
Value: cookieParts[1],
})
} else if config.Username != "" && config.Password != "" {
cl = cl.SetBasicAuth(config.Username, config.Password)
}
// no redirect
cl.SetRedirectPolicy(resty.NoRedirectPolicy())
if v, ok := os.LookupEnv(envRedirectPolicyNoOfRedirects); ok {
nbr, err := strconv.Atoi(v)
if err != nil {
return nil, fmt.Errorf("error parsing env var %q value must be an integer", envRedirectPolicyNoOfRedirects)
}
cl.SetRedirectPolicy(resty.FlexibleRedirectPolicy(nbr))
} else {
// no redirect
cl.SetRedirectPolicy(resty.NoRedirectPolicy())
}
return &client{
host: u.Host,
host: config.Host,
client: cl,
log: l.With("host", u.Host),
log: l.With("host", config.Host),
}, nil
}
// Client AdguardHome API client interface
type Client interface {
Host() string
Status() (*types.Status, error)
Status() (*model.ServerStatus, error)
ToggleProtection(enable bool) error
RewriteList() (*types.RewriteEntries, error)
AddRewriteEntries(e ...types.RewriteEntry) error
DeleteRewriteEntries(e ...types.RewriteEntry) error
Filtering() (*types.FilteringStatus, error)
ToggleFiltering(enabled bool, interval float64) error
AddFilters(whitelist bool, e ...types.Filter) error
DeleteFilters(whitelist bool, e ...types.Filter) error
UpdateFilters(whitelist bool, e ...types.Filter) error
RewriteList() (*model.RewriteEntries, error)
AddRewriteEntries(e ...model.RewriteEntry) error
DeleteRewriteEntries(e ...model.RewriteEntry) error
Filtering() (*model.FilterStatus, error)
ToggleFiltering(enabled bool, interval int) error
AddFilter(whitelist bool, f model.Filter) error
DeleteFilter(whitelist bool, f model.Filter) error
UpdateFilter(whitelist bool, f model.Filter) error
RefreshFilters(whitelist bool) error
SetCustomRules(rules types.UserRules) error
SetCustomRules(rules *[]string) error
SafeBrowsing() (bool, error)
ToggleSafeBrowsing(enable bool) error
Parental() (bool, error)
ToggleParental(enable bool) error
SafeSearch() (bool, error)
ToggleSafeSearch(enable bool) error
Services() (types.Services, error)
SetServices(services types.Services) error
Clients() (*types.Clients, error)
AddClients(client ...types.Client) error
UpdateClients(client ...types.Client) error
DeleteClients(client ...types.Client) error
QueryLogConfig() (*types.QueryLogConfig, error)
SetQueryLogConfig(enabled bool, interval float64, anonymizeClientIP bool) error
StatsConfig() (*types.IntervalConfig, error)
SetStatsConfig(interval float64) error
SafeSearchConfig() (*model.SafeSearchConfig, error)
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
Setup() error
AccessList() (*types.AccessList, error)
SetAccessList(*types.AccessList) error
DNSConfig() (*types.DNSConfig, error)
SetDNSConfig(*types.DNSConfig) error
DHCPServerConfig() (*types.DHCPServerConfig, error)
SetDHCPServerConfig(*types.DHCPServerConfig) error
AddDHCPStaticLeases(leases ...types.Lease) error
DeleteDHCPStaticLeases(leases ...types.Lease) error
AccessList() (*model.AccessList, error)
SetAccessList(*model.AccessList) error
DNSConfig() (*model.DNSConfig, error)
SetDNSConfig(*model.DNSConfig) error
DhcpConfig() (*model.DhcpStatus, error)
SetDhcpConfig(*model.DhcpStatus) error
AddDHCPStaticLease(lease model.DhcpStaticLease) error
DeleteDHCPStaticLease(lease model.DhcpStaticLease) error
}
type client struct {
client *resty.Client
log *zap.SugaredLogger
host string
client *resty.Client
log *zap.SugaredLogger
host string
version string
}
func (cl *client) Host() string {
return cl.host
}
func (cl *client) doGet(req *resty.Request, url string) error {
rl := cl.log.With("method", "GET", "path", url)
if cl.client.UserInfo != nil {
rl = rl.With("username", cl.client.UserInfo.Username)
}
rl.Debug("do get")
resp, err := req.Get(url)
if err != nil {
if resp != nil && resp.StatusCode() == http.StatusFound {
loc := resp.Header().Get("Location")
if loc == "/install.html" {
return ErrSetupNeeded
}
func contentType(resp *resty.Response) string {
if ct, ok := resp.Header()["Content-Type"]; ok {
if len(ct) != 1 {
return fmt.Sprintf("%v", ct)
}
rl.With("status", resp.StatusCode(), "body", string(resp.Body()), "error", err).Debug("error in do get")
return err
return ct[0]
}
rl.With("status", resp.StatusCode(), "body", string(resp.Body())).Debug("got response")
if resp.StatusCode() != http.StatusOK {
return errors.New(resp.Status())
}
return nil
return ""
}
func (cl *client) doPost(req *resty.Request, url string) error {
rl := cl.log.With("method", "POST", "path", url)
if cl.client.UserInfo != nil {
rl = rl.With("username", cl.client.UserInfo.Username)
}
rl.Debug("do post")
resp, err := req.Post(url)
if err != nil {
rl.With("status", resp.StatusCode(), "body", string(resp.Body()), "error", err).Debug("error in do post")
return err
}
rl.With("status", resp.StatusCode(), "body", string(resp.Body())).Debug("got response")
if resp.StatusCode() != http.StatusOK {
return errors.New(resp.Status())
}
return nil
}
func (cl *client) Status() (*types.Status, error) {
status := &types.Status{}
func (cl *client) Status() (*model.ServerStatus, error) {
status := &model.ServerStatus{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(status), "status")
cl.version = status.Version
return status, err
}
func (cl *client) RewriteList() (*types.RewriteEntries, error) {
rewrites := &types.RewriteEntries{}
func (cl *client) RewriteList() (*model.RewriteEntries, error) {
rewrites := &model.RewriteEntries{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(&rewrites), "/rewrite/list")
return rewrites, err
}
func (cl *client) AddRewriteEntries(entries ...types.RewriteEntry) error {
func (cl *client) AddRewriteEntries(entries ...model.RewriteEntry) error {
for i := range entries {
e := entries[i]
cl.log.With("domain", e.Domain, "answer", e.Answer).Info("Add rewrite entry")
cl.log.With("domain", e.Domain, "answer", e.Answer).Info("Add DNS rewrite entry")
err := cl.doPost(cl.client.R().EnableTrace().SetBody(&e), "/rewrite/add")
if err != nil {
return err
@@ -181,10 +178,10 @@ func (cl *client) AddRewriteEntries(entries ...types.RewriteEntry) error {
return nil
}
func (cl *client) DeleteRewriteEntries(entries ...types.RewriteEntry) error {
func (cl *client) DeleteRewriteEntries(entries ...model.RewriteEntry) error {
for i := range entries {
e := entries[i]
cl.log.With("domain", e.Domain, "answer", e.Answer).Info("Delete rewrite entry")
cl.log.With("domain", e.Domain, "answer", e.Answer).Info("Delete DNS rewrite entry")
err := cl.doPost(cl.client.R().EnableTrace().SetBody(&e), "/rewrite/delete")
if err != nil {
return err
@@ -209,16 +206,8 @@ func (cl *client) ToggleParental(enable bool) error {
return cl.toggleBool("parental", enable)
}
func (cl *client) SafeSearch() (bool, error) {
return cl.toggleStatus("safesearch")
}
func (cl *client) ToggleSafeSearch(enable bool) error {
return cl.toggleBool("safesearch", enable)
}
func (cl *client) toggleStatus(mode string) (bool, error) {
fs := &types.EnableConfig{}
fs := &model.EnableConfig{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(fs), fmt.Sprintf("/%s/status", mode))
return fs.Enabled, err
}
@@ -234,51 +223,36 @@ func (cl *client) toggleBool(mode string, enable bool) error {
return cl.doPost(cl.client.R().EnableTrace(), fmt.Sprintf("/%s/%s", mode, target))
}
func (cl *client) Filtering() (*types.FilteringStatus, error) {
f := &types.FilteringStatus{}
func (cl *client) Filtering() (*model.FilterStatus, error) {
f := &model.FilterStatus{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(f), "/filtering/status")
return f, err
}
func (cl *client) AddFilters(whitelist bool, filters ...types.Filter) error {
for _, f := range filters {
cl.log.With("url", f.URL, "whitelist", whitelist, "enabled", f.Enabled).Info("Add filter")
ff := &types.Filter{Name: f.Name, URL: f.URL, Whitelist: whitelist}
err := cl.doPost(cl.client.R().EnableTrace().SetBody(ff), "/filtering/add_url")
if err != nil {
return err
}
}
return nil
func (cl *client) AddFilter(whitelist bool, f model.Filter) error {
cl.log.With("url", f.Url, "whitelist", whitelist, "enabled", f.Enabled).Info("Add filter")
ff := &model.AddUrlRequest{Name: utils.Ptr(f.Name), Url: utils.Ptr(f.Url), Whitelist: utils.Ptr(whitelist)}
return cl.doPost(cl.client.R().EnableTrace().SetBody(ff), "/filtering/add_url")
}
func (cl *client) DeleteFilters(whitelist bool, filters ...types.Filter) error {
for _, f := range filters {
cl.log.With("url", f.URL, "whitelist", whitelist, "enabled", f.Enabled).Info("Delete filter")
ff := &types.Filter{URL: f.URL, Whitelist: whitelist}
err := cl.doPost(cl.client.R().EnableTrace().SetBody(ff), "/filtering/remove_url")
if err != nil {
return err
}
}
return nil
func (cl *client) DeleteFilter(whitelist bool, f model.Filter) error {
cl.log.With("url", f.Url, "whitelist", whitelist, "enabled", f.Enabled).Info("Delete filter")
ff := &model.RemoveUrlRequest{Url: utils.Ptr(f.Url), Whitelist: utils.Ptr(whitelist)}
return cl.doPost(cl.client.R().EnableTrace().SetBody(ff), "/filtering/remove_url")
}
func (cl *client) UpdateFilters(whitelist bool, filters ...types.Filter) error {
for _, f := range filters {
cl.log.With("url", f.URL, "whitelist", whitelist, "enabled", f.Enabled).Info("Update filter")
fu := &types.FilterUpdate{Whitelist: whitelist, URL: f.URL, Data: types.Filter{ID: f.ID, Name: f.Name, URL: f.URL, Whitelist: whitelist, Enabled: f.Enabled}}
err := cl.doPost(cl.client.R().EnableTrace().SetBody(fu), "/filtering/set_url")
if err != nil {
return err
}
func (cl *client) UpdateFilter(whitelist bool, f model.Filter) error {
cl.log.With("url", f.Url, "whitelist", whitelist, "enabled", f.Enabled).Info("Update filter")
fu := &model.FilterSetUrl{
Whitelist: utils.Ptr(whitelist), Url: utils.Ptr(f.Url),
Data: &model.FilterSetUrlData{Name: f.Name, Url: f.Url, Enabled: f.Enabled},
}
return nil
return cl.doPost(cl.client.R().EnableTrace().SetBody(fu), "/filtering/set_url")
}
func (cl *client) RefreshFilters(whitelist bool) error {
cl.log.With("whitelist", whitelist).Info("Refresh filter")
return cl.doPost(cl.client.R().EnableTrace().SetBody(&types.RefreshFilter{Whitelist: whitelist}), "/filtering/refresh")
return cl.doPost(cl.client.R().EnableTrace().SetBody(&model.FilterRefreshRequest{Whitelist: utils.Ptr(whitelist)}), "/filtering/refresh")
}
func (cl *client) ToggleProtection(enable bool) error {
@@ -286,95 +260,82 @@ func (cl *client) ToggleProtection(enable bool) error {
return cl.doPost(cl.client.R().EnableTrace().SetBody(&types.Protection{ProtectionEnabled: enable}), "/dns_config")
}
func (cl *client) SetCustomRules(rules types.UserRules) error {
cl.log.With("rules", len(rules)).Info("Set user rules")
return cl.doPost(cl.client.R().EnableTrace().SetBody(rules.String()), "/filtering/set_rules")
func (cl *client) SetCustomRules(rules *[]string) error {
cl.log.With("rules", len(*rules)).Info("Set user rules")
return cl.doPost(cl.client.R().EnableTrace().SetBody(&model.SetRulesRequest{Rules: rules}), "/filtering/set_rules")
}
func (cl *client) ToggleFiltering(enabled bool, interval float64) error {
func (cl *client) ToggleFiltering(enabled bool, interval int) error {
cl.log.With("enabled", enabled, "interval", interval).Info("Toggle filtering")
return cl.doPost(cl.client.R().EnableTrace().SetBody(&types.FilteringConfig{
EnableConfig: types.EnableConfig{Enabled: enabled},
IntervalConfig: types.IntervalConfig{Interval: interval},
return cl.doPost(cl.client.R().EnableTrace().SetBody(&model.FilterConfig{
Enabled: utils.Ptr(enabled),
Interval: utils.Ptr(interval),
}), "/filtering/config")
}
func (cl *client) Services() (types.Services, error) {
svcs := types.Services{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(&svcs), "/blocked_services/list")
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) SetServices(services types.Services) error {
cl.log.With("services", len(services)).Info("Set services")
return cl.doPost(cl.client.R().EnableTrace().SetBody(&services), "/blocked_services/set")
func (cl *client) 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) Clients() (*types.Clients, error) {
clients := &types.Clients{}
func (cl *client) BlockedServicesSchedule() (*model.BlockedServicesSchedule, error) {
sched := &model.BlockedServicesSchedule{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(sched), "/blocked_services/get")
return sched, err
}
func (cl *client) SetBlockedServicesSchedule(schedule *model.BlockedServicesSchedule) error {
cl.log.With("services", schedule.ServicesString()).Info("Set blocked services schedule")
return cl.doPut(cl.client.R().EnableTrace().SetBody(schedule), "/blocked_services/update")
}
func (cl *client) Clients() (*model.Clients, error) {
clients := &model.Clients{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(clients), "/clients")
return clients, err
}
func (cl *client) AddClients(clients ...types.Client) error {
for i := range clients {
client := clients[i]
cl.log.With("name", client.Name).Info("Add client")
err := cl.doPost(cl.client.R().EnableTrace().SetBody(&client), "/clients/add")
if err != nil {
return err
}
}
return nil
func (cl *client) AddClient(client *model.Client) error {
cl.log.With("name", *client.Name).Info("Add client settings")
return cl.doPost(cl.client.R().EnableTrace().SetBody(client), "/clients/add")
}
func (cl *client) UpdateClients(clients ...types.Client) error {
for _, client := range clients {
cl.log.With("name", client.Name).Info("Update client")
err := cl.doPost(cl.client.R().EnableTrace().SetBody(&types.ClientUpdate{Name: client.Name, Data: client}), "/clients/update")
if err != nil {
return err
}
}
return nil
func (cl *client) UpdateClient(client *model.Client) error {
cl.log.With("name", *client.Name).Info("Update client settings")
return cl.doPost(cl.client.R().EnableTrace().SetBody(&model.ClientUpdate{Name: client.Name, Data: client}), "/clients/update")
}
func (cl *client) DeleteClients(clients ...types.Client) error {
for i := range clients {
client := clients[i]
cl.log.With("name", client.Name).Info("Delete client")
err := cl.doPost(cl.client.R().EnableTrace().SetBody(&client), "/clients/delete")
if err != nil {
return err
}
}
return nil
func (cl *client) DeleteClient(client *model.Client) error {
cl.log.With("name", *client.Name).Info("Delete client settings")
return cl.doPost(cl.client.R().EnableTrace().SetBody(client), "/clients/delete")
}
func (cl *client) QueryLogConfig() (*types.QueryLogConfig, error) {
qlc := &types.QueryLogConfig{}
func (cl *client) QueryLogConfig() (*model.QueryLogConfig, error) {
qlc := &model.QueryLogConfig{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(qlc), "/querylog_info")
return qlc, err
}
func (cl *client) SetQueryLogConfig(enabled bool, interval float64, anonymizeClientIP bool) error {
cl.log.With("enabled", enabled, "interval", interval, "anonymizeClientIP", anonymizeClientIP).Info("Set query log config")
return cl.doPost(cl.client.R().EnableTrace().SetBody(&types.QueryLogConfig{
EnableConfig: types.EnableConfig{Enabled: enabled},
IntervalConfig: types.IntervalConfig{Interval: interval},
AnonymizeClientIP: anonymizeClientIP,
}), "/querylog_config")
func (cl *client) SetQueryLogConfig(qlc *model.QueryLogConfig) 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")
}
func (cl *client) StatsConfig() (*types.IntervalConfig, error) {
stats := &types.IntervalConfig{}
func (cl *client) StatsConfig() (*model.StatsConfig, error) {
stats := &model.StatsConfig{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(stats), "/stats_info")
return stats, err
}
func (cl *client) SetStatsConfig(interval float64) error {
cl.log.With("interval", interval).Info("Set stats config")
return cl.doPost(cl.client.R().EnableTrace().SetBody(&types.IntervalConfig{Interval: interval}), "/stats_config")
func (cl *client) 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) Setup() error {
@@ -403,57 +364,75 @@ func (cl *client) Setup() error {
return cl.doPost(req, "/install/configure")
}
func (cl *client) AccessList() (*types.AccessList, error) {
al := &types.AccessList{}
func (cl *client) AccessList() (*model.AccessList, error) {
al := &model.AccessList{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(al), "/access/list")
return al, err
}
func (cl *client) SetAccessList(list *types.AccessList) error {
func (cl *client) SetAccessList(list *model.AccessList) error {
cl.log.Info("Set access list")
return cl.doPost(cl.client.R().EnableTrace().SetBody(list), "/access/set")
}
func (cl *client) DNSConfig() (*types.DNSConfig, error) {
cfg := &types.DNSConfig{}
func (cl *client) DNSConfig() (*model.DNSConfig, error) {
cfg := &model.DNSConfig{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(cfg), "/dns_info")
return cfg, err
}
func (cl *client) SetDNSConfig(config *types.DNSConfig) error {
func (cl *client) SetDNSConfig(config *model.DNSConfig) error {
cl.log.Info("Set dns config list")
return cl.doPost(cl.client.R().EnableTrace().SetBody(config), "/dns_config")
}
func (cl *client) DHCPServerConfig() (*types.DHCPServerConfig, error) {
cfg := &types.DHCPServerConfig{}
func (cl *client) DhcpConfig() (*model.DhcpStatus, error) {
cfg := &model.DhcpStatus{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(cfg), "/dhcp/status")
return cfg, err
}
func (cl *client) SetDHCPServerConfig(config *types.DHCPServerConfig) error {
func (cl *client) SetDhcpConfig(config *model.DhcpStatus) error {
cl.log.Info("Set dhcp server config")
return cl.doPost(cl.client.R().EnableTrace().SetBody(config), "/dhcp/set_config")
}
func (cl *client) AddDHCPStaticLeases(leases ...types.Lease) error {
for _, l := range leases {
cl.log.With("mac", l.HWAddr, "ip", l.IP, "hostname", l.Hostname).Info("Add static dhcp lease")
err := cl.doPost(cl.client.R().EnableTrace().SetBody(l), "/dhcp/add_static_lease")
if err != nil {
return err
}
func (cl *client) AddDHCPStaticLease(l model.DhcpStaticLease) error {
cl.log.With("mac", l.Mac, "ip", l.Ip, "hostname", l.Hostname).Info("Add static dhcp lease")
err := cl.doPost(cl.client.R().EnableTrace().SetBody(l), "/dhcp/add_static_lease")
if err != nil {
return err
}
return nil
}
func (cl *client) DeleteDHCPStaticLeases(leases ...types.Lease) error {
for _, l := range leases {
cl.log.With("mac", l.HWAddr, "ip", l.IP, "hostname", l.Hostname).Info("Delete static dhcp lease")
err := cl.doPost(cl.client.R().EnableTrace().SetBody(l), "/dhcp/remove_static_lease")
if err != nil {
return err
}
func (cl *client) DeleteDHCPStaticLease(l model.DhcpStaticLease) error {
cl.log.With("mac", l.Mac, "ip", l.Ip, "hostname", l.Hostname).Info("Delete static dhcp lease")
err := cl.doPost(cl.client.R().EnableTrace().SetBody(l), "/dhcp/remove_static_lease")
if err != nil {
return err
}
return nil
}
func (cl *client) SafeSearchConfig() (*model.SafeSearchConfig, error) {
sss := &model.SafeSearchConfig{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(sss), "/safesearch/status")
return sss, err
}
func (cl *client) SetSafeSearchConfig(settings *model.SafeSearchConfig) error {
cl.log.With("enabled", *settings.Enabled).Info("Set safesearch settings")
return cl.doPut(cl.client.R().EnableTrace().SetBody(settings), "/safesearch/settings")
}
func (cl *client) ProfileInfo() (*model.ProfileInfo, error) {
p := &model.ProfileInfo{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(p), "/profile")
return p, err
}
func (cl *client) SetProfileInfo(profile *model.ProfileInfo) error {
cl.log.With("language", profile.Language, "theme", profile.Theme).Info("Set profile")
return cl.doPut(cl.client.R().EnableTrace().SetBody(profile), "/profile/update")
}

View File

@@ -2,13 +2,16 @@ package client_test
import (
"fmt"
"io/ioutil"
"io"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"github.com/bakito/adguardhome-sync/pkg/client"
"github.com/bakito/adguardhome-sync/pkg/client/model"
"github.com/bakito/adguardhome-sync/pkg/types"
"github.com/bakito/adguardhome-sync/pkg/utils"
"github.com/google/uuid"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
@@ -32,19 +35,22 @@ var _ = Describe("Client", func() {
Context("Host", func() {
It("should read the current host", func() {
cl, _ := client.New(types.AdGuardInstance{URL: "https://foo.bar:3000"})
inst := types.AdGuardInstance{URL: "https://foo.bar:3000"}
err := inst.Init()
Ω(err).ShouldNot(HaveOccurred())
cl, _ := client.New(inst)
host := cl.Host()
Ω(host).Should(Equal("foo.bar:3000"))
})
})
Context("Filtering", func() {
It("should read filtering status", func() {
Context("Filter", func() {
It("should read filter status", func() {
ts, cl = ClientGet("filtering-status.json", "/filtering/status")
fs, err := cl.Filtering()
Ω(err).ShouldNot(HaveOccurred())
Ω(fs.Enabled).Should(BeTrue())
Ω(fs.Filters).Should(HaveLen(2))
Ω(*fs.Enabled).Should(BeTrue())
Ω(*fs.Filters).Should(HaveLen(2))
})
It("should enable protection", func() {
ts, cl = ClientPost("/filtering/config", `{"enabled":true,"interval":123}`)
@@ -63,35 +69,32 @@ var _ = Describe("Client", func() {
})
It("should add Filters", func() {
ts, cl = ClientPost("/filtering/add_url",
`{"id":0,"enabled":false,"url":"foo","name":"","rules_count":0,"whitelist":true}`,
`{"id":0,"enabled":false,"url":"bar","name":"","rules_count":0,"whitelist":true}`,
`{"name":"","url":"foo","whitelist":true}`,
`{"name":"","url":"bar","whitelist":true}`,
)
err := cl.AddFilters(true, types.Filter{URL: "foo"}, types.Filter{URL: "bar"})
err := cl.AddFilter(true, model.Filter{Url: "foo"})
Ω(err).ShouldNot(HaveOccurred())
err = cl.AddFilter(true, model.Filter{Url: "bar"})
Ω(err).ShouldNot(HaveOccurred())
})
It("should update Filters", func() {
ts, cl = ClientPost("/filtering/set_url",
`{"url":"foo","data":{"id":0,"enabled":false,"url":"foo","name":"","rules_count":0,"whitelist":true},"whitelist":true}`,
`{"url":"bar","data":{"id":0,"enabled":false,"url":"bar","name":"","rules_count":0,"whitelist":true},"whitelist":true}`,
`{"data":{"enabled":false,"name":"","url":"foo"},"url":"foo","whitelist":true}`,
`{"data":{"enabled":false,"name":"","url":"bar"},"url":"bar","whitelist":true}`,
)
err := cl.UpdateFilters(true, types.Filter{URL: "foo"}, types.Filter{URL: "bar"})
err := cl.UpdateFilter(true, model.Filter{Url: "foo"})
Ω(err).ShouldNot(HaveOccurred())
err = cl.UpdateFilter(true, model.Filter{Url: "bar"})
Ω(err).ShouldNot(HaveOccurred())
})
It("should delete Filters", func() {
ts, cl = ClientPost("/filtering/remove_url",
`{"id":0,"enabled":false,"url":"foo","name":"","rules_count":0,"whitelist":true}`,
`{"id":0,"enabled":false,"url":"bar","name":"","rules_count":0,"whitelist":true}`,
`{"url":"foo","whitelist":true}`,
`{"url":"bar","whitelist":true}`,
)
err := cl.DeleteFilters(true, types.Filter{URL: "foo"}, types.Filter{URL: "bar"})
err := cl.DeleteFilter(true, model.Filter{Url: "foo"})
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("CustomRules", func() {
It("should set SetCustomRules", func() {
ts, cl = ClientPost("/filtering/set_rules", `foo
bar`)
err := cl.SetCustomRules([]string{"foo", "bar"})
err = cl.DeleteFilter(true, model.Filter{Url: "bar"})
Ω(err).ShouldNot(HaveOccurred())
})
})
@@ -101,8 +104,8 @@ bar`)
ts, cl = ClientGet("status.json", "/status")
fs, err := cl.Status()
Ω(err).ShouldNot(HaveOccurred())
Ω(fs.DNSAddresses).Should(HaveLen(1))
Ω(fs.DNSAddresses[0]).Should(Equal("192.168.1.2"))
Ω(fs.DnsAddresses).Should(HaveLen(1))
Ω(fs.DnsAddresses[0]).Should(Equal("192.168.1.2"))
Ω(fs.Version).Should(Equal("v0.105.2"))
})
It("should return ErrSetupNeeded", func() {
@@ -134,13 +137,19 @@ bar`)
Ω(*rwl).Should(HaveLen(2))
})
It("should add RewriteList", func() {
ts, cl = ClientPost("/rewrite/add", `{"domain":"foo","answer":"foo"}`, `{"domain":"bar","answer":"bar"}`)
err := cl.AddRewriteEntries(types.RewriteEntry{Answer: "foo", Domain: "foo"}, types.RewriteEntry{Answer: "bar", Domain: "bar"})
ts, cl = ClientPost("/rewrite/add", `{"answer":"foo","domain":"foo"}`, `{"answer":"bar","domain":"bar"}`)
err := cl.AddRewriteEntries(
model.RewriteEntry{Answer: utils.Ptr("foo"), Domain: utils.Ptr("foo")},
model.RewriteEntry{Answer: utils.Ptr("bar"), Domain: utils.Ptr("bar")},
)
Ω(err).ShouldNot(HaveOccurred())
})
It("should delete RewriteList", func() {
ts, cl = ClientPost("/rewrite/delete", `{"domain":"foo","answer":"foo"}`, `{"domain":"bar","answer":"bar"}`)
err := cl.DeleteRewriteEntries(types.RewriteEntry{Answer: "foo", Domain: "foo"}, types.RewriteEntry{Answer: "bar", Domain: "bar"})
ts, cl = ClientPost("/rewrite/delete", `{"answer":"foo","domain":"foo"}`, `{"answer":"bar","domain":"bar"}`)
err := cl.DeleteRewriteEntries(
model.RewriteEntry{Answer: utils.Ptr("foo"), Domain: utils.Ptr("foo")},
model.RewriteEntry{Answer: utils.Ptr("bar"), Domain: utils.Ptr("bar")},
)
Ω(err).ShouldNot(HaveOccurred())
})
})
@@ -164,21 +173,22 @@ bar`)
})
})
Context("SafeSearch", func() {
Context("SafeSearchConfig", func() {
It("should read safesearch status", func() {
ts, cl = ClientGet("safesearch-status.json", "/safesearch/status")
ss, err := cl.SafeSearch()
ss, err := cl.SafeSearchConfig()
Ω(err).ShouldNot(HaveOccurred())
Ω(ss).Should(BeTrue())
Ω(ss.Enabled).ShouldNot(BeNil())
Ω(*ss.Enabled).Should(BeTrue())
})
It("should enable safesearch", func() {
ts, cl = ClientPost("/safesearch/enable", "")
err := cl.ToggleSafeSearch(true)
ts, cl = ClientPut("/safesearch/settings", `{"enabled":true}`)
err := cl.SetSafeSearchConfig(&model.SafeSearchConfig{Enabled: utils.Ptr(true)})
Ω(err).ShouldNot(HaveOccurred())
})
It("should disable safesearch", func() {
ts, cl = ClientPost("/safesearch/disable", "")
err := cl.ToggleSafeSearch(false)
ts, cl = ClientPut("/safesearch/settings", `{"enabled":false}`)
err := cl.SetSafeSearchConfig(&model.SafeSearchConfig{Enabled: utils.Ptr(false)})
Ω(err).ShouldNot(HaveOccurred())
})
})
@@ -215,16 +225,39 @@ bar`)
})
})
Context("Services", func() {
It("should read Services", func() {
Context("BlockedServices", func() {
It("should read BlockedServices", func() {
ts, cl = ClientGet("blockedservices-list.json", "/blocked_services/list")
s, err := cl.Services()
s, err := cl.BlockedServices()
Ω(err).ShouldNot(HaveOccurred())
Ω(s).Should(HaveLen(2))
Ω(*s).Should(HaveLen(2))
})
It("should set Services", func() {
ts, cl = ClientPost("/blocked_services/set", `["foo","bar"]`)
err := cl.SetServices([]string{"foo", "bar"})
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")
s, err := cl.BlockedServicesSchedule()
Ω(err).ShouldNot(HaveOccurred())
Ω(*s.Ids).Should(HaveLen(3))
})
It("should set BlockedServicesSchedule", func() {
ts, cl = ClientPost("/blocked_services/update",
`{"ids":["bar","foo"],"schedule":{"mon":{"end":99,"start":1}}}`)
err := cl.SetBlockedServicesSchedule(&model.BlockedServicesSchedule{
Ids: utils.Ptr([]string{"foo", "bar"}),
Schedule: &model.Schedule{
Mon: &model.DayRange{
Start: utils.Ptr(float32(1.0)),
End: utils.Ptr(float32(99.0)),
},
},
})
Ω(err).ShouldNot(HaveOccurred())
})
})
@@ -234,27 +267,27 @@ bar`)
ts, cl = ClientGet("clients.json", "/clients")
c, err := cl.Clients()
Ω(err).ShouldNot(HaveOccurred())
Ω(c.Clients).Should(HaveLen(2))
Ω(*c.Clients).Should(HaveLen(2))
})
It("should add Clients", func() {
ts, cl = ClientPost("/clients/add",
`{"ids":["id"],"use_global_settings":false,"use_global_blocked_services":false,"name":"foo","filtering_enabled":false,"parental_enabled":false,"safesearch_enabled":false,"safebrowsing_enabled":false,"disallowed":false,"disallowed_rule":""}`,
`{"ids":["id"],"name":"foo"}`,
)
err := cl.AddClients(types.Client{Name: "foo", Ids: []string{"id"}})
err := cl.AddClient(&model.Client{Name: utils.Ptr("foo"), Ids: utils.Ptr([]string{"id"})})
Ω(err).ShouldNot(HaveOccurred())
})
It("should update Clients", func() {
ts, cl = ClientPost("/clients/update",
`{"name":"foo","data":{"ids":["id"],"use_global_settings":false,"use_global_blocked_services":false,"name":"foo","filtering_enabled":false,"parental_enabled":false,"safesearch_enabled":false,"safebrowsing_enabled":false,"disallowed":false,"disallowed_rule":""}}`,
`{"data":{"ids":["id"],"name":"foo"},"name":"foo"}`,
)
err := cl.UpdateClients(types.Client{Name: "foo", Ids: []string{"id"}})
err := cl.UpdateClient(&model.Client{Name: utils.Ptr("foo"), Ids: utils.Ptr([]string{"id"})})
Ω(err).ShouldNot(HaveOccurred())
})
It("should delete Clients", func() {
ts, cl = ClientPost("/clients/delete",
`{"ids":["id"],"use_global_settings":false,"use_global_blocked_services":false,"name":"foo","filtering_enabled":false,"parental_enabled":false,"safesearch_enabled":false,"safebrowsing_enabled":false,"disallowed":false,"disallowed_rule":""}`,
`{"ids":["id"],"name":"foo"}`,
)
err := cl.DeleteClients(types.Client{Name: "foo", Ids: []string{"id"}})
err := cl.DeleteClient(&model.Client{Name: utils.Ptr("foo"), Ids: utils.Ptr([]string{"id"})})
Ω(err).ShouldNot(HaveOccurred())
})
})
@@ -264,12 +297,16 @@ bar`)
ts, cl = ClientGet("querylog_info.json", "/querylog_info")
qlc, err := cl.QueryLogConfig()
Ω(err).ShouldNot(HaveOccurred())
Ω(qlc.Enabled).Should(BeTrue())
Ω(qlc.Interval).Should(Equal(90.0))
Ω(qlc.Enabled).ShouldNot(BeNil())
Ω(*qlc.Enabled).Should(BeTrue())
Ω(qlc.Interval).ShouldNot(BeNil())
Ω(*qlc.Interval).Should(Equal(model.QueryLogConfigInterval(90)))
})
It("should set QueryLogConfig", func() {
ts, cl = ClientPost("/querylog_config", `{"enabled":true,"interval":123,"anonymize_client_ip":true}`)
err := cl.SetQueryLogConfig(true, 123, true)
ts, cl = ClientPost("/querylog_config", `{"anonymize_client_ip":true,"enabled":true,"interval":123}`)
var interval model.QueryLogConfigInterval = 123
err := cl.SetQueryLogConfig(&model.QueryLogConfig{AnonymizeClientIp: utils.Ptr(true), Interval: &interval, Enabled: utils.Ptr(true)})
Ω(err).ShouldNot(HaveOccurred())
})
})
@@ -278,11 +315,14 @@ bar`)
ts, cl = ClientGet("stats_info.json", "/stats_info")
sc, err := cl.StatsConfig()
Ω(err).ShouldNot(HaveOccurred())
Ω(sc.Interval).Should(Equal(1.0))
Ω(sc.Interval).ShouldNot(BeNil())
Ω(*sc.Interval).Should(Equal(model.StatsConfigInterval(1)))
})
It("should set StatsConfig", func() {
ts, cl = ClientPost("/stats_config", `{"interval":123}`)
err := cl.SetStatsConfig(123.0)
var interval model.StatsConfigInterval = 123
err := cl.SetStatsConfig(&model.StatsConfig{Interval: &interval})
Ω(err).ShouldNot(HaveOccurred())
})
})
@@ -307,7 +347,8 @@ bar`)
Context("doPost", func() {
It("should return an error on status code != 200", func() {
err := cl.SetStatsConfig(123)
var interval model.StatsConfigInterval = 123
err := cl.SetStatsConfig(&model.StatsConfig{Interval: &interval})
Ω(err).Should(HaveOccurred())
Ω(err.Error()).Should(Equal("401 Unauthorized"))
})
@@ -318,7 +359,7 @@ bar`)
func ClientGet(file string, path string) (*httptest.Server, client.Client) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Ω(r.URL.Path).Should(Equal(types.DefaultAPIPath + path))
b, err := ioutil.ReadFile(filepath.Join("../../testdata", file))
b, err := os.ReadFile(filepath.Join("../../testdata", file))
Ω(err).ShouldNot(HaveOccurred())
w.Header().Set("Content-Type", "application/json")
_, err = w.Write(b)
@@ -333,7 +374,22 @@ func ClientPost(path string, content ...string) (*httptest.Server, client.Client
index := 0
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Ω(r.URL.Path).Should(Equal(types.DefaultAPIPath + path))
body, err := ioutil.ReadAll(r.Body)
body, err := io.ReadAll(r.Body)
Ω(err).ShouldNot(HaveOccurred())
Ω(body).Should(Equal([]byte(content[index])))
index++
}))
cl, err := client.New(types.AdGuardInstance{URL: ts.URL, Username: username, Password: password})
Ω(err).ShouldNot(HaveOccurred())
return ts, cl
}
func ClientPut(path string, content ...string) (*httptest.Server, client.Client) {
index := 0
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Ω(r.URL.Path).Should(Equal(types.DefaultAPIPath + path))
body, err := io.ReadAll(r.Body)
Ω(err).ShouldNot(HaveOccurred())
Ω(body).Should(Equal([]byte(content[index])))
index++

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,388 @@
package model
import (
"fmt"
"sort"
"strings"
"github.com/bakito/adguardhome-sync/pkg/utils"
"github.com/jinzhu/copier"
)
// Clone the config
func (c *DhcpStatus) Clone() *DhcpStatus {
clone := &DhcpStatus{}
_ = copier.Copy(clone, c)
return clone
}
// Equals dhcp server config equal check
func (c *DhcpStatus) Equals(o *DhcpStatus) bool {
return utils.JsonEquals(c, o)
}
func (c *DhcpStatus) HasConfig() bool {
return (c.V4 != nil && c.V4.isValid()) || (c.V6 != nil && c.V6.isValid())
}
func (j DhcpConfigV4) isValid() bool {
return j.GatewayIp != nil && j.SubnetMask != nil && j.RangeStart != nil && j.RangeEnd != nil
}
func (j DhcpConfigV6) isValid() bool {
return j.RangeStart != nil
}
type DhcpStaticLeases []DhcpStaticLease
// MergeDhcpStaticLeases the leases
func MergeDhcpStaticLeases(l *[]DhcpStaticLease, other *[]DhcpStaticLease) (DhcpStaticLeases, DhcpStaticLeases) {
var thisLeases []DhcpStaticLease
var otherLeases []DhcpStaticLease
if l != nil {
thisLeases = *l
}
if other != nil {
otherLeases = *other
}
current := make(map[string]DhcpStaticLease)
var adds DhcpStaticLeases
var removes DhcpStaticLeases
for _, le := range thisLeases {
current[le.Mac] = le
}
for _, le := range otherLeases {
if _, ok := current[le.Mac]; ok {
delete(current, le.Mac)
} else {
adds = append(adds, le)
}
}
for _, rr := range current {
removes = append(removes, rr)
}
return adds, removes
}
// Equals dns config equal check
func (c *DNSConfig) Equals(o *DNSConfig) bool {
cc := c.Clone()
oo := o.Clone()
cc.Sort()
oo.Sort()
return utils.JsonEquals(cc, oo)
}
func (c *DNSConfig) Clone() *DNSConfig {
return utils.Clone(c, &DNSConfig{})
}
// Sort sort dns config
func (c *DNSConfig) Sort() {
if c.UpstreamDns != nil {
sort.Strings(*c.UpstreamDns)
}
if c.UpstreamDns != nil {
sort.Strings(*c.BootstrapDns)
}
if c.UpstreamDns != nil {
sort.Strings(*c.LocalPtrUpstreams)
}
}
// Equals access list equal check
func (al *AccessList) Equals(o *AccessList) bool {
return EqualsStringSlice(al.AllowedClients, o.AllowedClients, true) &&
EqualsStringSlice(al.DisallowedClients, o.DisallowedClients, true) &&
EqualsStringSlice(al.BlockedHosts, o.BlockedHosts, true)
}
func EqualsStringSlice(a *[]string, b *[]string, sortIt bool) bool {
if a == nil && b == nil {
return true
}
if a == nil || b == nil {
return false
}
aa := *a
bb := *b
if sortIt {
sort.Strings(aa)
sort.Strings(bb)
}
if len(aa) != len(bb) {
return false
}
for i, v := range aa {
if v != bb[i] {
return false
}
}
return true
}
// Sort clients
func (cl *Client) Sort() {
if cl.Ids != nil {
sort.Strings(*cl.Ids)
}
if cl.Tags != nil {
sort.Strings(*cl.Tags)
}
if cl.BlockedServices != nil {
sort.Strings(*cl.BlockedServices)
}
if cl.Upstreams != nil {
sort.Strings(*cl.Upstreams)
}
}
// Equals Clients equal check
func (cl *Client) Equals(o *Client) bool {
cl.Sort()
o.Sort()
return utils.JsonEquals(cl, o)
}
// Add ac client
func (clients *Clients) Add(cl Client) {
if clients.Clients == nil {
clients.Clients = &ClientsArray{cl}
} else {
a := append(*clients.Clients, cl)
clients.Clients = &a
}
}
// Merge merge Clients
func (clients *Clients) Merge(other *Clients) ([]*Client, []*Client, []*Client) {
current := make(map[string]*Client)
if clients.Clients != nil {
cc := *clients.Clients
for i := range cc {
client := cc[i]
current[*client.Name] = &client
}
}
expected := make(map[string]*Client)
if other.Clients != nil {
oc := *other.Clients
for i := range oc {
client := oc[i]
expected[*client.Name] = &client
}
}
var adds []*Client
var removes []*Client
var updates []*Client
for _, cl := range expected {
if oc, ok := current[*cl.Name]; ok {
if !cl.Equals(oc) {
updates = append(updates, cl)
}
delete(current, *cl.Name)
} else {
adds = append(adds, cl)
}
}
for _, rr := range current {
removes = append(removes, rr)
}
return adds, updates, removes
}
// Key RewriteEntry key
func (re *RewriteEntry) Key() string {
var d string
var a string
if re.Domain != nil {
d = *re.Domain
}
if re.Answer != nil {
a = *re.Answer
}
return fmt.Sprintf("%s#%s", d, a)
}
// RewriteEntries list of RewriteEntry
type RewriteEntries []RewriteEntry
// Merge RewriteEntries
func (rwe *RewriteEntries) Merge(other *RewriteEntries) (RewriteEntries, RewriteEntries, RewriteEntries) {
current := make(map[string]RewriteEntry)
var adds RewriteEntries
var removes RewriteEntries
var duplicates RewriteEntries
processed := make(map[string]bool)
for _, rr := range *rwe {
if _, ok := processed[rr.Key()]; !ok {
current[rr.Key()] = rr
processed[rr.Key()] = true
} else {
// remove duplicate
removes = append(removes, rr)
}
}
for _, rr := range *other {
if _, ok := current[rr.Key()]; ok {
delete(current, rr.Key())
} else {
if _, ok := processed[rr.Key()]; !ok {
adds = append(adds, rr)
processed[rr.Key()] = true
} else {
// skip duplicate
duplicates = append(duplicates, rr)
}
}
}
for _, rr := range current {
removes = append(removes, rr)
}
return adds, removes, duplicates
}
func MergeFilters(this *[]Filter, other *[]Filter) ([]Filter, []Filter, []Filter) {
if this == nil && other == nil {
return nil, nil, nil
}
current := make(map[string]*Filter)
var adds []Filter
var updates []Filter
var removes []Filter
if this != nil {
for i := range *this {
fi := (*this)[i]
current[fi.Url] = &fi
}
}
if other != nil {
for i := range *other {
rr := (*other)[i]
if c, ok := current[rr.Url]; ok {
if !c.Equals(&rr) {
updates = append(updates, rr)
}
delete(current, rr.Url)
} else {
adds = append(adds, rr)
}
}
}
for _, rr := range current {
removes = append(removes, *rr)
}
return adds, updates, removes
}
// Equals Filter equal check
func (f *Filter) Equals(o *Filter) bool {
return f.Enabled == o.Enabled && f.Url == o.Url && f.Name == o.Name
}
// 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)
}
// Equals QueryLogConfigInterval equal check
func (qlc *QueryLogConfigInterval) Equals(o *QueryLogConfigInterval) bool {
return ptrEquals(qlc, o)
}
func ptrEquals[T comparable](a *T, b *T) bool {
if a == nil && b == nil {
return true
}
var aa T
if a != nil {
aa = *a
}
var bb T
if b != nil {
bb = *b
}
return aa == bb
}
// EnableConfig API struct
type EnableConfig struct {
Enabled bool `json:"enabled"`
}
func (ssc *SafeSearchConfig) Equals(o *SafeSearchConfig) bool {
return ptrEquals(ssc.Enabled, o.Enabled) &&
ptrEquals(ssc.Bing, o.Bing) &&
ptrEquals(ssc.Duckduckgo, o.Duckduckgo) &&
ptrEquals(ssc.Google, o.Google) &&
ptrEquals(ssc.Pixabay, o.Pixabay) &&
ptrEquals(ssc.Yandex, o.Yandex) &&
ptrEquals(ssc.Youtube, o.Youtube)
}
func (pi *ProfileInfo) Equals(o *ProfileInfo) bool {
return pi.Language == o.Language &&
pi.Theme == o.Theme
}
func (pi *ProfileInfo) ShouldSyncFor(o *ProfileInfo) *ProfileInfo {
if pi.Equals(o) {
return nil
}
merged := &ProfileInfo{Name: pi.Name, Language: pi.Language, Theme: pi.Theme}
if o.Language != "" {
merged.Language = o.Language
}
if o.Theme != "" {
merged.Theme = o.Theme
}
if merged.Name == "" || merged.Language == "" || merged.Theme == "" || merged.Equals(pi) {
return nil
}
return merged
}
func (bss *BlockedServicesSchedule) Equals(o *BlockedServicesSchedule) bool {
return utils.JsonEquals(bss, o)
}
func (bss *BlockedServicesSchedule) ServicesString() string {
return ArrayString(bss.Ids)
}
func ArrayString(a *[]string) string {
if a == nil {
return "[]"
}
sorted := *a
sort.Strings(sorted)
return fmt.Sprintf("[%s]", strings.Join(sorted, ","))
}

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@@ -7,7 +7,7 @@ package client
import (
reflect "reflect"
types "github.com/bakito/adguardhome-sync/pkg/types"
model "github.com/bakito/adguardhome-sync/pkg/client/model"
gomock "github.com/golang/mock/gomock"
)
@@ -35,10 +35,10 @@ func (m *MockClient) EXPECT() *MockClientMockRecorder {
}
// AccessList mocks base method.
func (m *MockClient) AccessList() (*types.AccessList, error) {
func (m *MockClient) AccessList() (*model.AccessList, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AccessList")
ret0, _ := ret[0].(*types.AccessList)
ret0, _ := ret[0].(*model.AccessList)
ret1, _ := ret[1].(error)
return ret0, ret1
}
@@ -49,63 +49,50 @@ func (mr *MockClientMockRecorder) AccessList() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AccessList", reflect.TypeOf((*MockClient)(nil).AccessList))
}
// AddClients mocks base method.
func (m *MockClient) AddClients(arg0 ...types.Client) error {
// AddClient mocks base method.
func (m *MockClient) AddClient(arg0 *model.Client) error {
m.ctrl.T.Helper()
varargs := []interface{}{}
for _, a := range arg0 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "AddClients", varargs...)
ret := m.ctrl.Call(m, "AddClient", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// AddClients indicates an expected call of AddClients.
func (mr *MockClientMockRecorder) AddClients(arg0 ...interface{}) *gomock.Call {
// AddClient indicates an expected call of AddClient.
func (mr *MockClientMockRecorder) AddClient(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddClients", reflect.TypeOf((*MockClient)(nil).AddClients), arg0...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddClient", reflect.TypeOf((*MockClient)(nil).AddClient), arg0)
}
// AddDHCPStaticLeases mocks base method.
func (m *MockClient) AddDHCPStaticLeases(arg0 ...types.Lease) error {
// AddDHCPStaticLease mocks base method.
func (m *MockClient) AddDHCPStaticLease(arg0 model.DhcpStaticLease) error {
m.ctrl.T.Helper()
varargs := []interface{}{}
for _, a := range arg0 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "AddDHCPStaticLeases", varargs...)
ret := m.ctrl.Call(m, "AddDHCPStaticLease", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// AddDHCPStaticLeases indicates an expected call of AddDHCPStaticLeases.
func (mr *MockClientMockRecorder) AddDHCPStaticLeases(arg0 ...interface{}) *gomock.Call {
// AddDHCPStaticLease indicates an expected call of AddDHCPStaticLease.
func (mr *MockClientMockRecorder) AddDHCPStaticLease(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddDHCPStaticLeases", reflect.TypeOf((*MockClient)(nil).AddDHCPStaticLeases), arg0...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddDHCPStaticLease", reflect.TypeOf((*MockClient)(nil).AddDHCPStaticLease), arg0)
}
// AddFilters mocks base method.
func (m *MockClient) AddFilters(arg0 bool, arg1 ...types.Filter) error {
// AddFilter mocks base method.
func (m *MockClient) AddFilter(arg0 bool, arg1 model.Filter) error {
m.ctrl.T.Helper()
varargs := []interface{}{arg0}
for _, a := range arg1 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "AddFilters", varargs...)
ret := m.ctrl.Call(m, "AddFilter", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// AddFilters indicates an expected call of AddFilters.
func (mr *MockClientMockRecorder) AddFilters(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
// AddFilter indicates an expected call of AddFilter.
func (mr *MockClientMockRecorder) AddFilter(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0}, arg1...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddFilters", reflect.TypeOf((*MockClient)(nil).AddFilters), varargs...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddFilter", reflect.TypeOf((*MockClient)(nil).AddFilter), arg0, arg1)
}
// AddRewriteEntries mocks base method.
func (m *MockClient) AddRewriteEntries(arg0 ...types.RewriteEntry) error {
func (m *MockClient) AddRewriteEntries(arg0 ...model.RewriteEntry) error {
m.ctrl.T.Helper()
varargs := []interface{}{}
for _, a := range arg0 {
@@ -122,11 +109,41 @@ func (mr *MockClientMockRecorder) AddRewriteEntries(arg0 ...interface{}) *gomock
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()
ret := m.ctrl.Call(m, "BlockedServicesSchedule")
ret0, _ := ret[0].(*model.BlockedServicesSchedule)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// BlockedServicesSchedule indicates an expected call of BlockedServicesSchedule.
func (mr *MockClientMockRecorder) BlockedServicesSchedule() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BlockedServicesSchedule", reflect.TypeOf((*MockClient)(nil).BlockedServicesSchedule))
}
// Clients mocks base method.
func (m *MockClient) Clients() (*types.Clients, error) {
func (m *MockClient) Clients() (*model.Clients, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Clients")
ret0, _ := ret[0].(*types.Clients)
ret0, _ := ret[0].(*model.Clients)
ret1, _ := ret[1].(error)
return ret0, ret1
}
@@ -137,26 +154,11 @@ func (mr *MockClientMockRecorder) Clients() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Clients", reflect.TypeOf((*MockClient)(nil).Clients))
}
// DHCPServerConfig mocks base method.
func (m *MockClient) DHCPServerConfig() (*types.DHCPServerConfig, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DHCPServerConfig")
ret0, _ := ret[0].(*types.DHCPServerConfig)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// DHCPServerConfig indicates an expected call of DHCPServerConfig.
func (mr *MockClientMockRecorder) DHCPServerConfig() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DHCPServerConfig", reflect.TypeOf((*MockClient)(nil).DHCPServerConfig))
}
// DNSConfig mocks base method.
func (m *MockClient) DNSConfig() (*types.DNSConfig, error) {
func (m *MockClient) DNSConfig() (*model.DNSConfig, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DNSConfig")
ret0, _ := ret[0].(*types.DNSConfig)
ret0, _ := ret[0].(*model.DNSConfig)
ret1, _ := ret[1].(error)
return ret0, ret1
}
@@ -167,63 +169,50 @@ func (mr *MockClientMockRecorder) DNSConfig() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DNSConfig", reflect.TypeOf((*MockClient)(nil).DNSConfig))
}
// DeleteClients mocks base method.
func (m *MockClient) DeleteClients(arg0 ...types.Client) error {
// DeleteClient mocks base method.
func (m *MockClient) DeleteClient(arg0 *model.Client) error {
m.ctrl.T.Helper()
varargs := []interface{}{}
for _, a := range arg0 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "DeleteClients", varargs...)
ret := m.ctrl.Call(m, "DeleteClient", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteClients indicates an expected call of DeleteClients.
func (mr *MockClientMockRecorder) DeleteClients(arg0 ...interface{}) *gomock.Call {
// DeleteClient indicates an expected call of DeleteClient.
func (mr *MockClientMockRecorder) DeleteClient(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteClients", reflect.TypeOf((*MockClient)(nil).DeleteClients), arg0...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteClient", reflect.TypeOf((*MockClient)(nil).DeleteClient), arg0)
}
// DeleteDHCPStaticLeases mocks base method.
func (m *MockClient) DeleteDHCPStaticLeases(arg0 ...types.Lease) error {
// DeleteDHCPStaticLease mocks base method.
func (m *MockClient) DeleteDHCPStaticLease(arg0 model.DhcpStaticLease) error {
m.ctrl.T.Helper()
varargs := []interface{}{}
for _, a := range arg0 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "DeleteDHCPStaticLeases", varargs...)
ret := m.ctrl.Call(m, "DeleteDHCPStaticLease", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteDHCPStaticLeases indicates an expected call of DeleteDHCPStaticLeases.
func (mr *MockClientMockRecorder) DeleteDHCPStaticLeases(arg0 ...interface{}) *gomock.Call {
// DeleteDHCPStaticLease indicates an expected call of DeleteDHCPStaticLease.
func (mr *MockClientMockRecorder) DeleteDHCPStaticLease(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteDHCPStaticLeases", reflect.TypeOf((*MockClient)(nil).DeleteDHCPStaticLeases), arg0...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteDHCPStaticLease", reflect.TypeOf((*MockClient)(nil).DeleteDHCPStaticLease), arg0)
}
// DeleteFilters mocks base method.
func (m *MockClient) DeleteFilters(arg0 bool, arg1 ...types.Filter) error {
// DeleteFilter mocks base method.
func (m *MockClient) DeleteFilter(arg0 bool, arg1 model.Filter) error {
m.ctrl.T.Helper()
varargs := []interface{}{arg0}
for _, a := range arg1 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "DeleteFilters", varargs...)
ret := m.ctrl.Call(m, "DeleteFilter", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteFilters indicates an expected call of DeleteFilters.
func (mr *MockClientMockRecorder) DeleteFilters(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
// DeleteFilter indicates an expected call of DeleteFilter.
func (mr *MockClientMockRecorder) DeleteFilter(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0}, arg1...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteFilters", reflect.TypeOf((*MockClient)(nil).DeleteFilters), varargs...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteFilter", reflect.TypeOf((*MockClient)(nil).DeleteFilter), arg0, arg1)
}
// DeleteRewriteEntries mocks base method.
func (m *MockClient) DeleteRewriteEntries(arg0 ...types.RewriteEntry) error {
func (m *MockClient) DeleteRewriteEntries(arg0 ...model.RewriteEntry) error {
m.ctrl.T.Helper()
varargs := []interface{}{}
for _, a := range arg0 {
@@ -240,11 +229,26 @@ func (mr *MockClientMockRecorder) DeleteRewriteEntries(arg0 ...interface{}) *gom
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteRewriteEntries", reflect.TypeOf((*MockClient)(nil).DeleteRewriteEntries), arg0...)
}
// DhcpConfig mocks base method.
func (m *MockClient) DhcpConfig() (*model.DhcpStatus, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DhcpConfig")
ret0, _ := ret[0].(*model.DhcpStatus)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// DhcpConfig indicates an expected call of DhcpConfig.
func (mr *MockClientMockRecorder) DhcpConfig() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DhcpConfig", reflect.TypeOf((*MockClient)(nil).DhcpConfig))
}
// Filtering mocks base method.
func (m *MockClient) Filtering() (*types.FilteringStatus, error) {
func (m *MockClient) Filtering() (*model.FilterStatus, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Filtering")
ret0, _ := ret[0].(*types.FilteringStatus)
ret0, _ := ret[0].(*model.FilterStatus)
ret1, _ := ret[1].(error)
return ret0, ret1
}
@@ -284,11 +288,26 @@ func (mr *MockClientMockRecorder) Parental() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Parental", reflect.TypeOf((*MockClient)(nil).Parental))
}
// ProfileInfo mocks base method.
func (m *MockClient) ProfileInfo() (*model.ProfileInfo, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ProfileInfo")
ret0, _ := ret[0].(*model.ProfileInfo)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ProfileInfo indicates an expected call of ProfileInfo.
func (mr *MockClientMockRecorder) ProfileInfo() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ProfileInfo", reflect.TypeOf((*MockClient)(nil).ProfileInfo))
}
// QueryLogConfig mocks base method.
func (m *MockClient) QueryLogConfig() (*types.QueryLogConfig, error) {
func (m *MockClient) QueryLogConfig() (*model.QueryLogConfig, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "QueryLogConfig")
ret0, _ := ret[0].(*types.QueryLogConfig)
ret0, _ := ret[0].(*model.QueryLogConfig)
ret1, _ := ret[1].(error)
return ret0, ret1
}
@@ -314,10 +333,10 @@ func (mr *MockClientMockRecorder) RefreshFilters(arg0 interface{}) *gomock.Call
}
// RewriteList mocks base method.
func (m *MockClient) RewriteList() (*types.RewriteEntries, error) {
func (m *MockClient) RewriteList() (*model.RewriteEntries, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "RewriteList")
ret0, _ := ret[0].(*types.RewriteEntries)
ret0, _ := ret[0].(*model.RewriteEntries)
ret1, _ := ret[1].(error)
return ret0, ret1
}
@@ -343,38 +362,23 @@ func (mr *MockClientMockRecorder) SafeBrowsing() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SafeBrowsing", reflect.TypeOf((*MockClient)(nil).SafeBrowsing))
}
// SafeSearch mocks base method.
func (m *MockClient) SafeSearch() (bool, error) {
// SafeSearchConfig mocks base method.
func (m *MockClient) SafeSearchConfig() (*model.SafeSearchConfig, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SafeSearch")
ret0, _ := ret[0].(bool)
ret := m.ctrl.Call(m, "SafeSearchConfig")
ret0, _ := ret[0].(*model.SafeSearchConfig)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// SafeSearch indicates an expected call of SafeSearch.
func (mr *MockClientMockRecorder) SafeSearch() *gomock.Call {
// SafeSearchConfig indicates an expected call of SafeSearchConfig.
func (mr *MockClientMockRecorder) SafeSearchConfig() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SafeSearch", reflect.TypeOf((*MockClient)(nil).SafeSearch))
}
// Services mocks base method.
func (m *MockClient) Services() (types.Services, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Services")
ret0, _ := ret[0].(types.Services)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Services indicates an expected call of Services.
func (mr *MockClientMockRecorder) Services() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Services", reflect.TypeOf((*MockClient)(nil).Services))
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SafeSearchConfig", reflect.TypeOf((*MockClient)(nil).SafeSearchConfig))
}
// SetAccessList mocks base method.
func (m *MockClient) SetAccessList(arg0 *types.AccessList) error {
func (m *MockClient) SetAccessList(arg0 *model.AccessList) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetAccessList", arg0)
ret0, _ := ret[0].(error)
@@ -387,8 +391,36 @@ func (mr *MockClientMockRecorder) SetAccessList(arg0 interface{}) *gomock.Call {
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()
ret := m.ctrl.Call(m, "SetBlockedServicesSchedule", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// SetBlockedServicesSchedule indicates an expected call of SetBlockedServicesSchedule.
func (mr *MockClientMockRecorder) SetBlockedServicesSchedule(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBlockedServicesSchedule", reflect.TypeOf((*MockClient)(nil).SetBlockedServicesSchedule), arg0)
}
// SetCustomRules mocks base method.
func (m *MockClient) SetCustomRules(arg0 types.UserRules) error {
func (m *MockClient) SetCustomRules(arg0 *[]string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetCustomRules", arg0)
ret0, _ := ret[0].(error)
@@ -401,22 +433,8 @@ func (mr *MockClientMockRecorder) SetCustomRules(arg0 interface{}) *gomock.Call
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetCustomRules", reflect.TypeOf((*MockClient)(nil).SetCustomRules), arg0)
}
// SetDHCPServerConfig mocks base method.
func (m *MockClient) SetDHCPServerConfig(arg0 *types.DHCPServerConfig) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetDHCPServerConfig", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// SetDHCPServerConfig indicates an expected call of SetDHCPServerConfig.
func (mr *MockClientMockRecorder) SetDHCPServerConfig(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDHCPServerConfig", reflect.TypeOf((*MockClient)(nil).SetDHCPServerConfig), arg0)
}
// SetDNSConfig mocks base method.
func (m *MockClient) SetDNSConfig(arg0 *types.DNSConfig) error {
func (m *MockClient) SetDNSConfig(arg0 *model.DNSConfig) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetDNSConfig", arg0)
ret0, _ := ret[0].(error)
@@ -429,36 +447,64 @@ func (mr *MockClientMockRecorder) SetDNSConfig(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDNSConfig", reflect.TypeOf((*MockClient)(nil).SetDNSConfig), arg0)
}
// SetQueryLogConfig mocks base method.
func (m *MockClient) SetQueryLogConfig(arg0 bool, arg1 float64, arg2 bool) error {
// SetDhcpConfig mocks base method.
func (m *MockClient) SetDhcpConfig(arg0 *model.DhcpStatus) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetQueryLogConfig", arg0, arg1, arg2)
ret := m.ctrl.Call(m, "SetDhcpConfig", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// SetDhcpConfig indicates an expected call of SetDhcpConfig.
func (mr *MockClientMockRecorder) SetDhcpConfig(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDhcpConfig", reflect.TypeOf((*MockClient)(nil).SetDhcpConfig), arg0)
}
// SetProfileInfo mocks base method.
func (m *MockClient) SetProfileInfo(arg0 *model.ProfileInfo) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetProfileInfo", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// SetProfileInfo indicates an expected call of SetProfileInfo.
func (mr *MockClientMockRecorder) SetProfileInfo(arg0 interface{}) *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 {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetQueryLogConfig", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// SetQueryLogConfig indicates an expected call of SetQueryLogConfig.
func (mr *MockClientMockRecorder) SetQueryLogConfig(arg0, arg1, arg2 interface{}) *gomock.Call {
func (mr *MockClientMockRecorder) SetQueryLogConfig(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetQueryLogConfig", reflect.TypeOf((*MockClient)(nil).SetQueryLogConfig), arg0, arg1, arg2)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetQueryLogConfig", reflect.TypeOf((*MockClient)(nil).SetQueryLogConfig), arg0)
}
// SetServices mocks base method.
func (m *MockClient) SetServices(arg0 types.Services) error {
// SetSafeSearchConfig mocks base method.
func (m *MockClient) SetSafeSearchConfig(arg0 *model.SafeSearchConfig) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetServices", arg0)
ret := m.ctrl.Call(m, "SetSafeSearchConfig", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// SetServices indicates an expected call of SetServices.
func (mr *MockClientMockRecorder) SetServices(arg0 interface{}) *gomock.Call {
// SetSafeSearchConfig indicates an expected call of SetSafeSearchConfig.
func (mr *MockClientMockRecorder) SetSafeSearchConfig(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetServices", reflect.TypeOf((*MockClient)(nil).SetServices), arg0)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSafeSearchConfig", reflect.TypeOf((*MockClient)(nil).SetSafeSearchConfig), arg0)
}
// SetStatsConfig mocks base method.
func (m *MockClient) SetStatsConfig(arg0 float64) error {
func (m *MockClient) SetStatsConfig(arg0 *model.StatsConfig) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetStatsConfig", arg0)
ret0, _ := ret[0].(error)
@@ -486,10 +532,10 @@ func (mr *MockClientMockRecorder) Setup() *gomock.Call {
}
// StatsConfig mocks base method.
func (m *MockClient) StatsConfig() (*types.IntervalConfig, error) {
func (m *MockClient) StatsConfig() (*model.StatsConfig, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "StatsConfig")
ret0, _ := ret[0].(*types.IntervalConfig)
ret0, _ := ret[0].(*model.StatsConfig)
ret1, _ := ret[1].(error)
return ret0, ret1
}
@@ -501,10 +547,10 @@ func (mr *MockClientMockRecorder) StatsConfig() *gomock.Call {
}
// Status mocks base method.
func (m *MockClient) Status() (*types.Status, error) {
func (m *MockClient) Status() (*model.ServerStatus, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Status")
ret0, _ := ret[0].(*types.Status)
ret0, _ := ret[0].(*model.ServerStatus)
ret1, _ := ret[1].(error)
return ret0, ret1
}
@@ -516,7 +562,7 @@ func (mr *MockClientMockRecorder) Status() *gomock.Call {
}
// ToggleFiltering mocks base method.
func (m *MockClient) ToggleFiltering(arg0 bool, arg1 float64) error {
func (m *MockClient) ToggleFiltering(arg0 bool, arg1 int) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ToggleFiltering", arg0, arg1)
ret0, _ := ret[0].(error)
@@ -571,53 +617,30 @@ func (mr *MockClientMockRecorder) ToggleSafeBrowsing(arg0 interface{}) *gomock.C
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleSafeBrowsing", reflect.TypeOf((*MockClient)(nil).ToggleSafeBrowsing), arg0)
}
// ToggleSafeSearch mocks base method.
func (m *MockClient) ToggleSafeSearch(arg0 bool) error {
// UpdateClient mocks base method.
func (m *MockClient) UpdateClient(arg0 *model.Client) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ToggleSafeSearch", arg0)
ret := m.ctrl.Call(m, "UpdateClient", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// ToggleSafeSearch indicates an expected call of ToggleSafeSearch.
func (mr *MockClientMockRecorder) ToggleSafeSearch(arg0 interface{}) *gomock.Call {
// UpdateClient indicates an expected call of UpdateClient.
func (mr *MockClientMockRecorder) UpdateClient(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleSafeSearch", reflect.TypeOf((*MockClient)(nil).ToggleSafeSearch), arg0)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateClient", reflect.TypeOf((*MockClient)(nil).UpdateClient), arg0)
}
// UpdateClients mocks base method.
func (m *MockClient) UpdateClients(arg0 ...types.Client) error {
// UpdateFilter mocks base method.
func (m *MockClient) UpdateFilter(arg0 bool, arg1 model.Filter) error {
m.ctrl.T.Helper()
varargs := []interface{}{}
for _, a := range arg0 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "UpdateClients", varargs...)
ret := m.ctrl.Call(m, "UpdateFilter", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// UpdateClients indicates an expected call of UpdateClients.
func (mr *MockClientMockRecorder) UpdateClients(arg0 ...interface{}) *gomock.Call {
// UpdateFilter indicates an expected call of UpdateFilter.
func (mr *MockClientMockRecorder) UpdateFilter(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateClients", reflect.TypeOf((*MockClient)(nil).UpdateClients), arg0...)
}
// UpdateFilters mocks base method.
func (m *MockClient) UpdateFilters(arg0 bool, arg1 ...types.Filter) error {
m.ctrl.T.Helper()
varargs := []interface{}{arg0}
for _, a := range arg1 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "UpdateFilters", varargs...)
ret0, _ := ret[0].(error)
return ret0
}
// UpdateFilters indicates an expected call of UpdateFilters.
func (mr *MockClientMockRecorder) UpdateFilters(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0}, arg1...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateFilters", reflect.TypeOf((*MockClient)(nil).UpdateFilters), varargs...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateFilter", reflect.TypeOf((*MockClient)(nil).UpdateFilter), arg0, arg1)
}

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

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

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

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

View File

@@ -2,11 +2,10 @@ package sync
import (
"context"
// import embed for html page
_ "embed"
"errors"
"fmt"
"html/template"
"net"
"net/http"
"os"
@@ -16,6 +15,8 @@ import (
"time"
"github.com/bakito/adguardhome-sync/pkg/log"
"github.com/bakito/adguardhome-sync/version"
"github.com/gin-gonic/gin"
)
var (
@@ -25,77 +26,57 @@ var (
favicon []byte
)
func (w *worker) handleSync(rw http.ResponseWriter, req *http.Request) {
switch req.Method {
case http.MethodPost:
l.With("remote-addr", req.RemoteAddr).Info("Starting sync from API")
w.sync()
default:
http.Error(rw, "only POST allowed", http.StatusBadRequest)
}
func (w *worker) handleSync(c *gin.Context) {
l.With("remote-addr", c.Request.RemoteAddr).Info("Starting sync from API")
w.sync()
}
func (w *worker) handleRoot(rw http.ResponseWriter, _ *http.Request) {
rw.Header().Set("Content-Type", "text/html")
_, _ = rw.Write(index)
func (w *worker) handleRoot(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", map[string]interface{}{
"DarkMode": w.cfg.API.DarkMode,
"Version": version.Version,
"Build": version.Build,
"SyncStatus": w.status(),
},
)
}
func (w *worker) handleFavicon(rw http.ResponseWriter, _ *http.Request) {
rw.Header().Set("Content-Type", "image/x-icon")
_, _ = rw.Write(favicon)
func (w *worker) handleFavicon(c *gin.Context) {
c.Data(http.StatusOK, "image/x-icon", favicon)
}
func (w *worker) handleLogs(rw http.ResponseWriter, _ *http.Request) {
_, _ = rw.Write([]byte(strings.Join(log.Logs(), "")))
func (w *worker) handleLogs(c *gin.Context) {
c.Data(http.StatusOK, "text/plain", []byte(strings.Join(log.Logs(), "")))
}
func (w *worker) basicAuth(h http.HandlerFunc) http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
username, password, authOK := r.BasicAuth()
if !authOK {
http.Error(rw, "Not authorized", 401)
return
}
if username != w.cfg.API.Username || password != w.cfg.API.Password {
http.Error(rw, "Not authorized", 401)
return
}
h.ServeHTTP(rw, r)
}
}
func use(h http.HandlerFunc, middleware ...func(http.HandlerFunc) http.HandlerFunc) http.HandlerFunc {
for _, m := range middleware {
h = m(h)
}
return h
func (w *worker) handleStatus(c *gin.Context) {
c.JSON(http.StatusOK, w.status())
}
func (w *worker) listenAndServe() {
l.With("port", w.cfg.API.Port).Info("Starting API server")
ctx, cancel := context.WithCancel(context.Background())
mux := http.NewServeMux()
httpServer := &http.Server{
Addr: fmt.Sprintf(":%d", w.cfg.API.Port),
Handler: mux,
BaseContext: func(_ net.Listener) context.Context { return ctx },
}
var mw []func(http.HandlerFunc) http.HandlerFunc
gin.SetMode(gin.ReleaseMode)
r := gin.New()
r.Use(gin.Recovery())
if w.cfg.API.Username != "" && w.cfg.API.Password != "" {
mw = append(mw, w.basicAuth)
r.Use(gin.BasicAuth(map[string]string{w.cfg.API.Username: w.cfg.API.Password}))
}
httpServer := &http.Server{
Addr: fmt.Sprintf(":%d", w.cfg.API.Port),
Handler: r,
BaseContext: func(_ net.Listener) context.Context { return ctx },
ReadHeaderTimeout: 1 * time.Second,
}
mux.HandleFunc("/api/v1/sync", use(w.handleSync, mw...))
mux.HandleFunc("/api/v1/logs", use(w.handleLogs, mw...))
mux.HandleFunc("/favicon.ico", use(w.handleFavicon, mw...))
mux.HandleFunc("/", use(w.handleRoot, mw...))
r.SetHTMLTemplate(template.Must(template.New("index.html").Parse(string(index))))
r.POST("/api/v1/sync", w.handleSync)
r.GET("/api/v1/logs", w.handleLogs)
r.GET("/api/v1/status", w.handleStatus)
r.GET("/favicon.ico", w.handleFavicon)
r.GET("/", w.handleRoot)
go func() {
if err := httpServer.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
@@ -140,3 +121,16 @@ func (w *worker) listenAndServe() {
defer os.Exit(0)
}
type syncStatus struct {
Origin replicaStatus `json:"origin"`
Replicas []replicaStatus `json:"replicas"`
}
type replicaStatus struct {
Host string `json:"host"`
URL string `json:"url"`
Status string `json:"status"`
Error string `json:"error,omitempty"`
ProtectionEnabled *bool `json:"protection_enabled"`
}

View File

@@ -1,10 +1,16 @@
<html>
<html lang="en">
<head>
<title>AdGuardHome sync</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-KyZXEAg3QhqLMpG8r+8fhAXLRk2vvoC2f3B09zVXn8CA5QIVfZOJ3BCsw2P0p/We" crossorigin="anonymous">
<script type="text/javascript" src="https://code.jquery.com/jquery-3.6.0.min.js">
</script>
{{- if .DarkMode }}
<link rel="stylesheet" href="https://bootswatch.com/5/darkly/bootstrap.min.css"
crossorigin="anonymous">
{{- else }}
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-KyZXEAg3QhqLMpG8r+8fhAXLRk2vvoC2f3B09zVXn8CA5QIVfZOJ3BCsw2P0p/We"
crossOrigin="anonymous">
{{- end }}
<script type="text/javascript">
$(document).ready(function () {
$("#showLogs").click(function () {
@@ -12,20 +18,35 @@
$('#logs').html(data);
}
);
$.get("api/v1/status", {}, function (status) {
$('#origin').removeClass(function (index, className) {
return (className.match(/(^|\s)btn-\S+/g) || []).join(' ');
}).addClass("btn-" + status.origin.status).attr('title', status.origin.error);
status.replicas.forEach(function (replica, i) {
$('#replica_' + i).removeClass(function (index, className) {
return (className.match(/(^|\s)btn-\S+/g) || []).join(' ');
}).addClass("btn-" + replica.status).attr('title', replica.error);
});
}
);
});
$("#sync").click(function () {
$.post("api/v1/sync", {}, function (data) {
});
$("#showLogs").click();
});
$("#showLogs").click()
$("#showLogs").click();
});
</script>
<link rel="shortcut icon" href="favicon.ico">
</head>
<body>
<div class="container-fluid px-4">
<div class="container-fluid px-4">
<div class="row">
<h1 class="display-4">AdGuardHome sync</h1>
<p class="h1">
AdGuardHome sync
<p class="h6">{{ .Version }} ({{ .Build }})</p>
</p>
</div>
<div class="row">
<div class="col">
@@ -34,6 +55,18 @@
<input class="btn btn-secondary" type="button" id="showLogs" value="Update Logs"/>
</div>
</div>
<div class="col col-md-auto">
<div class="float-right">
<a href="{{ .SyncStatus.Origin.URL }}" target="_blank" class="btn btn-{{ .SyncStatus.Origin.Status }}"
type="button" id="origin"
{{ if .SyncStatus.Origin.Error }} title="{{ .SyncStatus.Origin.Error }}" {{ end }}>Origin {{ .SyncStatus.Origin.Host }}</a>
{{ range $i, $r := .SyncStatus.Replicas }}
<a href="{{ $r.URL }}" target="_blank" class="btn btn-{{ $r.Status }}"
type="button" id="replica_{{ $i }}"
{{ if $r.Error }} title="{{ $r.Error }}" {{ end }} >Replica {{ $r.Host }}</a>
{{ end }}
</div>
</div>
</div>
<div class="row mt-3">
<div class="col-12">
@@ -42,4 +75,4 @@
</div>
</div>
</body>
</html>
</html>

View File

@@ -3,18 +3,21 @@ package sync
import (
"errors"
"fmt"
"runtime"
"sort"
"time"
"github.com/bakito/adguardhome-sync/pkg/client"
"github.com/bakito/adguardhome-sync/pkg/client/model"
"github.com/bakito/adguardhome-sync/pkg/log"
"github.com/bakito/adguardhome-sync/pkg/types"
"github.com/bakito/adguardhome-sync/pkg/utils"
"github.com/bakito/adguardhome-sync/pkg/versions"
"github.com/bakito/adguardhome-sync/version"
"github.com/robfig/cron/v3"
"go.uber.org/zap"
"golang.org/x/mod/semver"
)
const minAghVersion = "v0.107.0"
var l = log.GetLogger("sync")
// Sync config from origin to replica
@@ -27,7 +30,13 @@ func Sync(cfg *types.Config) error {
return fmt.Errorf("no replicas configured")
}
l.With("version", version.Version, "build", version.Build).Info("AdGuardHome sync")
l.With(
"version", version.Version,
"build", version.Build,
"os", runtime.GOOS,
"arch", runtime.GOARCH,
).Info("AdGuardHome sync")
cfg.Log(l)
cfg.Features.LogDisabled(l)
cfg.Origin.AutoSetup = false
@@ -40,7 +49,13 @@ func Sync(cfg *types.Config) error {
if cfg.Cron != "" {
w.cron = cron.New()
cl := l.With("cron", cfg.Cron)
_, err := w.cron.AddFunc(cfg.Cron, func() {
sched, err := cron.ParseStandard(cfg.Cron)
if err != nil {
cl.With("error", err).Error("Error parsing cron expression")
return err
}
cl = cl.With("next-execution", sched.Next(time.Now()))
_, err = w.cron.AddFunc(cfg.Cron, func() {
w.sync()
})
if err != nil {
@@ -75,6 +90,55 @@ type worker struct {
running bool
cron *cron.Cron
createClient func(instance types.AdGuardInstance) (client.Client, error)
actions []syncAction
}
func (w *worker) status() *syncStatus {
syncStatus := &syncStatus{
Origin: w.getStatus(w.cfg.Origin),
}
for _, replica := range w.cfg.Replicas {
st := w.getStatus(replica)
if w.running {
st.Status = "info"
}
syncStatus.Replicas = append(syncStatus.Replicas, st)
}
sort.Slice(syncStatus.Replicas, func(i, j int) bool {
return syncStatus.Replicas[i].Host < syncStatus.Replicas[j].Host
})
return syncStatus
}
func (w *worker) getStatus(inst types.AdGuardInstance) (st replicaStatus) {
st = replicaStatus{Host: inst.WebHost, URL: inst.WebURL}
oc, err := w.createClient(inst)
if err != nil {
l.With("error", err, "url", w.cfg.Origin.URL).Error("Error creating origin client")
st.Status = "danger"
st.Error = err.Error()
return
}
sl := l.With("from", inst.WebHost)
status, err := oc.Status()
if err != nil {
if errors.Is(err, client.ErrSetupNeeded) {
st.Status = "warning"
st.Error = err.Error()
return
}
sl.With("error", err).Error("Error getting origin status")
st.Status = "danger"
st.Error = err.Error()
return
}
st.Status = "success"
st.ProtectionEnabled = utils.Ptr(status.ProtectionEnabled)
return
}
func (w *worker) sync() {
@@ -100,8 +164,16 @@ func (w *worker) sync() {
return
}
if semver.Compare(o.status.Version, minAghVersion) == -1 {
sl.With("error", err, "version", o.status.Version).Errorf("Origin AdGuard Home version must be >= %s", minAghVersion)
if versions.IsNewerThan(versions.MinAgh, o.status.Version) {
sl.With("error", err, "version", o.status.Version).Errorf("Origin AdGuard Home version must be >= %s", versions.MinAgh)
return
}
sl.With("version", o.status.Version).Info("Connected to origin")
o.profileInfo, err = oc.ProfileInfo()
if err != nil {
sl.With("error", err).Error("Error getting profileInfo info")
return
}
@@ -110,7 +182,7 @@ func (w *worker) sync() {
sl.With("error", err).Error("Error getting parental status")
return
}
o.safeSearch, err = oc.SafeSearch()
o.safeSearch, err = oc.SafeSearchConfig()
if err != nil {
sl.With("error", err).Error("Error getting safe search status")
return
@@ -127,15 +199,21 @@ func (w *worker) sync() {
return
}
o.services, err = oc.Services()
o.blockedServices, err = oc.BlockedServices()
if err != nil {
sl.With("error", err).Error("Error getting origin services")
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")
return
}
o.filters, err = oc.Filtering()
if err != nil {
sl.With("error", err).Error("Error getting origin filters")
sl.With("error", err).Error("Error getting origin actionFilters")
return
}
o.clients, err = oc.Clients()
@@ -166,12 +244,16 @@ func (w *worker) sync() {
return
}
o.dhcpServerConfig, err = oc.DHCPServerConfig()
if err != nil {
sl.With("error", err).Error("Error getting dhcp server config")
return
if w.cfg.Features.DHCP.ServerConfig || w.cfg.Features.DHCP.StaticLeases {
o.dhcpServerConfig, err = oc.DhcpConfig()
if err != nil {
sl.With("error", err).Error("Error getting dhcp server config")
return
}
}
w.actions = setupActions(w.cfg)
replicas := w.cfg.UniqueReplicas()
for _, replica := range replicas {
w.syncTo(sl, o, replica)
@@ -188,69 +270,44 @@ func (w *worker) syncTo(l *zap.SugaredLogger, o *origin, replica types.AdGuardIn
rl := l.With("to", rc.Host())
rl.Info("Start sync")
rs, err := w.statusWithSetup(rl, replica, rc)
replicaStatus, err := w.statusWithSetup(rl, replica, rc)
if err != nil {
rl.With("error", err).Error("Error getting replica status")
return
}
if semver.Compare(rs.Version, minAghVersion) == -1 {
rl.With("error", err, "version", rs.Version).Errorf("Replica AdGuard Home version must be >= %s", minAghVersion)
rl.With("version", replicaStatus.Version).Info("Connected to replica")
if versions.IsNewerThan(versions.MinAgh, replicaStatus.Version) {
rl.With("error", err, "version", replicaStatus.Version).Errorf("Replica AdGuard Home version must be >= %s", versions.MinAgh)
return
}
if o.status.Version != rs.Version {
rl.With("originVersion", o.status.Version, "replicaVersion", rs.Version).Warn("Versions do not match")
if o.status.Version != replicaStatus.Version {
rl.With("originVersion", o.status.Version, "replicaVersion", replicaStatus.Version).Warn("Versions do not match")
}
err = w.syncGeneralSettings(o, rs, rc)
if err != nil {
rl.With("error", err).Error("Error syncing general settings")
return
ac := &actionContext{
continueOnError: w.cfg.ContinueOnError,
rl: rl,
origin: o,
replicaStatus: replicaStatus,
client: rc,
replica: replica,
}
err = w.syncConfigs(o, rc)
if err != nil {
rl.With("error", err).Error("Error syncing configs")
return
}
err = w.syncRewrites(rl, o.rewrites, rc)
if err != nil {
rl.With("error", err).Error("Error syncing rewrites")
return
}
err = w.syncFilters(o.filters, rc)
if err != nil {
rl.With("error", err).Error("Error syncing filters")
return
}
err = w.syncServices(o.services, rc)
if err != nil {
rl.With("error", err).Error("Error syncing services")
return
}
if err = w.syncClients(o.clients, rc); err != nil {
rl.With("error", err).Error("Error syncing clients")
return
}
if err = w.syncDNS(o.accessList, o.dnsConfig, rc); err != nil {
rl.With("error", err).Error("Error syncing dns")
return
}
if err = w.syncDHCPServer(o.dhcpServerConfig, rc); err != nil {
rl.With("error", err).Error("Error syncing dns")
return
for _, action := range w.actions {
if err := action.sync(ac); err != nil {
rl.With("error", err).Errorf("Error syncing %s", action.name())
if !w.cfg.ContinueOnError {
return
}
}
}
rl.Info("Sync done")
}
func (w *worker) statusWithSetup(rl *zap.SugaredLogger, replica types.AdGuardInstance, rc client.Client) (*types.Status, error) {
func (w *worker) statusWithSetup(rl *zap.SugaredLogger, replica types.AdGuardInstance, rc client.Client) (*model.ServerStatus, error) {
rs, err := rc.Status()
if err != nil {
if replica.AutoSetup && errors.Is(err, client.ErrSetupNeeded) {
@@ -265,240 +322,20 @@ func (w *worker) statusWithSetup(rl *zap.SugaredLogger, replica types.AdGuardIns
return rs, err
}
func (w *worker) syncServices(os types.Services, replica client.Client) error {
if w.cfg.Features.Services {
rs, err := replica.Services()
if err != nil {
return err
}
if !os.Equals(rs) {
if err := replica.SetServices(os); err != nil {
return err
}
}
}
return nil
}
func (w *worker) syncFilters(of *types.FilteringStatus, replica client.Client) error {
if w.cfg.Features.Filters {
rf, err := replica.Filtering()
if err != nil {
return err
}
if err = w.syncFilterType(of.Filters, rf.Filters, false, replica); err != nil {
return err
}
if err = w.syncFilterType(of.WhitelistFilters, rf.WhitelistFilters, true, replica); err != nil {
return err
}
if of.UserRules.String() != rf.UserRules.String() {
return replica.SetCustomRules(of.UserRules)
}
if of.Enabled != rf.Enabled || of.Interval != rf.Interval {
if err = replica.ToggleFiltering(of.Enabled, of.Interval); err != nil {
return err
}
}
}
return nil
}
func (w *worker) syncFilterType(of types.Filters, rFilters types.Filters, whitelist bool, replica client.Client) error {
fa, fu, fd := rFilters.Merge(of)
if err := replica.AddFilters(whitelist, fa...); err != nil {
return err
}
if err := replica.UpdateFilters(whitelist, fu...); err != nil {
return err
}
if len(fa) > 0 || len(fu) > 0 {
if err := replica.RefreshFilters(whitelist); err != nil {
return err
}
}
if err := replica.DeleteFilters(whitelist, fd...); err != nil {
return err
}
return nil
}
func (w *worker) syncRewrites(rl *zap.SugaredLogger, or *types.RewriteEntries, replica client.Client) error {
if w.cfg.Features.DNS.Rewrites {
replicaRewrites, err := replica.RewriteList()
if err != nil {
return err
}
a, r, d := replicaRewrites.Merge(or)
if err = replica.AddRewriteEntries(a...); err != nil {
return err
}
if err = replica.DeleteRewriteEntries(r...); err != nil {
return err
}
for _, dupl := range d {
rl.With("domain", dupl.Domain, "answer", dupl.Answer).Warn("Skipping duplicated rewrite from source")
}
}
return nil
}
func (w *worker) syncClients(oc *types.Clients, replica client.Client) error {
if w.cfg.Features.ClientSettings {
rc, err := replica.Clients()
if err != nil {
return err
}
a, u, r := rc.Merge(oc)
if err = replica.AddClients(a...); err != nil {
return err
}
if err = replica.UpdateClients(u...); err != nil {
return err
}
if err = replica.DeleteClients(r...); err != nil {
return err
}
}
return nil
}
func (w *worker) syncGeneralSettings(o *origin, rs *types.Status, replica client.Client) error {
if w.cfg.Features.GeneralSettings {
if o.status.ProtectionEnabled != rs.ProtectionEnabled {
if err := replica.ToggleProtection(o.status.ProtectionEnabled); err != nil {
return err
}
}
if rp, err := replica.Parental(); err != nil {
return err
} else if o.parental != rp {
if err = replica.ToggleParental(o.parental); err != nil {
return err
}
}
if rs, err := replica.SafeSearch(); err != nil {
return err
} else if o.safeSearch != rs {
if err = replica.ToggleSafeSearch(o.safeSearch); err != nil {
return err
}
}
if rs, err := replica.SafeBrowsing(); err != nil {
return err
} else if o.safeBrowsing != rs {
if err = replica.ToggleSafeBrowsing(o.safeBrowsing); err != nil {
return err
}
}
}
return nil
}
func (w *worker) syncConfigs(o *origin, rc client.Client) error {
if w.cfg.Features.QueryLogConfig {
qlc, err := rc.QueryLogConfig()
if err != nil {
return err
}
if !o.queryLogConfig.Equals(qlc) {
if err = rc.SetQueryLogConfig(o.queryLogConfig.Enabled, o.queryLogConfig.Interval, o.queryLogConfig.AnonymizeClientIP); err != nil {
return err
}
}
}
if w.cfg.Features.StatsConfig {
sc, err := rc.StatsConfig()
if err != nil {
return err
}
if o.statsConfig.Interval != sc.Interval {
if err = rc.SetStatsConfig(o.statsConfig.Interval); err != nil {
return err
}
}
}
return nil
}
func (w *worker) syncDNS(oal *types.AccessList, odc *types.DNSConfig, rc client.Client) error {
if w.cfg.Features.DNS.AccessLists {
al, err := rc.AccessList()
if err != nil {
return err
}
if !al.Equals(oal) {
if err = rc.SetAccessList(oal); err != nil {
return err
}
}
}
if w.cfg.Features.DNS.ServerConfig {
dc, err := rc.DNSConfig()
if err != nil {
return err
}
if !dc.Equals(odc) {
if err = rc.SetDNSConfig(odc); err != nil {
return err
}
}
}
return nil
}
func (w *worker) syncDHCPServer(osc *types.DHCPServerConfig, rc client.Client) error {
sc, err := rc.DHCPServerConfig()
if w.cfg.Features.DHCP.ServerConfig {
if err != nil {
return err
}
if !sc.Equals(osc) {
if err = rc.SetDHCPServerConfig(osc); err != nil {
return err
}
}
}
if w.cfg.Features.DHCP.StaticLeases {
a, r := sc.StaticLeases.Merge(osc.StaticLeases)
if err = rc.AddDHCPStaticLeases(a...); err != nil {
return err
}
if err = rc.DeleteDHCPStaticLeases(r...); err != nil {
return err
}
}
return nil
}
type origin struct {
status *types.Status
rewrites *types.RewriteEntries
services types.Services
filters *types.FilteringStatus
clients *types.Clients
queryLogConfig *types.QueryLogConfig
statsConfig *types.IntervalConfig
accessList *types.AccessList
dnsConfig *types.DNSConfig
dhcpServerConfig *types.DHCPServerConfig
parental bool
safeSearch bool
safeBrowsing bool
status *model.ServerStatus
rewrites *model.RewriteEntries
blockedServices *model.BlockedServicesArray
blockedServicesSchedule *model.BlockedServicesSchedule
filters *model.FilterStatus
clients *model.Clients
queryLogConfig *model.QueryLogConfig
statsConfig *model.StatsConfig
accessList *model.AccessList
dnsConfig *model.DNSConfig
dhcpServerConfig *model.DhcpStatus
parental bool
safeSearch *model.SafeSearchConfig
profileInfo *model.ProfileInfo
safeBrowsing bool
}

View File

@@ -4,8 +4,11 @@ import (
"errors"
"github.com/bakito/adguardhome-sync/pkg/client"
"github.com/bakito/adguardhome-sync/pkg/client/model"
clientmock "github.com/bakito/adguardhome-sync/pkg/mocks/client"
"github.com/bakito/adguardhome-sync/pkg/types"
"github.com/bakito/adguardhome-sync/pkg/utils"
"github.com/bakito/adguardhome-sync/pkg/versions"
gm "github.com/golang/mock/gomock"
"github.com/google/uuid"
. "github.com/onsi/ginkgo/v2"
@@ -18,6 +21,7 @@ var _ = Describe("Sync", func() {
cl *clientmock.MockClient
w *worker
te error
ac *actionContext
)
BeforeEach(func() {
@@ -45,251 +49,290 @@ var _ = Describe("Sync", func() {
StatsConfig: true,
QueryLogConfig: true,
},
Replicas: []types.AdGuardInstance{
{},
},
},
}
te = errors.New(uuid.NewString())
ac = &actionContext{
continueOnError: false,
rl: l,
origin: &origin{
profileInfo: &model.ProfileInfo{
Name: "origin",
Language: "en",
Theme: "auto",
},
status: &model.ServerStatus{},
safeSearch: &model.SafeSearchConfig{},
queryLogConfig: &model.QueryLogConfig{},
statsConfig: &model.StatsConfig{},
},
replicaStatus: &model.ServerStatus{},
client: cl,
replica: w.cfg.Replicas[0],
}
})
AfterEach(func() {
defer mockCtrl.Finish()
})
Context("worker", func() {
Context("syncRewrites", func() {
Context("actionDNSRewrites", func() {
var (
domain string
answer string
reO types.RewriteEntries
reR types.RewriteEntries
reO model.RewriteEntries
reR model.RewriteEntries
)
BeforeEach(func() {
domain = uuid.NewString()
answer = uuid.NewString()
reO = []types.RewriteEntry{{Domain: domain, Answer: answer}}
reR = []types.RewriteEntry{{Domain: domain, Answer: answer}}
reO = model.RewriteEntries{{Domain: utils.Ptr(domain), Answer: utils.Ptr(answer)}}
reR = model.RewriteEntries{{Domain: utils.Ptr(domain), Answer: utils.Ptr(answer)}}
})
It("should have no changes (empty slices)", func() {
ac.origin.rewrites = &reO
cl.EXPECT().RewriteList().Return(&reR, nil)
cl.EXPECT().AddRewriteEntries()
cl.EXPECT().DeleteRewriteEntries()
err := w.syncRewrites(l, &reO, cl)
err := actionDNSRewrites(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should add one rewrite entry", func() {
reR = []types.RewriteEntry{}
reR = []model.RewriteEntry{}
ac.origin.rewrites = &reO
cl.EXPECT().RewriteList().Return(&reR, nil)
cl.EXPECT().AddRewriteEntries(reO[0])
cl.EXPECT().DeleteRewriteEntries()
err := w.syncRewrites(l, &reO, cl)
err := actionDNSRewrites(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should remove one rewrite entry", func() {
reO = []types.RewriteEntry{}
reO = []model.RewriteEntry{}
ac.origin.rewrites = &reO
cl.EXPECT().RewriteList().Return(&reR, nil)
cl.EXPECT().AddRewriteEntries()
cl.EXPECT().DeleteRewriteEntries(reR[0])
err := w.syncRewrites(l, &reO, cl)
err := actionDNSRewrites(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should remove one rewrite entry", func() {
reO = []types.RewriteEntry{}
reO = []model.RewriteEntry{}
ac.origin.rewrites = &reO
cl.EXPECT().RewriteList().Return(&reR, nil)
cl.EXPECT().AddRewriteEntries()
cl.EXPECT().DeleteRewriteEntries(reR[0])
err := w.syncRewrites(l, &reO, cl)
err := actionDNSRewrites(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should return error when error on RewriteList()", func() {
ac.origin.rewrites = &reO
cl.EXPECT().RewriteList().Return(nil, te)
err := w.syncRewrites(l, &reO, cl)
err := actionDNSRewrites(ac)
Ω(err).Should(HaveOccurred())
})
It("should return error when error on AddRewriteEntries()", func() {
ac.origin.rewrites = &reO
cl.EXPECT().RewriteList().Return(&reR, nil)
cl.EXPECT().DeleteRewriteEntries()
cl.EXPECT().AddRewriteEntries().Return(te)
err := w.syncRewrites(l, &reO, cl)
err := actionDNSRewrites(ac)
Ω(err).Should(HaveOccurred())
})
It("should return error when error on DeleteRewriteEntries()", func() {
ac.origin.rewrites = &reO
cl.EXPECT().RewriteList().Return(&reR, nil)
cl.EXPECT().AddRewriteEntries()
cl.EXPECT().DeleteRewriteEntries().Return(te)
err := w.syncRewrites(l, &reO, cl)
err := actionDNSRewrites(ac)
Ω(err).Should(HaveOccurred())
})
})
Context("syncClients", func() {
Context("actionClientSettings", func() {
var (
clO *types.Clients
clR *types.Clients
clR *model.Clients
name string
)
BeforeEach(func() {
name = uuid.NewString()
clO = &types.Clients{Clients: []types.Client{{Name: name}}}
clR = &types.Clients{Clients: []types.Client{{Name: name}}}
ac.origin.clients = &model.Clients{Clients: &model.ClientsArray{{Name: utils.Ptr(name)}}}
clR = &model.Clients{Clients: &model.ClientsArray{{Name: utils.Ptr(name)}}}
})
It("should have no changes (empty slices)", func() {
cl.EXPECT().Clients().Return(clR, nil)
cl.EXPECT().AddClients()
cl.EXPECT().UpdateClients()
cl.EXPECT().DeleteClients()
err := w.syncClients(clO, cl)
err := actionClientSettings(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should add one client", func() {
clR.Clients = []types.Client{}
clR.Clients = &model.ClientsArray{}
cl.EXPECT().Clients().Return(clR, nil)
cl.EXPECT().AddClients(clO.Clients[0])
cl.EXPECT().UpdateClients()
cl.EXPECT().DeleteClients()
err := w.syncClients(clO, cl)
cl.EXPECT().AddClient(&(*ac.origin.clients.Clients)[0])
err := actionClientSettings(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should update one client", func() {
clR.Clients[0].Disallowed = true
(*clR.Clients)[0].FilteringEnabled = utils.Ptr(true)
cl.EXPECT().Clients().Return(clR, nil)
cl.EXPECT().AddClients()
cl.EXPECT().UpdateClients(clO.Clients[0])
cl.EXPECT().DeleteClients()
err := w.syncClients(clO, cl)
cl.EXPECT().UpdateClient(&(*ac.origin.clients.Clients)[0])
err := actionClientSettings(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should delete one client", func() {
clO.Clients = []types.Client{}
ac.origin.clients.Clients = &model.ClientsArray{}
cl.EXPECT().Clients().Return(clR, nil)
cl.EXPECT().AddClients()
cl.EXPECT().UpdateClients()
cl.EXPECT().DeleteClients(clR.Clients[0])
err := w.syncClients(clO, cl)
cl.EXPECT().DeleteClient(&(*clR.Clients)[0])
err := actionClientSettings(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should return error when error on Clients()", func() {
cl.EXPECT().Clients().Return(nil, te)
err := w.syncClients(clO, cl)
Ω(err).Should(HaveOccurred())
})
It("should return error when error on AddClients()", func() {
cl.EXPECT().Clients().Return(clR, nil)
cl.EXPECT().AddClients().Return(te)
err := w.syncClients(clO, cl)
Ω(err).Should(HaveOccurred())
})
It("should return error when error on UpdateClients()", func() {
cl.EXPECT().Clients().Return(clR, nil)
cl.EXPECT().AddClients()
cl.EXPECT().UpdateClients().Return(te)
err := w.syncClients(clO, cl)
Ω(err).Should(HaveOccurred())
})
It("should return error when error on DeleteClients()", func() {
cl.EXPECT().Clients().Return(clR, nil)
cl.EXPECT().AddClients()
cl.EXPECT().UpdateClients()
cl.EXPECT().DeleteClients().Return(te)
err := w.syncClients(clO, cl)
err := actionClientSettings(ac)
Ω(err).Should(HaveOccurred())
})
})
Context("syncGeneralSettings", func() {
var (
o *origin
rs *types.Status
)
BeforeEach(func() {
o = &origin{
status: &types.Status{},
}
rs = &types.Status{}
})
Context("actionParental", func() {
It("should have no changes", func() {
cl.EXPECT().Parental()
cl.EXPECT().SafeSearch()
cl.EXPECT().SafeBrowsing()
err := w.syncGeneralSettings(o, rs, cl)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have protection enabled changes", func() {
o.status.ProtectionEnabled = true
cl.EXPECT().ToggleProtection(true)
cl.EXPECT().Parental()
cl.EXPECT().SafeSearch()
cl.EXPECT().SafeBrowsing()
err := w.syncGeneralSettings(o, rs, cl)
err := actionParental(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have parental enabled changes", func() {
o.parental = true
ac.origin.parental = true
cl.EXPECT().Parental()
cl.EXPECT().ToggleParental(true)
cl.EXPECT().SafeSearch()
cl.EXPECT().SafeBrowsing()
err := w.syncGeneralSettings(o, rs, cl)
err := actionParental(ac)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("actionProtection", func() {
It("should have no changes", func() {
err := actionProtection(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have protection enabled changes", func() {
ac.origin.status.ProtectionEnabled = true
cl.EXPECT().ToggleProtection(true)
err := actionProtection(ac)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("actionSafeSearchConfig", func() {
It("should have no changes", func() {
cl.EXPECT().SafeSearchConfig().Return(ac.origin.safeSearch, nil)
err := actionSafeSearchConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have safeSearch enabled changes", func() {
o.safeSearch = true
cl.EXPECT().Parental()
cl.EXPECT().SafeSearch()
cl.EXPECT().ToggleSafeSearch(true)
cl.EXPECT().SafeBrowsing()
err := w.syncGeneralSettings(o, rs, cl)
ac.origin.safeSearch = &model.SafeSearchConfig{Enabled: utils.Ptr(true)}
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil)
cl.EXPECT().SetSafeSearchConfig(ac.origin.safeSearch)
err := actionSafeSearchConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have Duckduckgo safeSearch enabled changed", func() {
ac.origin.safeSearch = &model.SafeSearchConfig{Duckduckgo: utils.Ptr(true)}
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{Google: utils.Ptr(true)}, nil)
cl.EXPECT().SetSafeSearchConfig(ac.origin.safeSearch)
err := actionSafeSearchConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("actionProfileInfo", func() {
It("should have no changes", func() {
cl.EXPECT().ProfileInfo().Return(ac.origin.profileInfo, nil)
err := actionProfileInfo(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have profileInfo language changed", func() {
ac.origin.profileInfo.Language = "de"
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{Name: "replica", Language: "en"}, nil)
cl.EXPECT().SetProfileInfo(&model.ProfileInfo{
Language: "de",
Name: "replica",
Theme: "auto",
})
err := actionProfileInfo(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should not sync profileInfo if language is not set", func() {
ac.origin.profileInfo.Language = ""
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{Name: "replica", Language: "en", Theme: "auto"}, nil)
cl.EXPECT().SetProfileInfo(ac.origin.profileInfo).Times(0)
err := actionProfileInfo(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should not sync profileInfo if theme is not set", func() {
ac.origin.profileInfo.Theme = ""
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{Name: "replica", Language: "en", Theme: "auto"}, nil)
cl.EXPECT().SetProfileInfo(ac.origin.profileInfo).Times(0)
err := actionProfileInfo(ac)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("actionSafeBrowsing", func() {
It("should have no changes", func() {
cl.EXPECT().SafeBrowsing()
err := actionSafeBrowsing(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have safeBrowsing enabled changes", func() {
o.safeBrowsing = true
cl.EXPECT().Parental()
cl.EXPECT().SafeSearch()
ac.origin.safeBrowsing = true
cl.EXPECT().SafeBrowsing()
cl.EXPECT().ToggleSafeBrowsing(true)
err := w.syncGeneralSettings(o, rs, cl)
err := actionSafeBrowsing(ac)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("actionQueryLogConfig", func() {
var qlc *model.QueryLogConfig
BeforeEach(func() {
qlc = &model.QueryLogConfig{}
})
It("should have no changes", func() {
cl.EXPECT().QueryLogConfig().Return(qlc, nil)
err := actionQueryLogConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have QueryLogConfig changes", func() {
var interval model.QueryLogConfigInterval = 123
ac.origin.queryLogConfig.Interval = &interval
cl.EXPECT().QueryLogConfig().Return(qlc, nil)
cl.EXPECT().SetQueryLogConfig(&model.QueryLogConfig{AnonymizeClientIp: nil, Interval: &interval, Enabled: nil})
err := actionQueryLogConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("syncConfigs", func() {
var (
o *origin
qlc *types.QueryLogConfig
sc *types.IntervalConfig
)
var sc *model.StatsConfig
BeforeEach(func() {
o = &origin{
queryLogConfig: &types.QueryLogConfig{},
statsConfig: &types.IntervalConfig{},
}
qlc = &types.QueryLogConfig{}
sc = &types.IntervalConfig{}
sc = &model.StatsConfig{}
})
It("should have no changes", func() {
cl.EXPECT().QueryLogConfig().Return(qlc, nil)
cl.EXPECT().StatsConfig().Return(sc, nil)
err := w.syncConfigs(o, cl)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have QueryLogConfig changes", func() {
o.queryLogConfig.Interval = 123
cl.EXPECT().QueryLogConfig().Return(qlc, nil)
cl.EXPECT().SetQueryLogConfig(false, 123.0, false)
cl.EXPECT().StatsConfig().Return(sc, nil)
err := w.syncConfigs(o, cl)
err := actionStatsConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have StatsConfig changes", func() {
o.statsConfig.Interval = 123
cl.EXPECT().QueryLogConfig().Return(qlc, nil)
var interval model.StatsConfigInterval = 123
ac.origin.statsConfig.Interval = &interval
cl.EXPECT().StatsConfig().Return(sc, nil)
cl.EXPECT().SetStatsConfig(123.0)
err := w.syncConfigs(o, cl)
cl.EXPECT().SetStatsConfig(&model.StatsConfig{Interval: &interval})
err := actionStatsConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("statusWithSetup", func() {
var (
status *types.Status
status *model.ServerStatus
inst types.AdGuardInstance
)
BeforeEach(func() {
status = &types.Status{}
status = &model.ServerStatus{}
inst = types.AdGuardInstance{
AutoSetup: true,
}
@@ -316,110 +359,155 @@ var _ = Describe("Sync", func() {
Ω(st).Should(BeNil())
})
})
Context("syncServices", func() {
var (
os types.Services
rs types.Services
)
Context("actionBlockedServices", func() {
var rbs *model.BlockedServicesArray
BeforeEach(func() {
os = []string{"foo"}
rs = []string{"foo"}
ac.origin.blockedServices = &model.BlockedServicesArray{"foo"}
rbs = &model.BlockedServicesArray{"foo"}
})
It("should have no changes", func() {
cl.EXPECT().Services().Return(rs, nil)
err := w.syncServices(os, cl)
cl.EXPECT().BlockedServices().Return(rbs, nil)
err := actionBlockedServices(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have services changes", func() {
os = []string{"bar"}
cl.EXPECT().Services().Return(rs, nil)
cl.EXPECT().SetServices(os)
err := w.syncServices(os, cl)
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() {
ac.origin.blockedServicesSchedule = &model.BlockedServicesSchedule{}
rbss = &model.BlockedServicesSchedule{}
})
It("should have no changes", func() {
cl.EXPECT().BlockedServicesSchedule().Return(ac.origin.blockedServicesSchedule, nil)
err := actionBlockedServicesSchedule(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have blockedServices schedule changes", func() {
ac.origin.blockedServicesSchedule = &model.BlockedServicesSchedule{Ids: utils.Ptr([]string{"bar"})}
cl.EXPECT().BlockedServicesSchedule().Return(rbss, nil)
cl.EXPECT().SetBlockedServicesSchedule(ac.origin.blockedServicesSchedule)
err := actionBlockedServicesSchedule(ac)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("syncFilters", func() {
var (
of *types.FilteringStatus
rf *types.FilteringStatus
)
var rf *model.FilterStatus
BeforeEach(func() {
of = &types.FilteringStatus{}
rf = &types.FilteringStatus{}
ac.origin.filters = &model.FilterStatus{}
rf = &model.FilterStatus{}
})
It("should have no changes", func() {
cl.EXPECT().Filtering().Return(rf, nil)
cl.EXPECT().AddFilters(false)
cl.EXPECT().UpdateFilters(false)
cl.EXPECT().DeleteFilters(false)
cl.EXPECT().AddFilters(true)
cl.EXPECT().UpdateFilters(true)
cl.EXPECT().DeleteFilters(true)
err := w.syncFilters(of, cl)
err := actionFilters(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have changes user roles", func() {
of.UserRules = []string{"foo"}
ac.origin.filters.UserRules = utils.Ptr([]string{"foo"})
cl.EXPECT().Filtering().Return(rf, nil)
cl.EXPECT().AddFilters(false)
cl.EXPECT().UpdateFilters(false)
cl.EXPECT().DeleteFilters(false)
cl.EXPECT().AddFilters(true)
cl.EXPECT().UpdateFilters(true)
cl.EXPECT().DeleteFilters(true)
cl.EXPECT().SetCustomRules(of.UserRules)
err := w.syncFilters(of, cl)
cl.EXPECT().SetCustomRules(ac.origin.filters.UserRules)
err := actionFilters(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have changed filtering config", func() {
of.Enabled = true
of.Interval = 123
ac.origin.filters.Enabled = utils.Ptr(true)
ac.origin.filters.Interval = utils.Ptr(123)
cl.EXPECT().Filtering().Return(rf, nil)
cl.EXPECT().AddFilters(false)
cl.EXPECT().UpdateFilters(false)
cl.EXPECT().DeleteFilters(false)
cl.EXPECT().AddFilters(true)
cl.EXPECT().UpdateFilters(true)
cl.EXPECT().DeleteFilters(true)
cl.EXPECT().ToggleFiltering(of.Enabled, of.Interval)
err := w.syncFilters(of, cl)
cl.EXPECT().ToggleFiltering(*ac.origin.filters.Enabled, *ac.origin.filters.Interval)
err := actionFilters(ac)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("syncDNS", func() {
var (
oal *types.AccessList
ral *types.AccessList
odc *types.DNSConfig
rdc *types.DNSConfig
)
Context("actionDNSAccessLists", func() {
var ral *model.AccessList
BeforeEach(func() {
oal = &types.AccessList{}
ral = &types.AccessList{}
odc = &types.DNSConfig{}
rdc = &types.DNSConfig{}
ac.origin.accessList = &model.AccessList{}
ral = &model.AccessList{}
})
It("should have no changes", func() {
cl.EXPECT().AccessList().Return(ral, nil)
cl.EXPECT().DNSConfig().Return(rdc, nil)
err := w.syncDNS(oal, odc, cl)
err := actionDNSAccessLists(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have access list changes", func() {
ral.BlockedHosts = []string{"foo"}
ral.BlockedHosts = utils.Ptr([]string{"foo"})
cl.EXPECT().AccessList().Return(ral, nil)
cl.EXPECT().SetAccessList(ac.origin.accessList)
err := actionDNSAccessLists(ac)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("actionDNSServerConfig", func() {
var rdc *model.DNSConfig
BeforeEach(func() {
ac.origin.dnsConfig = &model.DNSConfig{}
rdc = &model.DNSConfig{}
})
It("should have no changes", func() {
cl.EXPECT().DNSConfig().Return(rdc, nil)
cl.EXPECT().SetAccessList(oal)
err := w.syncDNS(oal, odc, cl)
err := actionDNSServerConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have dns config changes", func() {
rdc.Bootstraps = []string{"foo"}
cl.EXPECT().AccessList().Return(ral, nil)
rdc.BootstrapDns = utils.Ptr([]string{"foo"})
cl.EXPECT().DNSConfig().Return(rdc, nil)
cl.EXPECT().SetDNSConfig(odc)
err := w.syncDNS(oal, odc, cl)
cl.EXPECT().SetDNSConfig(ac.origin.dnsConfig)
err := actionDNSServerConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("actionDHCPServerConfig", func() {
var rsc *model.DhcpStatus
BeforeEach(func() {
ac.origin.dhcpServerConfig = &model.DhcpStatus{V4: &model.DhcpConfigV4{
GatewayIp: utils.Ptr("1.2.3.4"),
RangeStart: utils.Ptr("1.2.3.5"),
RangeEnd: utils.Ptr("1.2.3.6"),
SubnetMask: utils.Ptr("255.255.255.0"),
}}
rsc = &model.DhcpStatus{}
w.cfg.Features.DHCP.StaticLeases = false
})
It("should have no changes", func() {
rsc.V4 = ac.origin.dhcpServerConfig.V4
cl.EXPECT().DhcpConfig().Return(rsc, nil)
err := actionDHCPServerConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have changes", func() {
rsc.Enabled = utils.Ptr(true)
cl.EXPECT().DhcpConfig().Return(rsc, nil)
cl.EXPECT().SetDhcpConfig(ac.origin.dhcpServerConfig)
err := actionDHCPServerConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should use replica interface name", func() {
ac.replica.InterfaceName = "foo"
cl.EXPECT().DhcpConfig().Return(rsc, nil)
oscClone := ac.origin.dhcpServerConfig.Clone()
oscClone.InterfaceName = utils.Ptr("foo")
cl.EXPECT().SetDhcpConfig(oscClone)
err := actionDHCPServerConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
It("should enable the target dhcp server", func() {
ac.replica.DHCPServerEnabled = utils.Ptr(true)
cl.EXPECT().DhcpConfig().Return(rsc, nil)
oscClone := ac.origin.dhcpServerConfig.Clone()
oscClone.Enabled = utils.Ptr(true)
cl.EXPECT().SetDhcpConfig(oscClone)
err := actionDHCPServerConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
})
@@ -428,7 +516,7 @@ var _ = Describe("Sync", func() {
BeforeEach(func() {
w.cfg = &types.Config{
Origin: types.AdGuardInstance{},
Replica: types.AdGuardInstance{URL: "foo"},
Replica: &types.AdGuardInstance{URL: "foo"},
Features: types.Features{
DHCP: types.DHCP{
ServerConfig: true,
@@ -451,76 +539,111 @@ var _ = Describe("Sync", func() {
It("should have no changes", func() {
// origin
cl.EXPECT().Host()
cl.EXPECT().Status().Return(&types.Status{Version: minAghVersion}, nil)
cl.EXPECT().Status().Return(&model.ServerStatus{Version: versions.MinAgh}, nil)
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{}, nil)
cl.EXPECT().Parental()
cl.EXPECT().SafeSearch()
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil)
cl.EXPECT().SafeBrowsing()
cl.EXPECT().RewriteList().Return(&types.RewriteEntries{}, nil)
cl.EXPECT().Services()
cl.EXPECT().Filtering().Return(&types.FilteringStatus{}, nil)
cl.EXPECT().Clients().Return(&types.Clients{}, nil)
cl.EXPECT().QueryLogConfig().Return(&types.QueryLogConfig{}, nil)
cl.EXPECT().StatsConfig().Return(&types.IntervalConfig{}, nil)
cl.EXPECT().AccessList().Return(&types.AccessList{}, nil)
cl.EXPECT().DNSConfig().Return(&types.DNSConfig{}, nil)
cl.EXPECT().DHCPServerConfig().Return(&types.DHCPServerConfig{}, nil)
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().AccessList().Return(&model.AccessList{}, nil)
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
cl.EXPECT().DhcpConfig().Return(&model.DhcpStatus{}, nil)
// replica
cl.EXPECT().Host()
cl.EXPECT().Status().Return(&types.Status{Version: minAghVersion}, nil)
cl.EXPECT().Status().Return(&model.ServerStatus{Version: versions.MinAgh}, nil)
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{}, nil)
cl.EXPECT().Parental()
cl.EXPECT().SafeSearch()
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil)
cl.EXPECT().SafeBrowsing()
cl.EXPECT().QueryLogConfig().Return(&types.QueryLogConfig{}, nil)
cl.EXPECT().StatsConfig().Return(&types.IntervalConfig{}, nil)
cl.EXPECT().RewriteList().Return(&types.RewriteEntries{}, nil)
cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfig{}, nil)
cl.EXPECT().StatsConfig().Return(&model.StatsConfig{}, nil)
cl.EXPECT().RewriteList().Return(&model.RewriteEntries{}, nil)
cl.EXPECT().AddRewriteEntries()
cl.EXPECT().DeleteRewriteEntries()
cl.EXPECT().Filtering().Return(&types.FilteringStatus{}, nil)
cl.EXPECT().AddFilters(false)
cl.EXPECT().UpdateFilters(false)
cl.EXPECT().DeleteFilters(false)
cl.EXPECT().AddFilters(true)
cl.EXPECT().UpdateFilters(true)
cl.EXPECT().DeleteFilters(true)
cl.EXPECT().Services()
cl.EXPECT().Clients().Return(&types.Clients{}, nil)
cl.EXPECT().AddClients()
cl.EXPECT().UpdateClients()
cl.EXPECT().DeleteClients()
cl.EXPECT().AccessList().Return(&types.AccessList{}, nil)
cl.EXPECT().DNSConfig().Return(&types.DNSConfig{}, nil)
cl.EXPECT().DHCPServerConfig().Return(&types.DHCPServerConfig{}, nil)
cl.EXPECT().AddDHCPStaticLeases().Return(nil)
cl.EXPECT().DeleteDHCPStaticLeases().Return(nil)
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)
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
cl.EXPECT().DhcpConfig().Return(&model.DhcpStatus{}, nil)
w.sync()
})
It("should not sync DHCP", func() {
w.cfg.Features.DHCP.ServerConfig = false
w.cfg.Features.DHCP.StaticLeases = false
// origin
cl.EXPECT().Host()
cl.EXPECT().Status().Return(&model.ServerStatus{Version: versions.MinAgh}, nil)
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{}, nil)
cl.EXPECT().Parental()
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil)
cl.EXPECT().SafeBrowsing()
cl.EXPECT().RewriteList().Return(&model.RewriteEntries{}, nil)
cl.EXPECT().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().AccessList().Return(&model.AccessList{}, nil)
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
// replica
cl.EXPECT().Host()
cl.EXPECT().Status().Return(&model.ServerStatus{Version: versions.MinAgh}, nil)
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{}, nil)
cl.EXPECT().Parental()
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil)
cl.EXPECT().SafeBrowsing()
cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfig{}, nil)
cl.EXPECT().StatsConfig().Return(&model.StatsConfig{}, 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)
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
w.sync()
})
It("origin version is too small", func() {
// origin
cl.EXPECT().Host()
cl.EXPECT().Status().Return(&types.Status{Version: "v0.106.9"}, nil)
cl.EXPECT().Status().Return(&model.ServerStatus{Version: "v0.106.9"}, nil)
w.sync()
})
It("replica version is too small", func() {
// origin
cl.EXPECT().Host()
cl.EXPECT().Status().Return(&types.Status{Version: minAghVersion}, nil)
cl.EXPECT().Status().Return(&model.ServerStatus{Version: versions.MinAgh}, nil)
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{}, nil)
cl.EXPECT().Parental()
cl.EXPECT().SafeSearch()
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil)
cl.EXPECT().SafeBrowsing()
cl.EXPECT().RewriteList().Return(&types.RewriteEntries{}, nil)
cl.EXPECT().Services()
cl.EXPECT().Filtering().Return(&types.FilteringStatus{}, nil)
cl.EXPECT().Clients().Return(&types.Clients{}, nil)
cl.EXPECT().QueryLogConfig().Return(&types.QueryLogConfig{}, nil)
cl.EXPECT().StatsConfig().Return(&types.IntervalConfig{}, nil)
cl.EXPECT().AccessList().Return(&types.AccessList{}, nil)
cl.EXPECT().DNSConfig().Return(&types.DNSConfig{}, nil)
cl.EXPECT().DHCPServerConfig().Return(&types.DHCPServerConfig{}, nil)
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().AccessList().Return(&model.AccessList{}, nil)
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
cl.EXPECT().DhcpConfig().Return(&model.DhcpStatus{}, nil)
// replica
cl.EXPECT().Host()
cl.EXPECT().Status().Return(&types.Status{Version: "v0.106.9"}, nil)
cl.EXPECT().Status().Return(&model.ServerStatus{Version: "v0.106.9"}, nil)
w.sync()
})
})

View File

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

View File

@@ -1,80 +0,0 @@
package types
import (
"encoding/json"
"net"
"time"
)
// DHCPServerConfig dhcp server config
type DHCPServerConfig struct {
V4 *V4ServerConfJSON `json:"v4"`
V6 *V6ServerConfJSON `json:"v6"`
InterfaceName string `json:"interface_name"`
Enabled bool `json:"enabled"`
Leases Leases `json:"leases,omitempty"`
StaticLeases Leases `json:"static_leases,omitempty"`
}
// Equals dhcp server config equal check
func (c *DHCPServerConfig) Equals(o *DHCPServerConfig) bool {
a, _ := json.Marshal(c)
b, _ := json.Marshal(o)
return string(a) == string(b)
}
// V4ServerConfJSON v4 server conf
type V4ServerConfJSON struct {
GatewayIP net.IP `json:"gateway_ip"`
SubnetMask net.IP `json:"subnet_mask"`
RangeStart net.IP `json:"range_start"`
RangeEnd net.IP `json:"range_end"`
LeaseDuration uint32 `json:"lease_duration"`
}
// V6ServerConfJSON v6 server conf
type V6ServerConfJSON struct {
RangeStart net.IP `json:"range_start"`
RangeEnd net.IP `json:"range_end"`
LeaseDuration uint32 `json:"lease_duration"`
}
// Leases slice of leases type
type Leases []Lease
// Merge the leases
func (l Leases) Merge(other Leases) ([]Lease, []Lease) {
current := make(map[string]Lease)
var adds Leases
var removes Leases
for _, le := range l {
current[le.HWAddr] = le
}
for _, le := range other {
if _, ok := current[le.HWAddr]; ok {
delete(current, le.HWAddr)
} else {
adds = append(adds, le)
}
}
for _, rr := range current {
removes = append(removes, rr)
}
return adds, removes
}
// Lease contains the necessary information about a DHCP lease
type Lease struct {
HWAddr string `json:"mac"`
IP net.IP `json:"ip"`
Hostname string `json:"hostname"`
// Lease expiration time
// 1: static lease
Expiry time.Time `json:"expires"`
}

View File

@@ -1,67 +0,0 @@
package types
import (
"encoding/json"
"net"
"sort"
)
// DNSConfig dns config
type DNSConfig struct {
Upstreams []string `json:"upstream_dns,omitempty"`
UpstreamsFile string `json:"upstream_dns_file"`
Bootstraps []string `json:"bootstrap_dns,omitempty"`
ProtectionEnabled bool `json:"protection_enabled"`
RateLimit uint32 `json:"ratelimit"`
BlockingMode string `json:"blocking_mode,omitempty"`
BlockingIPv4 net.IP `json:"blocking_ipv4,omitempty"`
BlockingIPv6 net.IP `json:"blocking_ipv6"`
EDNSCSEnabled bool `json:"edns_cs_enabled"`
DNSSECEnabled bool `json:"dnssec_enabled"`
DisableIPv6 bool `json:"disable_ipv6"`
UpstreamMode string `json:"upstream_mode,omitempty"`
CacheSize uint32 `json:"cache_size"`
CacheMinTTL uint32 `json:"cache_ttl_min"`
CacheMaxTTL uint32 `json:"cache_ttl_max"`
ResolveClients bool `json:"resolve_clients"`
LocalPTRUpstreams []string `json:"local_ptr_upstreams,omitempty"`
}
// Equals dns config equal check
func (c *DNSConfig) Equals(o *DNSConfig) bool {
c.Sort()
o.Sort()
a, _ := json.Marshal(c)
b, _ := json.Marshal(o)
return string(a) == string(b)
}
// Sort sort dns config
func (c *DNSConfig) Sort() {
sort.Strings(c.Upstreams)
sort.Strings(c.Bootstraps)
sort.Strings(c.LocalPTRUpstreams)
}
// AccessList access list
type AccessList struct {
AllowedClients []string `json:"allowed_clients"`
DisallowedClients []string `json:"disallowed_clients"`
BlockedHosts []string `json:"blocked_hosts"`
}
// Equals access list equal check
func (al *AccessList) Equals(o *AccessList) bool {
return equals(al.AllowedClients, o.AllowedClients) &&
equals(al.DisallowedClients, o.DisallowedClients) &&
equals(al.BlockedHosts, o.BlockedHosts)
}
// Sort sort access list
func (al *AccessList) Sort() {
sort.Strings(al.AllowedClients)
sort.Strings(al.DisallowedClients)
sort.Strings(al.BlockedHosts)
}

View File

@@ -39,13 +39,13 @@ func (f *Features) LogDisabled(l *zap.SugaredLogger) {
features = append(features, "DHCP.StaticLeases")
}
if !f.DNS.AccessLists {
features = append(features, "DHCP.AccessLists")
features = append(features, "DNS.AccessLists")
}
if !f.DNS.ServerConfig {
features = append(features, "DHCP.ServerConfig")
features = append(features, "DNS.ServerConfig")
}
if !f.DNS.Rewrites {
features = append(features, "DHCP.Rewrites")
features = append(features, "DNS.Rewrites")
}
if !f.GeneralSettings {
features = append(features, "GeneralSettings")
@@ -60,7 +60,7 @@ func (f *Features) LogDisabled(l *zap.SugaredLogger) {
features = append(features, "ClientSettings")
}
if !f.Services {
features = append(features, "Services")
features = append(features, "BlockedServices")
}
if !f.Filters {
features = append(features, "Filters")

View File

@@ -1,10 +1,10 @@
package types
import (
"encoding/json"
"fmt"
"sort"
"strings"
"net/url"
"go.uber.org/zap"
)
const (
@@ -13,14 +13,18 @@ const (
)
// Config application configuration struct
// +k8s:deepcopy-gen=true
type Config struct {
Origin AdGuardInstance `json:"origin" yaml:"origin"`
Replica AdGuardInstance `json:"replica,omitempty" yaml:"replica,omitempty"`
Replicas []AdGuardInstance `json:"replicas,omitempty" yaml:"replicas,omitempty"`
Cron string `json:"cron,omitempty" yaml:"cron,omitempty"`
RunOnStart bool `json:"runOnStart,omitempty" yaml:"runOnStart,omitempty"`
API API `json:"api,omitempty" yaml:"api,omitempty"`
Features Features `json:"features,omitempty" yaml:"features,omitempty"`
Origin AdGuardInstance `json:"origin" yaml:"origin"`
Replica *AdGuardInstance `json:"replica,omitempty" yaml:"replica,omitempty"`
Replicas []AdGuardInstance `json:"replicas,omitempty" yaml:"replicas,omitempty"`
Cron string `json:"cron,omitempty" yaml:"cron,omitempty"`
RunOnStart bool `json:"runOnStart,omitempty" yaml:"runOnStart,omitempty"`
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"`
Features Features `json:"features,omitempty" yaml:"features,omitempty"`
LogLevel string `json:"logLevel,omitempty" yaml:"logLevel,omitempty" mapstructure:"LOG_LEVEL"`
}
// API configuration
@@ -28,13 +32,14 @@ type API struct {
Port int `json:"port,omitempty" yaml:"port,omitempty"`
Username string `json:"username,omitempty" yaml:"username,omitempty"`
Password string `json:"password,omitempty" yaml:"password,omitempty"`
DarkMode bool `json:"darkMode,omitempty" yaml:"darkMode,omitempty"`
}
// UniqueReplicas get unique replication instances
func (cfg *Config) UniqueReplicas() []AdGuardInstance {
dedup := make(map[string]AdGuardInstance)
if cfg.Replica.URL != "" {
dedup[cfg.Replica.Key()] = cfg.Replica
if cfg.Replica != nil && cfg.Replica.URL != "" {
dedup[cfg.Replica.Key()] = *cfg.Replica
}
for _, replica := range cfg.Replicas {
if replica.URL != "" {
@@ -52,14 +57,52 @@ func (cfg *Config) UniqueReplicas() []AdGuardInstance {
return r
}
// Log the current config
func (cfg *Config) Log(l *zap.SugaredLogger) {
c := cfg.DeepCopy()
c.Origin.Mask()
if c.Replica != nil {
if c.Replica.URL == "" {
c.Replica = nil
} else {
c.Replica.Mask()
}
}
for i := range c.Replicas {
c.Replicas[i].Mask()
}
l.With("config", c).Debug("Using config")
}
func (cfg *Config) Init() error {
if err := cfg.Origin.Init(); err != nil {
return err
}
for i := range cfg.Replicas {
replica := &cfg.Replicas[i]
if err := replica.Init(); err != nil {
return err
}
}
return nil
}
// AdGuardInstance AdguardHome config instance
// +k8s:deepcopy-gen=true
type AdGuardInstance struct {
URL string `json:"url" yaml:"url"`
WebURL string `json:"webURL" yaml:"webURL"`
APIPath string `json:"apiPath,omitempty" yaml:"apiPath,omitempty"`
Username string `json:"username,omitempty" yaml:"username,omitempty"`
Password string `json:"password,omitempty" yaml:"password,omitempty"`
Cookie string `json:"cookie,omitempty" yaml:"cookie,omitempty"`
InsecureSkipVerify bool `json:"insecureSkipVerify" yaml:"insecureSkipVerify"`
AutoSetup bool `json:"autoSetup" yaml:"autoSetup"`
InterfaceName string `json:"interfaceName,omitempty" yaml:"interfaceName,omitempty"`
DHCPServerEnabled *bool `json:"dhcpServerEnabled,omitempty" yaml:"dhcpServerEnabled,omitempty"`
Host string `json:"-" yaml:"-"`
WebHost string `json:"-" yaml:"-"`
}
// Key AdGuardInstance key
@@ -67,295 +110,44 @@ func (i *AdGuardInstance) Key() string {
return fmt.Sprintf("%s#%s", i.URL, i.APIPath)
}
// Mask maks username and password
func (i *AdGuardInstance) Mask() {
i.Username = mask(i.Username)
i.Password = mask(i.Password)
}
func (i *AdGuardInstance) Init() error {
u, err := url.Parse(i.URL)
if err != nil {
return err
}
i.Host = u.Host
if i.WebURL == "" {
i.WebHost = i.Host
i.WebURL = i.URL
} else {
u, err := url.Parse(i.WebURL)
if err != nil {
return err
}
i.WebHost = u.Host
}
return nil
}
func mask(s string) string {
if s == "" {
return "***"
}
return fmt.Sprintf("%v***%v", string(s[0]), string(s[len(s)-1]))
}
// Protection API struct
type Protection struct {
ProtectionEnabled bool `json:"protection_enabled"`
}
// Status API struct
type Status struct {
Protection
DNSAddresses []string `json:"dns_addresses"`
DNSPort int `json:"dns_port"`
HTTPPort int `json:"http_port"`
DhcpAvailable bool `json:"dhcp_available"`
Running bool `json:"running"`
Version string `json:"version"`
Language string `json:"language"`
}
// RewriteEntries list of RewriteEntry
type RewriteEntries []RewriteEntry
// Merge RewriteEntries
func (rwe *RewriteEntries) Merge(other *RewriteEntries) (RewriteEntries, RewriteEntries, RewriteEntries) {
current := make(map[string]RewriteEntry)
var adds RewriteEntries
var removes RewriteEntries
var duplicates RewriteEntries
processed := make(map[string]bool)
for _, rr := range *rwe {
if _, ok := processed[rr.Key()]; !ok {
current[rr.Key()] = rr
processed[rr.Key()] = true
} else {
// remove duplicate
removes = append(removes, rr)
}
}
for _, rr := range *other {
if _, ok := current[rr.Key()]; ok {
delete(current, rr.Key())
} else {
if _, ok := processed[rr.Key()]; !ok {
adds = append(adds, rr)
processed[rr.Key()] = true
} else {
// skip duplicate
duplicates = append(duplicates, rr)
}
}
}
for _, rr := range current {
removes = append(removes, rr)
}
return adds, removes, duplicates
}
// RewriteEntry API struct
type RewriteEntry struct {
Domain string `json:"domain"`
Answer string `json:"answer"`
}
// Key RewriteEntry key
func (re *RewriteEntry) Key() string {
return fmt.Sprintf("%s#%s", re.Domain, re.Answer)
}
// Filters list of Filter
type Filters []Filter
// Merge merge Filters
func (f Filters) Merge(other Filters) (Filters, Filters, Filters) {
current := make(map[string]Filter)
var adds Filters
var updates Filters
var removes Filters
for _, f := range f {
current[f.URL] = f
}
for i := range other {
rr := other[i]
if c, ok := current[rr.URL]; ok {
if !c.Equals(&rr) {
updates = append(updates, rr)
}
delete(current, rr.URL)
} else {
adds = append(adds, rr)
}
}
for _, rr := range current {
removes = append(removes, rr)
}
return adds, updates, removes
}
// Filter API struct
type Filter struct {
ID int `json:"id"`
Enabled bool `json:"enabled"`
URL string `json:"url"` // needed for add
Name string `json:"name"` // needed for add
RulesCount int `json:"rules_count"`
Whitelist bool `json:"whitelist"` // needed for add
}
// Equals Filter equal check
func (f *Filter) Equals(o *Filter) bool {
return f.Enabled == o.Enabled && f.URL == o.URL && f.Name == o.Name
}
// FilterUpdate API struct
type FilterUpdate struct {
URL string `json:"url"`
Data Filter `json:"data"`
Whitelist bool `json:"whitelist"`
}
// FilteringStatus API struct
type FilteringStatus struct {
FilteringConfig
Filters Filters `json:"filters"`
WhitelistFilters Filters `json:"whitelist_filters"`
UserRules UserRules `json:"user_rules"`
}
// UserRules API struct
type UserRules []string
// String toString of Users
func (ur UserRules) String() string {
return strings.Join(ur, "\n")
}
// EnableConfig API struct
type EnableConfig struct {
Enabled bool `json:"enabled"`
}
// IntervalConfig API struct
type IntervalConfig struct {
Interval float64 `json:"interval"`
}
// FilteringConfig API struct
type FilteringConfig struct {
EnableConfig
IntervalConfig
}
// QueryLogConfig API struct
type QueryLogConfig struct {
EnableConfig
IntervalConfig
AnonymizeClientIP bool `json:"anonymize_client_ip"`
}
// Equals QueryLogConfig equal check
func (qlc *QueryLogConfig) Equals(o *QueryLogConfig) bool {
return qlc.Enabled == o.Enabled && qlc.AnonymizeClientIP == o.AnonymizeClientIP && qlc.Interval == o.Interval
}
// RefreshFilter API struct
type RefreshFilter struct {
Whitelist bool `json:"whitelist"`
}
// Services API struct
type Services []string
// Sort sort Services
func (s Services) Sort() {
sort.Strings(s)
}
// Equals Services equal check
func (s Services) Equals(o Services) bool {
s.Sort()
o.Sort()
return equals(s, o)
}
// Clients API struct
type Clients struct {
Clients []Client `json:"clients"`
AutoClients []struct {
IP string `json:"ip"`
Name string `json:"name"`
Source string `json:"source"`
WhoisInfo struct{} `json:"whois_info"`
} `json:"auto_clients"`
SupportedTags []string `json:"supported_tags"`
}
// Client API struct
type Client struct {
Ids []string `json:"ids,omitempty"`
Tags []string `json:"tags,omitempty"`
BlockedServices []string `json:"blocked_services,omitempty"`
Upstreams []string `json:"upstreams,omitempty"`
UseGlobalSettings bool `json:"use_global_settings"`
UseGlobalBlockedServices bool `json:"use_global_blocked_services"`
Name string `json:"name"`
FilteringEnabled bool `json:"filtering_enabled"`
ParentalEnabled bool `json:"parental_enabled"`
SafesearchEnabled bool `json:"safesearch_enabled"`
SafebrowsingEnabled bool `json:"safebrowsing_enabled"`
Disallowed bool `json:"disallowed"`
DisallowedRule string `json:"disallowed_rule"`
}
// Sort sort clients
func (cl *Client) Sort() {
sort.Strings(cl.Ids)
sort.Strings(cl.Tags)
sort.Strings(cl.BlockedServices)
sort.Strings(cl.Upstreams)
}
// Equals Clients equal check
func (cl *Client) Equals(o *Client) bool {
cl.Sort()
o.Sort()
a, _ := json.Marshal(cl)
b, _ := json.Marshal(o)
return string(a) == string(b)
}
// Merge merge Clients
func (clients *Clients) Merge(other *Clients) ([]Client, []Client, []Client) {
current := make(map[string]Client)
for _, client := range clients.Clients {
current[client.Name] = client
}
expected := make(map[string]Client)
for _, client := range other.Clients {
expected[client.Name] = client
}
var adds []Client
var removes []Client
var updates []Client
for _, cl := range expected {
if oc, ok := current[cl.Name]; ok {
if !cl.Equals(&oc) {
updates = append(updates, cl)
}
delete(current, cl.Name)
} else {
adds = append(adds, cl)
}
}
for _, rr := range current {
removes = append(removes, rr)
}
return adds, updates, removes
}
// ClientUpdate API struct
type ClientUpdate struct {
Name string `json:"name"`
Data Client `json:"data"`
}
func equals(a []string, b []string) bool {
if len(a) != len(b) {
return false
}
for i, v := range a {
if v != b[i] {
return false
}
}
return true
}
// InstallConfig AdguardHome install config
type InstallConfig struct {
Web InstallPort `json:"web"`

View File

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

View File

@@ -1,342 +1,38 @@
package types_test
import (
"encoding/json"
"io/ioutil"
"github.com/bakito/adguardhome-sync/pkg/types"
"github.com/google/uuid"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("Types", func() {
var (
url string
apiPath string
)
var _ = Describe("AdGuardInstance", func() {
var inst types.AdGuardInstance
BeforeEach(func() {
url = "https://" + uuid.NewString()
apiPath = "/" + uuid.NewString()
inst = types.AdGuardInstance{}
})
Context("FilteringStatus", func() {
It("should correctly parse json", func() {
b, err := ioutil.ReadFile("../..//testdata/filtering-status.json")
fs := &types.FilteringStatus{}
Ω(err).ShouldNot(HaveOccurred())
err = json.Unmarshal(b, fs)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("Filters", func() {
Context("Merge", func() {
var (
originFilters types.Filters
replicaFilters types.Filters
)
BeforeEach(func() {
originFilters = types.Filters{}
replicaFilters = types.Filters{}
})
It("should add a missing filter", func() {
originFilters = append(originFilters, types.Filter{URL: url})
a, u, d := replicaFilters.Merge(originFilters)
Ω(a).Should(HaveLen(1))
Ω(u).Should(BeEmpty())
Ω(d).Should(BeEmpty())
Ω(a[0].URL).Should(Equal(url))
})
It("should remove additional filter", func() {
replicaFilters = append(replicaFilters, types.Filter{URL: url})
a, u, d := replicaFilters.Merge(originFilters)
Ω(a).Should(BeEmpty())
Ω(u).Should(BeEmpty())
Ω(d).Should(HaveLen(1))
Ω(d[0].URL).Should(Equal(url))
})
It("should update existing filter when enabled differs", func() {
enabled := true
originFilters = append(originFilters, types.Filter{URL: url, Enabled: enabled})
replicaFilters = append(replicaFilters, types.Filter{URL: url, Enabled: !enabled})
a, u, d := replicaFilters.Merge(originFilters)
Ω(a).Should(BeEmpty())
Ω(u).Should(HaveLen(1))
Ω(d).Should(BeEmpty())
Ω(u[0].Enabled).Should(Equal(enabled))
})
It("should update existing filter when name differs", func() {
name1 := uuid.NewString()
name2 := uuid.NewString()
originFilters = append(originFilters, types.Filter{URL: url, Name: name1})
replicaFilters = append(replicaFilters, types.Filter{URL: url, Name: name2})
a, u, d := replicaFilters.Merge(originFilters)
Ω(a).Should(BeEmpty())
Ω(u).Should(HaveLen(1))
Ω(d).Should(BeEmpty())
Ω(u[0].Name).Should(Equal(name1))
})
It("should have no changes", func() {
originFilters = append(originFilters, types.Filter{URL: url})
replicaFilters = append(replicaFilters, types.Filter{URL: url})
a, u, d := replicaFilters.Merge(originFilters)
Ω(a).Should(BeEmpty())
Ω(u).Should(BeEmpty())
Ω(d).Should(BeEmpty())
})
})
})
Context("AdGuardInstance", func() {
It("should build a key with url and api apiPath", func() {
i := &types.AdGuardInstance{URL: url, APIPath: apiPath}
Ω(i.Key()).Should(Equal(url + "#" + apiPath))
})
})
Context("RewriteEntry", func() {
It("should build a key with url and api apiPath", func() {
domain := uuid.NewString()
answer := uuid.NewString()
re := &types.RewriteEntry{Domain: domain, Answer: answer}
Ω(re.Key()).Should(Equal(domain + "#" + answer))
})
})
Context("QueryLogConfig", func() {
Context("Equal", func() {
var (
a *types.QueryLogConfig
b *types.QueryLogConfig
)
BeforeEach(func() {
a = &types.QueryLogConfig{}
b = &types.QueryLogConfig{}
})
It("should be equal", func() {
a.Enabled = true
a.Interval = 1
a.AnonymizeClientIP = true
b.Enabled = true
b.Interval = 1
b.AnonymizeClientIP = true
Ω(a.Equals(b)).Should(BeTrue())
})
It("should not be equal when enabled differs", func() {
a.Enabled = true
b.Enabled = false
Ω(a.Equals(b)).ShouldNot(BeTrue())
})
It("should not be equal when interval differs", func() {
a.Interval = 1
b.Interval = 2
Ω(a.Equals(b)).ShouldNot(BeTrue())
})
It("should not be equal when anonymizeClientIP differs", func() {
a.AnonymizeClientIP = true
b.AnonymizeClientIP = false
Ω(a.Equals(b)).ShouldNot(BeTrue())
})
})
})
Context("RewriteEntries", func() {
Context("Merge", func() {
var (
originRE types.RewriteEntries
replicaRE types.RewriteEntries
domain string
)
BeforeEach(func() {
originRE = types.RewriteEntries{}
replicaRE = types.RewriteEntries{}
domain = uuid.NewString()
})
It("should add a missing rewrite entry", func() {
originRE = append(originRE, types.RewriteEntry{Domain: domain})
a, r, d := replicaRE.Merge(&originRE)
Ω(a).Should(HaveLen(1))
Ω(r).Should(BeEmpty())
Ω(d).Should(BeEmpty())
Ω(a[0].Domain).Should(Equal(domain))
})
It("should remove additional ewrite entry", func() {
replicaRE = append(replicaRE, types.RewriteEntry{Domain: domain})
a, r, d := replicaRE.Merge(&originRE)
Ω(a).Should(BeEmpty())
Ω(r).Should(HaveLen(1))
Ω(d).Should(BeEmpty())
Ω(r[0].Domain).Should(Equal(domain))
})
It("should have no changes", func() {
originRE = append(originRE, types.RewriteEntry{Domain: domain})
replicaRE = append(replicaRE, types.RewriteEntry{Domain: domain})
a, r, d := replicaRE.Merge(&originRE)
Ω(a).Should(BeEmpty())
Ω(r).Should(BeEmpty())
Ω(d).Should(BeEmpty())
})
It("should remove target duplicate", func() {
originRE = append(originRE, types.RewriteEntry{Domain: domain})
replicaRE = append(replicaRE, types.RewriteEntry{Domain: domain})
replicaRE = append(replicaRE, types.RewriteEntry{Domain: domain})
a, r, d := replicaRE.Merge(&originRE)
Ω(a).Should(BeEmpty())
Ω(r).Should(HaveLen(1))
Ω(d).Should(BeEmpty())
})
It("should remove target duplicate", func() {
originRE = append(originRE, types.RewriteEntry{Domain: domain})
originRE = append(originRE, types.RewriteEntry{Domain: domain})
replicaRE = append(replicaRE, types.RewriteEntry{Domain: domain})
a, r, d := replicaRE.Merge(&originRE)
Ω(a).Should(BeEmpty())
Ω(r).Should(BeEmpty())
Ω(d).Should(HaveLen(1))
})
})
})
Context("UserRules", func() {
It("should join the rules correctly", func() {
r1 := uuid.NewString()
r2 := uuid.NewString()
ur := types.UserRules([]string{r1, r2})
Ω(ur.String()).Should(Equal(r1 + "\n" + r2))
})
})
Context("Config", func() {
var cfg *types.Config
Context("Init", func() {
BeforeEach(func() {
cfg = &types.Config{}
inst.URL = "https://localhost:3000"
})
Context("UniqueReplicas", func() {
It("should be empty if noting defined", func() {
r := cfg.UniqueReplicas()
Ω(r).Should(BeEmpty())
})
It("should be empty if replica url is not set", func() {
cfg.Replica = types.AdGuardInstance{URL: ""}
r := cfg.UniqueReplicas()
Ω(r).Should(BeEmpty())
})
It("should be empty if replicas url is not set", func() {
cfg.Replicas = []types.AdGuardInstance{{URL: ""}}
r := cfg.UniqueReplicas()
Ω(r).Should(BeEmpty())
})
It("should return only one replica if same url and apiPath", func() {
cfg.Replica = types.AdGuardInstance{URL: url, APIPath: apiPath}
cfg.Replicas = []types.AdGuardInstance{{URL: url, APIPath: apiPath}, {URL: url, APIPath: apiPath}}
r := cfg.UniqueReplicas()
Ω(r).Should(HaveLen(1))
})
It("should return 3 one replicas if urls are different", func() {
cfg.Replica = types.AdGuardInstance{URL: url, APIPath: apiPath}
cfg.Replicas = []types.AdGuardInstance{{URL: url + "1", APIPath: apiPath}, {URL: url, APIPath: apiPath + "1"}}
r := cfg.UniqueReplicas()
Ω(r).Should(HaveLen(3))
})
It("should set default api apiPath if not set", func() {
cfg.Replica = types.AdGuardInstance{URL: url}
cfg.Replicas = []types.AdGuardInstance{{URL: url + "1"}}
r := cfg.UniqueReplicas()
Ω(r).Should(HaveLen(2))
Ω(r[0].APIPath).Should(Equal(types.DefaultAPIPath))
Ω(r[1].APIPath).Should(Equal(types.DefaultAPIPath))
})
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("Clients", func() {
Context("Merge", func() {
var (
originClients *types.Clients
replicaClients types.Clients
name string
)
BeforeEach(func() {
originClients = &types.Clients{}
replicaClients = types.Clients{}
name = uuid.NewString()
})
It("should add a missing client", func() {
originClients.Clients = append(originClients.Clients, types.Client{Name: name})
a, u, d := replicaClients.Merge(originClients)
Ω(a).Should(HaveLen(1))
Ω(u).Should(BeEmpty())
Ω(d).Should(BeEmpty())
Ω(a[0].Name).Should(Equal(name))
})
It("should remove additional client", func() {
replicaClients.Clients = append(replicaClients.Clients, types.Client{Name: name})
a, u, d := replicaClients.Merge(originClients)
Ω(a).Should(BeEmpty())
Ω(u).Should(BeEmpty())
Ω(d).Should(HaveLen(1))
Ω(d[0].Name).Should(Equal(name))
})
It("should update existing client when name differs", func() {
disallowed := true
originClients.Clients = append(originClients.Clients, types.Client{Name: name, Disallowed: disallowed})
replicaClients.Clients = append(replicaClients.Clients, types.Client{Name: name, Disallowed: !disallowed})
a, u, d := replicaClients.Merge(originClients)
Ω(a).Should(BeEmpty())
Ω(u).Should(HaveLen(1))
Ω(d).Should(BeEmpty())
Ω(u[0].Disallowed).Should(Equal(disallowed))
})
})
})
Context("Services", func() {
Context("Equals", func() {
It("should be equal", func() {
s1 := types.Services([]string{"a", "b"})
s2 := types.Services([]string{"b", "a"})
Ω(s1.Equals(s2)).Should(BeTrue())
})
It("should not be equal different values", func() {
s1 := types.Services([]string{"a", "b"})
s2 := types.Services([]string{"B", "a"})
Ω(s1.Equals(s2)).ShouldNot(BeTrue())
})
It("should not be equal different length", func() {
s1 := types.Services([]string{"a", "b"})
s2 := types.Services([]string{"b", "a", "c"})
Ω(s1.Equals(s2)).ShouldNot(BeTrue())
})
})
})
Context("DNSConfig", func() {
Context("Equals", func() {
It("should be equal", func() {
dc1 := &types.DNSConfig{Upstreams: []string{"a"}}
dc2 := &types.DNSConfig{Upstreams: []string{"a"}}
Ω(dc1.Equals(dc2)).Should(BeTrue())
})
It("should not be equal", func() {
dc1 := &types.DNSConfig{Upstreams: []string{"a"}}
dc2 := &types.DNSConfig{Upstreams: []string{"b"}}
Ω(dc1.Equals(dc2)).ShouldNot(BeTrue())
})
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"))
})
})
})

15
pkg/utils/clone.go Normal file
View File

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

14
pkg/utils/ptr.go Normal file
View File

@@ -0,0 +1,14 @@
package utils
import "fmt"
func Ptr[I interface{}](i I) *I {
return &i
}
func PtrToString[I interface{}](i *I) string {
if i == nil {
return ""
}
return fmt.Sprintf("%v", i)
}

23
pkg/versions/versions.go Normal file
View File

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

View File

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

View File

@@ -0,0 +1,24 @@
package versions_test
import (
"github.com/bakito/adguardhome-sync/pkg/versions"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("Versions", func() {
Context("IsNewerThan", func() {
It("should correctly parse json", func() {
Ω(versions.IsNewerThan("v0.106.10", "v0.106.9")).Should(BeTrue())
Ω(versions.IsNewerThan("v0.106.9", "v0.106.10")).Should(BeFalse())
Ω(versions.IsNewerThan("v0.106.10", "0.106.9")).Should(BeTrue())
Ω(versions.IsNewerThan("v0.106.9", "0.106.10")).Should(BeFalse())
})
})
Context("IsSame", func() {
It("should be the same version", func() {
Ω(versions.IsSame("v0.106.9", "v0.106.9")).Should(BeTrue())
Ω(versions.IsSame("0.106.9", "v0.106.9")).Should(BeTrue())
})
})
})

1
testdata/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
docker-compose.yaml

View File

@@ -0,0 +1,22 @@
{
"schedule": {
"time_zone": "Europe/Zurich",
"tue": {
"start": 0,
"end": 86340000
},
"thu": {
"start": 0,
"end": 86340000
},
"sat": {
"start": 0,
"end": 35940000
}
},
"ids": [
"9gag",
"dailymotion",
"disneyplus"
]
}

23
testdata/e2e/.helmignore vendored Normal file
View File

@@ -0,0 +1,23 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/

8
testdata/e2e/Chart.yaml vendored Normal file
View File

@@ -0,0 +1,8 @@
apiVersion: v2
name: agh-e2e
description: adguardhome sync test charts
type: application
version: 0.1.0
appVersion: "1.16.0"

4
testdata/e2e/bin/build-image.sh vendored Executable file
View File

@@ -0,0 +1,4 @@
#!/bin/bash
set -e
docker build -f Dockerfile --build-arg VERSION=e2e-tests -t localhost:5001/adguardhome-sync:e2e .
docker push localhost:5001/adguardhome-sync:e2e

9
testdata/e2e/bin/install-chart.sh vendored Executable file
View File

@@ -0,0 +1,9 @@
#!/bin/bash
set -e
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

View File

@@ -0,0 +1,6 @@
#!/bin/bash
set -e
echo "## AdGuardHome.yaml of latest replica" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
kubectl exec adguardhome-replica-latest -- cat /opt/adguardhome/conf/AdGuardHome.yaml >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY

7
testdata/e2e/bin/show-origin-logs.sh vendored Executable file
View File

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

18
testdata/e2e/bin/show-replica-logs.sh vendored Executable file
View File

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

10
testdata/e2e/bin/show-sync-logs.sh vendored Executable file
View File

@@ -0,0 +1,10 @@
#!/bin/bash
set -e
echo "## Pod adguardhome-sync logs" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
kubectl logs adguardhome-sync >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
ERRORS=$(kubectl logs adguardhome-sync | grep Error | wc -l)
echo "Found ${ERRORS} error(s) in adguardhome-sync log"; >> $GITHUB_STEP_SUMMARY
if [[ "${ERRORS}" != "0" ]]; then exit 1; fi

7
testdata/e2e/bin/wait-for-agh-pods.sh vendored Executable file
View File

@@ -0,0 +1,7 @@
#!/bin/bash
set -e
echo "wait for adguardhome pods"
for pod in $(kubectl get pods -l bakito.net/adguardhome-sync -o name); do
kubectl wait --for condition=Ready ${pod} --timeout=30s
done

4
testdata/e2e/bin/wait-for-sync.sh vendored Executable file
View File

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

206
testdata/e2e/resources/AdGuardHome.yaml vendored Normal file
View File

@@ -0,0 +1,206 @@
http:
pprof:
port: 6060
enabled: false
address: 0.0.0.0:3000
session_ttl: 720h
users:
- name: username
password: $2a$10$yrrX.EvDpUUnZxr74u6euOMeF6dPFd/mEyohDq1LkpH76JyeObPBm
auth_attempts: 5
block_auth_min: 15
http_proxy: ""
language: en
theme: auto
dns:
bind_hosts:
- 0.0.0.0
port: 53
anonymize_client_ip: false
ratelimit: 20
ratelimit_whitelist: [ ]
refuse_any: true
upstream_dns:
- https://dns10.quad9.net/dns-query
upstream_dns_file: ""
bootstrap_dns:
- 1.1.1.1:53
fallback_dns: [ ]
all_servers: false
fastest_addr: false
fastest_timeout: 1s
allowed_clients: [ ]
disallowed_clients: [ ]
blocked_hosts:
- version.bind
- id.server
- hostname.bind
trusted_proxies:
- 127.0.0.0/8
- ::1/128
cache_size: 4194304
cache_ttl_min: 0
cache_ttl_max: 0
cache_optimistic: true
bogus_nxdomain: [ ]
aaaa_disabled: false
enable_dnssec: false
edns_client_subnet:
custom_ip: ""
enabled: false
use_custom: false
max_goroutines: 300
handle_ddr: true
ipset: [ ]
ipset_file: ""
bootstrap_prefer_ipv6: false
upstream_timeout: 10s
private_networks: [ ]
use_private_ptr_resolvers: true
local_ptr_upstreams: [ ]
use_dns64: false
dns64_prefixes: [ ]
serve_http3: false
use_http3_upstreams: false
tls:
enabled: false
server_name: ""
force_https: false
port_https: 443
port_dns_over_tls: 853
port_dns_over_quic: 853
port_dnscrypt: 0
dnscrypt_config_file: ""
allow_unencrypted_doh: false
certificate_chain: ""
private_key: ""
certificate_path: ""
private_key_path: ""
strict_sni_check: false
querylog:
ignored: [ ]
interval: 6h
size_memory: 1000
enabled: true
file_enabled: true
statistics:
ignored: [ ]
interval: 24h
enabled: true
filters:
- enabled: true
url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt
name: AdGuard DNS filter
id: 1
- enabled: true
url: https://adaway.org/hosts.txt
name: AdAway Default Blocklist
id: 2
whitelist_filters: [ ]
user_rules:
- '||metrics2.data.hicloud.com^$important'
- '||www.curiouscorrespondence.com^$important'
- '||bluewizard.com^$important'
- '||facebook.com^$important'
dhcp:
enabled: false
interface_name: eth0
local_domain_name: lan
dhcpv4:
gateway_ip: 1.2.3.4
subnet_mask: 255.255.0.0
range_start: 1.2.3.5
range_end: 1.2.3.56
lease_duration: 86400
icmp_timeout_msec: 1000
options: [ ]
dhcpv6:
range_start: ""
lease_duration: 86400
ra_slaac_only: false
ra_allow_slaac: false
filtering:
blocking_ipv4: ""
blocking_ipv6: ""
blocked_services:
schedule:
time_zone: Europe/Zurich
tue:
start: 0s
end: 23h59m
thu:
start: 0s
end: 23h59m
sat:
start: 0s
end: 9h59m
ids:
- 9gag
- dailymotion
- disneyplus
protection_disabled_until: null
safe_search:
enabled: true
bing: true
duckduckgo: true
google: true
pixabay: true
yandex: true
youtube: true
blocking_mode: default
parental_block_host: family-block.dns.adguard.com
safebrowsing_block_host: standard-block.dns.adguard.com
rewrites: [ ]
safebrowsing_cache_size: 1048576
safesearch_cache_size: 1048576
parental_cache_size: 1048576
cache_time: 30
filters_update_interval: 24
blocked_response_ttl: 10
filtering_enabled: true
parental_enabled: true
safebrowsing_enabled: true
protection_enabled: true
clients:
runtime_sources:
whois: true
arp: true
rdns: true
dhcp: true
hosts: true
persistent:
- name: Device 1
tags:
- device_1
ids:
- 2.2.2.2
blocked_services:
schedule:
time_zone: Europe/Zurich
ids:
- facebook
- mail_ru
- qq
- vk
- ok
upstreams: [ ]
use_global_settings: true
filtering_enabled: false
parental_enabled: false
safebrowsing_enabled: false
use_global_blocked_services: false
ignore_querylog: false
ignore_statistics: false
log:
file: ""
max_backups: 0
max_size: 100
max_age: 3
compress: false
local_time: false
verbose: false
os:
group: ""
user: ""
rlimit_nofile: 0
schema_version: 27

4
testdata/e2e/templates/NOTES.txt vendored Normal file
View File

@@ -0,0 +1,4 @@
Installed adguardhome-sync end-2-end test with {{ len .Values.replica.versions }} replica instances.
{{- range $_, $version := .Values.replica.versions }}
- {{ $version }}
{{- end }}

View File

@@ -0,0 +1,8 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: origin-conf
namespace: {{ .Release.Namespace }}
data:
AdGuardHome.yaml: |
{{- .Files.Get "resources/AdGuardHome.yaml" | nindent 4 }}

View File

@@ -0,0 +1,17 @@
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

@@ -0,0 +1,37 @@
apiVersion: v1
kind: Pod
metadata:
name: adguardhome-origin
namespace: {{ $.Release.Namespace }}
labels:
app.kubernetes.io/name: adguardhome-origin
bakito.net/adguardhome-sync: origin
spec:
volumes:
- name: configmap
configMap:
name: origin-conf
- name: conf
emptyDir: { }
initContainers:
- name: init
image: busybox
volumeMounts:
- mountPath: /opt/adguardhome/configmap
name: configmap
- mountPath: /opt/adguardhome/conf
name: conf
command:
- cp
- /opt/adguardhome/configmap/AdGuardHome.yaml
- /opt/adguardhome/conf
containers:
- name: adguardhome
image: adguard/adguardhome:latest
volumeMounts:
- mountPath: /opt/adguardhome/conf
name: conf
ports:
- containerPort: 3000

View File

@@ -0,0 +1,17 @@
{{ range $_, $version := .Values.replica.versions }}
apiVersion: v1
kind: Pod
metadata:
name: adguardhome-replica-{{ $version | toString | replace "." "-" }}
namespace: {{ $.Release.Namespace }}
labels:
app.kubernetes.io/name: adguardhome-replica-{{ $version | toString | replace "." "-" }}
bakito.net/adguardhome-sync: replica
spec:
containers:
- name: adguardhome
image: "adguard/adguardhome:{{ $version }}"
ports:
- containerPort: 3000
---
{{- end }}

View File

@@ -0,0 +1,25 @@
apiVersion: v1
kind: Pod
metadata:
name: adguardhome-sync
namespace: {{ $.Release.Namespace }}
spec:
serviceAccountName: agh-e2e
initContainers:
- name: wait-for-others
image: bitnami/kubectl:1.24
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
envFrom:
- configMapRef:
name: sync-conf
restartPolicy: Never

30
testdata/e2e/templates/rbac.yaml vendored Normal file
View File

@@ -0,0 +1,30 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: agh-e2e
namespace: {{ .Release.Namespace }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: agh-e2e
namespace: {{ .Release.Namespace }}
rules:
- apiGroups: [ "" ]
resources: [ "pods" ]
verbs: [ "get", "watch", "list" ]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: agh-e2e
namespace: {{ .Release.Namespace }}
subjects:
- kind: ServiceAccount
name: agh-e2e
roleRef:
kind: Role
name: agh-e2e
apiGroup: rbac.authorization.k8s.io

View File

@@ -0,0 +1,12 @@
apiVersion: v1
kind: Service
metadata:
name: service-origin
namespace: {{ $.Release.Namespace }}
spec:
selector:
app.kubernetes.io/name: adguardhome-origin
ports:
- protocol: TCP
port: 3000
targetPort: 3000

View File

@@ -0,0 +1,14 @@
{{ range $i ,$version := .Values.replica.versions }}
apiVersion: v1
kind: Service
metadata:
name: service-replica-{{ $version | toString | replace "." "-" }}
spec:
selector:
app.kubernetes.io/name: adguardhome-replica-{{ $version | toString | replace "." "-" }}
ports:
- protocol: TCP
port: 3000
targetPort: 3000
---
{{- end }}

5
testdata/e2e/values.yaml vendored Normal file
View File

@@ -0,0 +1,5 @@
replica:
versions:
- v0.107.40
- v0.107.43
- latest

View File

@@ -7,8 +7,7 @@
"enabled": true,
"url": "https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt",
"name": "AdGuard DNS filter",
"rules_count": 37330,
"last_updated": ""
"rules_count": 37330
},
{
"id": 1616956421,
@@ -24,4 +23,4 @@
"||metrics2.data.hicloud.com^$important",
""
]
}
}