Compare commits

...

56 Commits

Author SHA1 Message Date
Marc Brugger
32c728ccf8 fix: handle small passwords in mask function #522 (#523) 2025-03-13 19:10:37 +01:00
dependabot[bot]
327d67c9ef build(deps): bump golang.org/x/net from 0.35.0 to 0.36.0 (#519)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.35.0 to 0.36.0.
- [Commits](https://github.com/golang/net/compare/v0.35.0...v0.36.0)

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

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

* Update validate.go

---------

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

View File

@@ -20,6 +20,10 @@ on:
schedule:
- cron: '32 19 * * 6'
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
analyze:
name: Analyze

View File

@@ -4,10 +4,17 @@ on:
workflow_dispatch: # allows manual triggering
schedule:
- cron: '0 0 * * *'
#pull_request:
# branches:
# - main
release:
types:
- published
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
images:
runs-on: ubuntu-latest
@@ -44,7 +51,7 @@ jobs:
run: |
sed -i -e "s|FROM scratch|FROM ${{ matrix.build.fromImage }}|g" Dockerfile
- name: Build and push ${{github.event.release.tag_name }}
- name: Build images ${{github.event.release.tag_name }}
id: docker_build_release
uses: docker/build-push-action@v6
if: ${{ github.event.release.tag_name != '' }}
@@ -63,7 +70,7 @@ jobs:
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
- name: Build images
id: docker_build_main
uses: docker/build-push-action@v6
if: ${{ github.event.release.tag_name == '' && env.NEW_COMMIT_COUNT > 0 }}

View File

@@ -8,6 +8,10 @@ on:
branches:
- main
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
e2e:
runs-on: ubuntu-latest

View File

@@ -6,6 +6,10 @@ on:
pull_request:
branches: [ main ]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
golangci:
name: lint

16
.github/workflows/virustotal.yaml vendored Normal file
View File

@@ -0,0 +1,16 @@
name: Scan GitHub Release with VirusTotal
on:
release:
types: [released]
jobs:
scan_release:
runs-on: ubuntu-latest
steps:
- name: Analyze Build Assets
uses: bakito/virustotal-action@main
with:
release_name: ${{github.event.release.tag_name}}
vt_api_key: ${{secrets.VT_API_KEY}}

View File

@@ -42,5 +42,6 @@ changelog:
exclude:
- '^docs:'
- '^test:'
- '^chore'
release:
prerelease: auto

View File

@@ -11,7 +11,9 @@ $(TB_LOCALBIN):
## Tool Binaries
TB_DEEPCOPY_GEN ?= $(TB_LOCALBIN)/deepcopy-gen
TB_GINKGO ?= $(TB_LOCALBIN)/ginkgo
TB_GOFUMPT ?= $(TB_LOCALBIN)/gofumpt
TB_GOLANGCI_LINT ?= $(TB_LOCALBIN)/golangci-lint
TB_GOLINES ?= $(TB_LOCALBIN)/golines
TB_GORELEASER ?= $(TB_LOCALBIN)/goreleaser
TB_MOCKGEN ?= $(TB_LOCALBIN)/mockgen
TB_OAPI_CODEGEN ?= $(TB_LOCALBIN)/oapi-codegen
@@ -19,11 +21,15 @@ TB_SEMVER ?= $(TB_LOCALBIN)/semver
## Tool Versions
# renovate: packageName=k8s.io/code-generator/cmd/deepcopy-gen
TB_DEEPCOPY_GEN_VERSION ?= v0.32.0
TB_DEEPCOPY_GEN_VERSION ?= v0.32.3
# renovate: packageName=mvdan.cc/gofumpt
TB_GOFUMPT_VERSION ?= v0.7.0
# renovate: packageName=github.com/golangci/golangci-lint/cmd/golangci-lint
TB_GOLANGCI_LINT_VERSION ?= v1.62.2
TB_GOLANGCI_LINT_VERSION ?= v1.64.7
# renovate: packageName=github.com/segmentio/golines
TB_GOLINES_VERSION ?= v0.12.2
# renovate: packageName=github.com/goreleaser/goreleaser/v2
TB_GORELEASER_VERSION ?= v2.5.0
TB_GORELEASER_VERSION ?= v2.8.0
# renovate: packageName=go.uber.org/mock/mockgen
TB_MOCKGEN_VERSION ?= v0.5.0
# renovate: packageName=github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen
@@ -40,10 +46,18 @@ $(TB_DEEPCOPY_GEN): $(TB_LOCALBIN)
tb.ginkgo: $(TB_GINKGO) ## Download ginkgo locally if necessary.
$(TB_GINKGO): $(TB_LOCALBIN)
test -s $(TB_LOCALBIN)/ginkgo || GOBIN=$(TB_LOCALBIN) go install github.com/onsi/ginkgo/v2/ginkgo
.PHONY: tb.gofumpt
tb.gofumpt: $(TB_GOFUMPT) ## Download gofumpt locally if necessary.
$(TB_GOFUMPT): $(TB_LOCALBIN)
test -s $(TB_LOCALBIN)/gofumpt || GOBIN=$(TB_LOCALBIN) go install mvdan.cc/gofumpt@$(TB_GOFUMPT_VERSION)
.PHONY: tb.golangci-lint
tb.golangci-lint: $(TB_GOLANGCI_LINT) ## Download golangci-lint locally if necessary.
$(TB_GOLANGCI_LINT): $(TB_LOCALBIN)
test -s $(TB_LOCALBIN)/golangci-lint || GOBIN=$(TB_LOCALBIN) go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(TB_GOLANGCI_LINT_VERSION)
.PHONY: tb.golines
tb.golines: $(TB_GOLINES) ## Download golines locally if necessary.
$(TB_GOLINES): $(TB_LOCALBIN)
test -s $(TB_LOCALBIN)/golines || GOBIN=$(TB_LOCALBIN) go install github.com/segmentio/golines@$(TB_GOLINES_VERSION)
.PHONY: tb.goreleaser
tb.goreleaser: $(TB_GORELEASER) ## Download goreleaser locally if necessary.
$(TB_GORELEASER): $(TB_LOCALBIN)
@@ -67,7 +81,9 @@ tb.reset:
@rm -f \
$(TB_LOCALBIN)/deepcopy-gen \
$(TB_LOCALBIN)/ginkgo \
$(TB_LOCALBIN)/gofumpt \
$(TB_LOCALBIN)/golangci-lint \
$(TB_LOCALBIN)/golines \
$(TB_LOCALBIN)/goreleaser \
$(TB_LOCALBIN)/mockgen \
$(TB_LOCALBIN)/oapi-codegen \
@@ -78,7 +94,9 @@ tb.reset:
tb.update: tb.reset
toolbox makefile --renovate -f $(TB_LOCALDIR)/Makefile \
k8s.io/code-generator/cmd/deepcopy-gen@github.com/kubernetes/code-generator \
mvdan.cc/gofumpt@github.com/mvdan/gofumpt \
github.com/golangci/golangci-lint/cmd/golangci-lint \
github.com/segmentio/golines \
github.com/goreleaser/goreleaser/v2 \
go.uber.org/mock/mockgen@github.com/uber-go/mock \
github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen \

View File

@@ -1,4 +1,4 @@
FROM golang:1.23-alpine AS builder
FROM golang:1.24-alpine AS builder
WORKDIR /go/src/app

View File

@@ -14,8 +14,14 @@ generate: tb.deepcopy-gen
@touch ./tmp/deepcopy-gen-boilerplate.go.txt
$(TB_DEEPCOPY_GEN) --go-header-file ./tmp/deepcopy-gen-boilerplate.go.txt --bounding-dirs ./pkg/types
fmt: tb.golines tb.gofumpt
$(TB_GOLINES) --base-formatter="$(TB_GOFUMPT)" --max-len=120 --write-output .
# Run tests
test: generate lint test-ci
test: generate fmt lint test-ci
fuzz:
go test -fuzz=FuzzMask -v ./pkg/types/ -fuzztime=60s
# Run ci tests
test-ci: mocks tidy tb.ginkgo
@@ -65,7 +71,7 @@ kind-test:
@./testdata/e2e/bin/install-chart.sh
# renovate: packageName=AdguardTeam/AdGuardHome
ADGUARD_HOME_VERSION ?= v0.107.55
ADGUARD_HOME_VERSION ?= v0.107.57
model: tb.oapi-codegen
@mkdir -p tmp

View File

@@ -245,6 +245,11 @@ services:
restart: unless-stopped
```
### Unraid
Note when running the Docker container in Unraid please remove unneeded env variables if don't needed.
If replica2 isn't used this can cause sync errors.
### Config file
location: $HOME/.adguardhome-sync.yaml

View File

@@ -5,7 +5,6 @@ import (
"github.com/bakito/adguardhome-sync/pkg/log"
"github.com/bakito/adguardhome-sync/pkg/sync"
"github.com/spf13/cobra"
"gopkg.in/yaml.v3"
)
// runCmd represents the run command
@@ -26,18 +25,16 @@ var doCmd = &cobra.Command{
return err
}
if cfg.PrintConfigOnly {
config, err := yaml.Marshal(cfg)
if err != nil {
if cfg.PrintConfigOnly() {
if err := cfg.Print(); 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)
return sync.Sync(cfg.Get())
},
}
@@ -50,7 +47,8 @@ func init() {
doCmd.PersistentFlags().Bool(config.FlagContinueOnError, false, "If enabled, the synchronisation task "+
"will not fail on single errors, but will log the errors and continue.")
doCmd.PersistentFlags().Int(config.FlagApiPort, 8080, "Sync API Port, the API endpoint will be started to enable remote triggering; if 0 port API is disabled.")
doCmd.PersistentFlags().
Int(config.FlagApiPort, 8080, "Sync API Port, the API endpoint will be started to enable remote triggering; if 0 port API is disabled.")
doCmd.PersistentFlags().String(config.FlagApiUsername, "", "Sync API username")
doCmd.PersistentFlags().String(config.FlagApiPassword, "", "Sync API password")
doCmd.PersistentFlags().String(config.FlagApiDarkMode, "", "API UI in dark mode")
@@ -70,7 +68,8 @@ func init() {
doCmd.PersistentFlags().Bool(config.FlagFeatureFilters, true, "Enable filters sync feature")
doCmd.PersistentFlags().String(config.FlagOriginURL, "", "Origin instance url")
doCmd.PersistentFlags().String(config.FlagOriginWebURL, "", "Origin instance web url used in the web interface (default: <origin-url>)")
doCmd.PersistentFlags().
String(config.FlagOriginWebURL, "", "Origin instance web url used in the web interface (default: <origin-url>)")
doCmd.PersistentFlags().String(config.FlagOriginApiPath, "/control", "Origin instance API path")
doCmd.PersistentFlags().String(config.FlagOriginUsername, "", "Origin instance username")
doCmd.PersistentFlags().String(config.FlagOriginPassword, "", "Origin instance password")
@@ -78,12 +77,15 @@ func init() {
doCmd.PersistentFlags().Bool(config.FlagOriginISV, false, "Enable Origin instance InsecureSkipVerify")
doCmd.PersistentFlags().String(config.FlagReplicaURL, "", "Replica instance url")
doCmd.PersistentFlags().String(config.FlagReplicaWebURL, "", "Replica instance web url used in the web interface (default: <replica-url>)")
doCmd.PersistentFlags().
String(config.FlagReplicaWebURL, "", "Replica instance web url used in the web interface (default: <replica-url>)")
doCmd.PersistentFlags().String(config.FlagReplicaApiPath, "/control", "Replica instance API path")
doCmd.PersistentFlags().String(config.FlagReplicaUsername, "", "Replica instance username")
doCmd.PersistentFlags().String(config.FlagReplicaPassword, "", "Replica instance password")
doCmd.PersistentFlags().String(config.FlagReplicaCookie, "", "If Set, uses a cookie for authentication")
doCmd.PersistentFlags().Bool(config.FlagReplicaISV, false, "Enable Replica instance InsecureSkipVerify")
doCmd.PersistentFlags().Bool(config.FlagReplicaAutoSetup, false, "Enable automatic setup of new AdguardHome instances. This replaces the setup wizard.")
doCmd.PersistentFlags().String(config.FlagReplicaInterfaceName, "", "Optional change the interface name of the replica if it differs from the master")
doCmd.PersistentFlags().
Bool(config.FlagReplicaAutoSetup, false, "Enable automatic setup of new AdguardHome instances. This replaces the setup wizard.")
doCmd.PersistentFlags().
String(config.FlagReplicaInterfaceName, "", "Optional change the interface name of the replica if it differs from the master")
}

41
go.mod
View File

@@ -1,25 +1,26 @@
module github.com/bakito/adguardhome-sync
go 1.23.1
go 1.24.0
require (
github.com/caarlos0/env/v11 v11.2.2
github.com/caarlos0/env/v11 v11.3.1
github.com/gin-gonic/gin v1.10.0
github.com/go-resty/resty/v2 v2.16.2
github.com/go-faker/faker/v4 v4.6.0
github.com/go-resty/resty/v2 v2.16.5
github.com/google/uuid v1.6.0
github.com/jinzhu/copier v0.4.0
github.com/oapi-codegen/runtime v1.1.1
github.com/onsi/ginkgo/v2 v2.22.0
github.com/onsi/gomega v1.36.1
github.com/prometheus/client_golang v1.20.5
github.com/onsi/ginkgo/v2 v2.23.0
github.com/onsi/gomega v1.36.2
github.com/prometheus/client_golang v1.21.1
github.com/robfig/cron/v3 v3.0.1
github.com/spf13/cobra v1.8.1
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1
github.com/spf13/cobra v1.9.1
go.uber.org/mock v0.5.0
go.uber.org/zap v1.27.0
golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e
golang.org/x/mod v0.22.0
golang.org/x/mod v0.24.0
gopkg.in/yaml.v3 v3.0.1
k8s.io/apimachinery v0.32.0
k8s.io/apimachinery v0.32.3
k8s.io/utils v0.0.0-20241210054802-24370beab758
)
@@ -43,10 +44,10 @@ require (
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-20241029153458-d1b30febd7db // indirect
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
@@ -55,20 +56,20 @@ require (
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.31.0 // indirect
golang.org/x/net v0.32.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/tools v0.28.0 // indirect
google.golang.org/protobuf v1.35.1 // indirect
golang.org/x/crypto v0.35.0 // indirect
golang.org/x/net v0.36.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/text v0.22.0 // indirect
golang.org/x/tools v0.30.0 // indirect
google.golang.org/protobuf v1.36.1 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect

89
go.sum
View File

@@ -8,19 +8,21 @@ github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/caarlos0/env/v11 v11.2.2 h1:95fApNrUyueipoZN/EhA8mMxiNxrBwDa+oAZrMWl3Kg=
github.com/caarlos0/env/v11 v11.2.2/go.mod h1:JBfcdeQiBoI3Zh1QRAWfe+tpiNTmDtcCj/hHHHMx0vc=
github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA=
github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
@@ -29,6 +31,8 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-faker/faker/v4 v4.6.0 h1:6aOPzNptRiDwD14HuAnEtlTa+D1IfFuEHO8+vEFwjTs=
github.com/go-faker/faker/v4 v4.6.0/go.mod h1:ZmrHuVtTTm2Em9e0Du6CJ9CADaLEzGXW62z1YqFH0m0=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
@@ -39,8 +43,8 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-resty/resty/v2 v2.16.2 h1:CpRqTjIzq/rweXUt9+GxzzQdlkqMdt8Lm/fuK/CAbAg=
github.com/go-resty/resty/v2 v2.16.2/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU=
github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=
github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
@@ -53,8 +57,8 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg=
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
@@ -66,8 +70,8 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
@@ -91,21 +95,21 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro=
github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg=
github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg=
github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw=
github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
github.com/onsi/ginkgo/v2 v2.23.0 h1:FA1xjp8ieYDzlgS5ABTpdUDB7wtngggONc8a7ku2NqQ=
github.com/onsi/ginkgo/v2 v2.23.0/go.mod h1:zXTP6xIp3U8aVuXN8ENK9IXRaTjFnpVB9mGmaSRvxnM=
github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk=
github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
@@ -113,10 +117,12 @@ github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzG
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
@@ -128,8 +134,9 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
@@ -152,22 +159,18 @@ golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/exp v0.0.0-20241210194714-1829a127f884 h1:Y/Mj/94zIQQGHVSv1tTtQBDaQaJe62U9bkDZKKyhPCU=
golang.org/x/exp v0.0.0-20241210194714-1829a127f884/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e h1:4qufH0hlUYs6AO6XmZC3GqfDPGSXHVXUFR6OND+iJX4=
golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -176,26 +179,26 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
@@ -204,12 +207,10 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/apimachinery v0.32.0 h1:cFSE7N3rmEEtv4ei5X6DaJPHHX0C+upp+v5lVPiEwpg=
k8s.io/apimachinery v0.32.0/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U=
k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0=
k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=

View File

@@ -20,7 +20,9 @@ func main() {
}
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))
resp, err := http.Get(
fmt.Sprintf("https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/%s/openapi/openapi.yaml", version),
)
if err != nil {
log.Fatalln(err)
}
@@ -51,6 +53,11 @@ func main() {
}
}
}
correctEntries(schema)
addFakeTags(schema)
b, err := yaml.Marshal(&schema)
if err != nil {
log.Fatalln(err)
@@ -61,3 +68,26 @@ func main() {
log.Fatalln(err)
}
}
func correctEntries(schema map[string]interface{}) {
// https://github.com/AdguardTeam/AdGuardHome/pull/7678
if err := unstructured.SetNestedField(schema, "string", "components", "schemas", "QueryLogItem", "properties", "client_proto", "type"); err != nil {
log.Fatalln(err)
}
}
func addFakeTags(schema map[string]interface{}) {
fake := map[string]interface{}{"faker": `slice_len=24`}
if err := unstructured.SetNestedMap(schema, fake, "components", "schemas", "Stats", "properties", "blocked_filtering", "x-oapi-codegen-extra-tags"); err != nil {
log.Fatalln(err)
}
if err := unstructured.SetNestedMap(schema, fake, "components", "schemas", "Stats", "properties", "dns_queries", "x-oapi-codegen-extra-tags"); err != nil {
log.Fatalln(err)
}
if err := unstructured.SetNestedMap(schema, fake, "components", "schemas", "Stats", "properties", "replaced_parental", "x-oapi-codegen-extra-tags"); err != nil {
log.Fatalln(err)
}
if err := unstructured.SetNestedMap(schema, fake, "components", "schemas", "Stats", "properties", "replaced_safebrowsing", "x-oapi-codegen-extra-tags"); err != nil {
log.Fatalln(err)
}
}

View File

@@ -166,7 +166,10 @@ func (cl *client) Stats() (*model.Stats, error) {
func (cl *client) QueryLog(limit int) (*model.QueryLog, error) {
ql := &model.QueryLog{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(ql), fmt.Sprintf(`querylog?limit=%d&response_status="all"`, limit))
err := cl.doGet(
cl.client.R().EnableTrace().SetResult(ql),
fmt.Sprintf(`querylog?limit=%d&response_status="all"`, limit),
)
return ql, err
}
@@ -260,7 +263,10 @@ func (cl *client) UpdateFilter(whitelist bool, f model.Filter) error {
func (cl *client) RefreshFilters(whitelist bool) error {
cl.log.With("whitelist", whitelist).Info("Refresh filter")
return cl.doPost(cl.client.R().EnableTrace().SetBody(&model.FilterRefreshRequest{Whitelist: utils.Ptr(whitelist)}), "/filtering/refresh")
return cl.doPost(
cl.client.R().EnableTrace().SetBody(&model.FilterRefreshRequest{Whitelist: utils.Ptr(whitelist)}),
"/filtering/refresh",
)
}
func (cl *client) ToggleProtection(enable bool) error {
@@ -309,7 +315,10 @@ func (cl *client) AddClient(client *model.Client) error {
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")
return cl.doPost(
cl.client.R().EnableTrace().SetBody(&model.ClientUpdate{Name: client.Name, Data: client}),
"/clients/update",
)
}
func (cl *client) DeleteClient(client *model.Client) error {
@@ -324,7 +333,8 @@ func (cl *client) QueryLogConfig() (*model.QueryLogConfigWithIgnored, error) {
}
func (cl *client) SetQueryLogConfig(qlc *model.QueryLogConfigWithIgnored) error {
cl.log.With("enabled", *qlc.Enabled, "interval", *qlc.Interval, "anonymizeClientIP", *qlc.AnonymizeClientIp).Info("Set query log config")
cl.log.With("enabled", *qlc.Enabled, "interval", *qlc.Interval, "anonymizeClientIP", *qlc.AnonymizeClientIp).
Info("Set query log config")
return cl.doPut(cl.client.R().EnableTrace().SetBody(qlc), "/querylog/config/update")
}

View File

@@ -137,7 +137,14 @@ var _ = Describe("Client", func() {
Context("Setup", func() {
It("should add setup the instance", func() {
ts, cl = ClientPost("/install/configure", fmt.Sprintf(`{"web":{"ip":"0.0.0.0","port":3000,"status":"","can_autofix":false},"dns":{"ip":"0.0.0.0","port":53,"status":"","can_autofix":false},"username":"%s","password":"%s"}`, username, password))
ts, cl = ClientPost(
"/install/configure",
fmt.Sprintf(
`{"web":{"ip":"0.0.0.0","port":3000,"status":"","can_autofix":false},"dns":{"ip":"0.0.0.0","port":53,"status":"","can_autofix":false},"username":"%s","password":"%s"}`,
username,
password,
),
)
err := cl.Setup()
Ω(err).ShouldNot(HaveOccurred())
})
@@ -303,7 +310,10 @@ var _ = Describe("Client", func() {
Ω(*qlc.Interval).Should(Equal(model.QueryLogConfigInterval(90)))
})
It("should set QueryLogConfig", func() {
ts, cl = ClientPut("/querylog/config/update", `{"anonymize_client_ip":true,"enabled":true,"interval":123,"ignored":["foo.bar"]}`)
ts, cl = ClientPut(
"/querylog/config/update",
`{"anonymize_client_ip":true,"enabled":true,"interval":123,"ignored":["foo.bar"]}`,
)
var interval model.QueryLogConfigInterval = 123
err := cl.SetQueryLogConfig(&model.QueryLogConfigWithIgnored{

View File

@@ -442,7 +442,9 @@ func (c *DNSConfig) Sanitize(l *zap.SugaredLogger) {
// https://github.com/AdguardTeam/AdGuardHome/issues/6820
if c.UsePrivatePtrResolvers != nil && *c.UsePrivatePtrResolvers &&
(c.LocalPtrUpstreams == nil || len(*c.LocalPtrUpstreams) == 0) {
l.Warn("disabling replica 'Use private reverse DNS resolvers' as no 'Private reverse DNS servers' are configured on origin")
l.Warn(
"disabling replica 'Use private reverse DNS resolvers' as no 'Private reverse DNS servers' are configured on origin",
)
c.UsePrivatePtrResolvers = utils.Ptr(false)
}
}

View File

@@ -91,6 +91,15 @@ const (
QueryLogConfigIntervalN90 QueryLogConfigInterval = 90
)
// Defines values for QueryLogItemClientProto.
const (
Dnscrypt QueryLogItemClientProto = "dnscrypt"
Doh QueryLogItemClientProto = "doh"
Doq QueryLogItemClientProto = "doq"
Dot QueryLogItemClientProto = "dot"
Empty QueryLogItemClientProto = ""
)
// Defines values for QueryLogItemReason.
const (
QueryLogItemReasonFilteredBlackList QueryLogItemReason = "FilteredBlackList"
@@ -397,6 +406,17 @@ type ClientsFindEntry map[string]ClientFindSubEntry
// ClientsFindResponse Client search results.
type ClientsFindResponse = []ClientsFindEntry
// ClientsSearchRequest Client search request
type ClientsSearchRequest struct {
Clients *[]ClientsSearchRequestItem `json:"clients,omitempty"`
}
// ClientsSearchRequestItem defines model for ClientsSearchRequestItem.
type ClientsSearchRequestItem struct {
// Id Client IP address, CIDR, MAC address, or ClientID
Id *string `json:"id,omitempty"`
}
// DNSConfig DNS server configuration
type DNSConfig struct {
// BlockedResponseTtl TTL for blocked responses.
@@ -443,8 +463,11 @@ type DNSConfig struct {
UpstreamDnsFile *string `json:"upstream_dns_file,omitempty"`
// UpstreamMode Upstream modes enumeration.
UpstreamMode *DNSConfigUpstreamMode `json:"upstream_mode,omitempty"`
UsePrivatePtrResolvers *bool `json:"use_private_ptr_resolvers,omitempty"`
UpstreamMode *DNSConfigUpstreamMode `json:"upstream_mode,omitempty"`
// UpstreamTimeout The number of seconds to wait for a response from the upstream server
UpstreamTimeout *int `json:"upstream_timeout,omitempty"`
UsePrivatePtrResolvers *bool `json:"use_private_ptr_resolvers,omitempty"`
}
// DNSConfigBlockingMode defines model for DNSConfig.BlockingMode.
@@ -725,11 +748,18 @@ type Login struct {
// NetInterface Network interface info
type NetInterface struct {
// Flags Flags could be any combination of the following values, divided by the "|" character: "up", "broadcast", "loopback", "pointtopoint" and "multicast".
Flags string `json:"flags"`
HardwareAddress string `json:"hardware_address"`
IpAddresses *[]string `json:"ip_addresses,omitempty"`
Mtu int `json:"mtu"`
Name string `json:"name"`
Flags string `json:"flags"`
// GatewayIp The IP address of the gateway.
GatewayIp string `json:"gateway_ip"`
HardwareAddress string `json:"hardware_address"`
// Ipv4Addresses The addresses of the interface of v4 family.
Ipv4Addresses []string `json:"ipv4_addresses"`
// Ipv6Addresses The addresses of the interface of v6 family.
Ipv6Addresses []string `json:"ipv6_addresses"`
Name string `json:"name"`
}
// NetInterfaces Network interfaces dictionary, keys are interface names.
@@ -791,8 +821,8 @@ type QueryLogItem struct {
ClientId *string `json:"client_id,omitempty"`
// ClientInfo Client information for a query log item.
ClientInfo *QueryLogItemClient `json:"client_info,omitempty"`
ClientProto *interface{} `json:"client_proto,omitempty"`
ClientInfo *QueryLogItemClient `json:"client_info,omitempty"`
ClientProto *QueryLogItemClientProto `json:"client_proto,omitempty"`
// Ecs The IP network defined by an EDNS Client-Subnet option in the request message if any.
Ecs *string `json:"ecs,omitempty"`
@@ -833,6 +863,9 @@ type QueryLogItem struct {
Upstream *string `json:"upstream,omitempty"`
}
// QueryLogItemClientProto defines model for QueryLogItem.ClientProto.
type QueryLogItemClientProto string
// QueryLogItemReason Request filtering status.
type QueryLogItemReason string
@@ -968,8 +1001,8 @@ type SetRulesRequest struct {
type Stats struct {
// AvgProcessingTime Average time in seconds on processing a DNS request
AvgProcessingTime *float32 `json:"avg_processing_time,omitempty"`
BlockedFiltering *[]int `json:"blocked_filtering,omitempty"`
DnsQueries *[]int `json:"dns_queries,omitempty"`
BlockedFiltering *[]int `faker:"slice_len=24" json:"blocked_filtering,omitempty"`
DnsQueries *[]int `faker:"slice_len=24" json:"dns_queries,omitempty"`
// NumBlockedFiltering Number of requests blocked by filtering rules
NumBlockedFiltering *int `json:"num_blocked_filtering,omitempty"`
@@ -985,8 +1018,8 @@ type Stats struct {
// NumReplacedSafesearch Number of requests blocked by safesearch module
NumReplacedSafesearch *int `json:"num_replaced_safesearch,omitempty"`
ReplacedParental *[]int `json:"replaced_parental,omitempty"`
ReplacedSafebrowsing *[]int `json:"replaced_safebrowsing,omitempty"`
ReplacedParental *[]int `faker:"slice_len=24" json:"replaced_parental,omitempty"`
ReplacedSafebrowsing *[]int `faker:"slice_len=24" json:"replaced_safebrowsing,omitempty"`
// TimeUnits Time units
TimeUnits *StatsTimeUnits `json:"time_units,omitempty"`
@@ -1205,6 +1238,9 @@ type ClientsAddJSONRequestBody = Client
// ClientsDeleteJSONRequestBody defines body for ClientsDelete for application/json ContentType.
type ClientsDeleteJSONRequestBody = ClientDelete
// ClientsSearchJSONRequestBody defines body for ClientsSearch for application/json ContentType.
type ClientsSearchJSONRequestBody = ClientsSearchRequest
// ClientsUpdateJSONRequestBody defines body for ClientsUpdate for application/json ContentType.
type ClientsUpdateJSONRequestBody = ClientUpdate
@@ -1494,6 +1530,11 @@ type ClientInterface interface {
// ClientsFind request
ClientsFind(ctx context.Context, params *ClientsFindParams, reqEditors ...RequestEditorFn) (*http.Response, error)
// ClientsSearchWithBody request with any body
ClientsSearchWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error)
ClientsSearch(ctx context.Context, body ClientsSearchJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error)
// ClientsUpdateWithBody request with any body
ClientsUpdateWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error)
@@ -1986,6 +2027,30 @@ func (c *AdguardHomeClient) ClientsFind(ctx context.Context, params *ClientsFind
return c.Client.Do(req)
}
func (c *AdguardHomeClient) ClientsSearchWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewClientsSearchRequestWithBody(c.Server, contentType, body)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
return nil, err
}
return c.Client.Do(req)
}
func (c *AdguardHomeClient) ClientsSearch(ctx context.Context, body ClientsSearchJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewClientsSearchRequest(c.Server, body)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
return nil, err
}
return c.Client.Do(req)
}
func (c *AdguardHomeClient) ClientsUpdateWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewClientsUpdateRequestWithBody(c.Server, contentType, body)
if err != nil {
@@ -3674,6 +3739,46 @@ func NewClientsFindRequest(server string, params *ClientsFindParams) (*http.Requ
return req, nil
}
// NewClientsSearchRequest calls the generic ClientsSearch builder with application/json body
func NewClientsSearchRequest(server string, body ClientsSearchJSONRequestBody) (*http.Request, error) {
var bodyReader io.Reader
buf, err := json.Marshal(body)
if err != nil {
return nil, err
}
bodyReader = bytes.NewReader(buf)
return NewClientsSearchRequestWithBody(server, "application/json", bodyReader)
}
// NewClientsSearchRequestWithBody generates requests for ClientsSearch with any type of body
func NewClientsSearchRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) {
var err error
serverURL, err := url.Parse(server)
if err != nil {
return nil, err
}
operationPath := fmt.Sprintf("/clients/search")
if operationPath[0] == '/' {
operationPath = "." + operationPath
}
queryURL, err := serverURL.Parse(operationPath)
if err != nil {
return nil, err
}
req, err := http.NewRequest("POST", queryURL.String(), body)
if err != nil {
return nil, err
}
req.Header.Add("Content-Type", contentType)
return req, nil
}
// NewClientsUpdateRequest calls the generic ClientsUpdate builder with application/json body
func NewClientsUpdateRequest(server string, body ClientsUpdateJSONRequestBody) (*http.Request, error) {
var bodyReader io.Reader
@@ -5984,6 +6089,11 @@ type ClientWithResponsesInterface interface {
// ClientsFindWithResponse request
ClientsFindWithResponse(ctx context.Context, params *ClientsFindParams, reqEditors ...RequestEditorFn) (*ClientsFindResp, error)
// ClientsSearchWithBodyWithResponse request with any body
ClientsSearchWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ClientsSearchResp, error)
ClientsSearchWithResponse(ctx context.Context, body ClientsSearchJSONRequestBody, reqEditors ...RequestEditorFn) (*ClientsSearchResp, error)
// ClientsUpdateWithBodyWithResponse request with any body
ClientsUpdateWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ClientsUpdateResp, error)
@@ -6560,6 +6670,28 @@ func (r ClientsFindResp) StatusCode() int {
return 0
}
type ClientsSearchResp struct {
Body []byte
HTTPResponse *http.Response
JSON200 *ClientsFindResponse
}
// Status returns HTTPResponse.Status
func (r ClientsSearchResp) Status() string {
if r.HTTPResponse != nil {
return r.HTTPResponse.Status
}
return http.StatusText(0)
}
// StatusCode returns HTTPResponse.StatusCode
func (r ClientsSearchResp) StatusCode() int {
if r.HTTPResponse != nil {
return r.HTTPResponse.StatusCode
}
return 0
}
type ClientsUpdateResp struct {
Body []byte
HTTPResponse *http.Response
@@ -8099,6 +8231,23 @@ func (c *ClientWithResponses) ClientsFindWithResponse(ctx context.Context, param
return ParseClientsFindResp(rsp)
}
// ClientsSearchWithBodyWithResponse request with arbitrary body returning *ClientsSearchResp
func (c *ClientWithResponses) ClientsSearchWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ClientsSearchResp, error) {
rsp, err := c.ClientsSearchWithBody(ctx, contentType, body, reqEditors...)
if err != nil {
return nil, err
}
return ParseClientsSearchResp(rsp)
}
func (c *ClientWithResponses) ClientsSearchWithResponse(ctx context.Context, body ClientsSearchJSONRequestBody, reqEditors ...RequestEditorFn) (*ClientsSearchResp, error) {
rsp, err := c.ClientsSearch(ctx, body, reqEditors...)
if err != nil {
return nil, err
}
return ParseClientsSearchResp(rsp)
}
// ClientsUpdateWithBodyWithResponse request with arbitrary body returning *ClientsUpdateResp
func (c *ClientWithResponses) ClientsUpdateWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ClientsUpdateResp, error) {
rsp, err := c.ClientsUpdateWithBody(ctx, contentType, body, reqEditors...)
@@ -9244,6 +9393,32 @@ func ParseClientsFindResp(rsp *http.Response) (*ClientsFindResp, error) {
return response, nil
}
// ParseClientsSearchResp parses an HTTP response from a ClientsSearchWithResponse call
func ParseClientsSearchResp(rsp *http.Response) (*ClientsSearchResp, error) {
bodyBytes, err := io.ReadAll(rsp.Body)
defer func() { _ = rsp.Body.Close() }()
if err != nil {
return nil, err
}
response := &ClientsSearchResp{
Body: bodyBytes,
HTTPResponse: rsp,
}
switch {
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
var dest ClientsFindResponse
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}
response.JSON200 = &dest
}
return response, nil
}
// ParseClientsUpdateResp parses an HTTP response from a ClientsUpdateWithResponse call
func ParseClientsUpdateResp(rsp *http.Response) (*ClientsUpdateResp, error) {
bodyBytes, err := io.ReadAll(rsp.Body)

View File

@@ -242,7 +242,10 @@ var _ = Describe("Types", func() {
})
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"}}
cfg.Replicas = []types.AdGuardInstance{
{URL: url + "1", APIPath: apiPath},
{URL: url, APIPath: apiPath + "1"},
}
r := cfg.UniqueReplicas()
Ω(r).Should(HaveLen(3))
})
@@ -311,8 +314,14 @@ var _ = Describe("Types", func() {
cl2 *model.Client
)
BeforeEach(func() {
cl1 = &model.Client{Name: utils.Ptr("foo"), BlockedServicesSchedule: &model.Schedule{TimeZone: utils.Ptr("UTC")}}
cl2 = &model.Client{Name: utils.Ptr("foo"), BlockedServicesSchedule: &model.Schedule{TimeZone: utils.Ptr("Local")}}
cl1 = &model.Client{
Name: utils.Ptr("foo"),
BlockedServicesSchedule: &model.Schedule{TimeZone: utils.Ptr("UTC")},
}
cl2 = &model.Client{
Name: utils.Ptr("foo"),
BlockedServicesSchedule: &model.Schedule{TimeZone: utils.Ptr("Local")},
}
})
It("should equal if only timezone differs on empty blocked service schedule", func() {

View File

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

View File

@@ -14,16 +14,39 @@ var (
logger = log.GetLogger("config")
)
func Get(configFile string, flags Flags) (*types.Config, error) {
type AppConfig struct {
cfg *types.Config
filePath string
content string
}
func (ac *AppConfig) PrintConfigOnly() bool {
return ac.cfg.PrintConfigOnly
}
func (ac *AppConfig) Get() *types.Config {
return ac.cfg
}
func (ac *AppConfig) Init() error {
return ac.cfg.Init()
}
func Get(configFile string, flags Flags) (*AppConfig, error) {
path, err := configFilePath(configFile)
if err != nil {
return nil, err
}
if err = validateSchema(path); err != nil {
return nil, err
}
cfg := initialConfig()
// read yaml config
if err := readFile(cfg, path); err != nil {
var content string
if content, err = readFile(cfg, path); err != nil {
return nil, err
}
@@ -82,7 +105,7 @@ func Get(configFile string, flags Flags) (*types.Config, error) {
cfg.Replicas, err = enrichReplicasFromEnv(cfg.Replicas)
return cfg, err
return &AppConfig{cfg: cfg, filePath: path, content: content}, err
}
func initialConfig() *types.Config {

View File

@@ -49,7 +49,7 @@ var _ = Describe("Config", func() {
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Origin.URL).Should(Equal("https://origin-file:443"))
Ω(cfg.Get().Origin.URL).Should(Equal("https://origin-file:443"))
})
It("should have the origin URL from the config flags", func() {
flags.EXPECT().Changed(config.FlagOriginURL).Return(true).AnyTimes()
@@ -58,7 +58,7 @@ var _ = Describe("Config", func() {
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Origin.URL).Should(Equal("https://origin-flag:443"))
Ω(cfg.Get().Origin.URL).Should(Equal("https://origin-flag:443"))
})
It("should have the origin URL from the config env var", func() {
setEnv("ORIGIN_URL", "https://origin-env:443")
@@ -68,7 +68,7 @@ var _ = Describe("Config", func() {
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Origin.URL).Should(Equal("https://origin-env:443"))
Ω(cfg.Get().Origin.URL).Should(Equal("https://origin-env:443"))
})
})
Context("Replica insecure skip verify", func() {
@@ -77,7 +77,7 @@ var _ = Describe("Config", func() {
cfg, err := config.Get("../../testdata/config_test_replica.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].InsecureSkipVerify).Should(BeFalse())
Ω(cfg.Get().Replicas[0].InsecureSkipVerify).Should(BeFalse())
})
It("should have the insecure skip verify from the config flags", func() {
flags.EXPECT().Changed(config.FlagReplicaISV).Return(true).AnyTimes()
@@ -86,7 +86,7 @@ var _ = Describe("Config", func() {
cfg, err := config.Get("../../testdata/config_test_replica.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].InsecureSkipVerify).Should(BeTrue())
Ω(cfg.Get().Replicas[0].InsecureSkipVerify).Should(BeTrue())
})
It("should have the insecure skip verify from the config env var", func() {
setEnv("REPLICA_INSECURE_SKIP_VERIFY", "false")
@@ -96,7 +96,7 @@ var _ = Describe("Config", func() {
cfg, err := config.Get("../../testdata/config_test_replica.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].InsecureSkipVerify).Should(BeFalse())
Ω(cfg.Get().Replicas[0].InsecureSkipVerify).Should(BeFalse())
})
})
@@ -106,7 +106,7 @@ var _ = Describe("Config", func() {
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].InsecureSkipVerify).Should(BeFalse())
Ω(cfg.Get().Replicas[0].InsecureSkipVerify).Should(BeFalse())
})
It("should have the insecure skip verify from the config env var", func() {
setEnv("REPLICA1_INSECURE_SKIP_VERIFY", "true")
@@ -114,7 +114,7 @@ var _ = Describe("Config", func() {
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].InsecureSkipVerify).Should(BeTrue())
Ω(cfg.Get().Replicas[0].InsecureSkipVerify).Should(BeTrue())
})
})
Context("API Port", func() {
@@ -122,7 +122,7 @@ var _ = Describe("Config", func() {
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.API.Port).Should(Equal(9090))
Ω(cfg.Get().API.Port).Should(Equal(9090))
})
It("should have the api port from the config flags", func() {
flags.EXPECT().Changed(config.FlagApiPort).Return(true).AnyTimes()
@@ -131,7 +131,7 @@ var _ = Describe("Config", func() {
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.API.Port).Should(Equal(9990))
Ω(cfg.Get().API.Port).Should(Equal(9990))
})
It("should have the api port from the config env var", func() {
setEnv("API_PORT", "9999")
@@ -141,7 +141,7 @@ var _ = Describe("Config", func() {
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.API.Port).Should(Equal(9999))
Ω(cfg.Get().API.Port).Should(Equal(9999))
})
})
@@ -151,8 +151,8 @@ var _ = Describe("Config", func() {
cfg, err := config.Get("../../testdata/config_test_replica.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].DHCPServerEnabled).ShouldNot(BeNil())
Ω(*cfg.Replicas[0].DHCPServerEnabled).Should(BeFalse())
Ω(cfg.Get().Replicas[0].DHCPServerEnabled).ShouldNot(BeNil())
Ω(*cfg.Get().Replicas[0].DHCPServerEnabled).Should(BeFalse())
})
})
@@ -162,8 +162,8 @@ var _ = Describe("Config", func() {
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Replicas[0].DHCPServerEnabled).ShouldNot(BeNil())
Ω(*cfg.Replicas[0].DHCPServerEnabled).Should(BeFalse())
Ω(cfg.Get().Replicas[0].DHCPServerEnabled).ShouldNot(BeNil())
Ω(*cfg.Get().Replicas[0].DHCPServerEnabled).Should(BeFalse())
})
})
Context("API Port", func() {
@@ -171,7 +171,7 @@ var _ = Describe("Config", func() {
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.API.Port).Should(Equal(9090))
Ω(cfg.Get().API.Port).Should(Equal(9090))
})
It("should have the api port from the config flags", func() {
flags.EXPECT().Changed(config.FlagApiPort).Return(true).AnyTimes()
@@ -180,7 +180,7 @@ var _ = Describe("Config", func() {
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.API.Port).Should(Equal(9990))
Ω(cfg.Get().API.Port).Should(Equal(9990))
})
It("should have the api port from the config env var", func() {
setEnv("API_PORT", "9999")
@@ -190,18 +190,16 @@ var _ = Describe("Config", func() {
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.API.Port).Should(Equal(9999))
Ω(cfg.Get().API.Port).Should(Equal(9999))
})
})
//////
Context("Feature DNS Server Config", func() {
It("should have the feature dns server config from the config file", func() {
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Features.DNS.ServerConfig).Should(BeFalse())
Ω(cfg.Get().Features.DNS.ServerConfig).Should(BeFalse())
})
It("should have the feature dns server config from the config flags", func() {
flags.EXPECT().Changed(config.FlagFeatureDnsServerConfig).Return(true).AnyTimes()
@@ -210,7 +208,7 @@ var _ = Describe("Config", func() {
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Features.DNS.ServerConfig).Should(BeTrue())
Ω(cfg.Get().Features.DNS.ServerConfig).Should(BeTrue())
})
It("should have the feature dns server config from the config env var", func() {
setEnv("FEATURES_DNS_SERVER_CONFIG", "false")
@@ -220,7 +218,7 @@ var _ = Describe("Config", func() {
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Features.DNS.ServerConfig).Should(BeFalse())
Ω(cfg.Get().Features.DNS.ServerConfig).Should(BeFalse())
})
It("should have the feature dns server config from the config DEPRECATED env var", func() {
setEnv("FEATURES_DNS_SERVERCONFIG", "false")
@@ -230,7 +228,7 @@ var _ = Describe("Config", func() {
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
Ω(err).ShouldNot(HaveOccurred())
Ω(cfg.Features.DNS.ServerConfig).Should(BeFalse())
Ω(cfg.Get().Features.DNS.ServerConfig).Should(BeFalse())
})
})
})

View File

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

View File

@@ -8,17 +8,19 @@ import (
"gopkg.in/yaml.v3"
)
func readFile(cfg *types.Config, path string) error {
func readFile(cfg *types.Config, path string) (string, error) {
var content string
if _, err := os.Stat(path); err == nil {
b, err := os.ReadFile(path)
if err != nil {
return err
return "", err
}
content = string(b)
if err := yaml.Unmarshal(b, cfg); err != nil {
return err
return "", err
}
}
return nil
return content, nil
}
func configFilePath(configFile string) (string, error) {

View File

@@ -0,0 +1,90 @@
package config
import (
"bytes"
_ "embed"
"os"
"runtime"
"sort"
"strings"
"text/template"
"github.com/bakito/adguardhome-sync/pkg/client"
"github.com/bakito/adguardhome-sync/pkg/types"
"github.com/bakito/adguardhome-sync/version"
"gopkg.in/yaml.v3"
)
//go:embed print-config.md
var printConfigTemplate string
func (ac *AppConfig) Print() error {
originVersion := aghVersion(ac.cfg.Origin)
var replicaVersions []string
for _, replica := range ac.cfg.Replicas {
replicaVersions = append(replicaVersions, aghVersion(replica))
}
out, err := ac.print(os.Environ(), originVersion, replicaVersions)
if err != nil {
return err
}
logger.Infof(
"Printing adguardhome-sync aggregated config (THE APPLICATION WILL NOT START IN THIS MODE):\n%s",
out,
)
return nil
}
func aghVersion(i types.AdGuardInstance) string {
cl, err := client.New(i)
if err != nil {
return "N/A"
}
stats, err := cl.Status()
if err != nil {
return "N/A"
}
return stats.Version
}
func (ac *AppConfig) print(env []string, originVersion string, replicaVersions []string) (string, error) {
config, err := yaml.Marshal(ac.Get())
if err != nil {
return "", err
}
funcMap := template.FuncMap{
// The name "inc" is what the function will be called in the template text.
"inc": func(i int) int {
return i + 1
},
}
t, err := template.New("printConfigTemplate").Funcs(funcMap).Parse(printConfigTemplate)
if err != nil {
return "", err
}
sort.Strings(env)
var buf bytes.Buffer
if err = t.Execute(&buf, map[string]interface{}{
"Version": version.Version,
"Build": version.Build,
"OperatingSystem": runtime.GOOS,
"Architecture": runtime.GOARCH,
"AggregatedConfig": string(config),
"ConfigFilePath": ac.filePath,
"ConfigFileContent": ac.content,
"EnvironmentVariables": strings.Join(env, "\n"),
"OriginVersion": originVersion,
"ReplicaVersions": replicaVersions,
}); err != nil {
return "", err
}
return buf.String(), nil
}

View File

@@ -0,0 +1,36 @@
<!-- PLEASE COPY THE FOLLOWING OUTPUT AS IS INTO THE GITHUB ISSUE (Don't forget to mask your usernames, passwords, IPs and other sensitive information when using this in an issue ) -->
### Runtime
AdguardHome-Sync Version: {{ .Version }}
Build: {{ .Build }}
OperatingSystem: {{ .OperatingSystem }}
Architecture: {{ .Architecture }}
OriginVersion: {{ .OriginVersion }}
ReplicaVersions:
{{- range $i,$rep := .ReplicaVersions }}
- Replica {{ inc $i }}: {{ $rep }}
{{- end }}
### AdGuardHome sync aggregated config
```yaml
{{ .AggregatedConfig }}
```
{{- if .ConfigFilePath }}
### AdGuardHome sync unmodified config file
Config file path: {{ .ConfigFilePath }}
```yaml
{{ .ConfigFileContent }}
```
{{- end }}
### Environment Variables
```ini
{{ .EnvironmentVariables }}
```
<!-- END OF GITHUB ISSUE CONTENT -->

View File

@@ -0,0 +1,58 @@
package config
import (
_ "embed"
"fmt"
"os"
"path/filepath"
"runtime"
"github.com/bakito/adguardhome-sync/pkg/types"
"github.com/bakito/adguardhome-sync/version"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("AppConfig", func() {
var (
ac *AppConfig
env []string
)
BeforeEach(func() {
ac = &AppConfig{
cfg: &types.Config{
Origin: types.AdGuardInstance{
URL: "https://ha.xxxx.net:3000",
},
},
content: `
origin:
url: https://ha.xxxx.net:3000
`,
}
env = []string{"FOO=foo", "BAR=bar"}
})
Context("print", func() {
It("should print config without file", func() {
out, err := ac.print(env, "v0.0.1", []string{"v0.0.2"})
Ω(err).ShouldNot(HaveOccurred())
Ω(
out,
).Should(Equal(fmt.Sprintf(expected(1), version.Version, version.Build, runtime.GOOS, runtime.GOARCH)))
})
It("should print config with file", func() {
ac.filePath = "config.yaml"
out, err := ac.print(env, "v0.0.1", []string{"v0.0.2"})
Ω(err).ShouldNot(HaveOccurred())
Ω(
out,
).Should(Equal(fmt.Sprintf(expected(2), version.Version, version.Build, runtime.GOOS, runtime.GOARCH)))
})
})
})
func expected(id int) string {
b, err := os.ReadFile(filepath.Join("../../testdata/config", fmt.Sprintf("print-config_test_expected%d.md", id)))
Ω(err).ShouldNot(HaveOccurred())
return string(b)
}

52
pkg/config/validate.go Normal file
View File

@@ -0,0 +1,52 @@
package config
import (
_ "embed"
"os"
"strings"
"github.com/santhosh-tekuri/jsonschema/v6"
"gopkg.in/yaml.v3"
)
const schemaURL = "config-schema.json"
//go:embed config-schema.json
var schemaData string
func validateSchema(cfgFile string) error {
// ignore if file not exists
if _, err := os.Stat(cfgFile); err != nil {
return nil
}
// Load YAML file
yamlContent, err := os.ReadFile(cfgFile)
if err != nil {
return err
}
return validateYAML(yamlContent)
}
func validateYAML(yamlContent []byte) error {
// Convert YAML to JSON
var yamlData interface{}
err := yaml.Unmarshal(yamlContent, &yamlData)
if err != nil {
return err
}
// Load JSON schema
sch, err := jsonschema.UnmarshalJSON(strings.NewReader(schemaData))
if err != nil {
return err
}
c := jsonschema.NewCompiler()
if err := c.AddResource(schemaURL, sch); err != nil {
return err
}
schema := c.MustCompile(schemaURL)
// validateSchema
return schema.Validate(yamlData)
}

View File

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

View File

@@ -4,7 +4,6 @@ import (
"github.com/bakito/adguardhome-sync/pkg/client/model"
"github.com/bakito/adguardhome-sync/pkg/log"
"github.com/prometheus/client_golang/prometheus"
"golang.org/x/exp/constraints"
)
const StatsTotal = "total"
@@ -156,8 +155,8 @@ func initMetric(name string, metric *prometheus.GaugeVec) {
l.With("name", name).Info("New Prometheus metric registered")
}
func Update(ims ...InstanceMetrics) {
for _, im := range ims {
func Update(iml InstanceMetricsList) {
for _, im := range iml.Metrics {
update(im)
stats[im.HostName] = im.Stats
}
@@ -231,6 +230,10 @@ func update(im InstanceMetrics) {
}
}
type InstanceMetricsList struct {
Metrics []InstanceMetrics `faker:"slice_len=5"`
}
type InstanceMetrics struct {
HostName string
Status *model.ServerStatus
@@ -249,18 +252,14 @@ func (os OverallStats) consolidate() OverallStats {
return consolidated
}
func safeMetric[T Number](v *T) float64 {
func safeMetric[T int | float64 | float32](v *T) float64 {
if v == nil {
return 0
}
return float64(*v)
}
type Number interface {
constraints.Float | constraints.Integer
}
func GetStats() OverallStats {
func getStats() OverallStats {
return stats.consolidate()
}

View File

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

101
pkg/metrics/metrics_test.go Normal file
View File

@@ -0,0 +1,101 @@
package metrics
import (
"github.com/bakito/adguardhome-sync/pkg/client/model"
"github.com/go-faker/faker/v4"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"k8s.io/utils/ptr"
)
var _ = Describe("Metrics", func() {
BeforeEach(func() {
stats = make(OverallStats)
})
Context("Update / getStats", func() {
It("generate correct stats", func() {
Update(InstanceMetricsList{[]InstanceMetrics{
{HostName: "foo", Status: &model.ServerStatus{}, Stats: &model.Stats{
NumDnsQueries: ptr.To(100),
DnsQueries: ptr.To(
[]int{10, 20, 30, 40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
),
}},
{HostName: "bar", Status: &model.ServerStatus{}, Stats: &model.Stats{
NumDnsQueries: ptr.To(200),
DnsQueries: ptr.To(
[]int{20, 40, 60, 80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
),
}},
{HostName: "aaa", Status: &model.ServerStatus{}, Stats: &model.Stats{
NumDnsQueries: ptr.To(300),
DnsQueries: ptr.To(
[]int{30, 60, 90, 120, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
),
}},
}})
Ω(stats).Should(HaveKey("foo"))
Ω(stats["foo"].NumDnsQueries).Should(Equal(ptr.To(100)))
Ω(stats).Should(HaveKey("bar"))
Ω(stats["bar"].NumDnsQueries).Should(Equal(ptr.To(200)))
Ω(stats).Should(HaveKey("aaa"))
Ω(stats["aaa"].NumDnsQueries).Should(Equal(ptr.To(300)))
os := getStats()
tot := os.Total()
Ω(*tot.NumDnsQueries).Should(Equal(600))
Ω(
*tot.DnsQueries,
).Should(Equal([]int{60, 120, 180, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}))
foo := os["foo"]
bar := os["bar"]
aaa := os["aaa"]
Ω(*foo.NumDnsQueries).Should(Equal(100))
Ω(*bar.NumDnsQueries).Should(Equal(200))
Ω(*aaa.NumDnsQueries).Should(Equal(300))
Ω(
*foo.DnsQueries,
).Should(Equal([]int{10, 20, 30, 40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}))
Ω(
*bar.DnsQueries,
).Should(Equal([]int{20, 40, 60, 80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}))
Ω(
*aaa.DnsQueries,
).Should(Equal([]int{30, 60, 90, 120, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}))
})
})
Context("StatsGraph", func() {
var metrics InstanceMetricsList
BeforeEach(func() {
err := faker.FakeData(&metrics)
Ω(err).ShouldNot(HaveOccurred())
})
It("should provide correct results with faked values", func() {
Update(metrics)
_, dns, blocked, malware, adult := StatsGraph()
verifyStats(dns)
verifyStats(blocked)
verifyStats(malware)
verifyStats(adult)
})
})
})
func verifyStats(lines []line) {
var total line
sum := make([]int, len(lines[0].Data))
for _, l := range lines {
if l.Title == labelTotal {
total = l
} else {
for i, d := range l.Data {
sum[i] = sum[i] + d
}
}
}
Ω(sum).Should(Equal(total.Data))
}

View File

@@ -1,13 +1,14 @@
package sync
package metrics
import (
"slices"
"strings"
"github.com/bakito/adguardhome-sync/pkg/client/model"
"github.com/bakito/adguardhome-sync/pkg/metrics"
"golang.org/x/exp/slices"
)
const labelTotal = "Total"
var (
blue = []int{78, 141, 245}
blueAlternatives = [][]int{
@@ -46,30 +47,43 @@ var (
}
)
func statsGraph() (*model.Stats, []line, []line, []line, []line) {
s := metrics.GetStats()
func StatsGraph() (*model.Stats, []line, []line, []line, []line) {
s := getStats()
t := s.Total()
dns := graphLines(t, s, blue, blueAlternatives, func(s *model.Stats) []int {
return *s.DnsQueries
return safeStats(s.DnsQueries)
})
blocked := graphLines(t, s, red, redAlternatives, func(s *model.Stats) []int {
return *s.BlockedFiltering
return safeStats(s.BlockedFiltering)
})
malware := graphLines(t, s, green, greenAlternatives, func(s *model.Stats) []int {
return *s.ReplacedSafebrowsing
return safeStats(s.ReplacedSafebrowsing)
})
adult := graphLines(t, s, yellow, yellowAlternatives, func(s *model.Stats) []int {
return *s.ReplacedParental
return safeStats(s.ReplacedParental)
})
return t, dns, blocked, malware, adult
}
func graphLines(t *model.Stats, s metrics.OverallStats, baseColor []int, altColors [][]int, dataCB func(s *model.Stats) []int) []line {
func safeStats(stats *[]int) []int {
if stats == nil {
return make([]int, 0)
}
return *stats
}
func graphLines(
t *model.Stats,
s OverallStats,
baseColor []int,
altColors [][]int,
dataCB func(s *model.Stats) []int,
) []line {
g := &graph{
total: line{
Fill: true,
Title: "Total",
Title: labelTotal,
Data: dataCB(t),
R: baseColor[0],
G: baseColor[1],
@@ -79,7 +93,7 @@ func graphLines(t *model.Stats, s metrics.OverallStats, baseColor []int, altColo
var i int
for name, data := range s {
if name != metrics.StatsTotal {
if name != StatsTotal {
g.replicas = append(g.replicas, line{
Fill: false,
Title: name,

View File

@@ -105,7 +105,8 @@ var (
return ac.client.SetCustomRules(ac.origin.filters.UserRules)
}
if !utils.PtrEquals(ac.origin.filters.Enabled, rf.Enabled) || !utils.PtrEquals(ac.origin.filters.Interval, rf.Interval) {
if !utils.PtrEquals(ac.origin.filters.Enabled, rf.Enabled) ||
!utils.PtrEquals(ac.origin.filters.Interval, rf.Interval) {
return ac.client.ToggleFiltering(*ac.origin.filters.Enabled, *ac.origin.filters.Interval)
}
return nil
@@ -236,7 +237,14 @@ var (
}
)
func syncFilterType(rl *zap.SugaredLogger, of *[]model.Filter, rFilters *[]model.Filter, whitelist bool, replica client.Client, continueOnError bool) error {
func syncFilterType(
rl *zap.SugaredLogger,
of *[]model.Filter,
rFilters *[]model.Filter,
whitelist bool,
replica client.Client,
continueOnError bool,
) error {
fa, fu, fd := model.MergeFilters(rFilters, of)
for _, f := range fd {

View File

@@ -33,7 +33,7 @@ func (w *worker) handleSync(c *gin.Context) {
}
func (w *worker) handleRoot(c *gin.Context) {
total, dns, blocked, malware, adult := statsGraph()
total, dns, blocked, malware, adult := metrics.StatsGraph()
c.HTML(http.StatusOK, "index.html", map[string]interface{}{
"DarkMode": w.cfg.API.DarkMode,
@@ -42,14 +42,23 @@ func (w *worker) handleRoot(c *gin.Context) {
"Build": version.Build,
"SyncStatus": w.status(),
"Stats": map[string]interface{}{
"Labels": getLast24Hours(),
"DNS": dns,
"Blocked": blocked,
"BlockedPercentage": fmt.Sprintf("%.2f", (float64(*total.NumBlockedFiltering)*100.0)/float64(*total.NumDnsQueries)),
"Malware": malware,
"MalwarePercentage": fmt.Sprintf("%.2f", (float64(*total.NumReplacedSafebrowsing)*100.0)/float64(*total.NumDnsQueries)),
"Adult": adult,
"AdultPercentage": fmt.Sprintf("%.2f", (float64(*total.NumReplacedParental)*100.0)/float64(*total.NumDnsQueries)),
"Labels": getLast24Hours(),
"DNS": dns,
"Blocked": blocked,
"BlockedPercentage": fmt.Sprintf(
"%.2f",
(float64(*total.NumBlockedFiltering)*100.0)/float64(*total.NumDnsQueries),
),
"Malware": malware,
"MalwarePercentage": fmt.Sprintf(
"%.2f",
(float64(*total.NumReplacedSafebrowsing)*100.0)/float64(*total.NumDnsQueries),
),
"Adult": adult,
"AdultPercentage": fmt.Sprintf(
"%.2f",
(float64(*total.NumReplacedParental)*100.0)/float64(*total.NumDnsQueries),
),
"TotalDNS": total.NumDnsQueries,
"TotalBlocked": total.NumBlockedFiltering,

View File

@@ -7,9 +7,9 @@
<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">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
crossorigin="anonymous">
{{- end }}
<script type="text/javascript">
$(document).ready(function () {
@@ -47,7 +47,6 @@
<link rel="shortcut icon" href="favicon.ico">
<style>
.stat-card {
background-color: rgb(43, 43, 43);
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
padding: 15px;
@@ -58,11 +57,6 @@
height: 100%;
}
{{- if .Metrics }}
body {
background-color: rgb(30, 30, 30);
color: rgb(255, 255, 255);
font-family: Arial, sans-serif;
}
.stat-card h3 {
margin: 0;
font-size: 2rem;
@@ -70,7 +64,6 @@
.stat-card p {
margin: 5px 0;
font-size: 0.9rem;
color: rgb(78, 141, 245);
}
.percentage {
font-size: 0.9rem;
@@ -142,7 +135,7 @@
<div class="btn-group" role="group">
<button type="button" class="btn btn-success" id="sync">Synchronize</button>
<button type="button" class="btn btn-secondary" id="showLogs">Update Logs</button>
<button type="button" class="btn btn-secondary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button type="button" class="btn btn-secondary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
</button>
<div class="dropdown-menu">
<a class="dropdown-item" href="#" id="clearLogs">Clear Logs</a>
@@ -171,14 +164,17 @@
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.12.9/dist/umd/popper.min.js"
integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous">
<!-- openssl dgst -sha384 -binary popper.min.js | openssl base64 -A -->
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js"
integrity="sha384-IQsoLXl5PILFhosVNubq5LC7Qb9DXgDA9i+tQ8Zj3iwWAwPtgFTxbJ8NT4GN1R8p" crossorigin="anonymous">
</script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/js/bootstrap.min.js"
integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.min.js"
integrity="sha384-0pUGZvbkm6XF6gxjEnlmuGrJXVbNuzT9qBBavbLwCsOGabYfZo0T0to5eqruptLy" crossorigin="anonymous">
</script>
{{- if .Metrics }}
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js"ŝ
integrity="sha384-vsrfeLOOY6KuIYKDlmVH5UiBmgIdB1oEf7p01YgWHuqmOHfZr374+odEv96n9tNC" crossorigin="anonymous">
</script>
<script>
// Function to create minimal line charts
function createChart(canvasId, data) {

View File

@@ -26,13 +26,13 @@ func (w *worker) startScraping() {
}
func (w *worker) scrape() {
var ims []metrics.InstanceMetrics
var iml metrics.InstanceMetricsList
ims = append(ims, w.getMetrics(w.cfg.Origin))
iml.Metrics = append(iml.Metrics, w.getMetrics(w.cfg.Origin))
for _, replica := range w.cfg.Replicas {
ims = append(ims, w.getMetrics(replica))
iml.Metrics = append(iml.Metrics, w.getMetrics(replica))
}
metrics.Update(ims...)
metrics.Update(iml)
}
func (w *worker) getMetrics(inst types.AdGuardInstance) (im metrics.InstanceMetrics) {

View File

@@ -66,16 +66,12 @@ func Sync(cfg *types.Config) error {
if cfg.API.Port != 0 {
w.cron.Start()
} else {
runOnStartAsync(cfg, w)
w.cron.Run()
}
}
if cfg.API.Port != 0 {
if cfg.RunOnStart {
go func() {
l.Info("Running sync on startup")
w.sync()
}()
}
runOnStartAsync(cfg, w)
w.listenAndServe()
} else if cfg.RunOnStart {
l.Info("Running sync on startup")
@@ -85,6 +81,15 @@ func Sync(cfg *types.Config) error {
return nil
}
func runOnStartAsync(cfg *types.Config, w *worker) {
if cfg.RunOnStart {
go func() {
l.Info("Running sync on startup")
w.sync()
}()
}
}
type worker struct {
cfg *types.Config
running bool
@@ -167,7 +172,8 @@ func (w *worker) sync() {
}
if versions.IsNewerThan(versions.MinAgh, o.status.Version) {
sl.With("error", err, "version", o.status.Version).Errorf("Origin AdGuard Home version must be >= %s", versions.MinAgh)
sl.With("error", err, "version", o.status.Version).
Errorf("Origin AdGuard Home version must be >= %s", versions.MinAgh)
return
}
@@ -275,12 +281,14 @@ func (w *worker) syncTo(l *zap.SugaredLogger, o *origin, replica types.AdGuardIn
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)
rl.With("error", err, "version", replicaStatus.Version).
Errorf("Replica AdGuard Home version must be >= %s", versions.MinAgh)
return
}
if o.status.Version != replicaStatus.Version {
rl.With("originVersion", o.status.Version, "replicaVersion", replicaStatus.Version).Warn("Versions do not match")
rl.With("originVersion", o.status.Version, "replicaVersion", replicaStatus.Version).
Warn("Versions do not match")
}
ac := &actionContext{
@@ -303,7 +311,11 @@ func (w *worker) syncTo(l *zap.SugaredLogger, o *origin, replica types.AdGuardIn
rl.Info("Sync done")
}
func (w *worker) statusWithSetup(rl *zap.SugaredLogger, replica types.AdGuardInstance, rc client.Client) (*model.ServerStatus, error) {
func (w *worker) statusWithSetup(
rl *zap.SugaredLogger,
replica types.AdGuardInstance,
rc client.Client,
) (*model.ServerStatus, error) {
rs, err := rc.Status()
if err != nil {
if replica.AutoSetup && errors.Is(err, client.ErrSetupNeeded) {

View File

@@ -273,14 +273,18 @@ var _ = Describe("Sync", func() {
})
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().
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().
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())
@@ -315,7 +319,8 @@ var _ = Describe("Sync", func() {
var interval model.QueryLogConfigInterval = 123
ac.origin.queryLogConfig.Interval = &interval
cl.EXPECT().QueryLogConfig().Return(qlc, nil)
cl.EXPECT().SetQueryLogConfig(&model.QueryLogConfigWithIgnored{QueryLogConfig: model.QueryLogConfig{AnonymizeClientIp: nil, Interval: &interval, Enabled: nil}})
cl.EXPECT().
SetQueryLogConfig(&model.QueryLogConfigWithIgnored{QueryLogConfig: model.QueryLogConfig{AnonymizeClientIp: nil, Interval: &interval, Enabled: nil}})
err := actionQueryLogConfig(ac)
Ω(err).ShouldNot(HaveOccurred())
})
@@ -434,7 +439,9 @@ var _ = Describe("Sync", func() {
Ω(err).ShouldNot(HaveOccurred())
})
It("should update a filter", func() {
ac.origin.filters.Filters = utils.Ptr([]model.Filter{{Name: "foo", Url: "https://foo.bar", Enabled: true}})
ac.origin.filters.Filters = utils.Ptr(
[]model.Filter{{Name: "foo", Url: "https://foo.bar", Enabled: true}},
)
rf.Filters = utils.Ptr([]model.Filter{{Name: "foo", Url: "https://foo.bar"}})
cl.EXPECT().Filtering().Return(rf, nil)
cl.EXPECT().UpdateFilter(false, model.Filter{Name: "foo", Url: "https://foo.bar", Enabled: true})
@@ -447,16 +454,22 @@ var _ = Describe("Sync", func() {
ac.cfg.ContinueOnError = false
ac.origin.filters.Filters = utils.Ptr([]model.Filter{{Name: "foo", Url: "https://foo.bar"}})
cl.EXPECT().Filtering().Return(rf, nil)
cl.EXPECT().AddFilter(false, model.Filter{Name: "foo", Url: "https://foo.bar"}).Return(errors.New("test failure"))
cl.EXPECT().
AddFilter(false, model.Filter{Name: "foo", Url: "https://foo.bar"}).
Return(errors.New("test failure"))
err := actionFilters(ac)
Ω(err).Should(HaveOccurred())
})
It("should continue after failed added filter", func() {
ac.cfg.ContinueOnError = true
ac.origin.filters.Filters = utils.Ptr([]model.Filter{{Name: "foo", Url: "https://foo.bar"}, {Name: "bar", Url: "https://bar.foo"}})
ac.origin.filters.Filters = utils.Ptr(
[]model.Filter{{Name: "foo", Url: "https://foo.bar"}, {Name: "bar", Url: "https://bar.foo"}},
)
cl.EXPECT().Filtering().Return(rf, nil)
cl.EXPECT().AddFilter(false, model.Filter{Name: "foo", Url: "https://foo.bar"}).Return(errors.New("test failure"))
cl.EXPECT().
AddFilter(false, model.Filter{Name: "foo", Url: "https://foo.bar"}).
Return(errors.New("test failure"))
cl.EXPECT().AddFilter(false, model.Filter{Name: "bar", Url: "https://bar.foo"})
cl.EXPECT().RefreshFilters(gm.Any())
err := actionFilters(ac)

View File

@@ -27,15 +27,15 @@ func NewFeatures(enabled bool) Features {
// Features feature flags
type Features struct {
DNS DNS `json:"dns" yaml:"dns"`
DHCP DHCP `json:"dhcp" yaml:"dhcp"`
DNS DNS `json:"dns" yaml:"dns"`
DHCP DHCP `json:"dhcp" yaml:"dhcp"`
GeneralSettings bool `json:"generalSettings" yaml:"generalSettings" env:"FEATURES_GENERAL_SETTINGS"`
QueryLogConfig bool `json:"queryLogConfig" yaml:"queryLogConfig" env:"FEATURES_QUERY_LOG_CONFIG"`
StatsConfig bool `json:"statsConfig" yaml:"statsConfig" env:"FEATURES_STATS_CONFIG"`
ClientSettings bool `json:"clientSettings" yaml:"clientSettings" env:"FEATURES_CLIENT_SETTINGS"`
Services bool `json:"services" yaml:"services" env:"FEATURES_SERVICES"`
Filters bool `json:"filters" yaml:"filters" env:"FEATURES_FILTERS"`
Theme bool `json:"theme" yaml:"theme" env:"FEATURES_THEME"`
QueryLogConfig bool `json:"queryLogConfig" yaml:"queryLogConfig" env:"FEATURES_QUERY_LOG_CONFIG"`
StatsConfig bool `json:"statsConfig" yaml:"statsConfig" env:"FEATURES_STATS_CONFIG"`
ClientSettings bool `json:"clientSettings" yaml:"clientSettings" env:"FEATURES_CLIENT_SETTINGS"`
Services bool `json:"services" yaml:"services" env:"FEATURES_SERVICES"`
Filters bool `json:"filters" yaml:"filters" env:"FEATURES_FILTERS"`
Theme bool `json:"theme" yaml:"theme" env:"FEATURES_THEME"`
}
// DHCP features
@@ -46,9 +46,9 @@ type DHCP struct {
// DNS features
type DNS struct {
AccessLists bool `json:"accessLists" yaml:"accessLists" env:"FEATURES_DNS_ACCESS_LISTS"`
AccessLists bool `json:"accessLists" yaml:"accessLists" env:"FEATURES_DNS_ACCESS_LISTS"`
ServerConfig bool `json:"serverConfig" yaml:"serverConfig" env:"FEATURES_DNS_SERVER_CONFIG"`
Rewrites bool `json:"rewrites" yaml:"rewrites" env:"FEATURES_DNS_REWRITES"`
Rewrites bool `json:"rewrites" yaml:"rewrites" env:"FEATURES_DNS_REWRITES"`
}
// LogDisabled log all disabled features

View File

@@ -18,39 +18,39 @@ const (
// Config application configuration struct
// +k8s:deepcopy-gen=true
type Config struct {
Origin AdGuardInstance `json:"origin" yaml:"origin" env:"ORIGIN"`
Replica *AdGuardInstance `json:"replica,omitempty" yaml:"replica,omitempty" env:"REPLICA"`
Replicas []AdGuardInstance `json:"replicas,omitempty" yaml:"replicas,omitempty"`
Cron string `json:"cron,omitempty" yaml:"cron,omitempty" env:"CRON"`
RunOnStart bool `json:"runOnStart,omitempty" yaml:"runOnStart,omitempty" env:"RUN_ON_START"`
Origin AdGuardInstance `json:"origin" yaml:"origin" env:"ORIGIN"`
Replica *AdGuardInstance `json:"replica,omitempty" yaml:"replica,omitempty" env:"REPLICA"`
Replicas []AdGuardInstance `json:"replicas,omitempty" yaml:"replicas,omitempty" faker:"slice_len=2"`
Cron string `json:"cron,omitempty" yaml:"cron,omitempty" env:"CRON"`
RunOnStart bool `json:"runOnStart,omitempty" yaml:"runOnStart,omitempty" env:"RUN_ON_START"`
PrintConfigOnly bool `json:"printConfigOnly,omitempty" yaml:"printConfigOnly,omitempty" env:"PRINT_CONFIG_ONLY"`
ContinueOnError bool `json:"continueOnError,omitempty" yaml:"continueOnError,omitempty" env:"CONTINUE_ON_ERROR"`
API API `json:"api,omitempty" yaml:"api,omitempty" env:"API"`
Features Features `json:"features,omitempty" yaml:"features,omitempty" env:"FEATURES_"`
API API `json:"api,omitempty" yaml:"api,omitempty" env:"API"`
Features Features `json:"features,omitempty" yaml:"features,omitempty" env:"FEATURES_"`
}
// API configuration
type API struct {
Port int `json:"port,omitempty" yaml:"port,omitempty" env:"API_PORT"`
Port int `json:"port,omitempty" yaml:"port,omitempty" env:"API_PORT"`
Username string `json:"username,omitempty" yaml:"username,omitempty" env:"API_USERNAME"`
Password string `json:"password,omitempty" yaml:"password,omitempty" env:"API_PASSWORD"`
DarkMode bool `json:"darkMode,omitempty" yaml:"darkMode,omitempty" env:"API_DARK_MODE"`
Metrics Metrics `json:"metrics,omitempty" yaml:"metrics,omitempty" env:"API_METRICS"`
TLS TLS `json:"tls,omitempty" yaml:"tls,omitempty" env:"API_TLS"`
Metrics Metrics `json:"metrics,omitempty" yaml:"metrics,omitempty" env:"API_METRICS"`
TLS TLS `json:"tls,omitempty" yaml:"tls,omitempty" env:"API_TLS"`
}
// Metrics configuration
type Metrics struct {
Enabled bool `json:"enabled,omitempty" yaml:"enabled,omitempty" env:"API_METRICS_ENABLED"`
Enabled bool `json:"enabled,omitempty" yaml:"enabled,omitempty" env:"API_METRICS_ENABLED"`
ScrapeInterval time.Duration `json:"scrapeInterval,omitempty" yaml:"scrapeInterval,omitempty" env:"API_METRICS_SCRAPE_INTERVAL"`
QueryLogLimit int `json:"queryLogLimit,omitempty" yaml:"queryLogLimit,omitempty" env:"API_METRICS_QUERY_LOG_LIMIT"`
QueryLogLimit int `json:"queryLogLimit,omitempty" yaml:"queryLogLimit,omitempty" env:"API_METRICS_QUERY_LOG_LIMIT"`
}
// TLS configuration
type TLS struct {
CertDir string `json:"certDir,omitempty" yaml:"certDir,omitempty" env:"API_TLS_CERT_DIR"`
CertDir string `json:"certDir,omitempty" yaml:"certDir,omitempty" env:"API_TLS_CERT_DIR"`
CertName string `json:"certName,omitempty" yaml:"certName,omitempty" env:"API_TLS_CERT_NAME"`
KeyName string `json:"keyName,omitempty" yaml:"keyName,omitempty" env:"API_TLS_KEY_NAME"`
KeyName string `json:"keyName,omitempty" yaml:"keyName,omitempty" env:"API_TLS_KEY_NAME"`
}
func (t TLS) Enabled() bool {
@@ -140,15 +140,15 @@ func (cfg *Config) Init() error {
// AdGuardInstance AdguardHome config instance
// +k8s:deepcopy-gen=true
type AdGuardInstance struct {
URL string `json:"url" yaml:"url" env:"URL"`
WebURL string `json:"webURL" yaml:"webURL" env:"WEB_URL"`
APIPath string `json:"apiPath,omitempty" yaml:"apiPath,omitempty" env:"API_PATH"`
Username string `json:"username,omitempty" yaml:"username,omitempty" env:"USERNAME"`
Password string `json:"password,omitempty" yaml:"password,omitempty" env:"PASSWORD"`
Cookie string `json:"cookie,omitempty" yaml:"cookie,omitempty" env:"COOKIE"`
InsecureSkipVerify bool `json:"insecureSkipVerify" yaml:"insecureSkipVerify" env:"INSECURE_SKIP_VERIFY"`
AutoSetup bool `json:"autoSetup" yaml:"autoSetup" env:"AUTO_SETUP"`
InterfaceName string `json:"interfaceName,omitempty" yaml:"interfaceName,omitempty" env:"INTERFACE_NAME"`
URL string `json:"url" yaml:"url" env:"URL" faker:"url"`
WebURL string `json:"webURL" yaml:"webURL" env:"WEB_URL" faker:"url"`
APIPath string `json:"apiPath,omitempty" yaml:"apiPath,omitempty" env:"API_PATH"`
Username string `json:"username,omitempty" yaml:"username,omitempty" env:"USERNAME"`
Password string `json:"password,omitempty" yaml:"password,omitempty" env:"PASSWORD"`
Cookie string `json:"cookie,omitempty" yaml:"cookie,omitempty" env:"COOKIE"`
InsecureSkipVerify bool `json:"insecureSkipVerify" yaml:"insecureSkipVerify" env:"INSECURE_SKIP_VERIFY"`
AutoSetup bool `json:"autoSetup" yaml:"autoSetup" env:"AUTO_SETUP"`
InterfaceName string `json:"interfaceName,omitempty" yaml:"interfaceName,omitempty" env:"INTERFACE_NAME"`
DHCPServerEnabled *bool `json:"dhcpServerEnabled,omitempty" yaml:"dhcpServerEnabled,omitempty" env:"DHCP_SERVER_ENABLED"`
Host string `json:"-" yaml:"-"`
@@ -187,8 +187,8 @@ func (i *AdGuardInstance) Init() error {
}
func mask(s string) string {
if s == "" {
return "***"
if len(s) < 3 {
return strings.Repeat("*", len(s))
}
mask := strings.Repeat("*", len(s)-2)
return fmt.Sprintf("%v%s%v", string(s[0]), mask, string(s[len(s)-1]))

View File

@@ -0,0 +1,15 @@
package types
import (
"testing"
)
func FuzzMask(f *testing.F) {
testcases := []string{"", "a", "ab", "abc", "abcd"}
for _, tc := range testcases {
f.Add(tc)
}
f.Fuzz(func(t *testing.T, value string) {
_ = mask(value)
})
}

View File

@@ -83,6 +83,15 @@ var _ = Describe("Types", func() {
Ω(masked.API.Password).Should(Equal("p**s"))
})
})
DescribeTable("mask should work correctly",
func(value, expected string) {
Ω(mask(value)).Should(Equal(expected))
},
Entry(`Empty password`, "", ""),
Entry(`1 char password`, "a", "*"),
Entry(`2 char password`, "ab", "**"),
Entry(`3 char password`, "abc", "a*c"),
)
})
Context("Feature", func() {
Context("LogDisabled", func() {

View File

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

View File

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