Compare commits
78 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bbf5d2d178 | ||
|
|
1c4ea24da1 | ||
|
|
f277460b9d | ||
|
|
15c1643d34 | ||
|
|
e7f0a05e1a | ||
|
|
8506b5d5a5 | ||
|
|
0d629127a9 | ||
|
|
d6d8d2148d | ||
|
|
5a3f2004bc | ||
|
|
0e86fc8094 | ||
|
|
3c6c4b88ef | ||
|
|
4b4a10be7e | ||
|
|
4f1491858b | ||
|
|
33433b1aaf | ||
|
|
d85ebe0d34 | ||
|
|
235bd85510 | ||
|
|
5c29345f48 | ||
|
|
ebcb2b84ae | ||
|
|
080021e73d | ||
|
|
f447e28c86 | ||
|
|
f3b2ca644c | ||
|
|
4fdead0add | ||
|
|
059c6e9df7 | ||
|
|
a3f0efa8bc | ||
|
|
a79ad5abeb | ||
|
|
c6f8a14643 | ||
|
|
d262357c87 | ||
|
|
5da3730015 | ||
|
|
b444a4887b | ||
|
|
cc7133927f | ||
|
|
4b01042a4f | ||
|
|
4b94ef998d | ||
|
|
db5764b3d7 | ||
|
|
3c0115b71c | ||
|
|
c401c790bc | ||
|
|
271dcacf19 | ||
|
|
983d29d033 | ||
|
|
078f6e1cc4 | ||
|
|
9f8f6bc814 | ||
|
|
ef35178396 | ||
|
|
b3781c6d8f | ||
|
|
2319739f6d | ||
|
|
f9ac1ca698 | ||
|
|
9802bf2f37 | ||
|
|
843433e35d | ||
|
|
15c651ca96 | ||
|
|
4c1e56ccce | ||
|
|
906dfc680a | ||
|
|
583197f1c8 | ||
|
|
5fca3b1002 | ||
|
|
1edf5ae52a | ||
|
|
39f7f41e6d | ||
|
|
3139ad605f | ||
|
|
a9de069f6b | ||
|
|
4a8e2aab51 | ||
|
|
749c5f178c | ||
|
|
680989bc2e | ||
|
|
418989989b | ||
|
|
9a06a6ac10 | ||
|
|
ff7bae0bba | ||
|
|
215ee946dd | ||
|
|
cc13b9318d | ||
|
|
9d26eec6b0 | ||
|
|
bb969a0005 | ||
|
|
37b8fda889 | ||
|
|
0c9487a53d | ||
|
|
a039704f1b | ||
|
|
ec9be5aed6 | ||
|
|
542aacb002 | ||
|
|
e7e391f85c | ||
|
|
8ccd773b4b | ||
|
|
cb52a43940 | ||
|
|
2463809356 | ||
|
|
b71df4d8ed | ||
|
|
4cd60e818a | ||
|
|
31aad9471b | ||
|
|
34dac9e091 | ||
|
|
c1c81bb8f6 |
28
.github/ISSUE_TEMPLATE/bug_report.md
vendored
28
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,28 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. What version of AdGuardHome sync used?
|
||||
2. What version of AdGuardHome us used?
|
||||
3. How does the configuration look?
|
||||
4. What is the error message?
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Log Files**
|
||||
If applicable, add log files or json responses from AdGuardHome to help explain your problem.
|
||||
Please set the environment variable `LOG_LEVEL=debug` to generate debug logs.
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
67
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal file
67
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
name: Bug report
|
||||
description: Create a report to help us improve
|
||||
labels: ['bug']
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this issue report! If you have usage questions, please try the [FAQ](https://github.com/bakito/adguardhome-sync/wiki/FAQ) first.
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
label: What happened?
|
||||
description: Also tell us, what did you expect to happen?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: adguardhome-sync-version
|
||||
attributes:
|
||||
label: AdguardHome-Sync Version
|
||||
description: What version of adguardhome-sync was running when you discovered this issue?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: adguardhome-version
|
||||
attributes:
|
||||
label: AdguardHome Version
|
||||
description: What version of adguardhome was running when you discovered this issue?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: config
|
||||
attributes:
|
||||
label: Configuration
|
||||
description: |
|
||||
- How did you configure adguardhome-sync?
|
||||
- Please provide your configuration
|
||||
render: shell
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: applied-config
|
||||
attributes:
|
||||
label: Current Applied Configuration
|
||||
description: |
|
||||
- Run adguardhome-sync with environment variable `PRINT_CONFIG_ONLY=true` and provide the output here
|
||||
render: shell
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Relevant log output
|
||||
description: |
|
||||
Please copy and paste any relevant **debug** log output. This will be automatically formatted into code, so no need for backticks.
|
||||
Enable debug logs by defining the following environment variable `LOG_LEVEL=debug`.
|
||||
|
||||
Please also check adguardhome logs and paste any relevant logs/errors to this issue.
|
||||
render: shell
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Anything else?
|
||||
description: |
|
||||
Links? References? Anything that will provide more context about the issue you are encountering!
|
||||
|
||||
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
|
||||
validations:
|
||||
required: false
|
||||
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
blank_issues_enabled: false
|
||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,20 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
36
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
Normal file
36
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
name: Feature request
|
||||
description: Suggest an idea for this project
|
||||
labels: ['enhancement']
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this issue report!
|
||||
- type: textarea
|
||||
id: relation
|
||||
attributes:
|
||||
label: Is your feature request related to a problem? Please describe.
|
||||
description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: solution
|
||||
attributes:
|
||||
label: Describe the solution you'd like*
|
||||
description: |
|
||||
A clear and concise description of what you want to happen.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: Describe alternatives you've considered
|
||||
description: |
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Anything else?
|
||||
description: |
|
||||
Add any other context or screenshots about the feature request here.
|
||||
validations:
|
||||
required: false
|
||||
60
.github/ISSUE_TEMPLATE/general_issue.yaml
vendored
Normal file
60
.github/ISSUE_TEMPLATE/general_issue.yaml
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
name: General Issue
|
||||
description: Report an issue with your setup
|
||||
labels: []
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this issue report! If you have usage questions, please try the [FAQ](https://github.com/bakito/adguardhome-sync/wiki/FAQ) first.
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
label: What happened?
|
||||
description: Also tell us, what did you expect to happen?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: adguardhome-sync-version
|
||||
attributes:
|
||||
label: AdguardHome-Sync Version
|
||||
description: What version of adguardhome-sync was running when you discovered this issue?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: adguardhome-version
|
||||
attributes:
|
||||
label: AdguardHome Version
|
||||
description: What version of adguardhome was running when you discovered this issue?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: config
|
||||
attributes:
|
||||
label: Configuration
|
||||
description: |
|
||||
- How did you configure adguardhome-sync?
|
||||
- Please provide your configuration
|
||||
render: shell
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Relevant log output
|
||||
description: |
|
||||
Please copy and paste any relevant **debug** log output. This will be automatically formatted into code, so no need for backticks.
|
||||
Enable debug logs by defining the following environment variable `LOG_LEVEL=debug`.
|
||||
|
||||
Please also check adguardhome logs and paste any relevant logs/errors to this issue.
|
||||
render: shell
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Anything else?
|
||||
description: |
|
||||
Links? References? Anything that will provide more context about the issue you are encountering!
|
||||
|
||||
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
|
||||
validations:
|
||||
required: false
|
||||
13
.github/dependabot.yml
vendored
13
.github/dependabot.yml
vendored
@@ -9,6 +9,19 @@ updates:
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
groups:
|
||||
k8s:
|
||||
patterns:
|
||||
- "k8s.io/*"
|
||||
update-types:
|
||||
- "minor"
|
||||
- "patch"
|
||||
onsi:
|
||||
patterns:
|
||||
- "github.com/onsi/*"
|
||||
update-types:
|
||||
- "minor"
|
||||
- "patch"
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
|
||||
12
.github/workflows/codeql-analysis.yml
vendored
12
.github/workflows/codeql-analysis.yml
vendored
@@ -40,9 +40,15 @@ jobs:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -53,7 +59,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
@@ -67,4 +73,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
uses: github/codeql-action/analyze@v3
|
||||
|
||||
12
.github/workflows/e2e.yaml
vendored
12
.github/workflows/e2e.yaml
vendored
@@ -11,10 +11,18 @@ on:
|
||||
jobs:
|
||||
e2e:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
build:
|
||||
- mode: env
|
||||
- mode: file
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup jq
|
||||
uses: dcarbone/install-jq-action@v2
|
||||
|
||||
- name: Install kind with registry
|
||||
uses: bakito/kind-with-registry-action@main
|
||||
|
||||
@@ -22,7 +30,7 @@ jobs:
|
||||
run: ./testdata/e2e/bin/build-image.sh
|
||||
|
||||
- name: Install Helm Chart
|
||||
run: ./testdata/e2e/bin/install-chart.sh
|
||||
run: ./testdata/e2e/bin/install-chart.sh ${{ matrix.build.mode }}
|
||||
- name: Wait for sync to finish
|
||||
run: ./testdata/e2e/bin/wait-for-sync.sh
|
||||
- name: Show origin Logs
|
||||
@@ -31,5 +39,7 @@ jobs:
|
||||
run: ./testdata/e2e/bin/show-replica-logs.sh
|
||||
- name: Show Sync Logs
|
||||
run: ./testdata/e2e/bin/show-sync-logs.sh
|
||||
- name: Show Sync Metrics
|
||||
run: ./testdata/e2e/bin/show-sync-metrics.sh
|
||||
- name: Read latest replica config
|
||||
run: ./testdata/e2e/bin/read-latest-replica-config.sh
|
||||
|
||||
8
.github/workflows/go.yml
vendored
8
.github/workflows/go.yml
vendored
@@ -14,12 +14,12 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
uses: golangci/golangci-lint-action@v4
|
||||
with:
|
||||
skip-pkg-cache: true
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
|
||||
|
||||
13
.github/workflows/publish.yml
vendored
13
.github/workflows/publish.yml
vendored
@@ -1,9 +1,9 @@
|
||||
name: docker-image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch: # allows manual triggering
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
@@ -39,6 +39,7 @@ jobs:
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Modify Dockerfile
|
||||
run: |
|
||||
sed -i -e "s|FROM scratch|FROM ${{ matrix.build.fromImage }}|g" Dockerfile
|
||||
@@ -58,10 +59,14 @@ jobs:
|
||||
VERSION=${{ github.event.release.tag_name }}
|
||||
BUILD=${{ env.curr_date }}
|
||||
|
||||
- name: Check for commits in the last 24 hours
|
||||
run: echo "NEW_COMMIT_COUNT=$(git log --oneline --since '24 hours ago' | wc -l)" >> $GITHUB_ENV
|
||||
if: ${{ github.event.release.tag_name == '' }}
|
||||
|
||||
- name: Build and push main
|
||||
id: docker_build_main
|
||||
uses: docker/build-push-action@v5
|
||||
if: ${{ github.event.release.tag_name == '' }}
|
||||
if: ${{ github.event.release.tag_name == '' && env.NEW_COMMIT_COUNT > 0 }}
|
||||
with:
|
||||
context: .
|
||||
pull: true
|
||||
|
||||
17
.github/workflows/stale.yml
vendored
Normal file
17
.github/workflows/stale.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
name: Mark stale issues and pull requests
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
stale-issue-message: 'This issue has been inactive for 60 days. If the issue is still relevant please comment to re-activate the issue. If no action is taken within 7 days, the issue will be marked closed.'
|
||||
stale-pr-message: 'This pull request has been inactive for 60 days. If the pull request is still relevant please comment to re-activate the pull request. If no action is taken within 7 days, the pull request will be marked closed.'
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,5 +1,6 @@
|
||||
.vscode
|
||||
.idea
|
||||
.fleet
|
||||
.run
|
||||
coverage.out*
|
||||
dist
|
||||
@@ -8,5 +9,7 @@ main
|
||||
.adguardhome-sync.yaml
|
||||
tmp
|
||||
bin
|
||||
config.yaml
|
||||
config*.yaml
|
||||
*.log
|
||||
wiki
|
||||
Taskfile.yml
|
||||
|
||||
@@ -3,34 +3,35 @@ run:
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- asciicheck
|
||||
- bodyclose
|
||||
- dogsled
|
||||
- durationcheck
|
||||
- errcheck
|
||||
- errorlint
|
||||
- exportloopref
|
||||
- gci
|
||||
- gofmt
|
||||
- gofumpt
|
||||
- goimports
|
||||
- gosec
|
||||
- gosimple
|
||||
- govet
|
||||
- importas
|
||||
- ineffassign
|
||||
- megacheck
|
||||
- misspell
|
||||
- nakedret
|
||||
- nolintlint
|
||||
- staticcheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- unused
|
||||
- asciicheck
|
||||
- bodyclose
|
||||
- dogsled
|
||||
- durationcheck
|
||||
- errcheck
|
||||
- errorlint
|
||||
- gci
|
||||
- gofmt
|
||||
- gofumpt
|
||||
- goimports
|
||||
- gosec
|
||||
- gosimple
|
||||
- govet
|
||||
- importas
|
||||
- ineffassign
|
||||
- megacheck
|
||||
- misspell
|
||||
- nakedret
|
||||
- nolintlint
|
||||
- staticcheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- unused
|
||||
linters-settings:
|
||||
gosec:
|
||||
# Exclude generated files
|
||||
exclude-generated: true
|
||||
excludes:
|
||||
- G601 # not applicable in go 1.22 anymore
|
||||
gofmt:
|
||||
# simplify code: gofmt with `-s` option, true by default
|
||||
simplify: true
|
||||
|
||||
@@ -30,7 +30,7 @@ builds:
|
||||
hooks:
|
||||
post:
|
||||
# don't upx windows binaries as they make trouble with virus scanners
|
||||
- bash -c 'if [[ "{{ .Path }}" != *.exe ]]; then upx {{ .Path }}; fi'
|
||||
- bash -c 'if [[ "{{ .Path }}" != *.exe ]] && [[ "{{ .Path }}" != *darwin* ]]; then upx {{ .Path }}; fi'
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
snapshot:
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
FROM golang:1.21-bullseye as builder
|
||||
FROM golang:1.22-bullseye as builder
|
||||
|
||||
WORKDIR /go/src/app
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y upx && \
|
||||
apt-get install -y upx ca-certificates tzdata && \
|
||||
apt-get upgrade -y # upgrade to get latest ca-certs
|
||||
|
||||
ARG VERSION=main
|
||||
@@ -26,6 +26,7 @@ LABEL maintainer="bakito <github@bakito.ch>"
|
||||
EXPOSE 8080
|
||||
ENTRYPOINT ["/opt/go/adguardhome-sync"]
|
||||
CMD ["run", "--config", "/config/adguardhome-sync.yaml"]
|
||||
COPY --from=builder /go/src/app/adguardhome-sync /opt/go/adguardhome-sync
|
||||
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||
COPY --from=builder /usr/share/zoneinfo/ /usr/share/zoneinfo/
|
||||
COPY --from=builder /go/src/app/adguardhome-sync /opt/go/adguardhome-sync
|
||||
USER 1001
|
||||
|
||||
94
Makefile
94
Makefile
@@ -15,13 +15,14 @@ generate: deepcopy-gen
|
||||
test: generate lint test-ci
|
||||
|
||||
# Run ci tests
|
||||
test-ci: mocks tidy
|
||||
go test ./... -coverprofile=coverage.out.tmp
|
||||
test-ci: mocks tidy ginkgo
|
||||
$(GINKGO) --cover --coverprofile coverage.out.tmp ./...
|
||||
cat coverage.out.tmp | grep -v "_generated.go" > coverage.out
|
||||
go tool cover -func=coverage.out
|
||||
|
||||
mocks: mockgen
|
||||
$(MOCKGEN) -package client -destination pkg/mocks/client/mock.go github.com/bakito/adguardhome-sync/pkg/client Client
|
||||
$(MOCKGEN) -package client -destination pkg/mocks/flags/mock.go github.com/bakito/adguardhome-sync/pkg/config Flags
|
||||
|
||||
release: semver goreleaser
|
||||
@version=$$($(LOCALBIN)/semver); \
|
||||
@@ -29,7 +30,7 @@ release: semver goreleaser
|
||||
$(GORELEASER) --clean
|
||||
|
||||
test-release: goreleaser
|
||||
$(GORELEASER) --skip-publish --snapshot --clean
|
||||
$(GORELEASER) --skip=publish --snapshot --clean
|
||||
|
||||
## toolbox - start
|
||||
## Current working directory
|
||||
@@ -40,70 +41,65 @@ $(LOCALBIN):
|
||||
mkdir -p $(LOCALBIN)
|
||||
|
||||
## Tool Binaries
|
||||
SEMVER ?= $(LOCALBIN)/semver
|
||||
OAPI_CODEGEN ?= $(LOCALBIN)/oapi-codegen
|
||||
MOCKGEN ?= $(LOCALBIN)/mockgen
|
||||
DEEPCOPY_GEN ?= $(LOCALBIN)/deepcopy-gen
|
||||
GINKGO ?= $(LOCALBIN)/ginkgo
|
||||
GOLANGCI_LINT ?= $(LOCALBIN)/golangci-lint
|
||||
GORELEASER ?= $(LOCALBIN)/goreleaser
|
||||
DEEPCOPY_GEN ?= $(LOCALBIN)/deepcopy-gen
|
||||
|
||||
## Tool Versions
|
||||
SEMVER_VERSION ?= v1.1.3
|
||||
OAPI_CODEGEN_VERSION ?= v2.0.0
|
||||
MOCKGEN_VERSION ?= v1.6.0
|
||||
GOLANGCI_LINT_VERSION ?= v1.55.2
|
||||
GORELEASER_VERSION ?= v1.22.1
|
||||
DEEPCOPY_GEN_VERSION ?= v0.28.4
|
||||
MOCKGEN ?= $(LOCALBIN)/mockgen
|
||||
OAPI_CODEGEN ?= $(LOCALBIN)/oapi-codegen
|
||||
SEMVER ?= $(LOCALBIN)/semver
|
||||
|
||||
## Tool Installer
|
||||
.PHONY: semver
|
||||
semver: $(SEMVER) ## Download semver locally if necessary.
|
||||
$(SEMVER): $(LOCALBIN)
|
||||
test -s $(LOCALBIN)/semver || GOBIN=$(LOCALBIN) go install github.com/bakito/semver@$(SEMVER_VERSION)
|
||||
.PHONY: oapi-codegen
|
||||
oapi-codegen: $(OAPI_CODEGEN) ## Download oapi-codegen locally if necessary.
|
||||
$(OAPI_CODEGEN): $(LOCALBIN)
|
||||
test -s $(LOCALBIN)/oapi-codegen || GOBIN=$(LOCALBIN) go install github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen@$(OAPI_CODEGEN_VERSION)
|
||||
.PHONY: mockgen
|
||||
mockgen: $(MOCKGEN) ## Download mockgen locally if necessary.
|
||||
$(MOCKGEN): $(LOCALBIN)
|
||||
test -s $(LOCALBIN)/mockgen || GOBIN=$(LOCALBIN) go install github.com/golang/mock/mockgen@$(MOCKGEN_VERSION)
|
||||
.PHONY: golangci-lint
|
||||
golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary.
|
||||
$(GOLANGCI_LINT): $(LOCALBIN)
|
||||
test -s $(LOCALBIN)/golangci-lint || GOBIN=$(LOCALBIN) go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION)
|
||||
.PHONY: goreleaser
|
||||
goreleaser: $(GORELEASER) ## Download goreleaser locally if necessary.
|
||||
$(GORELEASER): $(LOCALBIN)
|
||||
test -s $(LOCALBIN)/goreleaser || GOBIN=$(LOCALBIN) go install github.com/goreleaser/goreleaser@$(GORELEASER_VERSION)
|
||||
.PHONY: deepcopy-gen
|
||||
deepcopy-gen: $(DEEPCOPY_GEN) ## Download deepcopy-gen locally if necessary.
|
||||
$(DEEPCOPY_GEN): $(LOCALBIN)
|
||||
test -s $(LOCALBIN)/deepcopy-gen || GOBIN=$(LOCALBIN) go install k8s.io/code-generator/cmd/deepcopy-gen@$(DEEPCOPY_GEN_VERSION)
|
||||
test -s $(LOCALBIN)/deepcopy-gen || GOBIN=$(LOCALBIN) go install k8s.io/code-generator/cmd/deepcopy-gen
|
||||
.PHONY: ginkgo
|
||||
ginkgo: $(GINKGO) ## Download ginkgo locally if necessary.
|
||||
$(GINKGO): $(LOCALBIN)
|
||||
test -s $(LOCALBIN)/ginkgo || GOBIN=$(LOCALBIN) go install github.com/onsi/ginkgo/v2/ginkgo
|
||||
.PHONY: golangci-lint
|
||||
golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary.
|
||||
$(GOLANGCI_LINT): $(LOCALBIN)
|
||||
test -s $(LOCALBIN)/golangci-lint || GOBIN=$(LOCALBIN) go install github.com/golangci/golangci-lint/cmd/golangci-lint
|
||||
.PHONY: goreleaser
|
||||
goreleaser: $(GORELEASER) ## Download goreleaser locally if necessary.
|
||||
$(GORELEASER): $(LOCALBIN)
|
||||
test -s $(LOCALBIN)/goreleaser || GOBIN=$(LOCALBIN) go install github.com/goreleaser/goreleaser
|
||||
.PHONY: mockgen
|
||||
mockgen: $(MOCKGEN) ## Download mockgen locally if necessary.
|
||||
$(MOCKGEN): $(LOCALBIN)
|
||||
test -s $(LOCALBIN)/mockgen || GOBIN=$(LOCALBIN) go install go.uber.org/mock/mockgen
|
||||
.PHONY: oapi-codegen
|
||||
oapi-codegen: $(OAPI_CODEGEN) ## Download oapi-codegen locally if necessary.
|
||||
$(OAPI_CODEGEN): $(LOCALBIN)
|
||||
test -s $(LOCALBIN)/oapi-codegen || GOBIN=$(LOCALBIN) go install github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen
|
||||
.PHONY: semver
|
||||
semver: $(SEMVER) ## Download semver locally if necessary.
|
||||
$(SEMVER): $(LOCALBIN)
|
||||
test -s $(LOCALBIN)/semver || GOBIN=$(LOCALBIN) go install github.com/bakito/semver
|
||||
|
||||
## Update Tools
|
||||
.PHONY: update-toolbox-tools
|
||||
update-toolbox-tools:
|
||||
@rm -f \
|
||||
$(LOCALBIN)/semver \
|
||||
$(LOCALBIN)/oapi-codegen \
|
||||
$(LOCALBIN)/mockgen \
|
||||
$(LOCALBIN)/deepcopy-gen \
|
||||
$(LOCALBIN)/ginkgo \
|
||||
$(LOCALBIN)/golangci-lint \
|
||||
$(LOCALBIN)/goreleaser \
|
||||
$(LOCALBIN)/deepcopy-gen
|
||||
toolbox makefile -f $(LOCALDIR)/Makefile \
|
||||
github.com/bakito/semver \
|
||||
github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen \
|
||||
github.com/golang/mock/mockgen \
|
||||
github.com/golangci/golangci-lint/cmd/golangci-lint \
|
||||
github.com/goreleaser/goreleaser \
|
||||
k8s.io/code-generator/cmd/deepcopy-gen@github.com/kubernetes/code-generator
|
||||
$(LOCALBIN)/mockgen \
|
||||
$(LOCALBIN)/oapi-codegen \
|
||||
$(LOCALBIN)/semver
|
||||
toolbox makefile -f $(LOCALDIR)/Makefile
|
||||
## toolbox - end
|
||||
|
||||
start-replica:
|
||||
docker run --pull always --name adguardhome-replica -p 9091:3000 --rm adguard/adguardhome:latest
|
||||
# docker run --pull always --name adguardhome-replica -p 9090:80 -p 9091:3000 --rm adguard/adguardhome:v0.107.13
|
||||
|
||||
copy-replica-config:
|
||||
docker cp adguardhome-replica:/opt/adguardhome/conf/AdGuardHome.yaml tmp/AdGuardHome.yaml
|
||||
|
||||
start-replica2:
|
||||
docker run --pull always --name adguardhome-replica2 -p 9093:3000 --rm adguard/adguardhome:latest
|
||||
# docker run --pull always --name adguardhome-replica -p 9090:80 -p 9091:3000 --rm adguard/adguardhome:v0.107.13
|
||||
@@ -128,10 +124,10 @@ kind-test:
|
||||
|
||||
model: oapi-codegen
|
||||
@mkdir -p tmp
|
||||
go run openapi/main.go v0.107.40
|
||||
go run openapi/main.go v0.107.46
|
||||
$(OAPI_CODEGEN) -package model -generate types,client -config .oapi-codegen.yaml tmp/schema.yaml > pkg/client/model/model_generated.go
|
||||
|
||||
model-diff:
|
||||
go run openapi/main.go v0.107.40
|
||||
go run openapi/main.go v0.107.46
|
||||
go run openapi/main.go
|
||||
diff tmp/schema.yaml tmp/schema-master.yaml
|
||||
|
||||
93
README.md
93
README.md
@@ -1,4 +1,5 @@
|
||||
[](https://github.com/bakito/adguardhome-sync/actions/workflows/go.yml)
|
||||
[](https://github.com/bakito/adguardhome-sync/actions/workflows/e2e.yaml)
|
||||
[](https://goreportcard.com/report/github.com/bakito/adguardhome-sync)
|
||||
[](https://coveralls.io/github/bakito/adguardhome-sync?branch=main)
|
||||
|
||||
@@ -6,6 +7,12 @@
|
||||
|
||||
Synchronize [AdGuardHome](https://github.com/AdguardTeam/AdGuardHome) config to replica instances.
|
||||
|
||||
## FAQ & Deprecations
|
||||
|
||||
Please check the wiki
|
||||
for [FAQ](https://github.com/bakito/adguardhome-sync/wiki/FAQ)
|
||||
and [Deprecations](https://github.com/bakito/adguardhome-sync/wiki/Deprecations).
|
||||
|
||||
## Current sync features
|
||||
|
||||
- General Settings
|
||||
@@ -39,7 +46,9 @@ Both the origin instance must be initially setup via the AdguardHome installatio
|
||||
|
||||
## Username / Password vs. Cookie
|
||||
|
||||
Some instances of AdGuard Home do not support basic authentication. For instance, many routers with built-in Adguard Home support do not. If this is the case, a valid cookie may be provided instead. If the router protects the AdGuard instance behind its own authentication, the cookie from an authenticated request may allow the sync to succeed.
|
||||
Some instances of AdGuard Home do not support basic authentication. For instance, many routers with built-in Adguard
|
||||
Home support do not. If this is the case, a valid cookie may be provided instead. If the router protects the AdGuard
|
||||
instance behind its own authentication, the cookie from an authenticated request may allow the sync to succeed.
|
||||
|
||||
- This has been tested successfully against GL.Inet routers with AdGuard Home.
|
||||
- Note: due to the short validity of cookies, this approach is likely only suitable for one-time syncs
|
||||
@@ -53,9 +62,9 @@ export ORIGIN_URL=https://192.168.1.2:3000
|
||||
export ORIGIN_USERNAME=username
|
||||
export ORIGIN_PASSWORD=password
|
||||
# export ORIGIN_COOKIE=Origin-Cookie-Name=CCCOOOKKKIIIEEE
|
||||
export REPLICA_URL=http://192.168.1.3
|
||||
export REPLICA_USERNAME=username
|
||||
export REPLICA_PASSWORD=password
|
||||
export REPLICA1_URL=http://192.168.1.3
|
||||
export REPLICA1_USERNAME=username
|
||||
export REPLICA1_PASSWORD=password
|
||||
# export REPLICA_COOKIE=Replica-Cookie-Name=CCCOOOKKKIIIEEE
|
||||
|
||||
# run once
|
||||
@@ -81,10 +90,10 @@ set ORIGIN_USERNAME=username
|
||||
set ORIGIN_PASSWORD=password
|
||||
# set ORIGIN_COOKIE=Origin-Cookie-Name=CCCOOOKKKIIIEEE
|
||||
|
||||
set REPLICA_URL=http://192.168.2.2:3000
|
||||
set REPLICA_USERNAME=username
|
||||
set REPLICA_PASSWORD=password
|
||||
# set REPLICA_COOKIE=Replica-Cookie-Name=CCCOOOKKKIIIEEE
|
||||
set REPLICA1_URL=http://192.168.2.2:3000
|
||||
set REPLICA1_USERNAME=username
|
||||
set REPLICA1_PASSWORD=password
|
||||
# set REPLICA1_COOKIE=Replica-Cookie-Name=CCCOOOKKKIIIEEE
|
||||
|
||||
set FEATURES_DHCP=false
|
||||
set FEATURES_DHCP_SERVERCONFIG=false
|
||||
@@ -140,35 +149,42 @@ services:
|
||||
environment:
|
||||
LOG_LEVEL: "info"
|
||||
ORIGIN_URL: "https://192.168.1.2:3000"
|
||||
# ORIGIN_WEB_URL: "https://some-other.url" # used in the web interface (default: <origin-url>
|
||||
|
||||
ORIGIN_USERNAME: "username"
|
||||
ORIGIN_PASSWORD: "password"
|
||||
REPLICA_URL: "http://192.168.1.3"
|
||||
REPLICA_USERNAME: "username"
|
||||
REPLICA_PASSWORD: "password"
|
||||
REPLICA1_URL: "http://192.168.1.4"
|
||||
REPLICA1_URL: "http://192.168.1.3"
|
||||
REPLICA1_USERNAME: "username"
|
||||
REPLICA1_PASSWORD: "password"
|
||||
REPLICA1_APIPATH: "/some/path/control"
|
||||
# REPLICA1_AUTOSETUP: true # if true, AdGuardHome is automatically initialized.
|
||||
# REPLICA1_INTERFACENAME: 'ens18' # use custom dhcp interface name
|
||||
# REPLICA1_DHCPSERVERENABLED: true/false (optional) enables/disables the dhcp server on the replica
|
||||
REPLICA2_URL: "http://192.168.1.4"
|
||||
REPLICA2_USERNAME: "username"
|
||||
REPLICA2_PASSWORD: "password"
|
||||
REPLICA2_API_PATH: "/some/path/control"
|
||||
# REPLICA2_WEB_URL: "https://some-other.url" # used in the web interface (default: <replica-url>
|
||||
# REPLICA2_AUTO_SETUP: true # if true, AdGuardHome is automatically initialized.
|
||||
# REPLICA2_INTERFACE_NAME: 'ens18' # use custom dhcp interface name
|
||||
# REPLICA2_DHCP_SERVER_ENABLED: true/false (optional) enables/disables the dhcp server on the replica
|
||||
CRON: "*/10 * * * *" # run every 10 minutes
|
||||
RUNONSTART: true
|
||||
# CONTINUE_ON_ERROR: false # If enabled, the synchronisation task will not fail on single errors, but will log the errors and continue
|
||||
|
||||
# Configure the sync API server, disabled if api port is 0
|
||||
API_PORT: 8080
|
||||
# API_DARK_MODE: true
|
||||
# API_USERNAME: admin
|
||||
# API_PASSWORD: secret
|
||||
|
||||
# Configure sync features; by default all features are enabled.
|
||||
# FEATURES_GENERALSETTINGS: true
|
||||
# FEATURES_QUERYLOGCONFIG: true
|
||||
# FEATURES_STATSCONFIG: true
|
||||
# FEATURES_CLIENTSETTINGS: true
|
||||
# FEATURES_GENERAL_SETTINGS: true
|
||||
# FEATURES_QUERY_LOG_CONFIG: true
|
||||
# FEATURES_STATS_CONFIG: true
|
||||
# FEATURES_CLIENT_SETTINGS: true
|
||||
# FEATURES_SERVICES: true
|
||||
# FEATURES_FILTERS: true
|
||||
# FEATURES_DHCP_SERVERCONFIG: true
|
||||
# FEATURES_DHCP_STATICLEASES: true
|
||||
# FEATURES_DNS_SERVERCONFIG: true
|
||||
# FEATURES_DNS_ACCESSLISTS: true
|
||||
# FEATURES_DHCP_SERVER_CONFIG: true
|
||||
# FEATURES_DHCP_STATIC_LEASES: true
|
||||
# FEATURES_DNS_SERVER_CONFIG: true
|
||||
# FEATURES_DNS_ACCESS_LISTS: true
|
||||
# FEATURES_DNS_REWRITES: true
|
||||
ports:
|
||||
- 8080:8080
|
||||
@@ -186,6 +202,9 @@ cron: "*/10 * * * *"
|
||||
# runs the synchronisation on startup
|
||||
runOnStart: true
|
||||
|
||||
# If enabled, the synchronisation task will not fail on single errors, but will log the errors and continue
|
||||
continueOnError: false
|
||||
|
||||
origin:
|
||||
# url of the origin instance
|
||||
url: https://192.168.1.2:3000
|
||||
@@ -195,15 +214,7 @@ origin:
|
||||
password: password
|
||||
# cookie: Origin-Cookie-Name=CCCOOOKKKIIIEEE
|
||||
|
||||
# replica instance (optional, if only one)
|
||||
replica:
|
||||
# url of the replica instance
|
||||
url: http://192.168.1.3
|
||||
username: username
|
||||
password: password
|
||||
# cookie: Replica-Cookie-Name=CCCOOOKKKIIIEEE
|
||||
|
||||
# replicas instances (optional, if more than one)
|
||||
# replicas instances
|
||||
replicas:
|
||||
# url of the replica instance
|
||||
- url: http://192.168.1.3
|
||||
@@ -215,6 +226,7 @@ replicas:
|
||||
password: password
|
||||
# cookie: Replica2-Cookie-Name=CCCOOOKKKIIIEEE
|
||||
# autoSetup: true # if true, AdGuardHome is automatically initialized.
|
||||
# webURL: "https://some-other.url" # used in the web interface (default: <replica-url>
|
||||
|
||||
# Configure the sync API server, disabled if api port is 0
|
||||
api:
|
||||
@@ -223,6 +235,14 @@ api:
|
||||
# if username and password are defined, basic auth is applied to the sync API
|
||||
username: username
|
||||
password: password
|
||||
# enable api dark mode
|
||||
darkMode: true
|
||||
|
||||
# enable metrics on path '/metrics' (api port must be != 0)
|
||||
# metrics:
|
||||
# enabled: true
|
||||
# scrapeInterval: 30s
|
||||
# queryLogLimit: 10000
|
||||
|
||||
# Configure sync features; by default all features are enabled.
|
||||
features:
|
||||
@@ -243,7 +263,7 @@ features:
|
||||
|
||||
## Log Level
|
||||
|
||||
The log level can be set with the environment variable: LOG_LEVEL
|
||||
The log level can be set with the environment variable: `LOG_LEVEL`
|
||||
|
||||
The following log levels are supported (default: info)
|
||||
|
||||
@@ -251,3 +271,8 @@ The following log levels are supported (default: info)
|
||||
- info
|
||||
- warn
|
||||
- error
|
||||
|
||||
## Log Format
|
||||
|
||||
Default log format is `console`.
|
||||
It can be changed to `json`by setting the environment variable: `LOG_FORMAT=json`
|
||||
|
||||
135
Taskfile.yml
Normal file
135
Taskfile.yml
Normal file
@@ -0,0 +1,135 @@
|
||||
version: '3'
|
||||
env:
|
||||
AGH_MODEL_VERSION: v0.107.43
|
||||
GOBIN: '{{.USER_WORKING_DIR}}/bin'
|
||||
|
||||
tasks:
|
||||
|
||||
install-go-tool:
|
||||
label: "Install {{ .TOOL_NAME }}"
|
||||
cmds:
|
||||
- go install {{ .TOOL_MODULE }}
|
||||
status:
|
||||
- test -f {{.GOBIN}}/{{.TOOL_NAME}}
|
||||
|
||||
deepcopy-gen:
|
||||
desc: Install deepcopy-gen
|
||||
cmd:
|
||||
task: install-go-tool
|
||||
vars:
|
||||
TOOL_NAME: deepcopy-gen
|
||||
TOOL_MODULE: k8s.io/code-generator/cmd/deepcopy-gen
|
||||
|
||||
ginkgo:
|
||||
cmd:
|
||||
task: install-go-tool
|
||||
vars:
|
||||
TOOL_NAME: ginkgo
|
||||
TOOL_MODULE: github.com/onsi/ginkgo/v2/ginkgo
|
||||
|
||||
goreleaser:
|
||||
cmd:
|
||||
task: install-go-tool
|
||||
vars:
|
||||
TOOL_NAME: goreleaser
|
||||
TOOL_MODULE: github.com/goreleaser/goreleaser
|
||||
|
||||
golangci-lint:
|
||||
cmd:
|
||||
task: install-go-tool
|
||||
vars:
|
||||
TOOL_NAME: golangci-lint
|
||||
TOOL_MODULE: github.com/golangci/golangci-lint/cmd/golangci-lint
|
||||
|
||||
mockgen:
|
||||
cmd:
|
||||
task: install-go-tool
|
||||
vars:
|
||||
TOOL_NAME: mockgen
|
||||
TOOL_MODULE: go.uber.org/mock/mockgen
|
||||
|
||||
oapi-codegen:
|
||||
cmd:
|
||||
task: install-go-tool
|
||||
vars:
|
||||
TOOL_NAME: oapi-codegen
|
||||
TOOL_MODULE: github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen
|
||||
|
||||
semver:
|
||||
cmd:
|
||||
task: install-go-tool
|
||||
vars:
|
||||
TOOL_NAME: semver
|
||||
TOOL_MODULE: github.com/bakito/semver
|
||||
|
||||
lint:
|
||||
deps:
|
||||
- golangci-lint
|
||||
cmds:
|
||||
- '{{.GOBIN}}/golangci-lint run --fix'
|
||||
|
||||
tidy:
|
||||
desc: Run go mod tidy
|
||||
cmd: go mod tidy
|
||||
|
||||
generate:
|
||||
deps:
|
||||
- deepcopy-gen
|
||||
cmds:
|
||||
- mkdir -p ./tmp
|
||||
- touch ./tmp/deepcopy-gen-boilerplate.go.txt
|
||||
- '{{.GOBIN}}/deepcopy-gen -h ./tmp/deepcopy-gen-boilerplate.go.txt -i ./pkg/types'
|
||||
|
||||
mocks:
|
||||
deps:
|
||||
- mockgen
|
||||
cmds:
|
||||
- '{{.GOBIN}}/mockgen -package client -destination pkg/mocks/client/mock.go github.com/bakito/adguardhome-sync/pkg/client Client'
|
||||
- '{{.GOBIN}}/mockgen -package client -destination pkg/mocks/flags/mock.go github.com/bakito/adguardhome-sync/pkg/config Flags'
|
||||
|
||||
test:
|
||||
cmds:
|
||||
- task: generate
|
||||
- task: lint
|
||||
- task: test-ci
|
||||
|
||||
test-ci:
|
||||
deps:
|
||||
- ginkgo
|
||||
- tidy
|
||||
- mocks
|
||||
cmds:
|
||||
- '{{.GOBIN}}/ginkgo --cover --coverprofile coverage.out.tmp ./...'
|
||||
- cat coverage.out.tmp | grep -v "_generated.go" > coverage.out
|
||||
- go tool cover -func=coverage.out
|
||||
|
||||
release:
|
||||
deps:
|
||||
- semver
|
||||
- goreleaser
|
||||
cmds:
|
||||
- git tag -s $$version -m"Release $({{.GOBIN}}/semver)
|
||||
- '{{.GOBIN}}/goreleaser --clean'
|
||||
|
||||
test-release:
|
||||
deps:
|
||||
- goreleaser
|
||||
- semver
|
||||
cmds:
|
||||
- '{{.GOBIN}}/goreleaser --skip=publish --snapshot --clean'
|
||||
|
||||
model:
|
||||
deps:
|
||||
- oapi-codegen
|
||||
cmds:
|
||||
- mkdir -p tmp
|
||||
- go run openapi/main.go {{.AGH_MODEL_VERSION}}
|
||||
- '{{.GOBIN}}/oapi-codegen -package model -generate types,client -config .oapi-codegen.yaml tmp/schema.yaml > pkg/client/model/model_generated.go'
|
||||
|
||||
model-diff:
|
||||
deps:
|
||||
- oapi-codegen
|
||||
cmds:
|
||||
- go run openapi/main.go {{.AGH_MODEL_VERSION}}
|
||||
- go run openapi/main.go
|
||||
- diff tmp/schema.yaml tmp/schema-master.yaml
|
||||
146
cmd/root.go
146
cmd/root.go
@@ -3,71 +3,15 @@ package cmd
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/bakito/adguardhome-sync/pkg/log"
|
||||
"github.com/bakito/adguardhome-sync/pkg/types"
|
||||
"github.com/bakito/adguardhome-sync/version"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const (
|
||||
configCron = "cron"
|
||||
configRunOnStart = "runOnStart"
|
||||
|
||||
configAPIPort = "api.port"
|
||||
configAPIUsername = "api.username"
|
||||
configAPIPassword = "api.password"
|
||||
configAPIDarkMode = "api.darkMode"
|
||||
|
||||
configFeatureDHCPServerConfig = "features.dhcp.serverConfig"
|
||||
configFeatureDHCPStaticLeases = "features.dhcp.staticLeases"
|
||||
configFeatureDNServerConfig = "features.dns.serverConfig"
|
||||
configFeatureDNSPAccessLists = "features.dns.accessLists"
|
||||
configFeatureDNSRewrites = "features.dns.rewrites"
|
||||
configFeatureGeneralSettings = "features.generalSettings"
|
||||
configFeatureQueryLogConfig = "features.queryLogConfig"
|
||||
configFeatureStatsConfig = "features.statsConfig"
|
||||
configFeatureClientSettings = "features.clientSettings"
|
||||
configFeatureServices = "features.services"
|
||||
configFeatureFilters = "features.filters"
|
||||
|
||||
configOriginURL = "origin.url"
|
||||
configOriginAPIPath = "origin.apiPath"
|
||||
configOriginUsername = "origin.username"
|
||||
configOriginPassword = "origin.password"
|
||||
configOriginCookie = "origin.cookie"
|
||||
configOriginInsecureSkipVerify = "origin.insecureSkipVerify"
|
||||
|
||||
configReplicaURL = "replica.url"
|
||||
configReplicaAPIPath = "replica.apiPath"
|
||||
configReplicaUsername = "replica.username"
|
||||
configReplicaPassword = "replica.password"
|
||||
configReplicaCookie = "replica.cookie"
|
||||
configReplicaInsecureSkipVerify = "replica.insecureSkipVerify"
|
||||
configReplicaAutoSetup = "replica.autoSetup"
|
||||
configReplicaInterfaceName = "replica.interfaceName"
|
||||
|
||||
envReplicasUsernameFormat = "REPLICA%s_USERNAME" // #nosec G101
|
||||
envReplicasPasswordFormat = "REPLICA%s_PASSWORD" // #nosec G101
|
||||
envReplicasCookieFormat = "REPLICA%s_COOKIE" // #nosec G101
|
||||
envReplicasAPIPathFormat = "REPLICA%s_APIPATH"
|
||||
envReplicasInsecureSkipVerifyFormat = "REPLICA%s_INSECURESKIPVERIFY"
|
||||
envReplicasAutoSetup = "REPLICA%s_AUTOSETUP"
|
||||
envReplicasInterfaceName = "REPLICA%s_INTERFACENAME"
|
||||
// Deprecated: use envReplicasInterfaceName instead
|
||||
envReplicasInterfaceNameDeprecated = "REPLICA%s_INTERFACWENAME"
|
||||
envDHCPServerEnabled = "REPLICA%s_DHCPSERVERENABLED"
|
||||
)
|
||||
|
||||
var (
|
||||
cfgFile string
|
||||
logger = log.GetLogger("root")
|
||||
envReplicasURLPattern = regexp.MustCompile(`^REPLICA(\d+)_URL=(.*)`)
|
||||
cfgFile string
|
||||
logger = log.GetLogger("root")
|
||||
)
|
||||
|
||||
// rootCmd represents the base command when called without any subcommands
|
||||
@@ -87,8 +31,6 @@ func Execute() {
|
||||
}
|
||||
|
||||
func init() {
|
||||
cobra.OnInitialize(initConfig)
|
||||
|
||||
// Here you will define your flags and configuration settings.
|
||||
// Cobra supports persistent flags, which, if defined here,
|
||||
// will be global for your application.
|
||||
@@ -99,87 +41,3 @@ func init() {
|
||||
// when this action is called directly.
|
||||
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||
}
|
||||
|
||||
// initConfig reads in config file and ENV variables if set.
|
||||
func initConfig() {
|
||||
if cfgFile != "" {
|
||||
// Use config file from the flag.
|
||||
viper.SetConfigFile(cfgFile)
|
||||
} else {
|
||||
// Find home directory.
|
||||
home, err := homedir.Dir()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Search config in home directory with name ".adguardhome-sync" (without extension).
|
||||
viper.AddConfigPath(home)
|
||||
viper.SetConfigName(".adguardhome-sync")
|
||||
}
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_", ".", "_"))
|
||||
viper.AutomaticEnv() // read in environment variables that match
|
||||
|
||||
// If a config file is found, read it in.
|
||||
if err := viper.ReadInConfig(); err == nil {
|
||||
logger.Info("Using config file:", viper.ConfigFileUsed())
|
||||
} else if cfgFile != "" {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func getConfig(logger *zap.SugaredLogger) (*types.Config, error) {
|
||||
cfg := &types.Config{}
|
||||
if err := viper.Unmarshal(cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(cfg.Replicas) == 0 {
|
||||
cfg.Replicas = append(cfg.Replicas, collectEnvReplicas(logger)...)
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// Manually collect replicas from env.
|
||||
func collectEnvReplicas(logger *zap.SugaredLogger) []types.AdGuardInstance {
|
||||
var replicas []types.AdGuardInstance
|
||||
for _, v := range os.Environ() {
|
||||
if envReplicasURLPattern.MatchString(v) {
|
||||
sm := envReplicasURLPattern.FindStringSubmatch(v)
|
||||
re := types.AdGuardInstance{
|
||||
URL: sm[2],
|
||||
Username: os.Getenv(fmt.Sprintf(envReplicasUsernameFormat, sm[1])),
|
||||
Password: os.Getenv(fmt.Sprintf(envReplicasPasswordFormat, sm[1])),
|
||||
Cookie: os.Getenv(fmt.Sprintf(envReplicasCookieFormat, sm[1])),
|
||||
APIPath: os.Getenv(fmt.Sprintf(envReplicasAPIPathFormat, sm[1])),
|
||||
InsecureSkipVerify: strings.EqualFold(os.Getenv(fmt.Sprintf(envReplicasInsecureSkipVerifyFormat, sm[1])), "true"),
|
||||
AutoSetup: strings.EqualFold(os.Getenv(fmt.Sprintf(envReplicasAutoSetup, sm[1])), "true"),
|
||||
InterfaceName: os.Getenv(fmt.Sprintf(envReplicasInterfaceName, sm[1])),
|
||||
}
|
||||
|
||||
if re.InterfaceName == "" {
|
||||
if in, ok := os.LookupEnv(fmt.Sprintf(envReplicasInterfaceNameDeprecated, sm[1])); ok {
|
||||
logger.
|
||||
With("correct", envReplicasInterfaceName, "deprecated", envReplicasInterfaceNameDeprecated).
|
||||
Warn("Deprecated env variable is used, please use the correct one")
|
||||
re.InterfaceName = in
|
||||
}
|
||||
}
|
||||
if dhcpEnabled, ok := os.LookupEnv(fmt.Sprintf(envDHCPServerEnabled, sm[1])); ok {
|
||||
if strings.EqualFold(dhcpEnabled, "true") {
|
||||
re.DHCPServerEnabled = boolPtr(true)
|
||||
} else if strings.EqualFold(dhcpEnabled, "false") {
|
||||
re.DHCPServerEnabled = boolPtr(false)
|
||||
}
|
||||
}
|
||||
replicas = append(replicas, re)
|
||||
}
|
||||
}
|
||||
|
||||
return replicas
|
||||
}
|
||||
|
||||
func boolPtr(b bool) *bool {
|
||||
return &b
|
||||
}
|
||||
|
||||
116
cmd/root_test.go
116
cmd/root_test.go
@@ -1,116 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/bakito/adguardhome-sync/pkg/log"
|
||||
"github.com/bakito/adguardhome-sync/pkg/types"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var envVars = []string{
|
||||
"FEATURES_GENERALSETTINGS",
|
||||
"FEATURES_QUERYLOGCONFIG",
|
||||
"FEATURES_STATSCONFIG",
|
||||
"FEATURES_CLIENTSETTINGS",
|
||||
"FEATURES_SERVICES",
|
||||
"FEATURES_FILTERS",
|
||||
"FEATURES_DHCP_SERVERCONFIG",
|
||||
"FEATURES_DHCP_STATICLEASES",
|
||||
"FEATURES_DNS_SERVERCONFIG",
|
||||
"FEATURES_DNS_ACCESSLISTS",
|
||||
"FEATURES_DNS_REWRITES",
|
||||
"REPLICA1_INTERFACENAME",
|
||||
"REPLICA1_INTERFACWENAME",
|
||||
"REPLICA1_DHCPSERVERENABLED",
|
||||
}
|
||||
|
||||
var _ = Describe("Run", func() {
|
||||
var logger *zap.SugaredLogger
|
||||
BeforeEach(func() {
|
||||
logger = log.GetLogger("root")
|
||||
for _, envVar := range envVars {
|
||||
Ω(os.Unsetenv(envVar)).ShouldNot(HaveOccurred())
|
||||
}
|
||||
initConfig()
|
||||
})
|
||||
AfterEach(func() {
|
||||
for _, envVar := range envVars {
|
||||
Ω(os.Unsetenv(envVar)).ShouldNot(HaveOccurred())
|
||||
}
|
||||
})
|
||||
Context("getConfig", func() {
|
||||
It("features should be true by default", func() {
|
||||
cfg, err := getConfig(logger)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
verifyFeatures(cfg, true)
|
||||
})
|
||||
It("features should be false", func() {
|
||||
for _, envVar := range envVars {
|
||||
Ω(os.Setenv(envVar, "false")).ShouldNot(HaveOccurred())
|
||||
}
|
||||
cfg, err := getConfig(logger)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
verifyFeatures(cfg, false)
|
||||
})
|
||||
Context("interface name", func() {
|
||||
It("should set interface name of replica 1", func() {
|
||||
Ω(os.Setenv("REPLICA1_URL", "https://foo.bar")).ShouldNot(HaveOccurred())
|
||||
Ω(os.Setenv(fmt.Sprintf(envReplicasInterfaceName, "1"), "eth0")).ShouldNot(HaveOccurred())
|
||||
cfg, err := getConfig(logger)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
Ω(cfg.Replicas[0].InterfaceName).Should(Equal("eth0"))
|
||||
})
|
||||
It("should set interface name of replica 1 from deprecated env", func() {
|
||||
Ω(os.Setenv("REPLICA1_URL", "https://foo.bar")).ShouldNot(HaveOccurred())
|
||||
Ω(os.Setenv(fmt.Sprintf(envReplicasInterfaceNameDeprecated, "1"), "eth0")).ShouldNot(HaveOccurred())
|
||||
cfg, err := getConfig(logger)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
Ω(cfg.Replicas[0].InterfaceName).Should(Equal("eth0"))
|
||||
})
|
||||
It("deprecated should not overwrite the correct", func() {
|
||||
Ω(os.Setenv("REPLICA1_URL", "https://foo.bar")).ShouldNot(HaveOccurred())
|
||||
Ω(os.Setenv(fmt.Sprintf(envReplicasInterfaceNameDeprecated, "1"), "eth1")).ShouldNot(HaveOccurred())
|
||||
Ω(os.Setenv(fmt.Sprintf(envReplicasInterfaceName, "1"), "eth0")).ShouldNot(HaveOccurred())
|
||||
cfg, err := getConfig(logger)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
Ω(cfg.Replicas[0].InterfaceName).Should(Equal("eth0"))
|
||||
})
|
||||
})
|
||||
Context("dhcp server", func() {
|
||||
It("should enable the dhcp server of replica 1", func() {
|
||||
Ω(os.Setenv("REPLICA1_URL", "https://foo.bar")).ShouldNot(HaveOccurred())
|
||||
Ω(os.Setenv(fmt.Sprintf(envDHCPServerEnabled, "1"), "true")).ShouldNot(HaveOccurred())
|
||||
cfg, err := getConfig(logger)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
Ω(cfg.Replicas[0].DHCPServerEnabled).ShouldNot(BeNil())
|
||||
Ω(*cfg.Replicas[0].DHCPServerEnabled).Should(BeTrue())
|
||||
})
|
||||
It("should disable the dhcp server of replica 1", func() {
|
||||
Ω(os.Setenv("REPLICA1_URL", "https://foo.bar")).ShouldNot(HaveOccurred())
|
||||
Ω(os.Setenv(fmt.Sprintf(envDHCPServerEnabled, "1"), "false")).ShouldNot(HaveOccurred())
|
||||
cfg, err := getConfig(logger)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
Ω(cfg.Replicas[0].DHCPServerEnabled).ShouldNot(BeNil())
|
||||
Ω(*cfg.Replicas[0].DHCPServerEnabled).Should(BeFalse())
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
func verifyFeatures(cfg *types.Config, value bool) {
|
||||
Ω(cfg.Features.GeneralSettings).Should(Equal(value))
|
||||
Ω(cfg.Features.QueryLogConfig).Should(Equal(value))
|
||||
Ω(cfg.Features.StatsConfig).Should(Equal(value))
|
||||
Ω(cfg.Features.ClientSettings).Should(Equal(value))
|
||||
Ω(cfg.Features.Services).Should(Equal(value))
|
||||
Ω(cfg.Features.Filters).Should(Equal(value))
|
||||
Ω(cfg.Features.DHCP.ServerConfig).Should(Equal(value))
|
||||
Ω(cfg.Features.DHCP.StaticLeases).Should(Equal(value))
|
||||
Ω(cfg.Features.DNS.ServerConfig).Should(Equal(value))
|
||||
Ω(cfg.Features.DNS.AccessLists).Should(Equal(value))
|
||||
Ω(cfg.Features.DNS.Rewrites).Should(Equal(value))
|
||||
}
|
||||
123
cmd/run.go
123
cmd/run.go
@@ -1,10 +1,11 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/bakito/adguardhome-sync/pkg/config"
|
||||
"github.com/bakito/adguardhome-sync/pkg/log"
|
||||
"github.com/bakito/adguardhome-sync/pkg/sync"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// runCmd represents the run command
|
||||
@@ -14,83 +15,75 @@ var doCmd = &cobra.Command{
|
||||
Long: `Synchronizes the configuration form an origin instance to a replica`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
logger = log.GetLogger("run")
|
||||
cfg, err := getConfig(logger)
|
||||
cfg, err := config.Get(cfgFile, cmd.Flags())
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := cfg.Init(); err != nil {
|
||||
logger.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if cfg.PrintConfigOnly {
|
||||
config, err := yaml.Marshal(cfg)
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
return err
|
||||
}
|
||||
logger.Infof("Printing adguardhome-sync config (THE APPLICATION WILL NOT START IN THIS MODE): \n%s",
|
||||
string(config))
|
||||
return nil
|
||||
}
|
||||
|
||||
return sync.Sync(cfg)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(doCmd)
|
||||
doCmd.PersistentFlags().String("cron", "", "The cron expression to run in daemon mode")
|
||||
_ = viper.BindPFlag(configCron, doCmd.PersistentFlags().Lookup("cron"))
|
||||
doCmd.PersistentFlags().Bool("runOnStart", true, "Run the sync job on start.")
|
||||
_ = viper.BindPFlag(configRunOnStart, doCmd.PersistentFlags().Lookup("runOnStart"))
|
||||
doCmd.PersistentFlags().Int("api-port", 8080, "Sync API Port, the API endpoint will be started to enable remote triggering; if 0 port API is disabled.")
|
||||
_ = viper.BindPFlag(configAPIPort, doCmd.PersistentFlags().Lookup("api-port"))
|
||||
doCmd.PersistentFlags().String("api-username", "", "Sync API username")
|
||||
_ = viper.BindPFlag(configAPIUsername, doCmd.PersistentFlags().Lookup("api-username"))
|
||||
doCmd.PersistentFlags().String("api-password", "", "Sync API password")
|
||||
_ = viper.BindPFlag(configAPIPassword, doCmd.PersistentFlags().Lookup("api-password"))
|
||||
doCmd.PersistentFlags().String("api-darkMode", "", "API UI in dark mode")
|
||||
_ = viper.BindPFlag(configAPIDarkMode, doCmd.PersistentFlags().Lookup("api-darkMode"))
|
||||
doCmd.PersistentFlags().String(config.FlagCron, "", "The cron expression to run in daemon mode")
|
||||
doCmd.PersistentFlags().Bool(config.FlagRunOnStart, true, "Run the sync job on start.")
|
||||
doCmd.PersistentFlags().Bool(config.FlagPrintConfigOnly, false, "Prints the configuration only and exists. "+
|
||||
"Can be used to debug the config E.g: when having authentication issues.")
|
||||
doCmd.PersistentFlags().Bool(config.FlagContinueOnError, false, "If enabled, the synchronisation task "+
|
||||
"will not fail on single errors, but will log the errors and continue.")
|
||||
|
||||
doCmd.PersistentFlags().Bool("feature-dhcp-server-config", true, "Enable DHCP server config feature")
|
||||
_ = viper.BindPFlag(configFeatureDHCPServerConfig, doCmd.PersistentFlags().Lookup("feature-dhcp-server-config"))
|
||||
doCmd.PersistentFlags().Bool("feature-dhcp-static-leases", true, "Enable DHCP server static leases feature")
|
||||
_ = viper.BindPFlag(configFeatureDHCPStaticLeases, doCmd.PersistentFlags().Lookup("feature-dhcp-static-leases"))
|
||||
doCmd.PersistentFlags().Int(config.FlagApiPort, 8080, "Sync API Port, the API endpoint will be started to enable remote triggering; if 0 port API is disabled.")
|
||||
doCmd.PersistentFlags().String(config.FlagApiUsername, "", "Sync API username")
|
||||
doCmd.PersistentFlags().String(config.FlagApiPassword, "", "Sync API password")
|
||||
doCmd.PersistentFlags().String(config.FlagApiDarkMode, "", "API UI in dark mode")
|
||||
|
||||
doCmd.PersistentFlags().Bool("feature-dns-server-config", true, "Enable DNS server config feature")
|
||||
_ = viper.BindPFlag(configFeatureDNServerConfig, doCmd.PersistentFlags().Lookup("feature-dns-server-config"))
|
||||
doCmd.PersistentFlags().Bool("feature-dns-access-lists", true, "Enable DNS server access lists feature")
|
||||
_ = viper.BindPFlag(configFeatureDNSPAccessLists, doCmd.PersistentFlags().Lookup("feature-dns-access-lists"))
|
||||
doCmd.PersistentFlags().Bool("feature-dns-rewrites", true, "Enable DNS rewrites feature")
|
||||
_ = viper.BindPFlag(configFeatureDNSRewrites, doCmd.PersistentFlags().Lookup("feature-dns-rewrites"))
|
||||
doCmd.PersistentFlags().Bool("feature-general-settings", true, "Enable general settings feature")
|
||||
_ = viper.BindPFlag(configFeatureGeneralSettings, doCmd.PersistentFlags().Lookup("feature-general-settings"))
|
||||
_ = viper.BindPFlag("features.generalSettings", doCmd.PersistentFlags().Lookup("feature-general-settings"))
|
||||
doCmd.PersistentFlags().Bool("feature-query-log-config", true, "Enable query log config feature")
|
||||
_ = viper.BindPFlag(configFeatureQueryLogConfig, doCmd.PersistentFlags().Lookup("feature-query-log-config"))
|
||||
doCmd.PersistentFlags().Bool("feature-stats-config", true, "Enable stats config feature")
|
||||
_ = viper.BindPFlag(configFeatureStatsConfig, doCmd.PersistentFlags().Lookup("feature-stats-config"))
|
||||
doCmd.PersistentFlags().Bool("feature-client-settings", true, "Enable client settings feature")
|
||||
_ = viper.BindPFlag(configFeatureClientSettings, doCmd.PersistentFlags().Lookup("feature-client-settings"))
|
||||
doCmd.PersistentFlags().Bool("feature-services", true, "Enable services sync feature")
|
||||
_ = viper.BindPFlag(configFeatureServices, doCmd.PersistentFlags().Lookup("feature-services"))
|
||||
doCmd.PersistentFlags().Bool("feature-filters", true, "Enable filters sync feature")
|
||||
_ = viper.BindPFlag(configFeatureFilters, doCmd.PersistentFlags().Lookup("feature-filters"))
|
||||
doCmd.PersistentFlags().Bool(config.FlagFeatureDhcpServerConfig, true, "Enable DHCP server config feature")
|
||||
doCmd.PersistentFlags().Bool(config.FlagFeatureDhcpStaticLeases, true, "Enable DHCP server static leases feature")
|
||||
|
||||
doCmd.PersistentFlags().String("origin-url", "", "Origin instance url")
|
||||
_ = viper.BindPFlag(configOriginURL, doCmd.PersistentFlags().Lookup("origin-url"))
|
||||
doCmd.PersistentFlags().String("origin-api-path", "/control", "Origin instance API path")
|
||||
_ = viper.BindPFlag(configOriginAPIPath, doCmd.PersistentFlags().Lookup("origin-api-path"))
|
||||
doCmd.PersistentFlags().String("origin-username", "", "Origin instance username")
|
||||
_ = viper.BindPFlag(configOriginUsername, doCmd.PersistentFlags().Lookup("origin-username"))
|
||||
doCmd.PersistentFlags().String("origin-password", "", "Origin instance password")
|
||||
_ = viper.BindPFlag(configOriginPassword, doCmd.PersistentFlags().Lookup("origin-password"))
|
||||
doCmd.PersistentFlags().String("origin-cookie", "", "If Set, uses a cookie for authentication")
|
||||
_ = viper.BindPFlag(configOriginCookie, doCmd.PersistentFlags().Lookup("origin-cookie"))
|
||||
doCmd.PersistentFlags().Bool("origin-insecure-skip-verify", false, "Enable Origin instance InsecureSkipVerify")
|
||||
_ = viper.BindPFlag(configOriginInsecureSkipVerify, doCmd.PersistentFlags().Lookup("origin-insecure-skip-verify"))
|
||||
doCmd.PersistentFlags().Bool(config.FlagFeatureDnsServerConfig, true, "Enable DNS server config feature")
|
||||
doCmd.PersistentFlags().Bool(config.FlagFeatureDnsAccessLists, true, "Enable DNS server access lists feature")
|
||||
doCmd.PersistentFlags().Bool(config.FlagFeatureDnsRewrites, true, "Enable DNS rewrites feature")
|
||||
|
||||
doCmd.PersistentFlags().String("replica-url", "", "Replica instance url")
|
||||
_ = viper.BindPFlag(configReplicaURL, doCmd.PersistentFlags().Lookup("replica-url"))
|
||||
doCmd.PersistentFlags().String("replica-api-path", "/control", "Replica instance API path")
|
||||
_ = viper.BindPFlag(configReplicaAPIPath, doCmd.PersistentFlags().Lookup("replica-api-path"))
|
||||
doCmd.PersistentFlags().String("replica-username", "", "Replica instance username")
|
||||
_ = viper.BindPFlag(configReplicaUsername, doCmd.PersistentFlags().Lookup("replica-username"))
|
||||
doCmd.PersistentFlags().String("replica-password", "", "Replica instance password")
|
||||
_ = viper.BindPFlag(configReplicaPassword, doCmd.PersistentFlags().Lookup("replica-password"))
|
||||
doCmd.PersistentFlags().String("replica-cookie", "", "If Set, uses a cookie for authentication")
|
||||
_ = viper.BindPFlag(configReplicaCookie, doCmd.PersistentFlags().Lookup("replica-cookie"))
|
||||
doCmd.PersistentFlags().Bool("replica-insecure-skip-verify", false, "Enable Replica instance InsecureSkipVerify")
|
||||
_ = viper.BindPFlag(configReplicaInsecureSkipVerify, doCmd.PersistentFlags().Lookup("replica-insecure-skip-verify"))
|
||||
doCmd.PersistentFlags().Bool("replica-auto-setup", false, "Enable automatic setup of new AdguardHome instances. This replaces the setup wizard.")
|
||||
_ = viper.BindPFlag(configReplicaAutoSetup, doCmd.PersistentFlags().Lookup("replica-auto-setup"))
|
||||
doCmd.PersistentFlags().String("replica-interface-name", "", "Optional change the interface name of the replica if it differs from the master")
|
||||
_ = viper.BindPFlag(configReplicaInterfaceName, doCmd.PersistentFlags().Lookup("replica-interface-name"))
|
||||
doCmd.PersistentFlags().Bool(config.FlagFeatureGeneral, true, "Enable general settings feature")
|
||||
doCmd.PersistentFlags().Bool(config.FlagFeatureQueryLog, true, "Enable query log config feature")
|
||||
doCmd.PersistentFlags().Bool(config.FlagFeatureStats, true, "Enable stats config feature")
|
||||
doCmd.PersistentFlags().Bool(config.FlagFeatureClient, true, "Enable client settings feature")
|
||||
doCmd.PersistentFlags().Bool(config.FlagFeatureServices, true, "Enable services sync feature")
|
||||
doCmd.PersistentFlags().Bool(config.FlagFeatureFilters, true, "Enable filters sync feature")
|
||||
|
||||
doCmd.PersistentFlags().String(config.FlagOriginURL, "", "Origin instance url")
|
||||
doCmd.PersistentFlags().String(config.FlagOriginWebURL, "", "Origin instance web url used in the web interface (default: <origin-url>)")
|
||||
doCmd.PersistentFlags().String(config.FlagOriginApiPath, "/control", "Origin instance API path")
|
||||
doCmd.PersistentFlags().String(config.FlagOriginUsername, "", "Origin instance username")
|
||||
doCmd.PersistentFlags().String(config.FlagOriginPassword, "", "Origin instance password")
|
||||
doCmd.PersistentFlags().String(config.FlagOriginCookie, "", "If Set, uses a cookie for authentication")
|
||||
doCmd.PersistentFlags().Bool(config.FlagOriginISV, false, "Enable Origin instance InsecureSkipVerify")
|
||||
|
||||
doCmd.PersistentFlags().String(config.FlagReplicaURL, "", "Replica instance url")
|
||||
doCmd.PersistentFlags().String(config.FlagReplicaWebURL, "", "Replica instance web url used in the web interface (default: <replica-url>)")
|
||||
doCmd.PersistentFlags().String(config.FlagReplicaApiPath, "/control", "Replica instance API path")
|
||||
doCmd.PersistentFlags().String(config.FlagReplicaUsername, "", "Replica instance username")
|
||||
doCmd.PersistentFlags().String(config.FlagReplicaPassword, "", "Replica instance password")
|
||||
doCmd.PersistentFlags().String(config.FlagReplicaCookie, "", "If Set, uses a cookie for authentication")
|
||||
doCmd.PersistentFlags().Bool(config.FlagReplicaISV, false, "Enable Replica instance InsecureSkipVerify")
|
||||
doCmd.PersistentFlags().Bool(config.FlagReplicaAutoSetup, false, "Enable automatic setup of new AdguardHome instances. This replaces the setup wizard.")
|
||||
doCmd.PersistentFlags().String(config.FlagReplicaInterfaceName, "", "Optional change the interface name of the replica if it differs from the master")
|
||||
}
|
||||
|
||||
432
go.mod
432
go.mod
@@ -1,80 +1,438 @@
|
||||
module github.com/bakito/adguardhome-sync
|
||||
|
||||
go 1.20
|
||||
go 1.22
|
||||
|
||||
require (
|
||||
github.com/bakito/semver v1.1.3
|
||||
github.com/caarlos0/env/v10 v10.0.0
|
||||
github.com/deepmap/oapi-codegen/v2 v2.1.0
|
||||
github.com/gin-gonic/gin v1.9.1
|
||||
github.com/go-resty/resty/v2 v2.10.0
|
||||
github.com/golang/mock v1.6.0
|
||||
github.com/google/uuid v1.4.0
|
||||
github.com/go-resty/resty/v2 v2.12.0
|
||||
github.com/golangci/golangci-lint v1.56.2
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/goreleaser/goreleaser v1.24.0
|
||||
github.com/jinzhu/copier v0.4.0
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/oapi-codegen/runtime v1.1.0
|
||||
github.com/onsi/ginkgo/v2 v2.13.1
|
||||
github.com/onsi/gomega v1.30.0
|
||||
github.com/oapi-codegen/runtime v1.1.1
|
||||
github.com/onsi/ginkgo/v2 v2.16.0
|
||||
github.com/onsi/gomega v1.31.1
|
||||
github.com/prometheus/client_golang v1.19.0
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/spf13/cobra v1.8.0
|
||||
github.com/spf13/viper v1.17.0
|
||||
go.uber.org/zap v1.26.0
|
||||
golang.org/x/mod v0.14.0
|
||||
go.uber.org/mock v0.4.0
|
||||
go.uber.org/zap v1.27.0
|
||||
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc
|
||||
golang.org/x/mod v0.16.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
k8s.io/apimachinery v0.28.4
|
||||
k8s.io/apimachinery v0.29.3
|
||||
k8s.io/code-generator v0.29.3
|
||||
)
|
||||
|
||||
require (
|
||||
4d63.com/gocheckcompilerdirectives v1.2.1 // indirect
|
||||
4d63.com/gochecknoglobals v0.2.1 // indirect
|
||||
cloud.google.com/go v0.110.10 // indirect
|
||||
cloud.google.com/go/compute v1.23.3 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
cloud.google.com/go/iam v1.1.5 // indirect
|
||||
cloud.google.com/go/kms v1.15.5 // indirect
|
||||
cloud.google.com/go/storage v1.35.1 // indirect
|
||||
code.gitea.io/sdk/gitea v0.17.1 // indirect
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
github.com/4meepo/tagalign v1.3.3 // indirect
|
||||
github.com/Abirdcfly/dupword v0.0.13 // indirect
|
||||
github.com/AlekSi/pointer v1.2.0 // indirect
|
||||
github.com/Antonboom/errname v0.1.12 // indirect
|
||||
github.com/Antonboom/nilnil v0.1.7 // indirect
|
||||
github.com/Antonboom/testifylint v1.1.2 // indirect
|
||||
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.0 // indirect
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||
github.com/Azure/go-autorest/autorest v0.11.29 // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.23 // indirect
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 // indirect
|
||||
github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 // indirect
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
||||
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
|
||||
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.0 // indirect
|
||||
github.com/BurntSushi/toml v1.3.2 // indirect
|
||||
github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect
|
||||
github.com/GaijinEntertainment/go-exhaustruct/v3 v3.2.0 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver v1.5.0 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.2.1 // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
github.com/OpenPeeDeeP/depguard/v2 v2.2.0 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.0.0 // indirect
|
||||
github.com/alecthomas/go-check-sumtype v0.1.4 // indirect
|
||||
github.com/alessio/shellescape v1.4.1 // indirect
|
||||
github.com/alexkohler/nakedret/v2 v2.0.2 // indirect
|
||||
github.com/alexkohler/prealloc v1.0.0 // indirect
|
||||
github.com/alingse/asasalint v0.0.11 // indirect
|
||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
|
||||
github.com/bytedance/sonic v1.10.0 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||
github.com/ashanbrown/forbidigo v1.6.0 // indirect
|
||||
github.com/ashanbrown/makezero v1.1.1 // indirect
|
||||
github.com/atc0005/go-teams-notify/v2 v2.9.0 // indirect
|
||||
github.com/aws/aws-sdk-go v1.50.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.24.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.26.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.16.12 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ecr v1.20.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.18.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.27.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.47.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.18.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.26.5 // indirect
|
||||
github.com/aws/smithy-go v1.19.0 // indirect
|
||||
github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20231024185945-8841054dbdb8 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bkielbasa/cyclop v1.2.1 // indirect
|
||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect
|
||||
github.com/blizzy78/varnamelen v0.8.0 // indirect
|
||||
github.com/bombsimon/wsl/v4 v4.2.1 // indirect
|
||||
github.com/breml/bidichk v0.2.7 // indirect
|
||||
github.com/breml/errchkjson v0.3.6 // indirect
|
||||
github.com/buger/jsonparser v1.1.1 // indirect
|
||||
github.com/butuzov/ireturn v0.3.0 // indirect
|
||||
github.com/butuzov/mirror v1.1.0 // indirect
|
||||
github.com/bytedance/sonic v1.10.2 // indirect
|
||||
github.com/caarlos0/ctrlc v1.2.0 // indirect
|
||||
github.com/caarlos0/env/v9 v9.0.0 // indirect
|
||||
github.com/caarlos0/go-reddit/v3 v3.0.1 // indirect
|
||||
github.com/caarlos0/go-shellwords v1.0.12 // indirect
|
||||
github.com/caarlos0/go-version v0.1.1 // indirect
|
||||
github.com/caarlos0/log v0.4.4 // indirect
|
||||
github.com/catenacyber/perfsprint v0.6.0 // indirect
|
||||
github.com/cavaliergopher/cpio v1.0.1 // indirect
|
||||
github.com/ccojocar/zxcvbn-go v1.0.2 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/charithe/durationcheck v0.0.10 // indirect
|
||||
github.com/charmbracelet/lipgloss v0.9.1 // indirect
|
||||
github.com/charmbracelet/x/exp/ordered v0.0.0-20231010190216-1cb11efc897d // indirect
|
||||
github.com/chavacava/garif v0.1.0 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
||||
github.com/chenzhuoyu/iasm v0.9.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||
github.com/chenzhuoyu/iasm v0.9.1 // indirect
|
||||
github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589 // indirect
|
||||
github.com/cloudflare/circl v1.3.7 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
|
||||
github.com/coreos/go-semver v0.3.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
|
||||
github.com/curioswitch/go-reassign v0.2.0 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
|
||||
github.com/daixiang0/gci v0.12.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/davidmz/go-pageant v1.0.2 // indirect
|
||||
github.com/denis-tingaikin/go-header v0.4.3 // indirect
|
||||
github.com/dghubble/go-twitter v0.0.0-20211115160449-93a8679adecb // indirect
|
||||
github.com/dghubble/oauth1 v0.7.2 // indirect
|
||||
github.com/dghubble/sling v1.4.0 // indirect
|
||||
github.com/dimchansky/utfbom v1.1.1 // indirect
|
||||
github.com/distribution/reference v0.5.0 // indirect
|
||||
github.com/docker/cli v24.0.7+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.3+incompatible // indirect
|
||||
github.com/docker/docker v24.0.9+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.8.0 // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/elliotchance/orderedmap/v2 v2.2.0 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/esimonov/ifshort v1.0.4 // indirect
|
||||
github.com/ettle/strcase v0.2.0 // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
|
||||
github.com/fatih/color v1.16.0 // indirect
|
||||
github.com/fatih/structtag v1.2.0 // indirect
|
||||
github.com/firefart/nonamedreturns v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/fzipp/gocyclo v0.6.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/getkin/kin-openapi v0.122.0 // indirect
|
||||
github.com/ghostiam/protogetter v0.3.4 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-logr/logr v1.3.0 // indirect
|
||||
github.com/go-critic/go-critic v0.11.1 // indirect
|
||||
github.com/go-fed/httpsig v1.1.0 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.5.0 // indirect
|
||||
github.com/go-git/go-git/v5 v5.11.0 // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
github.com/go-openapi/analysis v0.21.4 // indirect
|
||||
github.com/go-openapi/errors v0.20.4 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.20.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
github.com/go-openapi/loads v0.21.2 // indirect
|
||||
github.com/go-openapi/runtime v0.26.0 // indirect
|
||||
github.com/go-openapi/spec v0.20.9 // indirect
|
||||
github.com/go-openapi/strfmt v0.21.7 // indirect
|
||||
github.com/go-openapi/swag v0.22.4 // indirect
|
||||
github.com/go-openapi/validate v0.22.1 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.15.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.16.0 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect
|
||||
github.com/go-toolsmith/astcast v1.1.0 // indirect
|
||||
github.com/go-toolsmith/astcopy v1.1.0 // indirect
|
||||
github.com/go-toolsmith/astequal v1.2.0 // indirect
|
||||
github.com/go-toolsmith/astfmt v1.1.0 // indirect
|
||||
github.com/go-toolsmith/astp v1.1.0 // indirect
|
||||
github.com/go-toolsmith/strparse v1.1.0 // indirect
|
||||
github.com/go-toolsmith/typep v1.1.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 // indirect
|
||||
github.com/go-xmlfmt/xmlfmt v1.1.2 // indirect
|
||||
github.com/gobwas/glob v0.2.3 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/gofrs/flock v0.8.1 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.1.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 // indirect
|
||||
github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect
|
||||
github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe // indirect
|
||||
github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e // indirect
|
||||
github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 // indirect
|
||||
github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca // indirect
|
||||
github.com/golangci/misspell v0.4.1 // indirect
|
||||
github.com/golangci/revgrep v0.5.2 // indirect
|
||||
github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/go-containerregistry v0.19.0 // indirect
|
||||
github.com/google/go-github/v57 v57.0.0 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/google/ko v0.15.1 // indirect
|
||||
github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b // indirect
|
||||
github.com/google/rpmpack v0.5.0 // indirect
|
||||
github.com/google/s2a-go v0.1.7 // indirect
|
||||
github.com/google/safetext v0.0.0-20220905092116-b49f7bc46da2 // indirect
|
||||
github.com/google/wire v0.5.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
|
||||
github.com/gordonklaus/ineffassign v0.1.0 // indirect
|
||||
github.com/goreleaser/chglog v0.5.0 // indirect
|
||||
github.com/goreleaser/fileglob v1.3.0 // indirect
|
||||
github.com/goreleaser/nfpm/v2 v2.35.3 // indirect
|
||||
github.com/gorilla/websocket v1.5.1 // indirect
|
||||
github.com/gostaticanalysis/analysisutil v0.7.1 // indirect
|
||||
github.com/gostaticanalysis/comment v1.4.2 // indirect
|
||||
github.com/gostaticanalysis/forcetypeassert v0.1.0 // indirect
|
||||
github.com/gostaticanalysis/nilerr v0.1.1 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.4 // indirect
|
||||
github.com/hashicorp/go-version v1.6.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.1-vault-5 // indirect
|
||||
github.com/hexops/gotextdiff v1.0.3 // indirect
|
||||
github.com/huandu/xstrings v1.3.3 // indirect
|
||||
github.com/imdario/mergo v0.3.16 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/invopop/jsonschema v0.12.0 // indirect
|
||||
github.com/invopop/yaml v0.2.0 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/jgautheron/goconst v1.7.0 // indirect
|
||||
github.com/jingyugao/rowserrcheck v1.1.1 // indirect
|
||||
github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af // indirect
|
||||
github.com/jjti/go-spancheck v0.5.2 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
||||
github.com/julz/importas v0.1.0 // indirect
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/kisielk/errcheck v1.7.0 // indirect
|
||||
github.com/kisielk/gotool v1.0.0 // indirect
|
||||
github.com/kkHAIKE/contextcheck v1.1.4 // indirect
|
||||
github.com/klauspost/compress v1.17.5 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
|
||||
github.com/klauspost/pgzip v1.2.6 // indirect
|
||||
github.com/kulti/thelper v0.6.3 // indirect
|
||||
github.com/kunwardeep/paralleltest v1.0.9 // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/kyoh86/exportloopref v0.1.11 // indirect
|
||||
github.com/ldez/gomoddirectives v0.2.3 // indirect
|
||||
github.com/ldez/tagliatelle v0.5.0 // indirect
|
||||
github.com/leodido/go-urn v1.2.4 // indirect
|
||||
github.com/leonklingele/grouper v1.1.1 // indirect
|
||||
github.com/letsencrypt/boulder v0.0.0-20231026200631-000cd05d5491 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/lufeee/execinquery v1.2.1 // indirect
|
||||
github.com/macabu/inamedparam v0.1.3 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/maratori/testableexamples v1.0.0 // indirect
|
||||
github.com/maratori/testpackage v1.1.1 // indirect
|
||||
github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-mastodon v0.0.6 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/mbilski/exhaustivestruct v1.2.0 // indirect
|
||||
github.com/mgechev/revive v1.3.7 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||
github.com/moricho/tparallel v0.3.1 // indirect
|
||||
github.com/muesli/mango v0.1.0 // indirect
|
||||
github.com/muesli/mango-cobra v1.2.0 // indirect
|
||||
github.com/muesli/mango-pflag v0.1.0 // indirect
|
||||
github.com/muesli/reflow v0.3.0 // indirect
|
||||
github.com/muesli/roff v0.1.0 // indirect
|
||||
github.com/muesli/termenv v0.15.2 // indirect
|
||||
github.com/nakabonne/nestif v0.3.1 // indirect
|
||||
github.com/nishanths/exhaustive v0.12.0 // indirect
|
||||
github.com/nishanths/predeclared v0.2.2 // indirect
|
||||
github.com/nunnatsa/ginkgolinter v0.15.2 // indirect
|
||||
github.com/oklog/ulid v1.3.1 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0-rc5 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.1.1 // indirect
|
||||
github.com/perimeterx/marshmallow v1.1.5 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/polyfloyd/go-errorlint v1.4.8 // indirect
|
||||
github.com/prometheus/client_model v0.5.0 // indirect
|
||||
github.com/prometheus/common v0.48.0 // indirect
|
||||
github.com/prometheus/procfs v0.12.0 // indirect
|
||||
github.com/quasilyte/go-ruleguard v0.4.0 // indirect
|
||||
github.com/quasilyte/gogrep v0.5.0 // indirect
|
||||
github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect
|
||||
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect
|
||||
github.com/rivo/uniseg v0.4.2 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/ryancurrah/gomodguard v1.3.0 // indirect
|
||||
github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect
|
||||
github.com/sagikazarmark/locafero v0.3.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/sanposhiho/wastedassign/v2 v2.0.7 // indirect
|
||||
github.com/sashamelentyev/interfacebloat v1.1.0 // indirect
|
||||
github.com/sashamelentyev/usestdlibvars v1.25.0 // indirect
|
||||
github.com/secure-systems-lab/go-securesystemslib v0.7.0 // indirect
|
||||
github.com/securego/gosec/v2 v2.19.0 // indirect
|
||||
github.com/sergi/go-diff v1.2.0 // indirect
|
||||
github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect
|
||||
github.com/shopspring/decimal v1.2.0 // indirect
|
||||
github.com/sigstore/cosign/v2 v2.2.1 // indirect
|
||||
github.com/sigstore/rekor v1.3.3 // indirect
|
||||
github.com/sigstore/sigstore v1.7.5 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/sivchari/containedctx v1.0.3 // indirect
|
||||
github.com/sivchari/nosnakecase v1.7.0 // indirect
|
||||
github.com/sivchari/tenv v1.7.1 // indirect
|
||||
github.com/skeema/knownhosts v1.2.1 // indirect
|
||||
github.com/slack-go/slack v0.12.3 // indirect
|
||||
github.com/sonatard/noctx v0.0.2 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.10.0 // indirect
|
||||
github.com/sourcegraph/go-diff v0.7.0 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/cast v1.5.1 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.17.0 // indirect
|
||||
github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect
|
||||
github.com/stbenjam/no-sprintf-host-port v0.1.1 // indirect
|
||||
github.com/stretchr/objx v0.5.0 // indirect
|
||||
github.com/stretchr/testify v1.8.4 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c // indirect
|
||||
github.com/tdakkota/asciicheck v0.2.0 // indirect
|
||||
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
|
||||
github.com/tetafro/godot v1.4.16 // indirect
|
||||
github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 // indirect
|
||||
github.com/timonwong/loggercheck v0.9.4 // indirect
|
||||
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect
|
||||
github.com/tomarrell/wrapcheck/v2 v2.8.1 // indirect
|
||||
github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect
|
||||
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
github.com/ulikunitz/xz v0.5.11 // indirect
|
||||
github.com/ultraware/funlen v0.1.0 // indirect
|
||||
github.com/ultraware/whitespace v0.1.0 // indirect
|
||||
github.com/uudashr/gocognit v1.1.2 // indirect
|
||||
github.com/vbatts/tar-split v0.11.5 // indirect
|
||||
github.com/withfig/autocomplete-tools/integrations/cobra v1.2.1 // indirect
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
|
||||
github.com/xanzy/go-gitlab v0.97.0 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
github.com/xen0n/gosmopolitan v1.2.2 // indirect
|
||||
github.com/yagipy/maintidx v1.0.0 // indirect
|
||||
github.com/yeya24/promlinter v0.2.0 // indirect
|
||||
github.com/ykadowak/zerologlint v0.1.5 // indirect
|
||||
gitlab.com/bosi/decorder v0.4.1 // indirect
|
||||
gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect
|
||||
go-simpler.org/musttag v0.8.0 // indirect
|
||||
go-simpler.org/sloglint v0.4.0 // indirect
|
||||
go.mongodb.org/mongo-driver v1.12.1 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.uber.org/automaxprocs v1.5.3 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/arch v0.4.0 // indirect
|
||||
golang.org/x/crypto v0.14.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||
golang.org/x/net v0.17.0 // indirect
|
||||
golang.org/x/sys v0.14.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
golang.org/x/tools v0.14.0 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
|
||||
gocloud.dev v0.36.0 // indirect
|
||||
golang.org/x/arch v0.7.0 // indirect
|
||||
golang.org/x/crypto v0.21.0 // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20231219180239-dc181d75b848 // indirect
|
||||
golang.org/x/net v0.22.0 // indirect
|
||||
golang.org/x/oauth2 v0.16.0 // indirect
|
||||
golang.org/x/sync v0.6.0 // indirect
|
||||
golang.org/x/sys v0.18.0 // indirect
|
||||
golang.org/x/term v0.18.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.18.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
|
||||
google.golang.org/api v0.152.0 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
google.golang.org/genproto v0.0.0-20231120223509-83a465c0220f // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect
|
||||
google.golang.org/grpc v1.59.0 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/mail.v2 v2.3.1 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
k8s.io/klog/v2 v2.100.1 // indirect
|
||||
honnef.co/go/tools v0.4.6 // indirect
|
||||
k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01 // indirect
|
||||
k8s.io/klog/v2 v2.110.1 // indirect
|
||||
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
|
||||
mvdan.cc/gofumpt v0.6.0 // indirect
|
||||
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed // indirect
|
||||
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b // indirect
|
||||
mvdan.cc/unparam v0.0.0-20240104100049-c549a3470d14 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.3.0 // indirect
|
||||
sigs.k8s.io/kind v0.20.0 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
|
||||
sigs.k8s.io/yaml v1.4.0 // indirect
|
||||
)
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func (cl *client) doGet(req *resty.Request, url string) error {
|
||||
@@ -22,9 +23,13 @@ func (cl *client) doGet(req *resty.Request, url string) error {
|
||||
return ErrSetupNeeded
|
||||
}
|
||||
}
|
||||
|
||||
rl.With("status", resp.StatusCode(), "body", string(resp.Body()), "error", err).Debug("error in do get")
|
||||
return detailedError(resp, err)
|
||||
}
|
||||
|
||||
checkAuthenticationIssue(resp, rl)
|
||||
|
||||
rl.With(
|
||||
"status", resp.StatusCode(),
|
||||
"body", string(resp.Body()),
|
||||
@@ -48,6 +53,9 @@ func (cl *client) doPost(req *resty.Request, url string) error {
|
||||
rl.With("status", resp.StatusCode(), "body", string(resp.Body()), "error", err).Debug("error in do post")
|
||||
return detailedError(resp, err)
|
||||
}
|
||||
|
||||
checkAuthenticationIssue(resp, rl)
|
||||
|
||||
rl.With(
|
||||
"status", resp.StatusCode(),
|
||||
"body", string(resp.Body()),
|
||||
@@ -71,6 +79,9 @@ func (cl *client) doPut(req *resty.Request, url string) error {
|
||||
rl.With("status", resp.StatusCode(), "body", string(resp.Body()), "error", err).Debug("error in do put")
|
||||
return detailedError(resp, err)
|
||||
}
|
||||
|
||||
checkAuthenticationIssue(resp, rl)
|
||||
|
||||
rl.With(
|
||||
"status", resp.StatusCode(),
|
||||
"body", string(resp.Body()),
|
||||
@@ -81,3 +92,10 @@ func (cl *client) doPut(req *resty.Request, url string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkAuthenticationIssue(resp *resty.Response, rl *zap.SugaredLogger) {
|
||||
if resp != nil && (resp.StatusCode() == http.StatusUnauthorized || resp.StatusCode() == http.StatusForbidden) {
|
||||
rl.With("status", resp.StatusCode()).Error("there seems to be an authentication issue - " +
|
||||
"please check https://github.com/bakito/adguardhome-sync/wiki/FAQ")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,10 +53,8 @@ func New(config types.AdGuardInstance) (Client, error) {
|
||||
u.Path = path.Clean(u.Path)
|
||||
cl := resty.New().SetBaseURL(u.String()).SetDisableWarn(true)
|
||||
|
||||
if config.InsecureSkipVerify {
|
||||
// #nosec G402 has to be explicitly enabled
|
||||
cl.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
|
||||
}
|
||||
// #nosec G402 has to be explicitly enabled
|
||||
cl.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: config.InsecureSkipVerify})
|
||||
|
||||
cookieParts := strings.Split(config.Cookie, "=")
|
||||
if len(cookieParts) == 2 {
|
||||
@@ -80,9 +78,9 @@ func New(config types.AdGuardInstance) (Client, error) {
|
||||
}
|
||||
|
||||
return &client{
|
||||
host: u.Host,
|
||||
host: config.Host,
|
||||
client: cl,
|
||||
log: l.With("host", u.Host),
|
||||
log: l.With("host", config.Host),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -90,15 +88,17 @@ func New(config types.AdGuardInstance) (Client, error) {
|
||||
type Client interface {
|
||||
Host() string
|
||||
Status() (*model.ServerStatus, error)
|
||||
Stats() (*model.Stats, error)
|
||||
QueryLog(limit int) (*model.QueryLog, error)
|
||||
ToggleProtection(enable bool) error
|
||||
RewriteList() (*model.RewriteEntries, error)
|
||||
AddRewriteEntries(e ...model.RewriteEntry) error
|
||||
DeleteRewriteEntries(e ...model.RewriteEntry) error
|
||||
Filtering() (*model.FilterStatus, error)
|
||||
ToggleFiltering(enabled bool, interval int) error
|
||||
AddFilters(whitelist bool, e ...model.Filter) error
|
||||
DeleteFilters(whitelist bool, e ...model.Filter) error
|
||||
UpdateFilters(whitelist bool, e ...model.Filter) error
|
||||
AddFilter(whitelist bool, f model.Filter) error
|
||||
DeleteFilter(whitelist bool, f model.Filter) error
|
||||
UpdateFilter(whitelist bool, f model.Filter) error
|
||||
RefreshFilters(whitelist bool) error
|
||||
SetCustomRules(rules *[]string) error
|
||||
SafeBrowsing() (bool, error)
|
||||
@@ -114,9 +114,9 @@ type Client interface {
|
||||
SetBlockedServices(services *model.BlockedServicesArray) error
|
||||
SetBlockedServicesSchedule(schedule *model.BlockedServicesSchedule) error
|
||||
Clients() (*model.Clients, error)
|
||||
AddClients(client ...*model.Client) error
|
||||
UpdateClients(client ...*model.Client) error
|
||||
DeleteClients(client ...*model.Client) error
|
||||
AddClient(client *model.Client) error
|
||||
UpdateClient(client *model.Client) error
|
||||
DeleteClient(client *model.Client) error
|
||||
QueryLogConfig() (*model.QueryLogConfig, error)
|
||||
SetQueryLogConfig(*model.QueryLogConfig) error
|
||||
StatsConfig() (*model.StatsConfig, error)
|
||||
@@ -128,8 +128,8 @@ type Client interface {
|
||||
SetDNSConfig(*model.DNSConfig) error
|
||||
DhcpConfig() (*model.DhcpStatus, error)
|
||||
SetDhcpConfig(*model.DhcpStatus) error
|
||||
AddDHCPStaticLeases(leases ...model.DhcpStaticLease) error
|
||||
DeleteDHCPStaticLeases(leases ...model.DhcpStaticLease) error
|
||||
AddDHCPStaticLease(lease model.DhcpStaticLease) error
|
||||
DeleteDHCPStaticLease(lease model.DhcpStaticLease) error
|
||||
}
|
||||
|
||||
type client struct {
|
||||
@@ -160,6 +160,18 @@ func (cl *client) Status() (*model.ServerStatus, error) {
|
||||
return status, err
|
||||
}
|
||||
|
||||
func (cl *client) Stats() (*model.Stats, error) {
|
||||
stats := &model.Stats{}
|
||||
err := cl.doGet(cl.client.R().EnableTrace().SetResult(stats), "stats")
|
||||
return stats, err
|
||||
}
|
||||
|
||||
func (cl *client) QueryLog(limit int) (*model.QueryLog, error) {
|
||||
ql := &model.QueryLog{}
|
||||
err := cl.doGet(cl.client.R().EnableTrace().SetResult(ql), fmt.Sprintf(`querylog?limit=%d&response_status="all"`, limit))
|
||||
return ql, err
|
||||
}
|
||||
|
||||
func (cl *client) RewriteList() (*model.RewriteEntries, error) {
|
||||
rewrites := &model.RewriteEntries{}
|
||||
err := cl.doGet(cl.client.R().EnableTrace().SetResult(&rewrites), "/rewrite/list")
|
||||
@@ -167,9 +179,8 @@ func (cl *client) RewriteList() (*model.RewriteEntries, error) {
|
||||
}
|
||||
|
||||
func (cl *client) AddRewriteEntries(entries ...model.RewriteEntry) error {
|
||||
for i := range entries {
|
||||
e := entries[i]
|
||||
cl.log.With("domain", e.Domain, "answer", e.Answer).Info("Add rewrite entry")
|
||||
for _, e := range entries {
|
||||
cl.log.With("domain", e.Domain, "answer", e.Answer).Info("Add DNS rewrite entry")
|
||||
err := cl.doPost(cl.client.R().EnableTrace().SetBody(&e), "/rewrite/add")
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -179,9 +190,8 @@ func (cl *client) AddRewriteEntries(entries ...model.RewriteEntry) error {
|
||||
}
|
||||
|
||||
func (cl *client) DeleteRewriteEntries(entries ...model.RewriteEntry) error {
|
||||
for i := range entries {
|
||||
e := entries[i]
|
||||
cl.log.With("domain", e.Domain, "answer", e.Answer).Info("Delete rewrite entry")
|
||||
for _, e := range entries {
|
||||
cl.log.With("domain", e.Domain, "answer", e.Answer).Info("Delete DNS rewrite entry")
|
||||
err := cl.doPost(cl.client.R().EnableTrace().SetBody(&e), "/rewrite/delete")
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -229,43 +239,25 @@ func (cl *client) Filtering() (*model.FilterStatus, error) {
|
||||
return f, err
|
||||
}
|
||||
|
||||
func (cl *client) AddFilters(whitelist bool, filters ...model.Filter) error {
|
||||
for _, f := range filters {
|
||||
cl.log.With("url", f.Url, "whitelist", whitelist, "enabled", f.Enabled).Info("Add filter")
|
||||
ff := &model.AddUrlRequest{Name: utils.Ptr(f.Name), Url: utils.Ptr(f.Url), Whitelist: utils.Ptr(whitelist)}
|
||||
err := cl.doPost(cl.client.R().EnableTrace().SetBody(ff), "/filtering/add_url")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
func (cl *client) AddFilter(whitelist bool, f model.Filter) error {
|
||||
cl.log.With("url", f.Url, "whitelist", whitelist, "enabled", f.Enabled).Info("Add filter")
|
||||
ff := &model.AddUrlRequest{Name: utils.Ptr(f.Name), Url: utils.Ptr(f.Url), Whitelist: utils.Ptr(whitelist)}
|
||||
return cl.doPost(cl.client.R().EnableTrace().SetBody(ff), "/filtering/add_url")
|
||||
}
|
||||
|
||||
func (cl *client) DeleteFilters(whitelist bool, filters ...model.Filter) error {
|
||||
for _, f := range filters {
|
||||
cl.log.With("url", f.Url, "whitelist", whitelist, "enabled", f.Enabled).Info("Delete filter")
|
||||
ff := &model.RemoveUrlRequest{Url: utils.Ptr(f.Url), Whitelist: utils.Ptr(whitelist)}
|
||||
err := cl.doPost(cl.client.R().EnableTrace().SetBody(ff), "/filtering/remove_url")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
func (cl *client) DeleteFilter(whitelist bool, f model.Filter) error {
|
||||
cl.log.With("url", f.Url, "whitelist", whitelist, "enabled", f.Enabled).Info("Delete filter")
|
||||
ff := &model.RemoveUrlRequest{Url: utils.Ptr(f.Url), Whitelist: utils.Ptr(whitelist)}
|
||||
return cl.doPost(cl.client.R().EnableTrace().SetBody(ff), "/filtering/remove_url")
|
||||
}
|
||||
|
||||
func (cl *client) UpdateFilters(whitelist bool, filters ...model.Filter) error {
|
||||
for _, f := range filters {
|
||||
cl.log.With("url", f.Url, "whitelist", whitelist, "enabled", f.Enabled).Info("Update filter")
|
||||
fu := &model.FilterSetUrl{
|
||||
Whitelist: utils.Ptr(whitelist), Url: utils.Ptr(f.Url),
|
||||
Data: &model.FilterSetUrlData{Name: f.Name, Url: f.Url, Enabled: f.Enabled},
|
||||
}
|
||||
err := cl.doPost(cl.client.R().EnableTrace().SetBody(fu), "/filtering/set_url")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
func (cl *client) UpdateFilter(whitelist bool, f model.Filter) error {
|
||||
cl.log.With("url", f.Url, "whitelist", whitelist, "enabled", f.Enabled).Info("Update filter")
|
||||
fu := &model.FilterSetUrl{
|
||||
Whitelist: utils.Ptr(whitelist), Url: utils.Ptr(f.Url),
|
||||
Data: &model.FilterSetUrlData{Name: f.Name, Url: f.Url, Enabled: f.Enabled},
|
||||
}
|
||||
return nil
|
||||
return cl.doPost(cl.client.R().EnableTrace().SetBody(fu), "/filtering/set_url")
|
||||
}
|
||||
|
||||
func (cl *client) RefreshFilters(whitelist bool) error {
|
||||
@@ -279,7 +271,11 @@ func (cl *client) ToggleProtection(enable bool) error {
|
||||
}
|
||||
|
||||
func (cl *client) SetCustomRules(rules *[]string) error {
|
||||
cl.log.With("rules", len(*rules)).Info("Set user rules")
|
||||
var l int
|
||||
if rules != nil {
|
||||
l = len(*rules)
|
||||
}
|
||||
cl.log.With("rules", l).Info("Set user rules")
|
||||
return cl.doPost(cl.client.R().EnableTrace().SetBody(&model.SetRulesRequest{Rules: rules}), "/filtering/set_rules")
|
||||
}
|
||||
|
||||
@@ -319,39 +315,19 @@ func (cl *client) Clients() (*model.Clients, error) {
|
||||
return clients, err
|
||||
}
|
||||
|
||||
func (cl *client) AddClients(clients ...*model.Client) error {
|
||||
for i := range clients {
|
||||
client := clients[i]
|
||||
cl.log.With("name", *client.Name).Info("Add client")
|
||||
err := cl.doPost(cl.client.R().EnableTrace().SetBody(client), "/clients/add")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
func (cl *client) AddClient(client *model.Client) error {
|
||||
cl.log.With("name", *client.Name).Info("Add client settings")
|
||||
return cl.doPost(cl.client.R().EnableTrace().SetBody(client), "/clients/add")
|
||||
}
|
||||
|
||||
func (cl *client) UpdateClients(clients ...*model.Client) error {
|
||||
for _, client := range clients {
|
||||
cl.log.With("name", *client.Name).Info("Update client")
|
||||
err := cl.doPost(cl.client.R().EnableTrace().SetBody(&model.ClientUpdate{Name: client.Name, Data: client}), "/clients/update")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
func (cl *client) UpdateClient(client *model.Client) error {
|
||||
cl.log.With("name", *client.Name).Info("Update client settings")
|
||||
return cl.doPost(cl.client.R().EnableTrace().SetBody(&model.ClientUpdate{Name: client.Name, Data: client}), "/clients/update")
|
||||
}
|
||||
|
||||
func (cl *client) DeleteClients(clients ...*model.Client) error {
|
||||
for i := range clients {
|
||||
client := clients[i]
|
||||
cl.log.With("name", *client.Name).Info("Delete client")
|
||||
err := cl.doPost(cl.client.R().EnableTrace().SetBody(client), "/clients/delete")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
func (cl *client) DeleteClient(client *model.Client) error {
|
||||
cl.log.With("name", *client.Name).Info("Delete client settings")
|
||||
return cl.doPost(cl.client.R().EnableTrace().SetBody(client), "/clients/delete")
|
||||
}
|
||||
|
||||
func (cl *client) QueryLogConfig() (*model.QueryLogConfig, error) {
|
||||
@@ -435,24 +411,20 @@ func (cl *client) SetDhcpConfig(config *model.DhcpStatus) error {
|
||||
return cl.doPost(cl.client.R().EnableTrace().SetBody(config), "/dhcp/set_config")
|
||||
}
|
||||
|
||||
func (cl *client) AddDHCPStaticLeases(leases ...model.DhcpStaticLease) error {
|
||||
for _, l := range leases {
|
||||
cl.log.With("mac", l.Mac, "ip", l.Ip, "hostname", l.Hostname).Info("Add static dhcp lease")
|
||||
err := cl.doPost(cl.client.R().EnableTrace().SetBody(l), "/dhcp/add_static_lease")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
func (cl *client) AddDHCPStaticLease(l model.DhcpStaticLease) error {
|
||||
cl.log.With("mac", l.Mac, "ip", l.Ip, "hostname", l.Hostname).Info("Add static dhcp lease")
|
||||
err := cl.doPost(cl.client.R().EnableTrace().SetBody(l), "/dhcp/add_static_lease")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cl *client) DeleteDHCPStaticLeases(leases ...model.DhcpStaticLease) error {
|
||||
for _, l := range leases {
|
||||
cl.log.With("mac", l.Mac, "ip", l.Ip, "hostname", l.Hostname).Info("Delete static dhcp lease")
|
||||
err := cl.doPost(cl.client.R().EnableTrace().SetBody(l), "/dhcp/remove_static_lease")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
func (cl *client) DeleteDHCPStaticLease(l model.DhcpStaticLease) error {
|
||||
cl.log.With("mac", l.Mac, "ip", l.Ip, "hostname", l.Hostname).Info("Delete static dhcp lease")
|
||||
err := cl.doPost(cl.client.R().EnableTrace().SetBody(l), "/dhcp/remove_static_lease")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -35,7 +35,10 @@ var _ = Describe("Client", func() {
|
||||
|
||||
Context("Host", func() {
|
||||
It("should read the current host", func() {
|
||||
cl, _ := client.New(types.AdGuardInstance{URL: "https://foo.bar:3000"})
|
||||
inst := types.AdGuardInstance{URL: "https://foo.bar:3000"}
|
||||
err := inst.Init()
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
cl, _ := client.New(inst)
|
||||
host := cl.Host()
|
||||
Ω(host).Should(Equal("foo.bar:3000"))
|
||||
})
|
||||
@@ -69,7 +72,9 @@ var _ = Describe("Client", func() {
|
||||
`{"name":"","url":"foo","whitelist":true}`,
|
||||
`{"name":"","url":"bar","whitelist":true}`,
|
||||
)
|
||||
err := cl.AddFilters(true, model.Filter{Url: "foo"}, model.Filter{Url: "bar"})
|
||||
err := cl.AddFilter(true, model.Filter{Url: "foo"})
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
err = cl.AddFilter(true, model.Filter{Url: "bar"})
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
It("should update Filters", func() {
|
||||
@@ -77,7 +82,9 @@ var _ = Describe("Client", func() {
|
||||
`{"data":{"enabled":false,"name":"","url":"foo"},"url":"foo","whitelist":true}`,
|
||||
`{"data":{"enabled":false,"name":"","url":"bar"},"url":"bar","whitelist":true}`,
|
||||
)
|
||||
err := cl.UpdateFilters(true, model.Filter{Url: "foo"}, model.Filter{Url: "bar"})
|
||||
err := cl.UpdateFilter(true, model.Filter{Url: "foo"})
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
err = cl.UpdateFilter(true, model.Filter{Url: "bar"})
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
It("should delete Filters", func() {
|
||||
@@ -85,7 +92,23 @@ var _ = Describe("Client", func() {
|
||||
`{"url":"foo","whitelist":true}`,
|
||||
`{"url":"bar","whitelist":true}`,
|
||||
)
|
||||
err := cl.DeleteFilters(true, model.Filter{Url: "foo"}, model.Filter{Url: "bar"})
|
||||
err := cl.DeleteFilter(true, model.Filter{Url: "foo"})
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
err = cl.DeleteFilter(true, model.Filter{Url: "bar"})
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
It("should set empty filter rules", func() {
|
||||
ts, cl = ClientPost("/filtering/set_rules",
|
||||
`{"rules":[]}`,
|
||||
)
|
||||
err := cl.SetCustomRules(utils.Ptr([]string{}))
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
It("should set nil filter rules", func() {
|
||||
ts, cl = ClientPost("/filtering/set_rules",
|
||||
`{}`,
|
||||
)
|
||||
err := cl.SetCustomRules(nil)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
})
|
||||
@@ -264,21 +287,21 @@ var _ = Describe("Client", func() {
|
||||
ts, cl = ClientPost("/clients/add",
|
||||
`{"ids":["id"],"name":"foo"}`,
|
||||
)
|
||||
err := cl.AddClients(&model.Client{Name: utils.Ptr("foo"), Ids: utils.Ptr([]string{"id"})})
|
||||
err := cl.AddClient(&model.Client{Name: utils.Ptr("foo"), Ids: utils.Ptr([]string{"id"})})
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
It("should update Clients", func() {
|
||||
ts, cl = ClientPost("/clients/update",
|
||||
`{"data":{"ids":["id"],"name":"foo"},"name":"foo"}`,
|
||||
)
|
||||
err := cl.UpdateClients(&model.Client{Name: utils.Ptr("foo"), Ids: utils.Ptr([]string{"id"})})
|
||||
err := cl.UpdateClient(&model.Client{Name: utils.Ptr("foo"), Ids: utils.Ptr([]string{"id"})})
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
It("should delete Clients", func() {
|
||||
ts, cl = ClientPost("/clients/delete",
|
||||
`{"ids":["id"],"name":"foo"}`,
|
||||
)
|
||||
err := cl.DeleteClients(&model.Client{Name: utils.Ptr("foo"), Ids: utils.Ptr([]string{"id"})})
|
||||
err := cl.DeleteClient(&model.Client{Name: utils.Ptr("foo"), Ids: utils.Ptr([]string{"id"})})
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
@@ -33,12 +33,11 @@ func New(config types.AdGuardInstance) (Client, error) {
|
||||
}
|
||||
u.Path = path.Clean(u.Path)
|
||||
|
||||
httpClient := &http.Client{}
|
||||
if config.InsecureSkipVerify {
|
||||
// #nosec G402 has to be explicitly enabled
|
||||
httpClient.Transport = &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
httpClient := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
// #nosec G402 has to be explicitly enabled
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: config.InsecureSkipVerify},
|
||||
},
|
||||
}
|
||||
|
||||
aghClient, err := model.NewClient(u.String(), func(client *model.AdguardHomeClient) error {
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/bakito/adguardhome-sync/pkg/utils"
|
||||
"github.com/jinzhu/copier"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// Clone the config
|
||||
@@ -16,6 +17,22 @@ func (c *DhcpStatus) Clone() *DhcpStatus {
|
||||
return clone
|
||||
}
|
||||
|
||||
func (c *DhcpStatus) cleanV4V6() {
|
||||
if c.V4 != nil && !c.V4.isValid() {
|
||||
c.V4 = nil
|
||||
}
|
||||
if c.V6 != nil && !c.V6.isValid() {
|
||||
c.V6 = nil
|
||||
}
|
||||
}
|
||||
|
||||
// CleanAndEquals dhcp server config equal check where V4 and V6 are cleaned in advance
|
||||
func (c *DhcpStatus) CleanAndEquals(o *DhcpStatus) bool {
|
||||
c.cleanV4V6()
|
||||
o.cleanV4V6()
|
||||
return c.Equals(o)
|
||||
}
|
||||
|
||||
// Equals dhcp server config equal check
|
||||
func (c *DhcpStatus) Equals(o *DhcpStatus) bool {
|
||||
return utils.JsonEquals(c, o)
|
||||
@@ -26,11 +43,14 @@ func (c *DhcpStatus) HasConfig() bool {
|
||||
}
|
||||
|
||||
func (j DhcpConfigV4) isValid() bool {
|
||||
return j.GatewayIp != nil && j.SubnetMask != nil && j.RangeStart != nil && j.RangeEnd != nil
|
||||
return j.GatewayIp != nil && *j.GatewayIp != "" &&
|
||||
j.SubnetMask != nil && *j.SubnetMask != "" &&
|
||||
j.RangeStart != nil && *j.RangeStart != "" &&
|
||||
j.RangeEnd != nil && *j.RangeEnd != ""
|
||||
}
|
||||
|
||||
func (j DhcpConfigV6) isValid() bool {
|
||||
return j.RangeStart != nil
|
||||
return j.RangeStart != nil && *j.RangeStart != ""
|
||||
}
|
||||
|
||||
type DhcpStaticLeases []DhcpStaticLease
|
||||
@@ -170,8 +190,7 @@ func (clients *Clients) Merge(other *Clients) ([]*Client, []*Client, []*Client)
|
||||
current := make(map[string]*Client)
|
||||
if clients.Clients != nil {
|
||||
cc := *clients.Clients
|
||||
for i := range cc {
|
||||
client := cc[i]
|
||||
for _, client := range cc {
|
||||
current[*client.Name] = &client
|
||||
}
|
||||
}
|
||||
@@ -179,8 +198,7 @@ func (clients *Clients) Merge(other *Clients) ([]*Client, []*Client, []*Client)
|
||||
expected := make(map[string]*Client)
|
||||
if other.Clients != nil {
|
||||
oc := *other.Clients
|
||||
for i := range oc {
|
||||
client := oc[i]
|
||||
for _, client := range oc {
|
||||
expected[*client.Name] = &client
|
||||
}
|
||||
}
|
||||
@@ -273,15 +291,13 @@ func MergeFilters(this *[]Filter, other *[]Filter) ([]Filter, []Filter, []Filter
|
||||
var updates []Filter
|
||||
var removes []Filter
|
||||
if this != nil {
|
||||
for i := range *this {
|
||||
fi := (*this)[i]
|
||||
for _, fi := range *this {
|
||||
current[fi.Url] = &fi
|
||||
}
|
||||
}
|
||||
|
||||
if other != nil {
|
||||
for i := range *other {
|
||||
rr := (*other)[i]
|
||||
for _, rr := range *other {
|
||||
if c, ok := current[rr.Url]; ok {
|
||||
if !c.Equals(&rr) {
|
||||
updates = append(updates, rr)
|
||||
@@ -386,3 +402,12 @@ func ArrayString(a *[]string) string {
|
||||
sort.Strings(sorted)
|
||||
return fmt.Sprintf("[%s]", strings.Join(sorted, ","))
|
||||
}
|
||||
|
||||
func (c *DNSConfig) Sanitize(l *zap.SugaredLogger) {
|
||||
// disable UsePrivatePtrResolvers if not configured
|
||||
if c.UsePrivatePtrResolvers != nil && *c.UsePrivatePtrResolvers &&
|
||||
(c.LocalPtrUpstreams == nil || len(*c.LocalPtrUpstreams) == 0) {
|
||||
l.Warn("disabling replica 'Use private reverse DNS resolvers' as no 'Private reverse DNS servers' are configured on origin")
|
||||
c.UsePrivatePtrResolvers = utils.Ptr(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Package model provides primitives to interact with the openapi HTTP API.
|
||||
//
|
||||
// Code generated by github.com/deepmap/oapi-codegen/v2 version v2.0.0 DO NOT EDIT.
|
||||
// Code generated by github.com/deepmap/oapi-codegen/v2 version v2.1.0 DO NOT EDIT.
|
||||
package model
|
||||
|
||||
import (
|
||||
@@ -37,6 +37,14 @@ const (
|
||||
Refused DNSConfigBlockingMode = "refused"
|
||||
)
|
||||
|
||||
// Defines values for DNSConfigUpstreamMode.
|
||||
const (
|
||||
MapConstDeprecatedTrueDescriptionUseloadBalanceInstead DNSConfigUpstreamMode = "map[const: deprecated:true description:Use `load_balance` instead.]"
|
||||
MapConstFastestAddr DNSConfigUpstreamMode = "map[const:fastest_addr]"
|
||||
MapConstLoadBalance DNSConfigUpstreamMode = "map[const:load_balance]"
|
||||
MapConstParallel DNSConfigUpstreamMode = "map[const:parallel]"
|
||||
)
|
||||
|
||||
// Defines values for DhcpSearchResultOtherServerFound.
|
||||
const (
|
||||
DhcpSearchResultOtherServerFoundError DhcpSearchResultOtherServerFound = "error"
|
||||
@@ -287,11 +295,28 @@ type Client struct {
|
||||
SafeSearch *SafeSearchConfig `json:"safe_search,omitempty"`
|
||||
SafebrowsingEnabled *bool `json:"safebrowsing_enabled,omitempty"`
|
||||
// Deprecated:
|
||||
SafesearchEnabled *bool `json:"safesearch_enabled,omitempty"`
|
||||
Tags *[]string `json:"tags,omitempty"`
|
||||
Upstreams *[]string `json:"upstreams,omitempty"`
|
||||
UseGlobalBlockedServices *bool `json:"use_global_blocked_services,omitempty"`
|
||||
UseGlobalSettings *bool `json:"use_global_settings,omitempty"`
|
||||
SafesearchEnabled *bool `json:"safesearch_enabled,omitempty"`
|
||||
Tags *[]string `json:"tags,omitempty"`
|
||||
Upstreams *[]string `json:"upstreams,omitempty"`
|
||||
|
||||
// UpstreamsCacheEnabled NOTE: If `upstreams_cache_enabled` is not set in HTTP API
|
||||
// `GET /clients/add` request then default value (false) will be used.
|
||||
//
|
||||
// If `upstreams_cache_enabled` is not set in HTTP API
|
||||
// `GET /clients/update` request then the existing value will not be
|
||||
// changed.
|
||||
//
|
||||
// This behaviour can be changed in the future versions.
|
||||
UpstreamsCacheEnabled *bool `json:"upstreams_cache_enabled,omitempty"`
|
||||
|
||||
// UpstreamsCacheSize NOTE: If `upstreams_cache_enabled` is not set in HTTP API
|
||||
// `GET /clients/update` request then the existing value will not be
|
||||
// changed.
|
||||
//
|
||||
// This behaviour can be changed in the future versions.
|
||||
UpstreamsCacheSize *int `json:"upstreams_cache_size,omitempty"`
|
||||
UseGlobalBlockedServices *bool `json:"use_global_blocked_services,omitempty"`
|
||||
UseGlobalSettings *bool `json:"use_global_settings,omitempty"`
|
||||
}
|
||||
|
||||
// ClientAuto Auto-Client information
|
||||
@@ -402,18 +427,32 @@ type DNSConfig struct {
|
||||
ProtectionDisabledUntil *string `json:"protection_disabled_until,omitempty"`
|
||||
ProtectionEnabled *bool `json:"protection_enabled,omitempty"`
|
||||
Ratelimit *int `json:"ratelimit,omitempty"`
|
||||
ResolveClients *bool `json:"resolve_clients,omitempty"`
|
||||
|
||||
// RatelimitSubnetSubnetLenIpv4 Length of the subnet mask for IPv4 addresses.
|
||||
RatelimitSubnetSubnetLenIpv4 *int `json:"ratelimit_subnet_subnet_len_ipv4,omitempty"`
|
||||
|
||||
// RatelimitSubnetSubnetLenIpv6 Length of the subnet mask for IPv6 addresses.
|
||||
RatelimitSubnetSubnetLenIpv6 *int `json:"ratelimit_subnet_subnet_len_ipv6,omitempty"`
|
||||
|
||||
// RatelimitWhitelist List of IP addresses excluded from rate limiting.
|
||||
RatelimitWhitelist *[]string `json:"ratelimit_whitelist,omitempty"`
|
||||
ResolveClients *bool `json:"resolve_clients,omitempty"`
|
||||
|
||||
// UpstreamDns Upstream servers, port is optional after colon. Empty value will reset it to default values.
|
||||
UpstreamDns *[]string `json:"upstream_dns,omitempty"`
|
||||
UpstreamDnsFile *string `json:"upstream_dns_file,omitempty"`
|
||||
UpstreamMode *interface{} `json:"upstream_mode,omitempty"`
|
||||
UsePrivatePtrResolvers *bool `json:"use_private_ptr_resolvers,omitempty"`
|
||||
UpstreamDns *[]string `json:"upstream_dns,omitempty"`
|
||||
UpstreamDnsFile *string `json:"upstream_dns_file,omitempty"`
|
||||
|
||||
// UpstreamMode Upstream modes enumeration.
|
||||
UpstreamMode *DNSConfigUpstreamMode `json:"upstream_mode,omitempty"`
|
||||
UsePrivatePtrResolvers *bool `json:"use_private_ptr_resolvers,omitempty"`
|
||||
}
|
||||
|
||||
// DNSConfigBlockingMode defines model for DNSConfig.BlockingMode.
|
||||
type DNSConfigBlockingMode string
|
||||
|
||||
// DNSConfigUpstreamMode Upstream modes enumeration.
|
||||
type DNSConfigUpstreamMode string
|
||||
|
||||
// DayRange The single interval within a day. It begins at the `start` and ends before the `end`.
|
||||
type DayRange struct {
|
||||
// End The number of milliseconds elapsed from the start of a day. It is expected to be rounded to minutes. The maximum value is `86400000` (24 hours).
|
||||
@@ -1020,6 +1059,9 @@ type TlsConfig struct {
|
||||
// PrivateKeySaved Set to true if the user has previously saved a private key as a string. This is used so that the server and the client don't have to send the private key between each other every time, which might lead to security issues.
|
||||
PrivateKeySaved *bool `json:"private_key_saved,omitempty"`
|
||||
|
||||
// ServePlainDns Set to true if plain DNS is allowed for incoming requests.
|
||||
ServePlainDns *bool `json:"serve_plain_dns,omitempty"`
|
||||
|
||||
// ServerName server_name is the hostname of your HTTPS/TLS server
|
||||
ServerName *string `json:"server_name,omitempty"`
|
||||
|
||||
|
||||
103
pkg/client/model/model_private_test.go
Normal file
103
pkg/client/model/model_private_test.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/bakito/adguardhome-sync/pkg/log"
|
||||
"github.com/bakito/adguardhome-sync/pkg/utils"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
"github.com/onsi/gomega"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var _ = Describe("Types", func() {
|
||||
Context("DhcpConfigV4", func() {
|
||||
DescribeTable("DhcpConfigV4 should not be valid",
|
||||
func(v4 DhcpConfigV4) {
|
||||
gomega.Ω(v4.isValid()).Should(gomega.BeFalse())
|
||||
},
|
||||
Entry(`When GatewayIp is nil`, DhcpConfigV4{
|
||||
GatewayIp: nil,
|
||||
SubnetMask: utils.Ptr("2.2.2.2"),
|
||||
RangeStart: utils.Ptr("3.3.3.3"),
|
||||
RangeEnd: utils.Ptr("4.4.4.4"),
|
||||
}),
|
||||
Entry(`When GatewayIp is ""`, DhcpConfigV4{
|
||||
GatewayIp: utils.Ptr(""),
|
||||
SubnetMask: utils.Ptr("2.2.2.2"),
|
||||
RangeStart: utils.Ptr("3.3.3.3"),
|
||||
RangeEnd: utils.Ptr("4.4.4.4"),
|
||||
}),
|
||||
Entry(`When SubnetMask is nil`, DhcpConfigV4{
|
||||
GatewayIp: utils.Ptr("1.1.1.1"),
|
||||
SubnetMask: nil,
|
||||
RangeStart: utils.Ptr("3.3.3.3"),
|
||||
RangeEnd: utils.Ptr("4.4.4.4"),
|
||||
}),
|
||||
Entry(`When SubnetMask is ""`, DhcpConfigV4{
|
||||
GatewayIp: utils.Ptr("1.1.1.1"),
|
||||
SubnetMask: utils.Ptr(""),
|
||||
RangeStart: utils.Ptr("3.3.3.3"),
|
||||
RangeEnd: utils.Ptr("4.4.4.4"),
|
||||
}),
|
||||
Entry(`When SubnetMask is nil`, DhcpConfigV4{
|
||||
GatewayIp: utils.Ptr("1.1.1.1"),
|
||||
SubnetMask: utils.Ptr("2.2.2.2"),
|
||||
RangeStart: nil,
|
||||
RangeEnd: utils.Ptr("4.4.4.4"),
|
||||
}),
|
||||
Entry(`When SubnetMask is ""`, DhcpConfigV4{
|
||||
GatewayIp: utils.Ptr("1.1.1.1"),
|
||||
SubnetMask: utils.Ptr("2.2.2.2"),
|
||||
RangeStart: utils.Ptr(""),
|
||||
RangeEnd: utils.Ptr("4.4.4.4"),
|
||||
}),
|
||||
Entry(`When RangeEnd is nil`, DhcpConfigV4{
|
||||
GatewayIp: utils.Ptr("1.1.1.1"),
|
||||
SubnetMask: utils.Ptr("2.2.2.2"),
|
||||
RangeStart: utils.Ptr("3.3.3.3"),
|
||||
RangeEnd: nil,
|
||||
}),
|
||||
Entry(`When RangeEnd is ""`, DhcpConfigV4{
|
||||
GatewayIp: utils.Ptr("1.1.1.1"),
|
||||
SubnetMask: utils.Ptr("2.2.2.2"),
|
||||
RangeStart: utils.Ptr("3.3.3.3"),
|
||||
RangeEnd: utils.Ptr(""),
|
||||
}),
|
||||
)
|
||||
})
|
||||
Context("DhcpConfigV6", func() {
|
||||
DescribeTable("DhcpConfigV6 should not be valid",
|
||||
func(v6 DhcpConfigV6) {
|
||||
gomega.Ω(v6.isValid()).Should(gomega.BeFalse())
|
||||
},
|
||||
Entry(`When SubnetMask is nil`, DhcpConfigV6{RangeStart: nil}),
|
||||
Entry(`When SubnetMask is ""`, DhcpConfigV6{RangeStart: utils.Ptr("")}),
|
||||
)
|
||||
})
|
||||
Context("DNSConfig", func() {
|
||||
var (
|
||||
cfg *DNSConfig
|
||||
l *zap.SugaredLogger
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
cfg = &DNSConfig{
|
||||
UsePrivatePtrResolvers: utils.Ptr(true),
|
||||
}
|
||||
l = log.GetLogger("test")
|
||||
})
|
||||
Context("Sanitize", func() {
|
||||
It("should disable UsePrivatePtrResolvers resolvers is nil ", func() {
|
||||
cfg.LocalPtrUpstreams = nil
|
||||
cfg.Sanitize(l)
|
||||
gomega.Ω(cfg.UsePrivatePtrResolvers).ShouldNot(gomega.BeNil())
|
||||
gomega.Ω(*cfg.UsePrivatePtrResolvers).Should(gomega.Equal(false))
|
||||
})
|
||||
It("should disable UsePrivatePtrResolvers resolvers is empty ", func() {
|
||||
cfg.LocalPtrUpstreams = utils.Ptr([]string{})
|
||||
cfg.Sanitize(l)
|
||||
gomega.Ω(cfg.UsePrivatePtrResolvers).ShouldNot(gomega.BeNil())
|
||||
gomega.Ω(*cfg.UsePrivatePtrResolvers).Should(gomega.Equal(false))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -403,7 +403,29 @@ var _ = Describe("Types", func() {
|
||||
}
|
||||
Ω(dc1.HasConfig()).Should(BeFalse())
|
||||
})
|
||||
It("should not have a v4 config", func() {
|
||||
It("should not have a v4 config with nil IP", func() {
|
||||
dc1 := &model.DhcpStatus{
|
||||
V4: &model.DhcpConfigV4{
|
||||
GatewayIp: nil,
|
||||
},
|
||||
V6: &model.DhcpConfigV6{
|
||||
RangeStart: utils.Ptr("1.2.3.5"),
|
||||
},
|
||||
}
|
||||
Ω(dc1.HasConfig()).Should(BeTrue())
|
||||
})
|
||||
It("should not have a v4 config with empty IP", func() {
|
||||
dc1 := &model.DhcpStatus{
|
||||
V4: &model.DhcpConfigV4{
|
||||
GatewayIp: utils.Ptr(""),
|
||||
},
|
||||
V6: &model.DhcpConfigV6{
|
||||
RangeStart: utils.Ptr("1.2.3.5"),
|
||||
},
|
||||
}
|
||||
Ω(dc1.HasConfig()).Should(BeTrue())
|
||||
})
|
||||
It("should not have a v6 config", func() {
|
||||
dc1 := &model.DhcpStatus{
|
||||
V4: &model.DhcpConfigV4{
|
||||
GatewayIp: utils.Ptr("1.2.3.4"),
|
||||
@@ -416,15 +438,6 @@ var _ = Describe("Types", func() {
|
||||
}
|
||||
Ω(dc1.HasConfig()).Should(BeTrue())
|
||||
})
|
||||
It("should not have a v6 config", func() {
|
||||
dc1 := &model.DhcpStatus{
|
||||
V4: &model.DhcpConfigV4{},
|
||||
V6: &model.DhcpConfigV6{
|
||||
RangeStart: utils.Ptr("1.2.3.5"),
|
||||
},
|
||||
}
|
||||
Ω(dc1.HasConfig()).Should(BeTrue())
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
95
pkg/config/config.go
Normal file
95
pkg/config/config.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"regexp"
|
||||
|
||||
"github.com/bakito/adguardhome-sync/pkg/log"
|
||||
"github.com/bakito/adguardhome-sync/pkg/types"
|
||||
"github.com/caarlos0/env/v10"
|
||||
)
|
||||
|
||||
var (
|
||||
envReplicasURLPattern = regexp.MustCompile(`^REPLICA(\d+)_URL=(.*)`)
|
||||
logger = log.GetLogger("config")
|
||||
)
|
||||
|
||||
func Get(configFile string, flags Flags) (*types.Config, error) {
|
||||
path, err := configFilePath(configFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cfg := initialConfig()
|
||||
|
||||
// read yaml config
|
||||
if err := readFile(cfg, path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// overwrite from command flags
|
||||
if err := readFlags(cfg, flags); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// *bool field creates issues when already not nil
|
||||
cfg.Origin.DHCPServerEnabled = nil // origin filed makes no sense to be set.
|
||||
|
||||
// keep previously set value
|
||||
replicaDhcpServer := cfg.Replica.DHCPServerEnabled
|
||||
cfg.Replica.DHCPServerEnabled = nil
|
||||
|
||||
// overwrite from env vars
|
||||
if err := env.Parse(cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := env.ParseWithOptions(cfg.Replica, env.Options{Prefix: "REPLICA_"}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// if not set from env, use previous value
|
||||
if cfg.Replica.DHCPServerEnabled == nil {
|
||||
cfg.Replica.DHCPServerEnabled = replicaDhcpServer
|
||||
}
|
||||
|
||||
if err := env.ParseWithOptions(&cfg.Origin, env.Options{Prefix: "ORIGIN_"}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if cfg.Replica != nil &&
|
||||
cfg.Replica.URL == "" &&
|
||||
cfg.Replica.Username == "" {
|
||||
cfg.Replica = nil
|
||||
}
|
||||
|
||||
if len(cfg.Replicas) > 0 && cfg.Replica != nil {
|
||||
return nil, errors.New("mixed replica config in use. " +
|
||||
"Do not use single replica and numbered (list) replica config combined")
|
||||
}
|
||||
|
||||
handleDeprecatedEnvVars(cfg)
|
||||
|
||||
if cfg.Replica != nil {
|
||||
cfg.Replicas = []types.AdGuardInstance{*cfg.Replica}
|
||||
cfg.Replica = nil
|
||||
}
|
||||
|
||||
cfg.Replicas, err = enrichReplicasFromEnv(cfg.Replicas)
|
||||
|
||||
return cfg, err
|
||||
}
|
||||
|
||||
func initialConfig() *types.Config {
|
||||
return &types.Config{
|
||||
RunOnStart: true,
|
||||
Origin: types.AdGuardInstance{
|
||||
APIPath: "/control",
|
||||
},
|
||||
Replica: &types.AdGuardInstance{
|
||||
APIPath: "/control",
|
||||
},
|
||||
API: types.API{
|
||||
Port: 8080,
|
||||
},
|
||||
Features: types.NewFeatures(true),
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package cmd_test
|
||||
package config_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -9,5 +9,5 @@ import (
|
||||
|
||||
func TestCmd(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Cmd Suite")
|
||||
RunSpecs(t, "Config Suite")
|
||||
}
|
||||
238
pkg/config/config_test.go
Normal file
238
pkg/config/config_test.go
Normal file
@@ -0,0 +1,238 @@
|
||||
package config_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/bakito/adguardhome-sync/pkg/config"
|
||||
flagsmock "github.com/bakito/adguardhome-sync/pkg/mocks/flags"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
gm "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
var _ = Describe("Config", func() {
|
||||
Context("Get", func() {
|
||||
var (
|
||||
flags *flagsmock.MockFlags
|
||||
mockCtrl *gm.Controller
|
||||
changedEnvVars []string
|
||||
setEnv = func(name string, value string) {
|
||||
_ = os.Setenv(name, value)
|
||||
changedEnvVars = append(changedEnvVars, name)
|
||||
}
|
||||
)
|
||||
BeforeEach(func() {
|
||||
mockCtrl = gm.NewController(GinkgoT())
|
||||
flags = flagsmock.NewMockFlags(mockCtrl)
|
||||
changedEnvVars = nil
|
||||
})
|
||||
AfterEach(func() {
|
||||
for _, envVar := range changedEnvVars {
|
||||
_ = os.Unsetenv(envVar)
|
||||
println(envVar)
|
||||
}
|
||||
defer mockCtrl.Finish()
|
||||
})
|
||||
Context("Get", func() {
|
||||
Context("Mixed Config", func() {
|
||||
It("should have the origin URL from the config file", func() {
|
||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||
|
||||
_, err := config.Get("../../testdata/config_test_replicas_and_replica.yaml", flags)
|
||||
Ω(err).Should(HaveOccurred())
|
||||
Ω(err.Error()).Should(ContainSubstring("mixed replica config in use"))
|
||||
})
|
||||
})
|
||||
Context("Origin Url", func() {
|
||||
It("should have the origin URL from the config file", func() {
|
||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||
|
||||
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
Ω(cfg.Origin.URL).Should(Equal("https://origin-file:443"))
|
||||
})
|
||||
It("should have the origin URL from the config flags", func() {
|
||||
flags.EXPECT().Changed(config.FlagOriginURL).Return(true).AnyTimes()
|
||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||
flags.EXPECT().GetString(config.FlagOriginURL).Return("https://origin-flag:443", nil).AnyTimes()
|
||||
|
||||
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
Ω(cfg.Origin.URL).Should(Equal("https://origin-flag:443"))
|
||||
})
|
||||
It("should have the origin URL from the config env var", func() {
|
||||
setEnv("ORIGIN_URL", "https://origin-env:443")
|
||||
flags.EXPECT().Changed(config.FlagOriginURL).Return(true).AnyTimes()
|
||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||
flags.EXPECT().GetString(config.FlagOriginURL).Return("https://origin-flag:443", nil).AnyTimes()
|
||||
|
||||
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
Ω(cfg.Origin.URL).Should(Equal("https://origin-env:443"))
|
||||
})
|
||||
})
|
||||
Context("Replica insecure skip verify", func() {
|
||||
It("should have the insecure skip verify from the config file", func() {
|
||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||
|
||||
cfg, err := config.Get("../../testdata/config_test_replica.yaml", flags)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
Ω(cfg.Replicas[0].InsecureSkipVerify).Should(BeFalse())
|
||||
})
|
||||
It("should have the insecure skip verify from the config flags", func() {
|
||||
flags.EXPECT().Changed(config.FlagReplicaISV).Return(true).AnyTimes()
|
||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||
flags.EXPECT().GetBool(config.FlagReplicaISV).Return(true, nil).AnyTimes()
|
||||
|
||||
cfg, err := config.Get("../../testdata/config_test_replica.yaml", flags)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
Ω(cfg.Replicas[0].InsecureSkipVerify).Should(BeTrue())
|
||||
})
|
||||
It("should have the insecure skip verify from the config env var", func() {
|
||||
setEnv("REPLICA_INSECURE_SKIP_VERIFY", "false")
|
||||
flags.EXPECT().Changed(config.FlagReplicaISV).Return(true).AnyTimes()
|
||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||
flags.EXPECT().GetBool(config.FlagReplicaISV).Return(true, nil).AnyTimes()
|
||||
|
||||
cfg, err := config.Get("../../testdata/config_test_replica.yaml", flags)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
Ω(cfg.Replicas[0].InsecureSkipVerify).Should(BeFalse())
|
||||
})
|
||||
})
|
||||
|
||||
Context("Replica 1 insecure skip verify", func() {
|
||||
It("should have the insecure skip verify from the config file", func() {
|
||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||
|
||||
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
Ω(cfg.Replicas[0].InsecureSkipVerify).Should(BeFalse())
|
||||
})
|
||||
It("should have the insecure skip verify from the config env var", func() {
|
||||
setEnv("REPLICA1_INSECURE_SKIP_VERIFY", "true")
|
||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||
|
||||
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
Ω(cfg.Replicas[0].InsecureSkipVerify).Should(BeTrue())
|
||||
})
|
||||
})
|
||||
Context("API Port", func() {
|
||||
It("should have the api port from the config file", func() {
|
||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
Ω(cfg.API.Port).Should(Equal(9090))
|
||||
})
|
||||
It("should have the api port from the config flags", func() {
|
||||
flags.EXPECT().Changed(config.FlagApiPort).Return(true).AnyTimes()
|
||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||
flags.EXPECT().GetInt(config.FlagApiPort).Return(9990, nil).AnyTimes()
|
||||
|
||||
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
Ω(cfg.API.Port).Should(Equal(9990))
|
||||
})
|
||||
It("should have the api port from the config env var", func() {
|
||||
setEnv("API_PORT", "9999")
|
||||
flags.EXPECT().Changed(config.FlagApiPort).Return(true).AnyTimes()
|
||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||
flags.EXPECT().GetInt(config.FlagApiPort).Return(9990, nil).AnyTimes()
|
||||
|
||||
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
Ω(cfg.API.Port).Should(Equal(9999))
|
||||
})
|
||||
})
|
||||
|
||||
Context("Replica DHCPServerEnabled", func() {
|
||||
It("should have the dhcp server enabled from the config file", func() {
|
||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||
|
||||
cfg, err := config.Get("../../testdata/config_test_replica.yaml", flags)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
Ω(cfg.Replicas[0].DHCPServerEnabled).ShouldNot(BeNil())
|
||||
Ω(*cfg.Replicas[0].DHCPServerEnabled).Should(BeFalse())
|
||||
})
|
||||
})
|
||||
|
||||
Context("Replica 1 DHCPServerEnabled", func() {
|
||||
It("should have the dhcp server enabled from the config file", func() {
|
||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||
|
||||
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
Ω(cfg.Replicas[0].DHCPServerEnabled).ShouldNot(BeNil())
|
||||
Ω(*cfg.Replicas[0].DHCPServerEnabled).Should(BeFalse())
|
||||
})
|
||||
})
|
||||
Context("API Port", func() {
|
||||
It("should have the api port from the config file", func() {
|
||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
Ω(cfg.API.Port).Should(Equal(9090))
|
||||
})
|
||||
It("should have the api port from the config flags", func() {
|
||||
flags.EXPECT().Changed(config.FlagApiPort).Return(true).AnyTimes()
|
||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||
flags.EXPECT().GetInt(config.FlagApiPort).Return(9990, nil).AnyTimes()
|
||||
|
||||
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
Ω(cfg.API.Port).Should(Equal(9990))
|
||||
})
|
||||
It("should have the api port from the config env var", func() {
|
||||
setEnv("API_PORT", "9999")
|
||||
flags.EXPECT().Changed(config.FlagApiPort).Return(true).AnyTimes()
|
||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||
flags.EXPECT().GetInt(config.FlagApiPort).Return(9990, nil).AnyTimes()
|
||||
|
||||
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
Ω(cfg.API.Port).Should(Equal(9999))
|
||||
})
|
||||
})
|
||||
|
||||
//////
|
||||
|
||||
Context("Feature DNS Server Config", func() {
|
||||
It("should have the feature dns server config from the config file", func() {
|
||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
Ω(cfg.Features.DNS.ServerConfig).Should(BeFalse())
|
||||
})
|
||||
It("should have the feature dns server config from the config flags", func() {
|
||||
flags.EXPECT().Changed(config.FlagFeatureDnsServerConfig).Return(true).AnyTimes()
|
||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||
flags.EXPECT().GetBool(config.FlagFeatureDnsServerConfig).Return(true, nil).AnyTimes()
|
||||
|
||||
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
Ω(cfg.Features.DNS.ServerConfig).Should(BeTrue())
|
||||
})
|
||||
It("should have the feature dns server config from the config env var", func() {
|
||||
setEnv("FEATURES_DNS_SERVER_CONFIG", "false")
|
||||
flags.EXPECT().Changed(config.FlagFeatureDnsServerConfig).Return(true).AnyTimes()
|
||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||
flags.EXPECT().GetBool(config.FlagFeatureDnsServerConfig).Return(true, nil).AnyTimes()
|
||||
|
||||
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
Ω(cfg.Features.DNS.ServerConfig).Should(BeFalse())
|
||||
})
|
||||
It("should have the feature dns server config from the config DEPRECATED env var", func() {
|
||||
setEnv("FEATURES_DNS_SERVERCONFIG", "false")
|
||||
flags.EXPECT().Changed(config.FlagFeatureDnsServerConfig).Return(true).AnyTimes()
|
||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||
flags.EXPECT().GetBool(config.FlagFeatureDnsServerConfig).Return(true, nil).AnyTimes()
|
||||
|
||||
cfg, err := config.Get("../../testdata/config_test_replicas.yaml", flags)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
Ω(cfg.Features.DNS.ServerConfig).Should(BeFalse())
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
138
pkg/config/deprecated_env_test.go
Normal file
138
pkg/config/deprecated_env_test.go
Normal file
@@ -0,0 +1,138 @@
|
||||
package config_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/bakito/adguardhome-sync/pkg/config"
|
||||
"github.com/bakito/adguardhome-sync/pkg/types"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var envVars = []string{
|
||||
"FEATURES_GENERAL_SETTINGS",
|
||||
"FEATURES_QUERY_LOG_CONFIG",
|
||||
"FEATURES_STATS_CONFIG",
|
||||
"FEATURES_CLIENT_SETTINGS",
|
||||
"FEATURES_SERVICES",
|
||||
"FEATURES_FILTERS",
|
||||
"FEATURES_DHCP_SERVER_CONFIG",
|
||||
"FEATURES_DHCP_STATIC_LEASES",
|
||||
"FEATURES_DNS_SERVER_CONFIG",
|
||||
"FEATURES_DNS_ACCESS_LISTS",
|
||||
"FEATURES_DNS_REWRITES",
|
||||
"REPLICA1_INTERFACE_NAME",
|
||||
"REPLICA1_DHCP_SERVER_ENABLED",
|
||||
}
|
||||
|
||||
var deprecatedEnvVars = []string{
|
||||
"FEATURES_GENERALSETTINGS",
|
||||
"FEATURES_QUERYLOGCONFIG",
|
||||
"FEATURES_STATSCONFIG",
|
||||
"FEATURES_CLIENTSETTINGS",
|
||||
"FEATURES_SERVICES",
|
||||
"FEATURES_FILTERS",
|
||||
"FEATURES_DHCP_SERVERCONFIG",
|
||||
"FEATURES_DHCP_STATICLEASES",
|
||||
"FEATURES_DNS_SERVERCONFIG",
|
||||
"FEATURES_DNS_ACCESSLISTS",
|
||||
"FEATURES_DNS_REWRITES",
|
||||
"REPLICA1_INTERFACENAME",
|
||||
"REPLICA1_DHCPSERVERENABLED",
|
||||
}
|
||||
|
||||
var _ = Describe("Config", func() {
|
||||
Context("deprecated", func() {
|
||||
BeforeEach(func() {
|
||||
for _, envVar := range deprecatedEnvVars {
|
||||
Ω(os.Setenv(envVar, "false")).ShouldNot(HaveOccurred())
|
||||
}
|
||||
})
|
||||
AfterEach(func() {
|
||||
for _, envVar := range deprecatedEnvVars {
|
||||
Ω(os.Unsetenv(envVar)).ShouldNot(HaveOccurred())
|
||||
}
|
||||
})
|
||||
Context("Get", func() {
|
||||
It("features should be false", func() {
|
||||
cfg, err := config.Get("", nil)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
verifyFeatures(cfg, false)
|
||||
})
|
||||
})
|
||||
})
|
||||
Context("current", func() {
|
||||
BeforeEach(func() {
|
||||
for _, envVar := range envVars {
|
||||
Ω(os.Unsetenv(envVar)).ShouldNot(HaveOccurred())
|
||||
}
|
||||
})
|
||||
AfterEach(func() {
|
||||
for _, envVar := range envVars {
|
||||
Ω(os.Unsetenv(envVar)).ShouldNot(HaveOccurred())
|
||||
}
|
||||
})
|
||||
Context("Get", func() {
|
||||
It("features should be true by default", func() {
|
||||
cfg, err := config.Get("", nil)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
verifyFeatures(cfg, true)
|
||||
})
|
||||
It("features should be true by default", func() {
|
||||
cfg, err := config.Get("", nil)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
verifyFeatures(cfg, true)
|
||||
})
|
||||
It("features should be false", func() {
|
||||
for _, envVar := range envVars {
|
||||
Ω(os.Setenv(envVar, "false")).ShouldNot(HaveOccurred())
|
||||
}
|
||||
cfg, err := config.Get("", nil)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
verifyFeatures(cfg, false)
|
||||
})
|
||||
Context("interface name", func() {
|
||||
It("should set interface name of replica 1", func() {
|
||||
Ω(os.Setenv("REPLICA1_URL", "https://foo.bar")).ShouldNot(HaveOccurred())
|
||||
Ω(os.Setenv(fmt.Sprintf("REPLICA%s_INTERFACE_NAME", "1"), "eth0")).ShouldNot(HaveOccurred())
|
||||
cfg, err := config.Get("", nil)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
Ω(cfg.Replicas[0].InterfaceName).Should(Equal("eth0"))
|
||||
})
|
||||
})
|
||||
Context("dhcp server", func() {
|
||||
It("should enable the dhcp server of replica 1", func() {
|
||||
Ω(os.Setenv("REPLICA1_URL", "https://foo.bar")).ShouldNot(HaveOccurred())
|
||||
Ω(os.Setenv(fmt.Sprintf("REPLICA%s_DHCPSERVERENABLED", "1"), "true")).ShouldNot(HaveOccurred())
|
||||
cfg, err := config.Get("", nil)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
Ω(cfg.Replicas[0].DHCPServerEnabled).ShouldNot(BeNil())
|
||||
Ω(*cfg.Replicas[0].DHCPServerEnabled).Should(BeTrue())
|
||||
})
|
||||
It("should disable the dhcp server of replica 1", func() {
|
||||
Ω(os.Setenv("REPLICA1_URL", "https://foo.bar")).ShouldNot(HaveOccurred())
|
||||
Ω(os.Setenv(fmt.Sprintf("REPLICA%s_DHCPSERVERENABLED", "1"), "false")).ShouldNot(HaveOccurred())
|
||||
cfg, err := config.Get("", nil)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
Ω(cfg.Replicas[0].DHCPServerEnabled).ShouldNot(BeNil())
|
||||
Ω(*cfg.Replicas[0].DHCPServerEnabled).Should(BeFalse())
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
func verifyFeatures(cfg *types.Config, value bool) {
|
||||
Ω(cfg.Features.GeneralSettings).Should(Equal(value))
|
||||
Ω(cfg.Features.QueryLogConfig).Should(Equal(value))
|
||||
Ω(cfg.Features.StatsConfig).Should(Equal(value))
|
||||
Ω(cfg.Features.ClientSettings).Should(Equal(value))
|
||||
Ω(cfg.Features.Services).Should(Equal(value))
|
||||
Ω(cfg.Features.Filters).Should(Equal(value))
|
||||
Ω(cfg.Features.DHCP.ServerConfig).Should(Equal(value))
|
||||
Ω(cfg.Features.DHCP.StaticLeases).Should(Equal(value))
|
||||
Ω(cfg.Features.DNS.ServerConfig).Should(Equal(value))
|
||||
Ω(cfg.Features.DNS.AccessLists).Should(Equal(value))
|
||||
Ω(cfg.Features.DNS.Rewrites).Should(Equal(value))
|
||||
}
|
||||
145
pkg/config/env.go
Normal file
145
pkg/config/env.go
Normal file
@@ -0,0 +1,145 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/bakito/adguardhome-sync/pkg/types"
|
||||
"github.com/bakito/adguardhome-sync/pkg/utils"
|
||||
"github.com/caarlos0/env/v10"
|
||||
)
|
||||
|
||||
func handleDeprecatedEnvVars(cfg *types.Config) {
|
||||
if val, ok := checkDeprecatedEnvVar("RUNONSTART", "RUN_ON_START"); ok {
|
||||
cfg.RunOnStart, _ = strconv.ParseBool(val)
|
||||
}
|
||||
if val, ok := checkDeprecatedEnvVar("API_DARKMODE", "API_DARK_MODE"); ok {
|
||||
cfg.API.DarkMode, _ = strconv.ParseBool(val)
|
||||
}
|
||||
if val, ok := checkDeprecatedEnvVar("FEATURES_GENERALSETTINGS", "FEATURES_GENERAL_SETTINGS"); ok {
|
||||
cfg.Features.GeneralSettings, _ = strconv.ParseBool(val)
|
||||
}
|
||||
if val, ok := checkDeprecatedEnvVar("FEATURES_QUERYLOGCONFIG", "FEATURES_QUERY_LOG_CONFIG"); ok {
|
||||
cfg.Features.QueryLogConfig, _ = strconv.ParseBool(val)
|
||||
}
|
||||
if val, ok := checkDeprecatedEnvVar("FEATURES_STATSCONFIG", "FEATURES_STATS_CONFIG"); ok {
|
||||
cfg.Features.StatsConfig, _ = strconv.ParseBool(val)
|
||||
}
|
||||
if val, ok := checkDeprecatedEnvVar("FEATURES_CLIENTSETTINGS", "FEATURES_CLIENT_SETTINGS"); ok {
|
||||
cfg.Features.ClientSettings, _ = strconv.ParseBool(val)
|
||||
}
|
||||
if val, ok := checkDeprecatedEnvVar("FEATURES_DHCP_SERVERCONFIG", "FEATURES_DHCP_SERVER_CONFIG"); ok {
|
||||
cfg.Features.DHCP.ServerConfig, _ = strconv.ParseBool(val)
|
||||
}
|
||||
if val, ok := checkDeprecatedEnvVar("FEATURES_DHCP_STATICLEASES", "FEATURES_DHCP_STATIC_LEASES"); ok {
|
||||
cfg.Features.DHCP.StaticLeases, _ = strconv.ParseBool(val)
|
||||
}
|
||||
if val, ok := checkDeprecatedEnvVar("FEATURES_DNS_ACCESSLISTS", "FEATURES_DNS_ACCESS_LISTS"); ok {
|
||||
cfg.Features.DNS.AccessLists, _ = strconv.ParseBool(val)
|
||||
}
|
||||
if val, ok := checkDeprecatedEnvVar("FEATURES_DNS_SERVERCONFIG", "FEATURES_DNS_SERVER_CONFIG"); ok {
|
||||
cfg.Features.DNS.ServerConfig, _ = strconv.ParseBool(val)
|
||||
}
|
||||
|
||||
if cfg.Replica != nil {
|
||||
if val, ok := checkDeprecatedEnvVar("REPLICA_WEBURL", "REPLICA_WEB_URL"); ok {
|
||||
cfg.Replica.WebURL = val
|
||||
}
|
||||
if val, ok := checkDeprecatedEnvVar("REPLICA_AUTOSETUP", "REPLICA_AUTO_SETUP"); ok {
|
||||
cfg.Replica.AutoSetup, _ = strconv.ParseBool(val)
|
||||
}
|
||||
if val, ok := checkDeprecatedEnvVar("REPLICA_INTERFACENAME", "REPLICA_INTERFACE_NAME"); ok {
|
||||
cfg.Replica.InterfaceName = val
|
||||
}
|
||||
if val, ok := checkDeprecatedEnvVar("REPLICA_DHCPSERVERENABLED", "REPLICA_DHCP_SERVER_ENABLED"); ok {
|
||||
if b, err := strconv.ParseBool(val); err != nil {
|
||||
cfg.Replica.DHCPServerEnabled = utils.Ptr(b)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func checkDeprecatedEnvVar(oldName string, newName string) (string, bool) {
|
||||
old, oldOK := os.LookupEnv(oldName)
|
||||
if oldOK {
|
||||
logger.With("deprecated", oldName, "replacement", newName).
|
||||
Warn("Deprecated env variable is used, please use the correct one")
|
||||
}
|
||||
new, newOK := os.LookupEnv(newName)
|
||||
if newOK {
|
||||
return new, true
|
||||
}
|
||||
return old, oldOK
|
||||
}
|
||||
|
||||
func checkDeprecatedReplicaEnvVar(oldPattern string, newPattern string, replicaID int) (string, bool) {
|
||||
return checkDeprecatedEnvVar(fmt.Sprintf(oldPattern, replicaID), fmt.Sprintf(newPattern, replicaID))
|
||||
}
|
||||
|
||||
// Manually collect replicas from env.
|
||||
func enrichReplicasFromEnv(initialReplicas []types.AdGuardInstance) ([]types.AdGuardInstance, error) {
|
||||
var replicas []types.AdGuardInstance
|
||||
for _, v := range os.Environ() {
|
||||
if envReplicasURLPattern.MatchString(v) {
|
||||
sm := envReplicasURLPattern.FindStringSubmatch(v)
|
||||
id, _ := strconv.Atoi(sm[1])
|
||||
|
||||
if id <= 0 {
|
||||
return nil, fmt.Errorf("numbered replica env variables must have a number id >= 1, got %q", v)
|
||||
}
|
||||
|
||||
if id > len(initialReplicas) {
|
||||
replicas = append(replicas, types.AdGuardInstance{URL: sm[2]})
|
||||
} else {
|
||||
re := initialReplicas[id-1]
|
||||
re.URL = sm[2]
|
||||
replicas = append(replicas, re)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(replicas) == 0 {
|
||||
replicas = initialReplicas
|
||||
}
|
||||
|
||||
for i := range replicas {
|
||||
reID := i + 1
|
||||
|
||||
// keep previously set value
|
||||
replicaDhcpServer := replicas[i].DHCPServerEnabled
|
||||
replicas[i].DHCPServerEnabled = nil
|
||||
if err := env.ParseWithOptions(&replicas[i], env.Options{Prefix: fmt.Sprintf("REPLICA%d_", reID)}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if replicas[i].DHCPServerEnabled == nil {
|
||||
replicas[i].DHCPServerEnabled = replicaDhcpServer
|
||||
}
|
||||
if val, ok := checkDeprecatedReplicaEnvVar("REPLICA%d_APIPATH", "REPLICA%d_API_PATH", reID); ok {
|
||||
replicas[i].APIPath = val
|
||||
}
|
||||
if val, ok := checkDeprecatedReplicaEnvVar("REPLICA%d_INSECURESKIPVERIFY", "REPLICA%d_INSECURE_SKIP_VERIFY", reID); ok {
|
||||
replicas[i].InsecureSkipVerify = strings.EqualFold(val, "true")
|
||||
}
|
||||
if val, ok := checkDeprecatedReplicaEnvVar("REPLICA%d_AUTOSETUP", "REPLICA%d_AUTO_SETUP", reID); ok {
|
||||
replicas[i].AutoSetup = strings.EqualFold(val, "true")
|
||||
}
|
||||
if val, ok := checkDeprecatedReplicaEnvVar("REPLICA%d_INTERFACENAME", "REPLICA%d_INTERFACE_NAME", reID); ok {
|
||||
replicas[i].InterfaceName = val
|
||||
}
|
||||
|
||||
if dhcpEnabled, ok := checkDeprecatedReplicaEnvVar("REPLICA%d_DHCPSERVERENABLED", "REPLICA%d_DHCP_SERVER_ENABLED", reID); ok {
|
||||
if strings.EqualFold(dhcpEnabled, "true") {
|
||||
replicas[i].DHCPServerEnabled = utils.Ptr(true)
|
||||
} else if strings.EqualFold(dhcpEnabled, "false") {
|
||||
replicas[i].DHCPServerEnabled = utils.Ptr(false)
|
||||
}
|
||||
}
|
||||
if replicas[i].APIPath == "" {
|
||||
replicas[i].APIPath = "/control"
|
||||
}
|
||||
}
|
||||
|
||||
return replicas, nil
|
||||
}
|
||||
25
pkg/config/env_test.go
Normal file
25
pkg/config/env_test.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Config", func() {
|
||||
Context("env", func() {
|
||||
Context("enrichReplicasFromEnv", func() {
|
||||
It("should have the origin URL from the config env var", func() {
|
||||
_ = os.Setenv("REPLICA0_URL", "https://origin-env:443")
|
||||
defer func() {
|
||||
_ = os.Unsetenv("REPLICA0_URL")
|
||||
}()
|
||||
_, err := enrichReplicasFromEnv(nil)
|
||||
|
||||
Ω(err).Should(HaveOccurred())
|
||||
Ω(err.Error()).Should(ContainSubstring("numbered replica env variables must have a number id >= 1"))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
34
pkg/config/file.go
Normal file
34
pkg/config/file.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/bakito/adguardhome-sync/pkg/types"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func readFile(cfg *types.Config, path string) error {
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
b, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := yaml.Unmarshal(b, cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func configFilePath(configFile string) (string, error) {
|
||||
if configFile == "" {
|
||||
// Find home directory.
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Join(home, ".adguardhome-sync.yaml"), nil
|
||||
}
|
||||
return configFile, nil
|
||||
}
|
||||
32
pkg/config/file_test.go
Normal file
32
pkg/config/file_test.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/google/uuid"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Config", func() {
|
||||
var ()
|
||||
BeforeEach(func() {
|
||||
})
|
||||
Context("configFilePath", func() {
|
||||
It("should return the same value", func() {
|
||||
path := uuid.NewString()
|
||||
result, err := configFilePath(path)
|
||||
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
Ω(result).Should(Equal(path))
|
||||
})
|
||||
It("should the file in HOME dir", func() {
|
||||
home := os.Getenv("HOME")
|
||||
result, err := configFilePath("")
|
||||
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
Ω(result).Should(Equal(filepath.Join(home, "/.adguardhome-sync.yaml")))
|
||||
})
|
||||
})
|
||||
})
|
||||
44
pkg/config/flag-names.go
Normal file
44
pkg/config/flag-names.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package config
|
||||
|
||||
const (
|
||||
FlagCron = "cron"
|
||||
FlagRunOnStart = "runOnStart"
|
||||
FlagPrintConfigOnly = "printConfigOnly"
|
||||
FlagContinueOnError = "continueOnError"
|
||||
|
||||
FlagApiPort = "api-port"
|
||||
FlagApiUsername = "api-username"
|
||||
FlagApiPassword = "api-password"
|
||||
FlagApiDarkMode = "api-dark-mode"
|
||||
|
||||
FlagFeatureDhcpServerConfig = "feature-dhcp-server-config"
|
||||
FlagFeatureDhcpStaticLeases = "feature-dhcp-static-leases"
|
||||
FlagFeatureDnsServerConfig = "feature-dns-server-config"
|
||||
FlagFeatureDnsAccessLists = "feature-dns-access-lists"
|
||||
FlagFeatureDnsRewrites = "feature-dns-rewrites"
|
||||
FlagFeatureGeneral = "feature-general-settings"
|
||||
FlagFeatureQueryLog = "feature-query-log-config"
|
||||
FlagFeatureStats = "feature-stats-config"
|
||||
FlagFeatureClient = "feature-client-settings"
|
||||
FlagFeatureServices = "feature-services"
|
||||
FlagFeatureFilters = "feature-filters"
|
||||
|
||||
FlagOriginURL = "origin-url"
|
||||
FlagOriginWebURL = "origin-web-url"
|
||||
FlagOriginApiPath = "origin-api-path"
|
||||
FlagOriginUsername = "origin-username"
|
||||
|
||||
FlagOriginPassword = "origin-password"
|
||||
FlagOriginCookie = "origin-cookie"
|
||||
FlagOriginISV = "origin-insecure-skip-verify"
|
||||
|
||||
FlagReplicaURL = "replica-url"
|
||||
FlagReplicaWebURL = "replica-web-url"
|
||||
FlagReplicaApiPath = "replica-api-path"
|
||||
FlagReplicaUsername = "replica-username"
|
||||
FlagReplicaPassword = "replica-password"
|
||||
FlagReplicaCookie = "replica-cookie"
|
||||
FlagReplicaISV = "replica-insecure-skip-verify"
|
||||
FlagReplicaAutoSetup = "replica-auto-setup"
|
||||
FlagReplicaInterfaceName = "replica-interface-name"
|
||||
)
|
||||
283
pkg/config/flags.go
Normal file
283
pkg/config/flags.go
Normal file
@@ -0,0 +1,283 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/bakito/adguardhome-sync/pkg/types"
|
||||
)
|
||||
|
||||
func readFlags(cfg *types.Config, flags Flags) error {
|
||||
if flags == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
fr := &flagReader{
|
||||
cfg: cfg,
|
||||
flags: flags,
|
||||
}
|
||||
|
||||
if err := fr.readRootFlags(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := fr.readApiFlags(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := fr.readFeatureFlags(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := fr.readOriginFlags(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := fr.readReplicaFlags(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type flagReader struct {
|
||||
cfg *types.Config
|
||||
flags Flags
|
||||
}
|
||||
|
||||
func (fr *flagReader) readReplicaFlags() error {
|
||||
if err := fr.setStringFlag(FlagReplicaURL, func(cgf *types.Config, value string) {
|
||||
fr.cfg.Replica.URL = value
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fr.setStringFlag(FlagReplicaWebURL, func(cgf *types.Config, value string) {
|
||||
fr.cfg.Replica.WebURL = value
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fr.setStringFlag(FlagReplicaApiPath, func(cgf *types.Config, value string) {
|
||||
fr.cfg.Replica.APIPath = value
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fr.setStringFlag(FlagReplicaUsername, func(cgf *types.Config, value string) {
|
||||
fr.cfg.Replica.Username = value
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fr.setStringFlag(FlagReplicaPassword, func(cgf *types.Config, value string) {
|
||||
fr.cfg.Replica.Password = value
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fr.setStringFlag(FlagReplicaCookie, func(cgf *types.Config, value string) {
|
||||
fr.cfg.Replica.Cookie = value
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fr.setBoolFlag(FlagReplicaISV, func(cgf *types.Config, value bool) {
|
||||
fr.cfg.Replica.InsecureSkipVerify = value
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fr.setBoolFlag(FlagReplicaAutoSetup, func(cgf *types.Config, value bool) {
|
||||
fr.cfg.Replica.AutoSetup = value
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fr.setStringFlag(FlagReplicaInterfaceName, func(cgf *types.Config, value string) {
|
||||
fr.cfg.Replica.InterfaceName = value
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fr *flagReader) readOriginFlags() error {
|
||||
if err := fr.setStringFlag(FlagOriginURL, func(cgf *types.Config, value string) {
|
||||
fr.cfg.Origin.URL = value
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fr.setStringFlag(FlagOriginWebURL, func(cgf *types.Config, value string) {
|
||||
fr.cfg.Origin.WebURL = value
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fr.setStringFlag(FlagOriginApiPath, func(cgf *types.Config, value string) {
|
||||
fr.cfg.Origin.APIPath = value
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fr.setStringFlag(FlagOriginUsername, func(cgf *types.Config, value string) {
|
||||
fr.cfg.Origin.Username = value
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fr.setStringFlag(FlagOriginPassword, func(cgf *types.Config, value string) {
|
||||
fr.cfg.Origin.Password = value
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fr.setStringFlag(FlagOriginCookie, func(cgf *types.Config, value string) {
|
||||
fr.cfg.Origin.Cookie = value
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fr.setBoolFlag(FlagOriginISV, func(cgf *types.Config, value bool) {
|
||||
fr.cfg.Origin.InsecureSkipVerify = value
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fr *flagReader) readFeatureFlags() error {
|
||||
if err := fr.setBoolFlag(FlagFeatureDhcpServerConfig, func(cgf *types.Config, value bool) {
|
||||
fr.cfg.Features.DHCP.ServerConfig = value
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fr.setBoolFlag(FlagFeatureDhcpStaticLeases, func(cgf *types.Config, value bool) {
|
||||
fr.cfg.Features.DHCP.StaticLeases = value
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := fr.setBoolFlag(FlagFeatureDnsServerConfig, func(cgf *types.Config, value bool) {
|
||||
fr.cfg.Features.DNS.ServerConfig = value
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fr.setBoolFlag(FlagFeatureDnsAccessLists, func(cgf *types.Config, value bool) {
|
||||
fr.cfg.Features.DNS.AccessLists = value
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fr.setBoolFlag(FlagFeatureDnsRewrites, func(cgf *types.Config, value bool) {
|
||||
fr.cfg.Features.DNS.Rewrites = value
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := fr.setBoolFlag(FlagFeatureGeneral, func(cgf *types.Config, value bool) {
|
||||
fr.cfg.Features.GeneralSettings = value
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fr.setBoolFlag(FlagFeatureQueryLog, func(cgf *types.Config, value bool) {
|
||||
fr.cfg.Features.QueryLogConfig = value
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fr.setBoolFlag(FlagFeatureStats, func(cgf *types.Config, value bool) {
|
||||
fr.cfg.Features.StatsConfig = value
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fr.setBoolFlag(FlagFeatureClient, func(cgf *types.Config, value bool) {
|
||||
fr.cfg.Features.ClientSettings = value
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fr.setBoolFlag(FlagFeatureServices, func(cgf *types.Config, value bool) {
|
||||
fr.cfg.Features.Services = value
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fr.setBoolFlag(FlagFeatureFilters, func(cgf *types.Config, value bool) {
|
||||
fr.cfg.Features.Filters = value
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fr *flagReader) readApiFlags() (err error) {
|
||||
if err = fr.setIntFlag(FlagApiPort, func(cgf *types.Config, value int) {
|
||||
fr.cfg.API.Port = value
|
||||
}); err != nil {
|
||||
return
|
||||
}
|
||||
if err = fr.setStringFlag(FlagApiUsername, func(cgf *types.Config, value string) {
|
||||
fr.cfg.API.Username = value
|
||||
}); err != nil {
|
||||
return
|
||||
}
|
||||
if err = fr.setStringFlag(FlagApiPassword, func(cgf *types.Config, value string) {
|
||||
fr.cfg.API.Password = value
|
||||
}); err != nil {
|
||||
return
|
||||
}
|
||||
if err = fr.setBoolFlag(FlagApiDarkMode, func(cgf *types.Config, value bool) {
|
||||
fr.cfg.API.DarkMode = value
|
||||
}); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (fr *flagReader) readRootFlags() (err error) {
|
||||
if err = fr.setStringFlag(FlagCron, func(cgf *types.Config, value string) {
|
||||
fr.cfg.Cron = value
|
||||
}); err != nil {
|
||||
return
|
||||
}
|
||||
if err = fr.setBoolFlag(FlagRunOnStart, func(cgf *types.Config, value bool) {
|
||||
fr.cfg.RunOnStart = value
|
||||
}); err != nil {
|
||||
return
|
||||
}
|
||||
if err = fr.setBoolFlag(FlagPrintConfigOnly, func(cgf *types.Config, value bool) {
|
||||
fr.cfg.PrintConfigOnly = value
|
||||
}); err != nil {
|
||||
return
|
||||
}
|
||||
if err = fr.setBoolFlag(FlagContinueOnError, func(cgf *types.Config, value bool) {
|
||||
fr.cfg.ContinueOnError = value
|
||||
}); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type Flags interface {
|
||||
Changed(name string) bool
|
||||
GetString(name string) (string, error)
|
||||
GetInt(name string) (int, error)
|
||||
GetBool(name string) (bool, error)
|
||||
}
|
||||
|
||||
func (fr *flagReader) setStringFlag(name string, cb callback[string]) (err error) {
|
||||
if fr.flags.Changed(name) {
|
||||
if value, err := fr.flags.GetString(name); err != nil {
|
||||
return err
|
||||
} else {
|
||||
cb(fr.cfg, value)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fr *flagReader) setBoolFlag(name string, cb callback[bool]) (err error) {
|
||||
if fr.flags.Changed(name) {
|
||||
if value, err := fr.flags.GetBool(name); err != nil {
|
||||
return err
|
||||
} else {
|
||||
cb(fr.cfg, value)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fr *flagReader) setIntFlag(name string, cb callback[int]) (err error) {
|
||||
if fr.flags.Changed(name) {
|
||||
if value, err := fr.flags.GetInt(name); err != nil {
|
||||
return err
|
||||
} else {
|
||||
cb(fr.cfg, value)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type callback[T any] func(cgf *types.Config, value T)
|
||||
235
pkg/config/flags_test.go
Normal file
235
pkg/config/flags_test.go
Normal file
@@ -0,0 +1,235 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
flagsmock "github.com/bakito/adguardhome-sync/pkg/mocks/flags"
|
||||
"github.com/bakito/adguardhome-sync/pkg/types"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
gm "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
var _ = Describe("Config", func() {
|
||||
var (
|
||||
cfg *types.Config
|
||||
flags *flagsmock.MockFlags
|
||||
mockCtrl *gm.Controller
|
||||
)
|
||||
BeforeEach(func() {
|
||||
cfg = &types.Config{
|
||||
Replica: &types.AdGuardInstance{},
|
||||
Features: types.Features{
|
||||
DNS: types.DNS{
|
||||
AccessLists: true,
|
||||
ServerConfig: true,
|
||||
Rewrites: true,
|
||||
},
|
||||
DHCP: types.DHCP{
|
||||
ServerConfig: true,
|
||||
StaticLeases: true,
|
||||
},
|
||||
GeneralSettings: true,
|
||||
QueryLogConfig: true,
|
||||
StatsConfig: true,
|
||||
ClientSettings: true,
|
||||
Services: true,
|
||||
Filters: true,
|
||||
},
|
||||
}
|
||||
mockCtrl = gm.NewController(GinkgoT())
|
||||
flags = flagsmock.NewMockFlags(mockCtrl)
|
||||
})
|
||||
AfterEach(func() {
|
||||
defer mockCtrl.Finish()
|
||||
})
|
||||
Context("readFlags", func() {
|
||||
It("should not change the config with nil flags", func() {
|
||||
clone := cfg.DeepCopy()
|
||||
err := readFlags(cfg, nil)
|
||||
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
Ω(cfg).Should(Equal(clone))
|
||||
})
|
||||
It("should not change the config with no changed flags", func() {
|
||||
clone := cfg.DeepCopy()
|
||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||
err := readFlags(cfg, flags)
|
||||
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
Ω(cfg).Should(Equal(clone))
|
||||
})
|
||||
})
|
||||
Context("readFeatureFlags", func() {
|
||||
It("should disable all flags", func() {
|
||||
flags.EXPECT().Changed(gm.Any()).DoAndReturn(func(name string) bool {
|
||||
return strings.HasPrefix(name, "feature")
|
||||
}).AnyTimes()
|
||||
flags.EXPECT().GetBool(gm.Any()).Return(false, nil).AnyTimes()
|
||||
err := readFlags(cfg, flags)
|
||||
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
Ω(cfg.Features).Should(Equal(types.Features{
|
||||
DNS: types.DNS{
|
||||
AccessLists: false,
|
||||
ServerConfig: false,
|
||||
Rewrites: false,
|
||||
},
|
||||
DHCP: types.DHCP{
|
||||
ServerConfig: false,
|
||||
StaticLeases: false,
|
||||
},
|
||||
GeneralSettings: false,
|
||||
QueryLogConfig: false,
|
||||
StatsConfig: false,
|
||||
ClientSettings: false,
|
||||
Services: false,
|
||||
Filters: false,
|
||||
}))
|
||||
})
|
||||
})
|
||||
Context("readApiFlags", func() {
|
||||
It("should change all values", func() {
|
||||
cfg.API = types.API{
|
||||
Port: 1111,
|
||||
Username: "2222",
|
||||
Password: "3333",
|
||||
DarkMode: false,
|
||||
}
|
||||
flags.EXPECT().Changed(gm.Any()).DoAndReturn(func(name string) bool {
|
||||
return strings.HasPrefix(name, "api")
|
||||
}).AnyTimes()
|
||||
flags.EXPECT().GetInt(FlagApiPort).Return(9999, nil)
|
||||
flags.EXPECT().GetString(FlagApiUsername).Return("aaaa", nil)
|
||||
flags.EXPECT().GetString(FlagApiPassword).Return("bbbb", nil)
|
||||
flags.EXPECT().GetBool(FlagApiDarkMode).Return(true, nil)
|
||||
err := readFlags(cfg, flags)
|
||||
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
Ω(cfg.API).Should(Equal(types.API{
|
||||
Port: 9999,
|
||||
Username: "aaaa",
|
||||
Password: "bbbb",
|
||||
DarkMode: true,
|
||||
}))
|
||||
})
|
||||
})
|
||||
Context("readRootFlags", func() {
|
||||
It("should change all values", func() {
|
||||
cfg.Cron = "*/10 * * * *"
|
||||
cfg.PrintConfigOnly = false
|
||||
cfg.ContinueOnError = false
|
||||
cfg.RunOnStart = false
|
||||
|
||||
flags.EXPECT().Changed(FlagCron).Return(true)
|
||||
flags.EXPECT().Changed(FlagRunOnStart).Return(true)
|
||||
flags.EXPECT().Changed(FlagPrintConfigOnly).Return(true)
|
||||
flags.EXPECT().Changed(FlagContinueOnError).Return(true)
|
||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||
|
||||
flags.EXPECT().GetString(FlagCron).Return("*/30 * * * *", nil)
|
||||
flags.EXPECT().GetBool(FlagRunOnStart).Return(true, nil)
|
||||
flags.EXPECT().GetBool(FlagPrintConfigOnly).Return(true, nil)
|
||||
flags.EXPECT().GetBool(FlagContinueOnError).Return(true, nil)
|
||||
err := readFlags(cfg, flags)
|
||||
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
Ω(cfg.Cron).Should(Equal("*/30 * * * *"))
|
||||
Ω(cfg.RunOnStart).Should(BeTrue())
|
||||
Ω(cfg.PrintConfigOnly).Should(BeTrue())
|
||||
Ω(cfg.ContinueOnError).Should(BeTrue())
|
||||
})
|
||||
})
|
||||
Context("readOriginFlags", func() {
|
||||
It("should change all values", func() {
|
||||
cfg.Origin = types.AdGuardInstance{
|
||||
URL: "1",
|
||||
WebURL: "2",
|
||||
APIPath: "3",
|
||||
Username: "4",
|
||||
Password: "5",
|
||||
Cookie: "6",
|
||||
InsecureSkipVerify: false,
|
||||
}
|
||||
|
||||
flags.EXPECT().Changed(FlagOriginURL).Return(true)
|
||||
flags.EXPECT().Changed(FlagOriginWebURL).Return(true)
|
||||
flags.EXPECT().Changed(FlagOriginApiPath).Return(true)
|
||||
flags.EXPECT().Changed(FlagOriginUsername).Return(true)
|
||||
flags.EXPECT().Changed(FlagOriginPassword).Return(true)
|
||||
flags.EXPECT().Changed(FlagOriginCookie).Return(true)
|
||||
flags.EXPECT().Changed(FlagOriginISV).Return(true)
|
||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||
|
||||
flags.EXPECT().GetString(FlagOriginURL).Return("a", nil)
|
||||
flags.EXPECT().GetString(FlagOriginWebURL).Return("b", nil)
|
||||
flags.EXPECT().GetString(FlagOriginApiPath).Return("c", nil)
|
||||
flags.EXPECT().GetString(FlagOriginUsername).Return("d", nil)
|
||||
flags.EXPECT().GetString(FlagOriginPassword).Return("e", nil)
|
||||
flags.EXPECT().GetString(FlagOriginCookie).Return("f", nil)
|
||||
flags.EXPECT().GetBool(FlagOriginISV).Return(true, nil)
|
||||
err := readFlags(cfg, flags)
|
||||
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
Ω(cfg.Origin).Should(Equal(types.AdGuardInstance{
|
||||
URL: "a",
|
||||
WebURL: "b",
|
||||
APIPath: "c",
|
||||
Username: "d",
|
||||
Password: "e",
|
||||
Cookie: "f",
|
||||
InsecureSkipVerify: true,
|
||||
}))
|
||||
})
|
||||
})
|
||||
Context("readReplicaFlags", func() {
|
||||
It("should change all values", func() {
|
||||
cfg.Replica = &types.AdGuardInstance{
|
||||
URL: "1",
|
||||
WebURL: "2",
|
||||
APIPath: "3",
|
||||
Username: "4",
|
||||
Password: "5",
|
||||
Cookie: "6",
|
||||
InsecureSkipVerify: false,
|
||||
AutoSetup: false,
|
||||
InterfaceName: "7",
|
||||
}
|
||||
|
||||
flags.EXPECT().Changed(FlagReplicaURL).Return(true)
|
||||
flags.EXPECT().Changed(FlagReplicaWebURL).Return(true)
|
||||
flags.EXPECT().Changed(FlagReplicaApiPath).Return(true)
|
||||
flags.EXPECT().Changed(FlagReplicaUsername).Return(true)
|
||||
flags.EXPECT().Changed(FlagReplicaPassword).Return(true)
|
||||
flags.EXPECT().Changed(FlagReplicaCookie).Return(true)
|
||||
flags.EXPECT().Changed(FlagReplicaISV).Return(true)
|
||||
flags.EXPECT().Changed(FlagReplicaAutoSetup).Return(true)
|
||||
flags.EXPECT().Changed(FlagReplicaInterfaceName).Return(true)
|
||||
flags.EXPECT().Changed(gm.Any()).Return(false).AnyTimes()
|
||||
|
||||
flags.EXPECT().GetString(FlagReplicaURL).Return("a", nil)
|
||||
flags.EXPECT().GetString(FlagReplicaWebURL).Return("b", nil)
|
||||
flags.EXPECT().GetString(FlagReplicaApiPath).Return("c", nil)
|
||||
flags.EXPECT().GetString(FlagReplicaUsername).Return("d", nil)
|
||||
flags.EXPECT().GetString(FlagReplicaPassword).Return("e", nil)
|
||||
flags.EXPECT().GetString(FlagReplicaCookie).Return("f", nil)
|
||||
flags.EXPECT().GetBool(FlagReplicaISV).Return(true, nil)
|
||||
flags.EXPECT().GetBool(FlagReplicaAutoSetup).Return(true, nil)
|
||||
flags.EXPECT().GetString(FlagReplicaInterfaceName).Return("g", nil)
|
||||
err := readFlags(cfg, flags)
|
||||
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
Ω(cfg.Replica).Should(Equal(&types.AdGuardInstance{
|
||||
URL: "a",
|
||||
WebURL: "b",
|
||||
APIPath: "c",
|
||||
Username: "d",
|
||||
Password: "e",
|
||||
Cookie: "f",
|
||||
InsecureSkipVerify: true,
|
||||
AutoSetup: true,
|
||||
InterfaceName: "g",
|
||||
}))
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
const (
|
||||
logHistorySize = 50
|
||||
envLogLevel = "LOG_LEVEL"
|
||||
envLogFormat = "LOG_FORMAT"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -31,14 +32,23 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
format := "console"
|
||||
if fmt, ok := os.LookupEnv(envLogFormat); ok {
|
||||
format = fmt
|
||||
}
|
||||
|
||||
cfg := zap.Config{
|
||||
Level: zap.NewAtomicLevelAt(level),
|
||||
Development: false,
|
||||
Encoding: "console",
|
||||
EncoderConfig: zap.NewDevelopmentEncoderConfig(),
|
||||
Encoding: format,
|
||||
EncoderConfig: zap.NewProductionEncoderConfig(),
|
||||
OutputPaths: []string{"stdout"},
|
||||
ErrorOutputPaths: []string{"stderr"},
|
||||
}
|
||||
cfg.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
|
||||
cfg.EncoderConfig.EncodeDuration = zapcore.StringDurationEncoder
|
||||
cfg.EncoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
|
||||
|
||||
opt := zap.WrapCore(func(c zapcore.Core) zapcore.Core {
|
||||
return zapcore.NewTee(c, &logList{
|
||||
enc: zapcore.NewConsoleEncoder(cfg.EncoderConfig),
|
||||
|
||||
14
pkg/metrics/handler.go
Normal file
14
pkg/metrics/handler.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
)
|
||||
|
||||
func Handler() gin.HandlerFunc {
|
||||
h := promhttp.Handler()
|
||||
|
||||
return func(c *gin.Context) {
|
||||
h.ServeHTTP(c.Writer, c.Request)
|
||||
}
|
||||
}
|
||||
245
pkg/metrics/metrics.go
Normal file
245
pkg/metrics/metrics.go
Normal file
@@ -0,0 +1,245 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"github.com/bakito/adguardhome-sync/pkg/client/model"
|
||||
"github.com/bakito/adguardhome-sync/pkg/log"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"golang.org/x/exp/constraints"
|
||||
)
|
||||
|
||||
var (
|
||||
l = log.GetLogger("metrics")
|
||||
|
||||
// avgProcessingTime - Average processing time for a DNS query
|
||||
avgProcessingTime = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "avg_processing_time",
|
||||
Namespace: "adguard",
|
||||
Help: "This represent the average processing time for a DNS query in s",
|
||||
},
|
||||
[]string{"hostname"},
|
||||
)
|
||||
|
||||
// dnsQueries - Number of DNS queries
|
||||
dnsQueries = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "num_dns_queries",
|
||||
Namespace: "adguard",
|
||||
Help: "Number of DNS queries",
|
||||
},
|
||||
[]string{"hostname"},
|
||||
)
|
||||
|
||||
// blockedFiltering - Number of DNS queries blocked
|
||||
blockedFiltering = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "num_blocked_filtering",
|
||||
Namespace: "adguard",
|
||||
Help: "This represent the number of domains blocked",
|
||||
},
|
||||
[]string{"hostname"},
|
||||
)
|
||||
|
||||
// parentalFiltering - Number of DNS queries replaced by parental control
|
||||
parentalFiltering = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "num_replaced_parental",
|
||||
Namespace: "adguard",
|
||||
Help: "This represent the number of domains blocked (parental)",
|
||||
},
|
||||
[]string{"hostname"},
|
||||
)
|
||||
|
||||
// safeBrowsingFiltering - Number of DNS queries replaced by safe browsing
|
||||
safeBrowsingFiltering = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "num_replaced_safebrowsing",
|
||||
Namespace: "adguard",
|
||||
Help: "This represent the number of domains blocked (safe browsing)",
|
||||
},
|
||||
[]string{"hostname"},
|
||||
)
|
||||
|
||||
// safeSearchFiltering - Number of DNS queries replaced by safe search
|
||||
safeSearchFiltering = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "num_replaced_safesearch",
|
||||
Namespace: "adguard",
|
||||
Help: "This represent the number of domains blocked (safe search)",
|
||||
},
|
||||
[]string{"hostname"},
|
||||
)
|
||||
|
||||
// topQueries - The number of top queries
|
||||
topQueries = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "top_queried_domains",
|
||||
Namespace: "adguard",
|
||||
Help: "This represent the top queried domains",
|
||||
},
|
||||
[]string{"hostname", "domain"},
|
||||
)
|
||||
|
||||
// topBlocked - The number of top domains blocked
|
||||
topBlocked = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "top_blocked_domains",
|
||||
Namespace: "adguard",
|
||||
Help: "This represent the top bloacked domains",
|
||||
},
|
||||
[]string{"hostname", "domain"},
|
||||
)
|
||||
|
||||
// topClients - The number of top clients
|
||||
topClients = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "top_clients",
|
||||
Namespace: "adguard",
|
||||
Help: "This represent the top clients",
|
||||
},
|
||||
[]string{"hostname", "client"},
|
||||
)
|
||||
|
||||
// queryTypes - The type of DNS Queries (A, AAAA...)
|
||||
queryTypes = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "query_types",
|
||||
Namespace: "adguard",
|
||||
Help: "This represent the DNS query types",
|
||||
},
|
||||
[]string{"hostname", "type"},
|
||||
)
|
||||
|
||||
// running - If Adguard is running
|
||||
running = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "running",
|
||||
Namespace: "adguard",
|
||||
Help: "This represent if Adguard is running",
|
||||
},
|
||||
[]string{"hostname"},
|
||||
)
|
||||
|
||||
// protectionEnabled - If Adguard protection is enabled
|
||||
protectionEnabled = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "protection_enabled",
|
||||
Namespace: "adguard",
|
||||
Help: "This represent if Adguard Protection is enabled",
|
||||
},
|
||||
[]string{"hostname"},
|
||||
)
|
||||
)
|
||||
|
||||
// Init initializes all Prometheus metrics made available by AdGuard exporter.
|
||||
func Init() {
|
||||
initMetric("avg_processing_time", avgProcessingTime)
|
||||
initMetric("num_dns_queries", dnsQueries)
|
||||
initMetric("num_blocked_filtering", blockedFiltering)
|
||||
initMetric("num_replaced_parental", parentalFiltering)
|
||||
initMetric("num_replaced_safebrowsing", safeBrowsingFiltering)
|
||||
initMetric("num_replaced_safesearch", safeSearchFiltering)
|
||||
initMetric("top_queried_domains", topQueries)
|
||||
initMetric("top_blocked_domains", topBlocked)
|
||||
initMetric("top_clients", topClients)
|
||||
initMetric("query_types", queryTypes)
|
||||
initMetric("running", running)
|
||||
initMetric("protection_enabled", protectionEnabled)
|
||||
}
|
||||
|
||||
func initMetric(name string, metric *prometheus.GaugeVec) {
|
||||
prometheus.MustRegister(metric)
|
||||
l.With("name", name).Info("New Prometheus metric registered")
|
||||
}
|
||||
|
||||
func Update(ims ...InstanceMetrics) {
|
||||
for _, im := range ims {
|
||||
update(im)
|
||||
}
|
||||
|
||||
l.Debug("updated")
|
||||
}
|
||||
|
||||
func update(im InstanceMetrics) {
|
||||
// Status
|
||||
var isRunning int = 0
|
||||
if im.Status.Running {
|
||||
isRunning = 1
|
||||
}
|
||||
running.WithLabelValues(im.HostName).Set(float64(isRunning))
|
||||
|
||||
var isProtected int = 0
|
||||
if im.Status.ProtectionEnabled {
|
||||
isProtected = 1
|
||||
}
|
||||
protectionEnabled.WithLabelValues(im.HostName).Set(float64(isProtected))
|
||||
|
||||
// Stats
|
||||
avgProcessingTime.WithLabelValues(im.HostName).Set(safeMetric(im.Stats.AvgProcessingTime))
|
||||
dnsQueries.WithLabelValues(im.HostName).Set(safeMetric(im.Stats.NumDnsQueries))
|
||||
blockedFiltering.WithLabelValues(im.HostName).Set(safeMetric(im.Stats.NumBlockedFiltering))
|
||||
parentalFiltering.WithLabelValues(im.HostName).Set(safeMetric(im.Stats.NumReplacedParental))
|
||||
safeBrowsingFiltering.WithLabelValues(im.HostName).Set(safeMetric(im.Stats.NumReplacedSafebrowsing))
|
||||
safeSearchFiltering.WithLabelValues(im.HostName).Set(safeMetric(im.Stats.NumReplacedSafesearch))
|
||||
|
||||
if im.Stats.TopQueriedDomains != nil {
|
||||
for _, tq := range *im.Stats.TopQueriedDomains {
|
||||
for domain, value := range tq.AdditionalProperties {
|
||||
topQueries.WithLabelValues(im.HostName, domain).Set(float64(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
if im.Stats.TopBlockedDomains != nil {
|
||||
for _, tb := range *im.Stats.TopBlockedDomains {
|
||||
for domain, value := range tb.AdditionalProperties {
|
||||
topBlocked.WithLabelValues(im.HostName, domain).Set(float64(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
if im.Stats.TopClients != nil {
|
||||
for _, tc := range *im.Stats.TopClients {
|
||||
for source, value := range tc.AdditionalProperties {
|
||||
topClients.WithLabelValues(im.HostName, source).Set(float64(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// LogQuery
|
||||
m := make(map[string]int)
|
||||
if im.QueryLog != nil && im.QueryLog.Data != nil {
|
||||
logdata := *im.QueryLog.Data
|
||||
for _, ld := range logdata {
|
||||
if ld.Answer != nil {
|
||||
dnsanswer := *ld.Answer
|
||||
if len(dnsanswer) > 0 {
|
||||
for _, dnsa := range dnsanswer {
|
||||
dnsType := *dnsa.Type
|
||||
m[dnsType] += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for key, value := range m {
|
||||
queryTypes.WithLabelValues(im.HostName, key).Set(float64(value))
|
||||
}
|
||||
}
|
||||
|
||||
type InstanceMetrics struct {
|
||||
HostName string
|
||||
Status *model.ServerStatus
|
||||
Stats *model.Stats
|
||||
QueryLog *model.QueryLog
|
||||
}
|
||||
|
||||
func safeMetric[T Number](v *T) float64 {
|
||||
if v == nil {
|
||||
return 0
|
||||
}
|
||||
return float64(*v)
|
||||
}
|
||||
|
||||
type Number interface {
|
||||
constraints.Float | constraints.Integer
|
||||
}
|
||||
@@ -1,5 +1,10 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/bakito/adguardhome-sync/pkg/client (interfaces: Client)
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -package client -destination pkg/mocks/client/mock.go github.com/bakito/adguardhome-sync/pkg/client Client
|
||||
//
|
||||
|
||||
// Package client is a generated GoMock package.
|
||||
package client
|
||||
@@ -8,7 +13,7 @@ import (
|
||||
reflect "reflect"
|
||||
|
||||
model "github.com/bakito/adguardhome-sync/pkg/client/model"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockClient is a mock of Client interface.
|
||||
@@ -49,65 +54,52 @@ func (mr *MockClientMockRecorder) AccessList() *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AccessList", reflect.TypeOf((*MockClient)(nil).AccessList))
|
||||
}
|
||||
|
||||
// AddClients mocks base method.
|
||||
func (m *MockClient) AddClients(arg0 ...*model.Client) error {
|
||||
// AddClient mocks base method.
|
||||
func (m *MockClient) AddClient(arg0 *model.Client) error {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{}
|
||||
for _, a := range arg0 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "AddClients", varargs...)
|
||||
ret := m.ctrl.Call(m, "AddClient", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// AddClients indicates an expected call of AddClients.
|
||||
func (mr *MockClientMockRecorder) AddClients(arg0 ...interface{}) *gomock.Call {
|
||||
// AddClient indicates an expected call of AddClient.
|
||||
func (mr *MockClientMockRecorder) AddClient(arg0 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddClients", reflect.TypeOf((*MockClient)(nil).AddClients), arg0...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddClient", reflect.TypeOf((*MockClient)(nil).AddClient), arg0)
|
||||
}
|
||||
|
||||
// AddDHCPStaticLeases mocks base method.
|
||||
func (m *MockClient) AddDHCPStaticLeases(arg0 ...model.DhcpStaticLease) error {
|
||||
// AddDHCPStaticLease mocks base method.
|
||||
func (m *MockClient) AddDHCPStaticLease(arg0 model.DhcpStaticLease) error {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{}
|
||||
for _, a := range arg0 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "AddDHCPStaticLeases", varargs...)
|
||||
ret := m.ctrl.Call(m, "AddDHCPStaticLease", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// AddDHCPStaticLeases indicates an expected call of AddDHCPStaticLeases.
|
||||
func (mr *MockClientMockRecorder) AddDHCPStaticLeases(arg0 ...interface{}) *gomock.Call {
|
||||
// AddDHCPStaticLease indicates an expected call of AddDHCPStaticLease.
|
||||
func (mr *MockClientMockRecorder) AddDHCPStaticLease(arg0 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddDHCPStaticLeases", reflect.TypeOf((*MockClient)(nil).AddDHCPStaticLeases), arg0...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddDHCPStaticLease", reflect.TypeOf((*MockClient)(nil).AddDHCPStaticLease), arg0)
|
||||
}
|
||||
|
||||
// AddFilters mocks base method.
|
||||
func (m *MockClient) AddFilters(arg0 bool, arg1 ...model.Filter) error {
|
||||
// AddFilter mocks base method.
|
||||
func (m *MockClient) AddFilter(arg0 bool, arg1 model.Filter) error {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{arg0}
|
||||
for _, a := range arg1 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "AddFilters", varargs...)
|
||||
ret := m.ctrl.Call(m, "AddFilter", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// AddFilters indicates an expected call of AddFilters.
|
||||
func (mr *MockClientMockRecorder) AddFilters(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
|
||||
// AddFilter indicates an expected call of AddFilter.
|
||||
func (mr *MockClientMockRecorder) AddFilter(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{arg0}, arg1...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddFilters", reflect.TypeOf((*MockClient)(nil).AddFilters), varargs...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddFilter", reflect.TypeOf((*MockClient)(nil).AddFilter), arg0, arg1)
|
||||
}
|
||||
|
||||
// AddRewriteEntries mocks base method.
|
||||
func (m *MockClient) AddRewriteEntries(arg0 ...model.RewriteEntry) error {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{}
|
||||
varargs := []any{}
|
||||
for _, a := range arg0 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
@@ -117,7 +109,7 @@ func (m *MockClient) AddRewriteEntries(arg0 ...model.RewriteEntry) error {
|
||||
}
|
||||
|
||||
// AddRewriteEntries indicates an expected call of AddRewriteEntries.
|
||||
func (mr *MockClientMockRecorder) AddRewriteEntries(arg0 ...interface{}) *gomock.Call {
|
||||
func (mr *MockClientMockRecorder) AddRewriteEntries(arg0 ...any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRewriteEntries", reflect.TypeOf((*MockClient)(nil).AddRewriteEntries), arg0...)
|
||||
}
|
||||
@@ -182,65 +174,52 @@ func (mr *MockClientMockRecorder) DNSConfig() *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DNSConfig", reflect.TypeOf((*MockClient)(nil).DNSConfig))
|
||||
}
|
||||
|
||||
// DeleteClients mocks base method.
|
||||
func (m *MockClient) DeleteClients(arg0 ...*model.Client) error {
|
||||
// DeleteClient mocks base method.
|
||||
func (m *MockClient) DeleteClient(arg0 *model.Client) error {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{}
|
||||
for _, a := range arg0 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "DeleteClients", varargs...)
|
||||
ret := m.ctrl.Call(m, "DeleteClient", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DeleteClients indicates an expected call of DeleteClients.
|
||||
func (mr *MockClientMockRecorder) DeleteClients(arg0 ...interface{}) *gomock.Call {
|
||||
// DeleteClient indicates an expected call of DeleteClient.
|
||||
func (mr *MockClientMockRecorder) DeleteClient(arg0 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteClients", reflect.TypeOf((*MockClient)(nil).DeleteClients), arg0...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteClient", reflect.TypeOf((*MockClient)(nil).DeleteClient), arg0)
|
||||
}
|
||||
|
||||
// DeleteDHCPStaticLeases mocks base method.
|
||||
func (m *MockClient) DeleteDHCPStaticLeases(arg0 ...model.DhcpStaticLease) error {
|
||||
// DeleteDHCPStaticLease mocks base method.
|
||||
func (m *MockClient) DeleteDHCPStaticLease(arg0 model.DhcpStaticLease) error {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{}
|
||||
for _, a := range arg0 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "DeleteDHCPStaticLeases", varargs...)
|
||||
ret := m.ctrl.Call(m, "DeleteDHCPStaticLease", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DeleteDHCPStaticLeases indicates an expected call of DeleteDHCPStaticLeases.
|
||||
func (mr *MockClientMockRecorder) DeleteDHCPStaticLeases(arg0 ...interface{}) *gomock.Call {
|
||||
// DeleteDHCPStaticLease indicates an expected call of DeleteDHCPStaticLease.
|
||||
func (mr *MockClientMockRecorder) DeleteDHCPStaticLease(arg0 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteDHCPStaticLeases", reflect.TypeOf((*MockClient)(nil).DeleteDHCPStaticLeases), arg0...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteDHCPStaticLease", reflect.TypeOf((*MockClient)(nil).DeleteDHCPStaticLease), arg0)
|
||||
}
|
||||
|
||||
// DeleteFilters mocks base method.
|
||||
func (m *MockClient) DeleteFilters(arg0 bool, arg1 ...model.Filter) error {
|
||||
// DeleteFilter mocks base method.
|
||||
func (m *MockClient) DeleteFilter(arg0 bool, arg1 model.Filter) error {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{arg0}
|
||||
for _, a := range arg1 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "DeleteFilters", varargs...)
|
||||
ret := m.ctrl.Call(m, "DeleteFilter", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DeleteFilters indicates an expected call of DeleteFilters.
|
||||
func (mr *MockClientMockRecorder) DeleteFilters(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
|
||||
// DeleteFilter indicates an expected call of DeleteFilter.
|
||||
func (mr *MockClientMockRecorder) DeleteFilter(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{arg0}, arg1...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteFilters", reflect.TypeOf((*MockClient)(nil).DeleteFilters), varargs...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteFilter", reflect.TypeOf((*MockClient)(nil).DeleteFilter), arg0, arg1)
|
||||
}
|
||||
|
||||
// DeleteRewriteEntries mocks base method.
|
||||
func (m *MockClient) DeleteRewriteEntries(arg0 ...model.RewriteEntry) error {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{}
|
||||
varargs := []any{}
|
||||
for _, a := range arg0 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
@@ -250,7 +229,7 @@ func (m *MockClient) DeleteRewriteEntries(arg0 ...model.RewriteEntry) error {
|
||||
}
|
||||
|
||||
// DeleteRewriteEntries indicates an expected call of DeleteRewriteEntries.
|
||||
func (mr *MockClientMockRecorder) DeleteRewriteEntries(arg0 ...interface{}) *gomock.Call {
|
||||
func (mr *MockClientMockRecorder) DeleteRewriteEntries(arg0 ...any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteRewriteEntries", reflect.TypeOf((*MockClient)(nil).DeleteRewriteEntries), arg0...)
|
||||
}
|
||||
@@ -329,6 +308,21 @@ func (mr *MockClientMockRecorder) ProfileInfo() *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ProfileInfo", reflect.TypeOf((*MockClient)(nil).ProfileInfo))
|
||||
}
|
||||
|
||||
// QueryLog mocks base method.
|
||||
func (m *MockClient) QueryLog(arg0 int) (*model.QueryLog, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "QueryLog", arg0)
|
||||
ret0, _ := ret[0].(*model.QueryLog)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// QueryLog indicates an expected call of QueryLog.
|
||||
func (mr *MockClientMockRecorder) QueryLog(arg0 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueryLog", reflect.TypeOf((*MockClient)(nil).QueryLog), arg0)
|
||||
}
|
||||
|
||||
// QueryLogConfig mocks base method.
|
||||
func (m *MockClient) QueryLogConfig() (*model.QueryLogConfig, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -353,7 +347,7 @@ func (m *MockClient) RefreshFilters(arg0 bool) error {
|
||||
}
|
||||
|
||||
// RefreshFilters indicates an expected call of RefreshFilters.
|
||||
func (mr *MockClientMockRecorder) RefreshFilters(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockClientMockRecorder) RefreshFilters(arg0 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RefreshFilters", reflect.TypeOf((*MockClient)(nil).RefreshFilters), arg0)
|
||||
}
|
||||
@@ -412,7 +406,7 @@ func (m *MockClient) SetAccessList(arg0 *model.AccessList) error {
|
||||
}
|
||||
|
||||
// SetAccessList indicates an expected call of SetAccessList.
|
||||
func (mr *MockClientMockRecorder) SetAccessList(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockClientMockRecorder) SetAccessList(arg0 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetAccessList", reflect.TypeOf((*MockClient)(nil).SetAccessList), arg0)
|
||||
}
|
||||
@@ -426,7 +420,7 @@ func (m *MockClient) SetBlockedServices(arg0 *[]string) error {
|
||||
}
|
||||
|
||||
// SetBlockedServices indicates an expected call of SetBlockedServices.
|
||||
func (mr *MockClientMockRecorder) SetBlockedServices(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockClientMockRecorder) SetBlockedServices(arg0 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBlockedServices", reflect.TypeOf((*MockClient)(nil).SetBlockedServices), arg0)
|
||||
}
|
||||
@@ -440,7 +434,7 @@ func (m *MockClient) SetBlockedServicesSchedule(arg0 *model.BlockedServicesSched
|
||||
}
|
||||
|
||||
// SetBlockedServicesSchedule indicates an expected call of SetBlockedServicesSchedule.
|
||||
func (mr *MockClientMockRecorder) SetBlockedServicesSchedule(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockClientMockRecorder) SetBlockedServicesSchedule(arg0 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBlockedServicesSchedule", reflect.TypeOf((*MockClient)(nil).SetBlockedServicesSchedule), arg0)
|
||||
}
|
||||
@@ -454,7 +448,7 @@ func (m *MockClient) SetCustomRules(arg0 *[]string) error {
|
||||
}
|
||||
|
||||
// SetCustomRules indicates an expected call of SetCustomRules.
|
||||
func (mr *MockClientMockRecorder) SetCustomRules(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockClientMockRecorder) SetCustomRules(arg0 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetCustomRules", reflect.TypeOf((*MockClient)(nil).SetCustomRules), arg0)
|
||||
}
|
||||
@@ -468,7 +462,7 @@ func (m *MockClient) SetDNSConfig(arg0 *model.DNSConfig) error {
|
||||
}
|
||||
|
||||
// SetDNSConfig indicates an expected call of SetDNSConfig.
|
||||
func (mr *MockClientMockRecorder) SetDNSConfig(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockClientMockRecorder) SetDNSConfig(arg0 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDNSConfig", reflect.TypeOf((*MockClient)(nil).SetDNSConfig), arg0)
|
||||
}
|
||||
@@ -482,7 +476,7 @@ func (m *MockClient) SetDhcpConfig(arg0 *model.DhcpStatus) error {
|
||||
}
|
||||
|
||||
// SetDhcpConfig indicates an expected call of SetDhcpConfig.
|
||||
func (mr *MockClientMockRecorder) SetDhcpConfig(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockClientMockRecorder) SetDhcpConfig(arg0 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDhcpConfig", reflect.TypeOf((*MockClient)(nil).SetDhcpConfig), arg0)
|
||||
}
|
||||
@@ -496,7 +490,7 @@ func (m *MockClient) SetProfileInfo(arg0 *model.ProfileInfo) error {
|
||||
}
|
||||
|
||||
// SetProfileInfo indicates an expected call of SetProfileInfo.
|
||||
func (mr *MockClientMockRecorder) SetProfileInfo(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockClientMockRecorder) SetProfileInfo(arg0 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetProfileInfo", reflect.TypeOf((*MockClient)(nil).SetProfileInfo), arg0)
|
||||
}
|
||||
@@ -510,7 +504,7 @@ func (m *MockClient) SetQueryLogConfig(arg0 *model.QueryLogConfig) error {
|
||||
}
|
||||
|
||||
// SetQueryLogConfig indicates an expected call of SetQueryLogConfig.
|
||||
func (mr *MockClientMockRecorder) SetQueryLogConfig(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockClientMockRecorder) SetQueryLogConfig(arg0 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetQueryLogConfig", reflect.TypeOf((*MockClient)(nil).SetQueryLogConfig), arg0)
|
||||
}
|
||||
@@ -524,7 +518,7 @@ func (m *MockClient) SetSafeSearchConfig(arg0 *model.SafeSearchConfig) error {
|
||||
}
|
||||
|
||||
// SetSafeSearchConfig indicates an expected call of SetSafeSearchConfig.
|
||||
func (mr *MockClientMockRecorder) SetSafeSearchConfig(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockClientMockRecorder) SetSafeSearchConfig(arg0 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSafeSearchConfig", reflect.TypeOf((*MockClient)(nil).SetSafeSearchConfig), arg0)
|
||||
}
|
||||
@@ -538,7 +532,7 @@ func (m *MockClient) SetStatsConfig(arg0 *model.StatsConfig) error {
|
||||
}
|
||||
|
||||
// SetStatsConfig indicates an expected call of SetStatsConfig.
|
||||
func (mr *MockClientMockRecorder) SetStatsConfig(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockClientMockRecorder) SetStatsConfig(arg0 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetStatsConfig", reflect.TypeOf((*MockClient)(nil).SetStatsConfig), arg0)
|
||||
}
|
||||
@@ -557,6 +551,21 @@ func (mr *MockClientMockRecorder) Setup() *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Setup", reflect.TypeOf((*MockClient)(nil).Setup))
|
||||
}
|
||||
|
||||
// Stats mocks base method.
|
||||
func (m *MockClient) Stats() (*model.Stats, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Stats")
|
||||
ret0, _ := ret[0].(*model.Stats)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Stats indicates an expected call of Stats.
|
||||
func (mr *MockClientMockRecorder) Stats() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stats", reflect.TypeOf((*MockClient)(nil).Stats))
|
||||
}
|
||||
|
||||
// StatsConfig mocks base method.
|
||||
func (m *MockClient) StatsConfig() (*model.StatsConfig, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -596,7 +605,7 @@ func (m *MockClient) ToggleFiltering(arg0 bool, arg1 int) error {
|
||||
}
|
||||
|
||||
// ToggleFiltering indicates an expected call of ToggleFiltering.
|
||||
func (mr *MockClientMockRecorder) ToggleFiltering(arg0, arg1 interface{}) *gomock.Call {
|
||||
func (mr *MockClientMockRecorder) ToggleFiltering(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleFiltering", reflect.TypeOf((*MockClient)(nil).ToggleFiltering), arg0, arg1)
|
||||
}
|
||||
@@ -610,7 +619,7 @@ func (m *MockClient) ToggleParental(arg0 bool) error {
|
||||
}
|
||||
|
||||
// ToggleParental indicates an expected call of ToggleParental.
|
||||
func (mr *MockClientMockRecorder) ToggleParental(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockClientMockRecorder) ToggleParental(arg0 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleParental", reflect.TypeOf((*MockClient)(nil).ToggleParental), arg0)
|
||||
}
|
||||
@@ -624,7 +633,7 @@ func (m *MockClient) ToggleProtection(arg0 bool) error {
|
||||
}
|
||||
|
||||
// ToggleProtection indicates an expected call of ToggleProtection.
|
||||
func (mr *MockClientMockRecorder) ToggleProtection(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockClientMockRecorder) ToggleProtection(arg0 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleProtection", reflect.TypeOf((*MockClient)(nil).ToggleProtection), arg0)
|
||||
}
|
||||
@@ -638,44 +647,35 @@ func (m *MockClient) ToggleSafeBrowsing(arg0 bool) error {
|
||||
}
|
||||
|
||||
// ToggleSafeBrowsing indicates an expected call of ToggleSafeBrowsing.
|
||||
func (mr *MockClientMockRecorder) ToggleSafeBrowsing(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockClientMockRecorder) ToggleSafeBrowsing(arg0 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleSafeBrowsing", reflect.TypeOf((*MockClient)(nil).ToggleSafeBrowsing), arg0)
|
||||
}
|
||||
|
||||
// UpdateClients mocks base method.
|
||||
func (m *MockClient) UpdateClients(arg0 ...*model.Client) error {
|
||||
// UpdateClient mocks base method.
|
||||
func (m *MockClient) UpdateClient(arg0 *model.Client) error {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{}
|
||||
for _, a := range arg0 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "UpdateClients", varargs...)
|
||||
ret := m.ctrl.Call(m, "UpdateClient", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// UpdateClients indicates an expected call of UpdateClients.
|
||||
func (mr *MockClientMockRecorder) UpdateClients(arg0 ...interface{}) *gomock.Call {
|
||||
// UpdateClient indicates an expected call of UpdateClient.
|
||||
func (mr *MockClientMockRecorder) UpdateClient(arg0 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateClients", reflect.TypeOf((*MockClient)(nil).UpdateClients), arg0...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateClient", reflect.TypeOf((*MockClient)(nil).UpdateClient), arg0)
|
||||
}
|
||||
|
||||
// UpdateFilters mocks base method.
|
||||
func (m *MockClient) UpdateFilters(arg0 bool, arg1 ...model.Filter) error {
|
||||
// UpdateFilter mocks base method.
|
||||
func (m *MockClient) UpdateFilter(arg0 bool, arg1 model.Filter) error {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{arg0}
|
||||
for _, a := range arg1 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "UpdateFilters", varargs...)
|
||||
ret := m.ctrl.Call(m, "UpdateFilter", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// UpdateFilters indicates an expected call of UpdateFilters.
|
||||
func (mr *MockClientMockRecorder) UpdateFilters(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
|
||||
// UpdateFilter indicates an expected call of UpdateFilter.
|
||||
func (mr *MockClientMockRecorder) UpdateFilter(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{arg0}, arg1...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateFilters", reflect.TypeOf((*MockClient)(nil).UpdateFilters), varargs...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateFilter", reflect.TypeOf((*MockClient)(nil).UpdateFilter), arg0, arg1)
|
||||
}
|
||||
|
||||
98
pkg/mocks/flags/mock.go
Normal file
98
pkg/mocks/flags/mock.go
Normal file
@@ -0,0 +1,98 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/bakito/adguardhome-sync/pkg/config (interfaces: Flags)
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -package client -destination pkg/mocks/flags/mock.go github.com/bakito/adguardhome-sync/pkg/config Flags
|
||||
//
|
||||
|
||||
// Package client is a generated GoMock package.
|
||||
package client
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockFlags is a mock of Flags interface.
|
||||
type MockFlags struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockFlagsMockRecorder
|
||||
}
|
||||
|
||||
// MockFlagsMockRecorder is the mock recorder for MockFlags.
|
||||
type MockFlagsMockRecorder struct {
|
||||
mock *MockFlags
|
||||
}
|
||||
|
||||
// NewMockFlags creates a new mock instance.
|
||||
func NewMockFlags(ctrl *gomock.Controller) *MockFlags {
|
||||
mock := &MockFlags{ctrl: ctrl}
|
||||
mock.recorder = &MockFlagsMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockFlags) EXPECT() *MockFlagsMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Changed mocks base method.
|
||||
func (m *MockFlags) Changed(arg0 string) bool {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Changed", arg0)
|
||||
ret0, _ := ret[0].(bool)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Changed indicates an expected call of Changed.
|
||||
func (mr *MockFlagsMockRecorder) Changed(arg0 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Changed", reflect.TypeOf((*MockFlags)(nil).Changed), arg0)
|
||||
}
|
||||
|
||||
// GetBool mocks base method.
|
||||
func (m *MockFlags) GetBool(arg0 string) (bool, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetBool", arg0)
|
||||
ret0, _ := ret[0].(bool)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetBool indicates an expected call of GetBool.
|
||||
func (mr *MockFlagsMockRecorder) GetBool(arg0 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBool", reflect.TypeOf((*MockFlags)(nil).GetBool), arg0)
|
||||
}
|
||||
|
||||
// GetInt mocks base method.
|
||||
func (m *MockFlags) GetInt(arg0 string) (int, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetInt", arg0)
|
||||
ret0, _ := ret[0].(int)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetInt indicates an expected call of GetInt.
|
||||
func (mr *MockFlagsMockRecorder) GetInt(arg0 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInt", reflect.TypeOf((*MockFlags)(nil).GetInt), arg0)
|
||||
}
|
||||
|
||||
// GetString mocks base method.
|
||||
func (m *MockFlags) GetString(arg0 string) (string, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetString", arg0)
|
||||
ret0, _ := ret[0].(string)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetString indicates an expected call of GetString.
|
||||
func (mr *MockFlagsMockRecorder) GetString(arg0 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetString", reflect.TypeOf((*MockFlags)(nil).GetString), arg0)
|
||||
}
|
||||
286
pkg/sync/action-general.go
Normal file
286
pkg/sync/action-general.go
Normal file
@@ -0,0 +1,286 @@
|
||||
package sync
|
||||
|
||||
import (
|
||||
"github.com/bakito/adguardhome-sync/pkg/client"
|
||||
"github.com/bakito/adguardhome-sync/pkg/client/model"
|
||||
"github.com/bakito/adguardhome-sync/pkg/utils"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var (
|
||||
actionProfileInfo = func(ac *actionContext) error {
|
||||
if pro, err := ac.client.ProfileInfo(); err != nil {
|
||||
return err
|
||||
} else if merged := pro.ShouldSyncFor(ac.origin.profileInfo); merged != nil {
|
||||
return ac.client.SetProfileInfo(merged)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
actionProtection = func(ac *actionContext) error {
|
||||
if ac.origin.status.ProtectionEnabled != ac.replicaStatus.ProtectionEnabled {
|
||||
return ac.client.ToggleProtection(ac.origin.status.ProtectionEnabled)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
actionParental = func(ac *actionContext) error {
|
||||
if rp, err := ac.client.Parental(); err != nil {
|
||||
return err
|
||||
} else if ac.origin.parental != rp {
|
||||
return ac.client.ToggleParental(ac.origin.parental)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
actionSafeSearchConfig = func(ac *actionContext) error {
|
||||
if ssc, err := ac.client.SafeSearchConfig(); err != nil {
|
||||
return err
|
||||
} else if !ac.origin.safeSearch.Equals(ssc) {
|
||||
return ac.client.SetSafeSearchConfig(ac.origin.safeSearch)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
actionSafeBrowsing = func(ac *actionContext) error {
|
||||
if rs, err := ac.client.SafeBrowsing(); err != nil {
|
||||
return err
|
||||
} else if ac.origin.safeBrowsing != rs {
|
||||
if err = ac.client.ToggleSafeBrowsing(ac.origin.safeBrowsing); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
actionQueryLogConfig = func(ac *actionContext) error {
|
||||
qlc, err := ac.client.QueryLogConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ac.origin.queryLogConfig.Equals(qlc) {
|
||||
return ac.client.SetQueryLogConfig(ac.origin.queryLogConfig)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
actionStatsConfig = func(ac *actionContext) error {
|
||||
sc, err := ac.client.StatsConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ac.origin.statsConfig.Interval != sc.Interval {
|
||||
return ac.client.SetStatsConfig(ac.origin.statsConfig)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
actionDNSRewrites = func(ac *actionContext) error {
|
||||
replicaRewrites, err := ac.client.RewriteList()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a, r, d := replicaRewrites.Merge(ac.origin.rewrites)
|
||||
|
||||
if err = ac.client.DeleteRewriteEntries(r...); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = ac.client.AddRewriteEntries(a...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, dupl := range d {
|
||||
ac.rl.With("domain", dupl.Domain, "answer", dupl.Answer).Warn("Skipping duplicated rewrite from source")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
actionFilters = func(ac *actionContext) error {
|
||||
rf, err := ac.client.Filtering()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = syncFilterType(ac.rl, ac.origin.filters.Filters, rf.Filters, false, ac.client, ac.continueOnError); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = syncFilterType(ac.rl, ac.origin.filters.WhitelistFilters, rf.WhitelistFilters, true, ac.client, ac.continueOnError); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if utils.PtrToString(ac.origin.filters.UserRules) != utils.PtrToString(rf.UserRules) {
|
||||
return ac.client.SetCustomRules(ac.origin.filters.UserRules)
|
||||
}
|
||||
|
||||
if ac.origin.filters.Enabled != rf.Enabled || ac.origin.filters.Interval != rf.Interval {
|
||||
return ac.client.ToggleFiltering(*ac.origin.filters.Enabled, *ac.origin.filters.Interval)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
actionBlockedServices = func(ac *actionContext) error {
|
||||
rs, err := ac.client.BlockedServices()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !model.EqualsStringSlice(ac.origin.blockedServices, rs, true) {
|
||||
return ac.client.SetBlockedServices(ac.origin.blockedServices)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
actionBlockedServicesSchedule = func(ac *actionContext) error {
|
||||
rbss, err := ac.client.BlockedServicesSchedule()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !ac.origin.blockedServicesSchedule.Equals(rbss) {
|
||||
return ac.client.SetBlockedServicesSchedule(ac.origin.blockedServicesSchedule)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
actionClientSettings = func(ac *actionContext) error {
|
||||
rc, err := ac.client.Clients()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a, u, r := rc.Merge(ac.origin.clients)
|
||||
|
||||
for _, client := range r {
|
||||
if err := ac.client.DeleteClient(client); err != nil {
|
||||
ac.rl.With("client-name", client.Name, "error", err).Error("error deleting client setting")
|
||||
if !ac.continueOnError {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, client := range a {
|
||||
if err := ac.client.AddClient(client); err != nil {
|
||||
ac.rl.With("client-name", client.Name, "error", err).Error("error adding client setting")
|
||||
if !ac.continueOnError {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, client := range u {
|
||||
if err := ac.client.UpdateClient(client); err != nil {
|
||||
ac.rl.With("client-name", client.Name, "error", err).Error("error updating client setting")
|
||||
if !ac.continueOnError {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
actionDNSAccessLists = func(ac *actionContext) error {
|
||||
al, err := ac.client.AccessList()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !al.Equals(ac.origin.accessList) {
|
||||
return ac.client.SetAccessList(ac.origin.accessList)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
actionDNSServerConfig = func(ac *actionContext) error {
|
||||
dc, err := ac.client.DNSConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dc.Sanitize(ac.rl)
|
||||
|
||||
if !dc.Equals(ac.origin.dnsConfig) {
|
||||
if err = ac.client.SetDNSConfig(ac.origin.dnsConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
actionDHCPServerConfig = func(ac *actionContext) error {
|
||||
if ac.origin.dhcpServerConfig.HasConfig() {
|
||||
sc, err := ac.client.DhcpConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
origClone := ac.origin.dhcpServerConfig.Clone()
|
||||
if ac.replica.InterfaceName != "" {
|
||||
// overwrite interface name
|
||||
origClone.InterfaceName = utils.Ptr(ac.replica.InterfaceName)
|
||||
}
|
||||
if ac.replica.DHCPServerEnabled != nil {
|
||||
// overwrite dhcp enabled
|
||||
origClone.Enabled = ac.replica.DHCPServerEnabled
|
||||
}
|
||||
|
||||
if !sc.CleanAndEquals(origClone) {
|
||||
return ac.client.SetDhcpConfig(origClone)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
actionDHCPStaticLeases = func(ac *actionContext) error {
|
||||
sc, err := ac.client.DhcpConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a, r := model.MergeDhcpStaticLeases(sc.StaticLeases, ac.origin.dhcpServerConfig.StaticLeases)
|
||||
|
||||
for _, lease := range r {
|
||||
if err := ac.client.DeleteDHCPStaticLease(lease); err != nil {
|
||||
ac.rl.With("hostname", lease.Hostname, "error", err).Error("error deleting dhcp static lease")
|
||||
if !ac.continueOnError {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, lease := range a {
|
||||
if err := ac.client.AddDHCPStaticLease(lease); err != nil {
|
||||
ac.rl.With("hostname", lease.Hostname, "error", err).Error("error adding dhcp static lease")
|
||||
if !ac.continueOnError {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
)
|
||||
|
||||
func syncFilterType(rl *zap.SugaredLogger, of *[]model.Filter, rFilters *[]model.Filter, whitelist bool, replica client.Client, continueOnError bool) error {
|
||||
fa, fu, fd := model.MergeFilters(rFilters, of)
|
||||
|
||||
for _, f := range fd {
|
||||
if err := replica.DeleteFilter(whitelist, f); err != nil {
|
||||
rl.With("filter", f.Name, "url", f.Url, "whitelist", whitelist, "error", err).Error("error deleting filter")
|
||||
if !continueOnError {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, f := range fa {
|
||||
if err := replica.AddFilter(whitelist, f); err != nil {
|
||||
rl.With("filter", f.Name, "url", f.Url, "whitelist", whitelist, "error", err).Error("error adding filter")
|
||||
if !continueOnError {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, f := range fu {
|
||||
if err := replica.UpdateFilter(whitelist, f); err != nil {
|
||||
rl.With("filter", f.Name, "url", f.Url, "whitelist", whitelist, "error", err).Error("error updating filter")
|
||||
if !continueOnError {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(fa) > 0 || len(fu) > 0 {
|
||||
if err := replica.RefreshFilters(whitelist); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
104
pkg/sync/action.go
Normal file
104
pkg/sync/action.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package sync
|
||||
|
||||
import (
|
||||
"github.com/bakito/adguardhome-sync/pkg/client"
|
||||
"github.com/bakito/adguardhome-sync/pkg/client/model"
|
||||
"github.com/bakito/adguardhome-sync/pkg/types"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func setupActions(cfg *types.Config) (actions []syncAction) {
|
||||
if cfg.Features.GeneralSettings {
|
||||
actions = append(actions,
|
||||
action("profile info", actionProfileInfo),
|
||||
action("protection", actionProtection),
|
||||
action("parental", actionParental),
|
||||
action("safe search config", actionSafeSearchConfig),
|
||||
action("safe browsing", actionSafeBrowsing),
|
||||
)
|
||||
}
|
||||
if cfg.Features.QueryLogConfig {
|
||||
actions = append(actions,
|
||||
action("query log config", actionQueryLogConfig),
|
||||
)
|
||||
}
|
||||
if cfg.Features.StatsConfig {
|
||||
actions = append(actions,
|
||||
action("stats config", actionStatsConfig),
|
||||
)
|
||||
}
|
||||
if cfg.Features.DNS.Rewrites {
|
||||
actions = append(actions,
|
||||
action("DNS rewrites", actionDNSRewrites),
|
||||
)
|
||||
}
|
||||
if cfg.Features.Filters {
|
||||
actions = append(actions,
|
||||
action("actionFilters", actionFilters),
|
||||
)
|
||||
}
|
||||
if cfg.Features.Services {
|
||||
actions = append(actions,
|
||||
action("blocked services", actionBlockedServices),
|
||||
action("blocked services schedule", actionBlockedServicesSchedule),
|
||||
)
|
||||
}
|
||||
if cfg.Features.ClientSettings {
|
||||
actions = append(actions,
|
||||
action("client settings", actionClientSettings),
|
||||
)
|
||||
}
|
||||
if cfg.Features.DNS.AccessLists {
|
||||
actions = append(actions,
|
||||
action("DNS access lists", actionDNSAccessLists),
|
||||
)
|
||||
}
|
||||
|
||||
if cfg.Features.DNS.ServerConfig {
|
||||
actions = append(actions,
|
||||
action("DNS server config", actionDNSServerConfig),
|
||||
)
|
||||
}
|
||||
if cfg.Features.DHCP.ServerConfig {
|
||||
actions = append(actions,
|
||||
action("DHCP server config", actionDHCPServerConfig),
|
||||
)
|
||||
}
|
||||
if cfg.Features.DHCP.StaticLeases {
|
||||
actions = append(actions,
|
||||
action("DHCP static leases", actionDHCPStaticLeases),
|
||||
)
|
||||
}
|
||||
return actions
|
||||
}
|
||||
|
||||
type syncAction interface {
|
||||
sync(ac *actionContext) error
|
||||
name() string
|
||||
}
|
||||
|
||||
type actionContext struct {
|
||||
rl *zap.SugaredLogger
|
||||
origin *origin
|
||||
client client.Client
|
||||
replicaStatus *model.ServerStatus
|
||||
continueOnError bool
|
||||
replica types.AdGuardInstance
|
||||
}
|
||||
|
||||
type defaultAction struct {
|
||||
myName string
|
||||
doSync func(ac *actionContext) error
|
||||
}
|
||||
|
||||
func action(name string, f func(ac *actionContext) error) syncAction {
|
||||
return &defaultAction{myName: name, doSync: f}
|
||||
}
|
||||
|
||||
func (d *defaultAction) sync(ac *actionContext) error {
|
||||
return d.doSync(ac)
|
||||
}
|
||||
|
||||
func (d *defaultAction) name() string {
|
||||
return d.myName
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/bakito/adguardhome-sync/pkg/log"
|
||||
"github.com/bakito/adguardhome-sync/pkg/metrics"
|
||||
"github.com/bakito/adguardhome-sync/version"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@@ -77,6 +78,11 @@ func (w *worker) listenAndServe() {
|
||||
r.GET("/api/v1/status", w.handleStatus)
|
||||
r.GET("/favicon.ico", w.handleFavicon)
|
||||
r.GET("/", w.handleRoot)
|
||||
if w.cfg.API.Metrics.Enabled {
|
||||
r.GET("/metrics", metrics.Handler())
|
||||
|
||||
go w.startScraping()
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := httpServer.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
|
||||
@@ -101,7 +107,7 @@ func (w *worker) listenAndServe() {
|
||||
l.Fatal("os.Kill - terminating...")
|
||||
}()
|
||||
|
||||
gracefullCtx, cancelShutdown := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
gracefulCtx, cancelShutdown := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancelShutdown()
|
||||
|
||||
if w.cron != nil {
|
||||
@@ -109,7 +115,7 @@ func (w *worker) listenAndServe() {
|
||||
w.cron.Stop()
|
||||
}
|
||||
|
||||
if err := httpServer.Shutdown(gracefullCtx); err != nil {
|
||||
if err := httpServer.Shutdown(gracefulCtx); err != nil {
|
||||
l.With("error", err).Error("Shutdown error")
|
||||
defer os.Exit(1)
|
||||
} else {
|
||||
@@ -123,13 +129,15 @@ func (w *worker) listenAndServe() {
|
||||
}
|
||||
|
||||
type syncStatus struct {
|
||||
Origin replicaStatus `json:"origin"`
|
||||
Replicas []replicaStatus `json:"replicas"`
|
||||
SyncRunning bool `json:"syncRunning"`
|
||||
Origin replicaStatus `json:"origin"`
|
||||
Replicas []replicaStatus `json:"replicas"`
|
||||
}
|
||||
|
||||
type replicaStatus struct {
|
||||
Host string `json:"origin"`
|
||||
URL string `json:"url"`
|
||||
Status string `json:"status"`
|
||||
Error string `json:"error"`
|
||||
Host string `json:"host"`
|
||||
URL string `json:"url"`
|
||||
Status string `json:"status"`
|
||||
Error string `json:"error,omitempty"`
|
||||
ProtectionEnabled *bool `json:"protection_enabled"`
|
||||
}
|
||||
|
||||
50
pkg/sync/scrape.go
Normal file
50
pkg/sync/scrape.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package sync
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/bakito/adguardhome-sync/pkg/metrics"
|
||||
"github.com/bakito/adguardhome-sync/pkg/types"
|
||||
)
|
||||
|
||||
func (w *worker) startScraping() {
|
||||
metrics.Init()
|
||||
if w.cfg.API.Metrics.ScrapeInterval == 0 {
|
||||
w.cfg.API.Metrics.ScrapeInterval = 30 * time.Second
|
||||
}
|
||||
if w.cfg.API.Metrics.QueryLogLimit == 0 {
|
||||
w.cfg.API.Metrics.QueryLogLimit = 10_000
|
||||
}
|
||||
l.With(
|
||||
"scrape-interval", w.cfg.API.Metrics.ScrapeInterval,
|
||||
"query-log-limit", w.cfg.API.Metrics.QueryLogLimit,
|
||||
).Info("setup metrics")
|
||||
w.scrape()
|
||||
for range time.Tick(w.cfg.API.Metrics.ScrapeInterval) {
|
||||
w.scrape()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *worker) scrape() {
|
||||
var ims []metrics.InstanceMetrics
|
||||
|
||||
ims = append(ims, w.getMetrics(w.cfg.Origin))
|
||||
for _, replica := range w.cfg.Replicas {
|
||||
ims = append(ims, w.getMetrics(replica))
|
||||
}
|
||||
metrics.Update(ims...)
|
||||
}
|
||||
|
||||
func (w *worker) getMetrics(inst types.AdGuardInstance) (im metrics.InstanceMetrics) {
|
||||
client, err := w.createClient(inst)
|
||||
if err != nil {
|
||||
l.With("error", err, "url", w.cfg.Origin.URL).Error("Error creating origin client")
|
||||
return
|
||||
}
|
||||
|
||||
im.HostName = inst.Host
|
||||
im.Status, _ = client.Status()
|
||||
im.Stats, _ = client.Stats()
|
||||
im.QueryLog, _ = client.QueryLog(w.cfg.API.Metrics.QueryLogLimit)
|
||||
return
|
||||
}
|
||||
353
pkg/sync/sync.go
353
pkg/sync/sync.go
@@ -90,6 +90,7 @@ type worker struct {
|
||||
running bool
|
||||
cron *cron.Cron
|
||||
createClient func(instance types.AdGuardInstance) (client.Client, error)
|
||||
actions []syncAction
|
||||
}
|
||||
|
||||
func (w *worker) status() *syncStatus {
|
||||
@@ -109,26 +110,37 @@ func (w *worker) status() *syncStatus {
|
||||
return syncStatus.Replicas[i].Host < syncStatus.Replicas[j].Host
|
||||
})
|
||||
|
||||
syncStatus.SyncRunning = w.running
|
||||
|
||||
return syncStatus
|
||||
}
|
||||
|
||||
func (w *worker) getStatus(inst types.AdGuardInstance) replicaStatus {
|
||||
func (w *worker) getStatus(inst types.AdGuardInstance) (st replicaStatus) {
|
||||
st = replicaStatus{Host: inst.WebHost, URL: inst.WebURL}
|
||||
|
||||
oc, err := w.createClient(inst)
|
||||
if err != nil {
|
||||
l.With("error", err, "url", w.cfg.Origin.URL).Error("Error creating origin client")
|
||||
return replicaStatus{Host: oc.Host(), URL: inst.URL, Error: err.Error(), Status: "danger"}
|
||||
st.Status = "danger"
|
||||
st.Error = err.Error()
|
||||
return
|
||||
}
|
||||
sl := l.With("from", oc.Host())
|
||||
_, err = oc.Status()
|
||||
sl := l.With("from", inst.WebHost)
|
||||
status, err := oc.Status()
|
||||
if err != nil {
|
||||
if errors.Is(err, client.ErrSetupNeeded) {
|
||||
return replicaStatus{Host: oc.Host(), URL: inst.URL, Error: err.Error(), Status: "warning"}
|
||||
st.Status = "warning"
|
||||
st.Error = err.Error()
|
||||
return
|
||||
}
|
||||
sl.With("error", err).Error("Error getting origin status")
|
||||
return replicaStatus{Host: oc.Host(), URL: inst.URL, Error: err.Error(), Status: "danger"}
|
||||
st.Status = "danger"
|
||||
st.Error = err.Error()
|
||||
return
|
||||
}
|
||||
st := replicaStatus{Host: oc.Host(), URL: inst.URL, Status: "success"}
|
||||
return st
|
||||
st.Status = "success"
|
||||
st.ProtectionEnabled = utils.Ptr(status.ProtectionEnabled)
|
||||
return
|
||||
}
|
||||
|
||||
func (w *worker) sync() {
|
||||
@@ -203,7 +215,7 @@ func (w *worker) sync() {
|
||||
|
||||
o.filters, err = oc.Filtering()
|
||||
if err != nil {
|
||||
sl.With("error", err).Error("Error getting origin filters")
|
||||
sl.With("error", err).Error("Error getting origin actionFilters")
|
||||
return
|
||||
}
|
||||
o.clients, err = oc.Clients()
|
||||
@@ -242,6 +254,8 @@ func (w *worker) sync() {
|
||||
}
|
||||
}
|
||||
|
||||
w.actions = setupActions(w.cfg)
|
||||
|
||||
replicas := w.cfg.UniqueReplicas()
|
||||
for _, replica := range replicas {
|
||||
w.syncTo(sl, o, replica)
|
||||
@@ -258,66 +272,37 @@ func (w *worker) syncTo(l *zap.SugaredLogger, o *origin, replica types.AdGuardIn
|
||||
rl := l.With("to", rc.Host())
|
||||
rl.Info("Start sync")
|
||||
|
||||
rs, err := w.statusWithSetup(rl, replica, rc)
|
||||
replicaStatus, err := w.statusWithSetup(rl, replica, rc)
|
||||
if err != nil {
|
||||
rl.With("error", err).Error("Error getting replica status")
|
||||
return
|
||||
}
|
||||
|
||||
rl.With("version", rs.Version).Info("Connected to replica")
|
||||
rl.With("version", replicaStatus.Version).Info("Connected to replica")
|
||||
|
||||
if versions.IsNewerThan(versions.MinAgh, rs.Version) {
|
||||
rl.With("error", err, "version", rs.Version).Errorf("Replica AdGuard Home version must be >= %s", versions.MinAgh)
|
||||
if versions.IsNewerThan(versions.MinAgh, replicaStatus.Version) {
|
||||
rl.With("error", err, "version", replicaStatus.Version).Errorf("Replica AdGuard Home version must be >= %s", versions.MinAgh)
|
||||
return
|
||||
}
|
||||
|
||||
if o.status.Version != rs.Version {
|
||||
rl.With("originVersion", o.status.Version, "replicaVersion", rs.Version).Warn("Versions do not match")
|
||||
if o.status.Version != replicaStatus.Version {
|
||||
rl.With("originVersion", o.status.Version, "replicaVersion", replicaStatus.Version).Warn("Versions do not match")
|
||||
}
|
||||
|
||||
err = w.syncGeneralSettings(o, rs, rc)
|
||||
if err != nil {
|
||||
rl.With("error", err).Error("Error syncing general settings")
|
||||
return
|
||||
ac := &actionContext{
|
||||
continueOnError: w.cfg.ContinueOnError,
|
||||
rl: rl,
|
||||
origin: o,
|
||||
replicaStatus: replicaStatus,
|
||||
client: rc,
|
||||
replica: replica,
|
||||
}
|
||||
|
||||
err = w.syncConfigs(o, rc)
|
||||
if err != nil {
|
||||
rl.With("error", err).Error("Error syncing configs")
|
||||
return
|
||||
}
|
||||
|
||||
err = w.syncRewrites(rl, o.rewrites, rc)
|
||||
if err != nil {
|
||||
rl.With("error", err).Error("Error syncing rewrites")
|
||||
return
|
||||
}
|
||||
err = w.syncFilters(o.filters, rc)
|
||||
if err != nil {
|
||||
rl.With("error", err).Error("Error syncing filters")
|
||||
return
|
||||
}
|
||||
|
||||
err = w.syncServices(o.blockedServices, o.blockedServicesSchedule, rc)
|
||||
if err != nil {
|
||||
rl.With("error", err).Error("Error syncing blockedServices")
|
||||
return
|
||||
}
|
||||
|
||||
if err = w.syncClients(o.clients, rc); err != nil {
|
||||
rl.With("error", err).Error("Error syncing clients")
|
||||
return
|
||||
}
|
||||
|
||||
if err = w.syncDNS(o.accessList, o.dnsConfig, rc); err != nil {
|
||||
rl.With("error", err).Error("Error syncing dns")
|
||||
return
|
||||
}
|
||||
|
||||
if w.cfg.Features.DHCP.ServerConfig || w.cfg.Features.DHCP.StaticLeases {
|
||||
if err = w.syncDHCPServer(o.dhcpServerConfig, rc, replica); err != nil {
|
||||
rl.With("error", err).Error("Error syncing dhcp")
|
||||
return
|
||||
for _, action := range w.actions {
|
||||
if err := action.sync(ac); err != nil {
|
||||
rl.With("error", err).Errorf("Error syncing %s", action.name())
|
||||
if !w.cfg.ContinueOnError {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,260 +324,6 @@ func (w *worker) statusWithSetup(rl *zap.SugaredLogger, replica types.AdGuardIns
|
||||
return rs, err
|
||||
}
|
||||
|
||||
func (w *worker) syncServices(os *model.BlockedServicesArray, obss *model.BlockedServicesSchedule, replica client.Client) error {
|
||||
if w.cfg.Features.Services {
|
||||
rs, err := replica.BlockedServices()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !model.EqualsStringSlice(os, rs, true) {
|
||||
if err := replica.SetBlockedServices(os); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
rbss, err := replica.BlockedServicesSchedule()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !obss.Equals(rbss) {
|
||||
if err := replica.SetBlockedServicesSchedule(obss); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *worker) syncFilters(of *model.FilterStatus, replica client.Client) error {
|
||||
if w.cfg.Features.Filters {
|
||||
rf, err := replica.Filtering()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = w.syncFilterType(of.Filters, rf.Filters, false, replica); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = w.syncFilterType(of.WhitelistFilters, rf.WhitelistFilters, true, replica); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if utils.PtrToString(of.UserRules) != utils.PtrToString(rf.UserRules) {
|
||||
return replica.SetCustomRules(of.UserRules)
|
||||
}
|
||||
|
||||
if of.Enabled != rf.Enabled || of.Interval != rf.Interval {
|
||||
if err = replica.ToggleFiltering(*of.Enabled, *of.Interval); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *worker) syncFilterType(of *[]model.Filter, rFilters *[]model.Filter, whitelist bool, replica client.Client) error {
|
||||
fa, fu, fd := model.MergeFilters(rFilters, of)
|
||||
|
||||
if err := replica.DeleteFilters(whitelist, fd...); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := replica.AddFilters(whitelist, fa...); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := replica.UpdateFilters(whitelist, fu...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(fa) > 0 || len(fu) > 0 {
|
||||
if err := replica.RefreshFilters(whitelist); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *worker) syncRewrites(rl *zap.SugaredLogger, or *model.RewriteEntries, replica client.Client) error {
|
||||
if w.cfg.Features.DNS.Rewrites {
|
||||
replicaRewrites, err := replica.RewriteList()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a, r, d := replicaRewrites.Merge(or)
|
||||
|
||||
if err = replica.DeleteRewriteEntries(r...); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = replica.AddRewriteEntries(a...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, dupl := range d {
|
||||
rl.With("domain", dupl.Domain, "answer", dupl.Answer).Warn("Skipping duplicated rewrite from source")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *worker) syncClients(oc *model.Clients, replica client.Client) error {
|
||||
if w.cfg.Features.ClientSettings {
|
||||
rc, err := replica.Clients()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a, u, r := rc.Merge(oc)
|
||||
|
||||
if err = replica.DeleteClients(r...); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = replica.AddClients(a...); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = replica.UpdateClients(u...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *worker) syncGeneralSettings(o *origin, rs *model.ServerStatus, replica client.Client) error {
|
||||
if w.cfg.Features.GeneralSettings {
|
||||
|
||||
if pro, err := replica.ProfileInfo(); err != nil {
|
||||
return err
|
||||
} else if merged := pro.ShouldSyncFor(o.profileInfo); merged != nil {
|
||||
if err = replica.SetProfileInfo(merged); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if o.status.ProtectionEnabled != rs.ProtectionEnabled {
|
||||
if err := replica.ToggleProtection(o.status.ProtectionEnabled); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if rp, err := replica.Parental(); err != nil {
|
||||
return err
|
||||
} else if o.parental != rp {
|
||||
if err = replica.ToggleParental(o.parental); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if ssc, err := replica.SafeSearchConfig(); err != nil {
|
||||
return err
|
||||
} else if !o.safeSearch.Equals(ssc) {
|
||||
if err = replica.SetSafeSearchConfig(o.safeSearch); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if rs, err := replica.SafeBrowsing(); err != nil {
|
||||
return err
|
||||
} else if o.safeBrowsing != rs {
|
||||
if err = replica.ToggleSafeBrowsing(o.safeBrowsing); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *worker) syncConfigs(o *origin, rc client.Client) error {
|
||||
if w.cfg.Features.QueryLogConfig {
|
||||
qlc, err := rc.QueryLogConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !o.queryLogConfig.Equals(qlc) {
|
||||
if err = rc.SetQueryLogConfig(o.queryLogConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if w.cfg.Features.StatsConfig {
|
||||
sc, err := rc.StatsConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if o.statsConfig.Interval != sc.Interval {
|
||||
if err = rc.SetStatsConfig(o.statsConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *worker) syncDNS(oal *model.AccessList, odc *model.DNSConfig, rc client.Client) error {
|
||||
if w.cfg.Features.DNS.AccessLists {
|
||||
al, err := rc.AccessList()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !al.Equals(oal) {
|
||||
if err = rc.SetAccessList(oal); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if w.cfg.Features.DNS.ServerConfig {
|
||||
dc, err := rc.DNSConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !dc.Equals(odc) {
|
||||
if err = rc.SetDNSConfig(odc); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *worker) syncDHCPServer(osc *model.DhcpStatus, rc client.Client, replica types.AdGuardInstance) error {
|
||||
if !w.cfg.Features.DHCP.ServerConfig && !w.cfg.Features.DHCP.StaticLeases {
|
||||
return nil
|
||||
}
|
||||
sc, err := rc.DhcpConfig()
|
||||
if w.cfg.Features.DHCP.ServerConfig && osc.HasConfig() {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
origClone := osc.Clone()
|
||||
if replica.InterfaceName != "" {
|
||||
// overwrite interface name
|
||||
origClone.InterfaceName = utils.Ptr(replica.InterfaceName)
|
||||
}
|
||||
if replica.DHCPServerEnabled != nil {
|
||||
// overwrite dhcp enabled
|
||||
origClone.Enabled = replica.DHCPServerEnabled
|
||||
}
|
||||
|
||||
if !sc.Equals(origClone) {
|
||||
if err = rc.SetDhcpConfig(origClone); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if w.cfg.Features.DHCP.StaticLeases {
|
||||
a, r := model.MergeDhcpStaticLeases(sc.StaticLeases, osc.StaticLeases)
|
||||
|
||||
if err = rc.DeleteDHCPStaticLeases(r...); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = rc.AddDHCPStaticLeases(a...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type origin struct {
|
||||
status *model.ServerStatus
|
||||
rewrites *model.RewriteEntries
|
||||
|
||||
@@ -9,20 +9,19 @@ import (
|
||||
"github.com/bakito/adguardhome-sync/pkg/types"
|
||||
"github.com/bakito/adguardhome-sync/pkg/utils"
|
||||
"github.com/bakito/adguardhome-sync/pkg/versions"
|
||||
gm "github.com/golang/mock/gomock"
|
||||
"github.com/google/uuid"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
gm "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
var boolTrue = true
|
||||
|
||||
var _ = Describe("Sync", func() {
|
||||
var (
|
||||
mockCtrl *gm.Controller
|
||||
cl *clientmock.MockClient
|
||||
w *worker
|
||||
te error
|
||||
ac *actionContext
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
@@ -50,16 +49,38 @@ var _ = Describe("Sync", func() {
|
||||
StatsConfig: true,
|
||||
QueryLogConfig: true,
|
||||
},
|
||||
Replicas: []types.AdGuardInstance{
|
||||
{},
|
||||
},
|
||||
},
|
||||
}
|
||||
te = errors.New(uuid.NewString())
|
||||
|
||||
ac = &actionContext{
|
||||
continueOnError: false,
|
||||
rl: l,
|
||||
origin: &origin{
|
||||
profileInfo: &model.ProfileInfo{
|
||||
Name: "origin",
|
||||
Language: "en",
|
||||
Theme: "auto",
|
||||
},
|
||||
status: &model.ServerStatus{},
|
||||
safeSearch: &model.SafeSearchConfig{},
|
||||
queryLogConfig: &model.QueryLogConfig{},
|
||||
statsConfig: &model.StatsConfig{},
|
||||
},
|
||||
replicaStatus: &model.ServerStatus{},
|
||||
client: cl,
|
||||
replica: w.cfg.Replicas[0],
|
||||
}
|
||||
})
|
||||
AfterEach(func() {
|
||||
defer mockCtrl.Finish()
|
||||
})
|
||||
|
||||
Context("worker", func() {
|
||||
Context("syncRewrites", func() {
|
||||
Context("actionDNSRewrites", func() {
|
||||
var (
|
||||
domain string
|
||||
answer string
|
||||
@@ -70,289 +91,238 @@ var _ = Describe("Sync", func() {
|
||||
BeforeEach(func() {
|
||||
domain = uuid.NewString()
|
||||
answer = uuid.NewString()
|
||||
reO = []model.RewriteEntry{{Domain: utils.Ptr(domain), Answer: utils.Ptr(answer)}}
|
||||
reR = []model.RewriteEntry{{Domain: utils.Ptr(domain), Answer: utils.Ptr(answer)}}
|
||||
reO = model.RewriteEntries{{Domain: utils.Ptr(domain), Answer: utils.Ptr(answer)}}
|
||||
reR = model.RewriteEntries{{Domain: utils.Ptr(domain), Answer: utils.Ptr(answer)}}
|
||||
})
|
||||
It("should have no changes (empty slices)", func() {
|
||||
ac.origin.rewrites = &reO
|
||||
cl.EXPECT().RewriteList().Return(&reR, nil)
|
||||
cl.EXPECT().AddRewriteEntries()
|
||||
cl.EXPECT().DeleteRewriteEntries()
|
||||
err := w.syncRewrites(l, &reO, cl)
|
||||
err := actionDNSRewrites(ac)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
It("should add one rewrite entry", func() {
|
||||
reR = []model.RewriteEntry{}
|
||||
ac.origin.rewrites = &reO
|
||||
cl.EXPECT().RewriteList().Return(&reR, nil)
|
||||
cl.EXPECT().AddRewriteEntries(reO[0])
|
||||
cl.EXPECT().DeleteRewriteEntries()
|
||||
err := w.syncRewrites(l, &reO, cl)
|
||||
err := actionDNSRewrites(ac)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
It("should remove one rewrite entry", func() {
|
||||
reO = []model.RewriteEntry{}
|
||||
ac.origin.rewrites = &reO
|
||||
cl.EXPECT().RewriteList().Return(&reR, nil)
|
||||
cl.EXPECT().AddRewriteEntries()
|
||||
cl.EXPECT().DeleteRewriteEntries(reR[0])
|
||||
err := w.syncRewrites(l, &reO, cl)
|
||||
err := actionDNSRewrites(ac)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
It("should remove one rewrite entry", func() {
|
||||
reO = []model.RewriteEntry{}
|
||||
ac.origin.rewrites = &reO
|
||||
cl.EXPECT().RewriteList().Return(&reR, nil)
|
||||
cl.EXPECT().AddRewriteEntries()
|
||||
cl.EXPECT().DeleteRewriteEntries(reR[0])
|
||||
err := w.syncRewrites(l, &reO, cl)
|
||||
err := actionDNSRewrites(ac)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
It("should return error when error on RewriteList()", func() {
|
||||
ac.origin.rewrites = &reO
|
||||
cl.EXPECT().RewriteList().Return(nil, te)
|
||||
err := w.syncRewrites(l, &reO, cl)
|
||||
err := actionDNSRewrites(ac)
|
||||
Ω(err).Should(HaveOccurred())
|
||||
})
|
||||
It("should return error when error on AddRewriteEntries()", func() {
|
||||
ac.origin.rewrites = &reO
|
||||
cl.EXPECT().RewriteList().Return(&reR, nil)
|
||||
cl.EXPECT().DeleteRewriteEntries()
|
||||
cl.EXPECT().AddRewriteEntries().Return(te)
|
||||
err := w.syncRewrites(l, &reO, cl)
|
||||
err := actionDNSRewrites(ac)
|
||||
Ω(err).Should(HaveOccurred())
|
||||
})
|
||||
It("should return error when error on DeleteRewriteEntries()", func() {
|
||||
ac.origin.rewrites = &reO
|
||||
cl.EXPECT().RewriteList().Return(&reR, nil)
|
||||
cl.EXPECT().DeleteRewriteEntries().Return(te)
|
||||
err := w.syncRewrites(l, &reO, cl)
|
||||
err := actionDNSRewrites(ac)
|
||||
Ω(err).Should(HaveOccurred())
|
||||
})
|
||||
})
|
||||
Context("syncClients", func() {
|
||||
Context("actionClientSettings", func() {
|
||||
var (
|
||||
clO *model.Clients
|
||||
clR *model.Clients
|
||||
name string
|
||||
)
|
||||
BeforeEach(func() {
|
||||
name = uuid.NewString()
|
||||
clO = &model.Clients{Clients: &model.ClientsArray{{Name: utils.Ptr(name)}}}
|
||||
ac.origin.clients = &model.Clients{Clients: &model.ClientsArray{{Name: utils.Ptr(name)}}}
|
||||
clR = &model.Clients{Clients: &model.ClientsArray{{Name: utils.Ptr(name)}}}
|
||||
})
|
||||
It("should have no changes (empty slices)", func() {
|
||||
cl.EXPECT().Clients().Return(clR, nil)
|
||||
cl.EXPECT().AddClients()
|
||||
cl.EXPECT().UpdateClients()
|
||||
cl.EXPECT().DeleteClients()
|
||||
err := w.syncClients(clO, cl)
|
||||
err := actionClientSettings(ac)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
It("should add one client", func() {
|
||||
clR.Clients = &model.ClientsArray{}
|
||||
cl.EXPECT().Clients().Return(clR, nil)
|
||||
cl.EXPECT().AddClients(&(*clO.Clients)[0])
|
||||
cl.EXPECT().UpdateClients()
|
||||
cl.EXPECT().DeleteClients()
|
||||
err := w.syncClients(clO, cl)
|
||||
cl.EXPECT().AddClient(&(*ac.origin.clients.Clients)[0])
|
||||
err := actionClientSettings(ac)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
It("should update one client", func() {
|
||||
(*clR.Clients)[0].FilteringEnabled = utils.Ptr(true)
|
||||
cl.EXPECT().Clients().Return(clR, nil)
|
||||
cl.EXPECT().AddClients()
|
||||
cl.EXPECT().UpdateClients(&(*clO.Clients)[0])
|
||||
cl.EXPECT().DeleteClients()
|
||||
err := w.syncClients(clO, cl)
|
||||
cl.EXPECT().UpdateClient(&(*ac.origin.clients.Clients)[0])
|
||||
err := actionClientSettings(ac)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
It("should delete one client", func() {
|
||||
clO.Clients = &model.ClientsArray{}
|
||||
ac.origin.clients.Clients = &model.ClientsArray{}
|
||||
cl.EXPECT().Clients().Return(clR, nil)
|
||||
cl.EXPECT().AddClients()
|
||||
cl.EXPECT().UpdateClients()
|
||||
cl.EXPECT().DeleteClients(&(*clR.Clients)[0])
|
||||
err := w.syncClients(clO, cl)
|
||||
cl.EXPECT().DeleteClient(&(*clR.Clients)[0])
|
||||
err := actionClientSettings(ac)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
It("should return error when error on Clients()", func() {
|
||||
cl.EXPECT().Clients().Return(nil, te)
|
||||
err := w.syncClients(clO, cl)
|
||||
Ω(err).Should(HaveOccurred())
|
||||
})
|
||||
It("should return error when error on AddClients()", func() {
|
||||
cl.EXPECT().Clients().Return(clR, nil)
|
||||
cl.EXPECT().DeleteClients()
|
||||
cl.EXPECT().AddClients().Return(te)
|
||||
err := w.syncClients(clO, cl)
|
||||
Ω(err).Should(HaveOccurred())
|
||||
})
|
||||
It("should return error when error on UpdateClients()", func() {
|
||||
cl.EXPECT().Clients().Return(clR, nil)
|
||||
cl.EXPECT().DeleteClients()
|
||||
cl.EXPECT().AddClients()
|
||||
cl.EXPECT().UpdateClients().Return(te)
|
||||
err := w.syncClients(clO, cl)
|
||||
Ω(err).Should(HaveOccurred())
|
||||
})
|
||||
It("should return error when error on DeleteClients()", func() {
|
||||
cl.EXPECT().Clients().Return(clR, nil)
|
||||
cl.EXPECT().DeleteClients().Return(te)
|
||||
err := w.syncClients(clO, cl)
|
||||
err := actionClientSettings(ac)
|
||||
Ω(err).Should(HaveOccurred())
|
||||
})
|
||||
})
|
||||
Context("syncGeneralSettings", func() {
|
||||
var (
|
||||
o *origin
|
||||
rs *model.ServerStatus
|
||||
)
|
||||
BeforeEach(func() {
|
||||
o = &origin{
|
||||
profileInfo: &model.ProfileInfo{
|
||||
Name: "origin",
|
||||
Language: "en",
|
||||
Theme: "auto",
|
||||
},
|
||||
status: &model.ServerStatus{},
|
||||
safeSearch: &model.SafeSearchConfig{},
|
||||
}
|
||||
rs = &model.ServerStatus{}
|
||||
})
|
||||
Context("actionParental", func() {
|
||||
It("should have no changes", func() {
|
||||
cl.EXPECT().Parental()
|
||||
cl.EXPECT().ProfileInfo().Return(o.profileInfo, nil)
|
||||
cl.EXPECT().SafeSearchConfig().Return(o.safeSearch, nil)
|
||||
cl.EXPECT().SafeBrowsing()
|
||||
err := w.syncGeneralSettings(o, rs, cl)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
It("should have protection enabled changes", func() {
|
||||
o.status.ProtectionEnabled = true
|
||||
cl.EXPECT().ToggleProtection(true)
|
||||
cl.EXPECT().Parental()
|
||||
cl.EXPECT().ProfileInfo().Return(o.profileInfo, nil)
|
||||
cl.EXPECT().SafeSearchConfig().Return(o.safeSearch, nil)
|
||||
cl.EXPECT().SafeBrowsing()
|
||||
err := w.syncGeneralSettings(o, rs, cl)
|
||||
err := actionParental(ac)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
It("should have parental enabled changes", func() {
|
||||
o.parental = true
|
||||
ac.origin.parental = true
|
||||
cl.EXPECT().Parental()
|
||||
cl.EXPECT().ToggleParental(true)
|
||||
cl.EXPECT().ProfileInfo().Return(o.profileInfo, nil)
|
||||
cl.EXPECT().SafeSearchConfig().Return(o.safeSearch, nil)
|
||||
cl.EXPECT().SafeBrowsing()
|
||||
err := w.syncGeneralSettings(o, rs, cl)
|
||||
err := actionParental(ac)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
})
|
||||
Context("actionProtection", func() {
|
||||
It("should have no changes", func() {
|
||||
err := actionProtection(ac)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
It("should have protection enabled changes", func() {
|
||||
ac.origin.status.ProtectionEnabled = true
|
||||
cl.EXPECT().ToggleProtection(true)
|
||||
err := actionProtection(ac)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
})
|
||||
Context("actionSafeSearchConfig", func() {
|
||||
It("should have no changes", func() {
|
||||
cl.EXPECT().SafeSearchConfig().Return(ac.origin.safeSearch, nil)
|
||||
|
||||
err := actionSafeSearchConfig(ac)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
It("should have safeSearch enabled changes", func() {
|
||||
o.safeSearch = &model.SafeSearchConfig{Enabled: utils.Ptr(true)}
|
||||
cl.EXPECT().Parental()
|
||||
ac.origin.safeSearch = &model.SafeSearchConfig{Enabled: utils.Ptr(true)}
|
||||
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil)
|
||||
cl.EXPECT().ProfileInfo().Return(o.profileInfo, nil)
|
||||
cl.EXPECT().SetSafeSearchConfig(o.safeSearch)
|
||||
cl.EXPECT().SafeBrowsing()
|
||||
err := w.syncGeneralSettings(o, rs, cl)
|
||||
cl.EXPECT().SetSafeSearchConfig(ac.origin.safeSearch)
|
||||
err := actionSafeSearchConfig(ac)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
It("should have Duckduckgo safeSearch enabled changed", func() {
|
||||
o.safeSearch = &model.SafeSearchConfig{Duckduckgo: utils.Ptr(true)}
|
||||
cl.EXPECT().Parental()
|
||||
cl.EXPECT().ProfileInfo().Return(o.profileInfo, nil)
|
||||
ac.origin.safeSearch = &model.SafeSearchConfig{Duckduckgo: utils.Ptr(true)}
|
||||
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{Google: utils.Ptr(true)}, nil)
|
||||
cl.EXPECT().SafeBrowsing()
|
||||
cl.EXPECT().SetSafeSearchConfig(o.safeSearch)
|
||||
|
||||
err := w.syncGeneralSettings(o, rs, cl)
|
||||
cl.EXPECT().SetSafeSearchConfig(ac.origin.safeSearch)
|
||||
err := actionSafeSearchConfig(ac)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
})
|
||||
Context("actionProfileInfo", func() {
|
||||
It("should have no changes", func() {
|
||||
cl.EXPECT().ProfileInfo().Return(ac.origin.profileInfo, nil)
|
||||
err := actionProfileInfo(ac)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
It("should have profileInfo language changed", func() {
|
||||
o.profileInfo.Language = "de"
|
||||
cl.EXPECT().Parental()
|
||||
ac.origin.profileInfo.Language = "de"
|
||||
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{Name: "replica", Language: "en"}, nil)
|
||||
cl.EXPECT().SafeSearchConfig().Return(o.safeSearch, nil)
|
||||
cl.EXPECT().SafeBrowsing()
|
||||
cl.EXPECT().SetProfileInfo(&model.ProfileInfo{
|
||||
Language: "de",
|
||||
Name: "replica",
|
||||
Theme: "auto",
|
||||
})
|
||||
err := w.syncGeneralSettings(o, rs, cl)
|
||||
err := actionProfileInfo(ac)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
It("should not sync profileInfo if language is not set", func() {
|
||||
o.profileInfo.Language = ""
|
||||
cl.EXPECT().Parental()
|
||||
ac.origin.profileInfo.Language = ""
|
||||
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{Name: "replica", Language: "en", Theme: "auto"}, nil)
|
||||
cl.EXPECT().SafeSearchConfig().Return(o.safeSearch, nil)
|
||||
cl.EXPECT().SafeBrowsing()
|
||||
cl.EXPECT().SetProfileInfo(o.profileInfo).Times(0)
|
||||
err := w.syncGeneralSettings(o, rs, cl)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
It("should not sync profileInfo if language is not set", func() {
|
||||
o.profileInfo.Language = ""
|
||||
cl.EXPECT().Parental()
|
||||
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{Name: "replica", Language: "en", Theme: "auto"}, nil)
|
||||
cl.EXPECT().SafeSearchConfig().Return(o.safeSearch, nil)
|
||||
cl.EXPECT().SafeBrowsing()
|
||||
cl.EXPECT().SetProfileInfo(o.profileInfo).Times(0)
|
||||
err := w.syncGeneralSettings(o, rs, cl)
|
||||
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() {
|
||||
o.profileInfo.Theme = ""
|
||||
cl.EXPECT().Parental()
|
||||
ac.origin.profileInfo.Theme = ""
|
||||
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{Name: "replica", Language: "en", Theme: "auto"}, nil)
|
||||
cl.EXPECT().SafeSearchConfig().Return(o.safeSearch, nil)
|
||||
cl.EXPECT().SafeBrowsing()
|
||||
cl.EXPECT().SetProfileInfo(o.profileInfo).Times(0)
|
||||
err := w.syncGeneralSettings(o, rs, cl)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
It("should have safeBrowsing enabled changes", func() {
|
||||
o.safeBrowsing = true
|
||||
cl.EXPECT().Parental()
|
||||
cl.EXPECT().ProfileInfo().Return(o.profileInfo, nil)
|
||||
cl.EXPECT().SafeSearchConfig().Return(o.safeSearch, nil)
|
||||
cl.EXPECT().SafeBrowsing()
|
||||
cl.EXPECT().ToggleSafeBrowsing(true)
|
||||
err := w.syncGeneralSettings(o, rs, cl)
|
||||
cl.EXPECT().SetProfileInfo(ac.origin.profileInfo).Times(0)
|
||||
err := actionProfileInfo(ac)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
})
|
||||
Context("syncConfigs", func() {
|
||||
var (
|
||||
o *origin
|
||||
qlc *model.QueryLogConfig
|
||||
sc *model.StatsConfig
|
||||
)
|
||||
Context("actionSafeBrowsing", func() {
|
||||
It("should have no changes", func() {
|
||||
cl.EXPECT().SafeBrowsing()
|
||||
err := actionSafeBrowsing(ac)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should have safeBrowsing enabled changes", func() {
|
||||
ac.origin.safeBrowsing = true
|
||||
cl.EXPECT().SafeBrowsing()
|
||||
cl.EXPECT().ToggleSafeBrowsing(true)
|
||||
err := actionSafeBrowsing(ac)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
})
|
||||
Context("actionQueryLogConfig", func() {
|
||||
var qlc *model.QueryLogConfig
|
||||
BeforeEach(func() {
|
||||
o = &origin{
|
||||
queryLogConfig: &model.QueryLogConfig{},
|
||||
statsConfig: &model.StatsConfig{},
|
||||
}
|
||||
qlc = &model.QueryLogConfig{}
|
||||
sc = &model.StatsConfig{}
|
||||
})
|
||||
It("should have no changes", func() {
|
||||
cl.EXPECT().QueryLogConfig().Return(qlc, nil)
|
||||
cl.EXPECT().StatsConfig().Return(sc, nil)
|
||||
err := w.syncConfigs(o, cl)
|
||||
err := actionQueryLogConfig(ac)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
It("should have QueryLogConfig changes", func() {
|
||||
var interval model.QueryLogConfigInterval = 123
|
||||
o.queryLogConfig.Interval = &interval
|
||||
ac.origin.queryLogConfig.Interval = &interval
|
||||
cl.EXPECT().QueryLogConfig().Return(qlc, nil)
|
||||
cl.EXPECT().SetQueryLogConfig(&model.QueryLogConfig{AnonymizeClientIp: nil, Interval: &interval, Enabled: nil})
|
||||
err := actionQueryLogConfig(ac)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
})
|
||||
Context("syncConfigs", func() {
|
||||
var sc *model.StatsConfig
|
||||
BeforeEach(func() {
|
||||
sc = &model.StatsConfig{}
|
||||
})
|
||||
It("should have no changes", func() {
|
||||
cl.EXPECT().StatsConfig().Return(sc, nil)
|
||||
err := w.syncConfigs(o, cl)
|
||||
err := actionStatsConfig(ac)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
It("should have StatsConfig changes", func() {
|
||||
var interval model.StatsConfigInterval = 123
|
||||
o.statsConfig.Interval = &interval
|
||||
cl.EXPECT().QueryLogConfig().Return(qlc, nil)
|
||||
ac.origin.statsConfig.Interval = &interval
|
||||
cl.EXPECT().StatsConfig().Return(sc, nil)
|
||||
cl.EXPECT().SetStatsConfig(&model.StatsConfig{Interval: &interval})
|
||||
err := w.syncConfigs(o, cl)
|
||||
err := actionStatsConfig(ac)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
})
|
||||
@@ -389,161 +359,209 @@ var _ = Describe("Sync", func() {
|
||||
Ω(st).Should(BeNil())
|
||||
})
|
||||
})
|
||||
Context("syncServices", func() {
|
||||
var (
|
||||
obs *model.BlockedServicesArray
|
||||
rbs *model.BlockedServicesArray
|
||||
obss *model.BlockedServicesSchedule
|
||||
)
|
||||
Context("actionBlockedServices", func() {
|
||||
var rbs *model.BlockedServicesArray
|
||||
BeforeEach(func() {
|
||||
obs = &model.BlockedServicesArray{"foo"}
|
||||
ac.origin.blockedServices = &model.BlockedServicesArray{"foo"}
|
||||
rbs = &model.BlockedServicesArray{"foo"}
|
||||
obss = &model.BlockedServicesSchedule{}
|
||||
})
|
||||
It("should have no changes", func() {
|
||||
cl.EXPECT().BlockedServices().Return(rbs, nil)
|
||||
cl.EXPECT().BlockedServicesSchedule().Return(obss, nil)
|
||||
err := w.syncServices(obs, obss, cl)
|
||||
err := actionBlockedServices(ac)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
It("should have blockedServices changes", func() {
|
||||
obs = &model.BlockedServicesArray{"bar"}
|
||||
ac.origin.blockedServices = &model.BlockedServicesArray{"bar"}
|
||||
|
||||
cl.EXPECT().BlockedServices().Return(rbs, nil)
|
||||
cl.EXPECT().BlockedServicesSchedule().Return(obss, nil)
|
||||
cl.EXPECT().SetBlockedServices(obs)
|
||||
err := w.syncServices(obs, obss, cl)
|
||||
cl.EXPECT().SetBlockedServices(ac.origin.blockedServices)
|
||||
err := actionBlockedServices(ac)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
})
|
||||
Context("actionBlockedServicesSchedule", func() {
|
||||
var rbss *model.BlockedServicesSchedule
|
||||
BeforeEach(func() {
|
||||
ac.origin.blockedServicesSchedule = &model.BlockedServicesSchedule{}
|
||||
rbss = &model.BlockedServicesSchedule{}
|
||||
})
|
||||
It("should have no changes", func() {
|
||||
cl.EXPECT().BlockedServicesSchedule().Return(ac.origin.blockedServicesSchedule, nil)
|
||||
err := actionBlockedServicesSchedule(ac)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
It("should have blockedServices schedule changes", func() {
|
||||
ac.origin.blockedServicesSchedule = &model.BlockedServicesSchedule{Ids: utils.Ptr([]string{"bar"})}
|
||||
|
||||
cl.EXPECT().BlockedServicesSchedule().Return(rbss, nil)
|
||||
cl.EXPECT().SetBlockedServicesSchedule(ac.origin.blockedServicesSchedule)
|
||||
err := actionBlockedServicesSchedule(ac)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
})
|
||||
Context("syncFilters", func() {
|
||||
var (
|
||||
of *model.FilterStatus
|
||||
rf *model.FilterStatus
|
||||
)
|
||||
var rf *model.FilterStatus
|
||||
BeforeEach(func() {
|
||||
of = &model.FilterStatus{}
|
||||
ac.origin.filters = &model.FilterStatus{}
|
||||
rf = &model.FilterStatus{}
|
||||
})
|
||||
It("should have no changes", func() {
|
||||
cl.EXPECT().Filtering().Return(rf, nil)
|
||||
cl.EXPECT().AddFilters(false)
|
||||
cl.EXPECT().UpdateFilters(false)
|
||||
cl.EXPECT().DeleteFilters(false)
|
||||
cl.EXPECT().AddFilters(true)
|
||||
cl.EXPECT().UpdateFilters(true)
|
||||
cl.EXPECT().DeleteFilters(true)
|
||||
err := w.syncFilters(of, cl)
|
||||
err := actionFilters(ac)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
It("should have changes user roles", func() {
|
||||
of.UserRules = utils.Ptr([]string{"foo"})
|
||||
ac.origin.filters.UserRules = utils.Ptr([]string{"foo"})
|
||||
cl.EXPECT().Filtering().Return(rf, nil)
|
||||
cl.EXPECT().AddFilters(false)
|
||||
cl.EXPECT().UpdateFilters(false)
|
||||
cl.EXPECT().DeleteFilters(false)
|
||||
cl.EXPECT().AddFilters(true)
|
||||
cl.EXPECT().UpdateFilters(true)
|
||||
cl.EXPECT().DeleteFilters(true)
|
||||
cl.EXPECT().SetCustomRules(of.UserRules)
|
||||
err := w.syncFilters(of, cl)
|
||||
cl.EXPECT().SetCustomRules(ac.origin.filters.UserRules)
|
||||
err := actionFilters(ac)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
It("should have changed filtering config", func() {
|
||||
of.Enabled = utils.Ptr(true)
|
||||
of.Interval = utils.Ptr(123)
|
||||
ac.origin.filters.Enabled = utils.Ptr(true)
|
||||
ac.origin.filters.Interval = utils.Ptr(123)
|
||||
cl.EXPECT().Filtering().Return(rf, nil)
|
||||
cl.EXPECT().AddFilters(false)
|
||||
cl.EXPECT().UpdateFilters(false)
|
||||
cl.EXPECT().DeleteFilters(false)
|
||||
cl.EXPECT().AddFilters(true)
|
||||
cl.EXPECT().UpdateFilters(true)
|
||||
cl.EXPECT().DeleteFilters(true)
|
||||
cl.EXPECT().ToggleFiltering(*of.Enabled, *of.Interval)
|
||||
err := w.syncFilters(of, cl)
|
||||
cl.EXPECT().ToggleFiltering(*ac.origin.filters.Enabled, *ac.origin.filters.Interval)
|
||||
err := actionFilters(ac)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
It("should add a filter", func() {
|
||||
ac.origin.filters.Filters = utils.Ptr([]model.Filter{{Name: "foo", Url: "https://foo.bar"}})
|
||||
cl.EXPECT().Filtering().Return(rf, nil)
|
||||
cl.EXPECT().AddFilter(false, model.Filter{Name: "foo", Url: "https://foo.bar"})
|
||||
cl.EXPECT().RefreshFilters(gm.Any())
|
||||
err := actionFilters(ac)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
It("should delete a filter", func() {
|
||||
rf.Filters = utils.Ptr([]model.Filter{{Name: "foo", Url: "https://foo.bar"}})
|
||||
cl.EXPECT().Filtering().Return(rf, nil)
|
||||
cl.EXPECT().DeleteFilter(false, model.Filter{Name: "foo", Url: "https://foo.bar"})
|
||||
err := actionFilters(ac)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
It("should update a filter", func() {
|
||||
ac.origin.filters.Filters = utils.Ptr([]model.Filter{{Name: "foo", Url: "https://foo.bar", Enabled: true}})
|
||||
rf.Filters = utils.Ptr([]model.Filter{{Name: "foo", Url: "https://foo.bar"}})
|
||||
cl.EXPECT().Filtering().Return(rf, nil)
|
||||
cl.EXPECT().UpdateFilter(false, model.Filter{Name: "foo", Url: "https://foo.bar", Enabled: true})
|
||||
cl.EXPECT().RefreshFilters(gm.Any())
|
||||
err := actionFilters(ac)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should abort after failed added filter", func() {
|
||||
ac.continueOnError = false
|
||||
ac.origin.filters.Filters = utils.Ptr([]model.Filter{{Name: "foo", Url: "https://foo.bar"}})
|
||||
cl.EXPECT().Filtering().Return(rf, nil)
|
||||
cl.EXPECT().AddFilter(false, model.Filter{Name: "foo", Url: "https://foo.bar"}).Return(errors.New("test failure"))
|
||||
err := actionFilters(ac)
|
||||
Ω(err).Should(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should continue after failed added filter", func() {
|
||||
ac.continueOnError = true
|
||||
ac.origin.filters.Filters = utils.Ptr([]model.Filter{{Name: "foo", Url: "https://foo.bar"}, {Name: "bar", Url: "https://bar.foo"}})
|
||||
cl.EXPECT().Filtering().Return(rf, nil)
|
||||
cl.EXPECT().AddFilter(false, model.Filter{Name: "foo", Url: "https://foo.bar"}).Return(errors.New("test failure"))
|
||||
cl.EXPECT().AddFilter(false, model.Filter{Name: "bar", Url: "https://bar.foo"})
|
||||
cl.EXPECT().RefreshFilters(gm.Any())
|
||||
err := actionFilters(ac)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
Context("syncDNS", func() {
|
||||
var (
|
||||
oal *model.AccessList
|
||||
ral *model.AccessList
|
||||
odc *model.DNSConfig
|
||||
rdc *model.DNSConfig
|
||||
)
|
||||
Context("actionDNSAccessLists", func() {
|
||||
var ral *model.AccessList
|
||||
BeforeEach(func() {
|
||||
oal = &model.AccessList{}
|
||||
ac.origin.accessList = &model.AccessList{}
|
||||
ral = &model.AccessList{}
|
||||
odc = &model.DNSConfig{}
|
||||
rdc = &model.DNSConfig{}
|
||||
})
|
||||
It("should have no changes", func() {
|
||||
cl.EXPECT().AccessList().Return(ral, nil)
|
||||
cl.EXPECT().DNSConfig().Return(rdc, nil)
|
||||
err := w.syncDNS(oal, odc, cl)
|
||||
err := actionDNSAccessLists(ac)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
It("should have access list changes", func() {
|
||||
ral.BlockedHosts = utils.Ptr([]string{"foo"})
|
||||
cl.EXPECT().AccessList().Return(ral, nil)
|
||||
cl.EXPECT().DNSConfig().Return(rdc, nil)
|
||||
cl.EXPECT().SetAccessList(oal)
|
||||
err := w.syncDNS(oal, odc, cl)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
It("should have dns config changes", func() {
|
||||
rdc.BootstrapDns = utils.Ptr([]string{"foo"})
|
||||
cl.EXPECT().AccessList().Return(ral, nil)
|
||||
cl.EXPECT().DNSConfig().Return(rdc, nil)
|
||||
cl.EXPECT().SetDNSConfig(odc)
|
||||
err := w.syncDNS(oal, odc, cl)
|
||||
cl.EXPECT().SetAccessList(ac.origin.accessList)
|
||||
err := actionDNSAccessLists(ac)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
Context("syncDHCPServer", func() {
|
||||
var (
|
||||
osc *model.DhcpStatus
|
||||
rsc *model.DhcpStatus
|
||||
)
|
||||
Context("actionDNSServerConfig", func() {
|
||||
var rdc *model.DNSConfig
|
||||
BeforeEach(func() {
|
||||
osc = &model.DhcpStatus{V4: &model.DhcpConfigV4{
|
||||
GatewayIp: utils.Ptr("1.2.3.4"),
|
||||
RangeStart: utils.Ptr("1.2.3.5"),
|
||||
RangeEnd: utils.Ptr("1.2.3.6"),
|
||||
SubnetMask: utils.Ptr("255.255.255.0"),
|
||||
}}
|
||||
ac.origin.dnsConfig = &model.DNSConfig{}
|
||||
rdc = &model.DNSConfig{}
|
||||
})
|
||||
It("should have no changes", func() {
|
||||
cl.EXPECT().DNSConfig().Return(rdc, nil)
|
||||
err := actionDNSServerConfig(ac)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
It("should have dns config changes", func() {
|
||||
rdc.BootstrapDns = utils.Ptr([]string{"foo"})
|
||||
cl.EXPECT().DNSConfig().Return(rdc, nil)
|
||||
cl.EXPECT().SetDNSConfig(ac.origin.dnsConfig)
|
||||
err := actionDNSServerConfig(ac)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
Context("actionDHCPServerConfig", func() {
|
||||
var rsc *model.DhcpStatus
|
||||
BeforeEach(func() {
|
||||
ac.origin.dhcpServerConfig = &model.DhcpStatus{
|
||||
V4: &model.DhcpConfigV4{
|
||||
GatewayIp: utils.Ptr("1.2.3.4"),
|
||||
RangeStart: utils.Ptr("1.2.3.5"),
|
||||
RangeEnd: utils.Ptr("1.2.3.6"),
|
||||
SubnetMask: utils.Ptr("255.255.255.0"),
|
||||
},
|
||||
}
|
||||
rsc = &model.DhcpStatus{}
|
||||
w.cfg.Features.DHCP.StaticLeases = false
|
||||
})
|
||||
It("should have no changes", func() {
|
||||
rsc.V4 = osc.V4
|
||||
rsc.V4 = ac.origin.dhcpServerConfig.V4
|
||||
cl.EXPECT().DhcpConfig().Return(rsc, nil)
|
||||
err := w.syncDHCPServer(osc, cl, types.AdGuardInstance{})
|
||||
err := actionDHCPServerConfig(ac)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
It("should have changes", func() {
|
||||
rsc.Enabled = utils.Ptr(true)
|
||||
cl.EXPECT().DhcpConfig().Return(rsc, nil)
|
||||
cl.EXPECT().SetDhcpConfig(osc)
|
||||
err := w.syncDHCPServer(osc, cl, types.AdGuardInstance{})
|
||||
cl.EXPECT().SetDhcpConfig(ac.origin.dhcpServerConfig)
|
||||
err := actionDHCPServerConfig(ac)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
It("should use replica interface name", func() {
|
||||
ac.replica.InterfaceName = "foo"
|
||||
cl.EXPECT().DhcpConfig().Return(rsc, nil)
|
||||
oscClone := osc.Clone()
|
||||
oscClone := ac.origin.dhcpServerConfig.Clone()
|
||||
oscClone.InterfaceName = utils.Ptr("foo")
|
||||
cl.EXPECT().SetDhcpConfig(oscClone)
|
||||
err := w.syncDHCPServer(osc, cl, types.AdGuardInstance{InterfaceName: "foo"})
|
||||
err := actionDHCPServerConfig(ac)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
It("should enable the target dhcp server", func() {
|
||||
ac.replica.DHCPServerEnabled = utils.Ptr(true)
|
||||
cl.EXPECT().DhcpConfig().Return(rsc, nil)
|
||||
oscClone := osc.Clone()
|
||||
oscClone := ac.origin.dhcpServerConfig.Clone()
|
||||
oscClone.Enabled = utils.Ptr(true)
|
||||
cl.EXPECT().SetDhcpConfig(oscClone)
|
||||
err := w.syncDHCPServer(osc, cl, types.AdGuardInstance{DHCPServerEnabled: &boolTrue})
|
||||
err := actionDHCPServerConfig(ac)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
It("should not sync empty IPv4", func() {
|
||||
ac.replica.DHCPServerEnabled = utils.Ptr(false)
|
||||
ac.origin.dhcpServerConfig.V4 = &model.DhcpConfigV4{
|
||||
GatewayIp: utils.Ptr(""),
|
||||
}
|
||||
err := actionDHCPServerConfig(ac)
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
})
|
||||
@@ -604,23 +622,12 @@ var _ = Describe("Sync", func() {
|
||||
cl.EXPECT().AddRewriteEntries()
|
||||
cl.EXPECT().DeleteRewriteEntries()
|
||||
cl.EXPECT().Filtering().Return(&model.FilterStatus{}, nil)
|
||||
cl.EXPECT().AddFilters(false)
|
||||
cl.EXPECT().UpdateFilters(false)
|
||||
cl.EXPECT().DeleteFilters(false)
|
||||
cl.EXPECT().AddFilters(true)
|
||||
cl.EXPECT().UpdateFilters(true)
|
||||
cl.EXPECT().DeleteFilters(true)
|
||||
cl.EXPECT().BlockedServices()
|
||||
cl.EXPECT().BlockedServicesSchedule()
|
||||
cl.EXPECT().Clients().Return(&model.Clients{}, nil)
|
||||
cl.EXPECT().AddClients()
|
||||
cl.EXPECT().UpdateClients()
|
||||
cl.EXPECT().DeleteClients()
|
||||
cl.EXPECT().AccessList().Return(&model.AccessList{}, nil)
|
||||
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
|
||||
cl.EXPECT().DhcpConfig().Return(&model.DhcpStatus{}, nil)
|
||||
cl.EXPECT().AddDHCPStaticLeases().Return(nil)
|
||||
cl.EXPECT().DeleteDHCPStaticLeases().Return(nil)
|
||||
w.sync()
|
||||
})
|
||||
It("should not sync DHCP", func() {
|
||||
@@ -656,18 +663,9 @@ var _ = Describe("Sync", func() {
|
||||
cl.EXPECT().AddRewriteEntries()
|
||||
cl.EXPECT().DeleteRewriteEntries()
|
||||
cl.EXPECT().Filtering().Return(&model.FilterStatus{}, nil)
|
||||
cl.EXPECT().AddFilters(false)
|
||||
cl.EXPECT().UpdateFilters(false)
|
||||
cl.EXPECT().DeleteFilters(false)
|
||||
cl.EXPECT().AddFilters(true)
|
||||
cl.EXPECT().UpdateFilters(true)
|
||||
cl.EXPECT().DeleteFilters(true)
|
||||
cl.EXPECT().BlockedServices()
|
||||
cl.EXPECT().BlockedServicesSchedule()
|
||||
cl.EXPECT().Clients().Return(&model.Clients{}, nil)
|
||||
cl.EXPECT().AddClients()
|
||||
cl.EXPECT().UpdateClients()
|
||||
cl.EXPECT().DeleteClients()
|
||||
cl.EXPECT().AccessList().Return(&model.AccessList{}, nil)
|
||||
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
|
||||
w.sync()
|
||||
|
||||
@@ -4,33 +4,61 @@ import (
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func NewFeatures(enabled bool) Features {
|
||||
return Features{
|
||||
DNS: DNS{
|
||||
AccessLists: enabled,
|
||||
ServerConfig: enabled,
|
||||
Rewrites: enabled,
|
||||
},
|
||||
DHCP: DHCP{
|
||||
ServerConfig: enabled,
|
||||
StaticLeases: enabled,
|
||||
},
|
||||
GeneralSettings: enabled,
|
||||
QueryLogConfig: enabled,
|
||||
StatsConfig: enabled,
|
||||
ClientSettings: enabled,
|
||||
Services: enabled,
|
||||
Filters: enabled,
|
||||
}
|
||||
}
|
||||
|
||||
// Features feature flags
|
||||
type Features struct {
|
||||
DNS DNS `json:"dns" yaml:"dns"`
|
||||
DHCP DHCP `json:"dhcp" yaml:"dhcp"`
|
||||
GeneralSettings bool `json:"generalSettings" yaml:"generalSettings"`
|
||||
QueryLogConfig bool `json:"queryLogConfig" yaml:"queryLogConfig"`
|
||||
StatsConfig bool `json:"statsConfig" yaml:"statsConfig"`
|
||||
ClientSettings bool `json:"clientSettings" yaml:"clientSettings"`
|
||||
Services bool `json:"services" yaml:"services"`
|
||||
Filters bool `json:"filters" yaml:"filters"`
|
||||
GeneralSettings bool `json:"generalSettings" yaml:"generalSettings" env:"FEATURES_GENERAL_SETTINGS"`
|
||||
QueryLogConfig bool `json:"queryLogConfig" yaml:"queryLogConfig" env:"FEATURES_QUERY_LOG_CONFIG"`
|
||||
StatsConfig bool `json:"statsConfig" yaml:"statsConfig" env:"FEATURES_STATS_CONFIG"`
|
||||
ClientSettings bool `json:"clientSettings" yaml:"clientSettings" env:"FEATURES_CLIENT_SETTINGS"`
|
||||
Services bool `json:"services" yaml:"services" env:"FEATURES_SERVICES"`
|
||||
Filters bool `json:"filters" yaml:"filters" env:"FEATURES_FILTERS"`
|
||||
}
|
||||
|
||||
// DHCP features
|
||||
type DHCP struct {
|
||||
ServerConfig bool `json:"serverConfig" yaml:"serverConfig"`
|
||||
StaticLeases bool `json:"staticLeases" yaml:"staticLeases"`
|
||||
ServerConfig bool `json:"serverConfig" yaml:"serverConfig" env:"FEATURES_DHCP_SERVER_CONFIG"`
|
||||
StaticLeases bool `json:"staticLeases" yaml:"staticLeases" env:"FEATURES_DHCP_STATIC_LEASES"`
|
||||
}
|
||||
|
||||
// DNS features
|
||||
type DNS struct {
|
||||
AccessLists bool `json:"accessLists" yaml:"accessLists"`
|
||||
ServerConfig bool `json:"serverConfig" yaml:"serverConfig"`
|
||||
Rewrites bool `json:"rewrites" yaml:"rewrites"`
|
||||
AccessLists bool `json:"accessLists" yaml:"accessLists" env:"FEATURES_DNS_ACCESS_LISTS"`
|
||||
ServerConfig bool `json:"serverConfig" yaml:"serverConfig" env:"FEATURES_DNS_SERVER_CONFIG"`
|
||||
Rewrites bool `json:"rewrites" yaml:"rewrites" env:"FEATURES_DNS_REWRITES"`
|
||||
}
|
||||
|
||||
// LogDisabled log all disabled features
|
||||
func (f *Features) LogDisabled(l *zap.SugaredLogger) {
|
||||
features := f.collectDisabled()
|
||||
|
||||
if len(features) > 0 {
|
||||
l.With("features", features).Info("Disabled features")
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Features) collectDisabled() []string {
|
||||
var features []string
|
||||
if !f.DHCP.ServerConfig {
|
||||
features = append(features, "DHCP.ServerConfig")
|
||||
@@ -65,8 +93,5 @@ func (f *Features) LogDisabled(l *zap.SugaredLogger) {
|
||||
if !f.Filters {
|
||||
features = append(features, "Filters")
|
||||
}
|
||||
|
||||
if len(features) > 0 {
|
||||
l.With("features", features).Info("Disabled features")
|
||||
}
|
||||
return features
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@ package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
@@ -14,30 +17,52 @@ const (
|
||||
// Config application configuration struct
|
||||
// +k8s:deepcopy-gen=true
|
||||
type Config struct {
|
||||
Origin AdGuardInstance `json:"origin" yaml:"origin"`
|
||||
Replica *AdGuardInstance `json:"replica,omitempty" yaml:"replica,omitempty"`
|
||||
Replicas []AdGuardInstance `json:"replicas,omitempty" yaml:"replicas,omitempty"`
|
||||
Cron string `json:"cron,omitempty" yaml:"cron,omitempty"`
|
||||
RunOnStart bool `json:"runOnStart,omitempty" yaml:"runOnStart,omitempty"`
|
||||
API API `json:"api,omitempty" yaml:"api,omitempty"`
|
||||
Features Features `json:"features,omitempty" yaml:"features,omitempty"`
|
||||
Origin AdGuardInstance `json:"origin" yaml:"origin" 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"`
|
||||
PrintConfigOnly bool `json:"printConfigOnly,omitempty" yaml:"printConfigOnly,omitempty" env:"PRINT_CONFIG_ONLY"`
|
||||
ContinueOnError bool `json:"continueOnError,omitempty" yaml:"continueOnError,omitempty" env:"CONTINUE_ON_ERROR"`
|
||||
API API `json:"api,omitempty" yaml:"api,omitempty" env:"API"`
|
||||
Features Features `json:"features,omitempty" yaml:"features,omitempty" env:"FEATURES_"`
|
||||
}
|
||||
|
||||
// API configuration
|
||||
type API struct {
|
||||
Port int `json:"port,omitempty" yaml:"port,omitempty"`
|
||||
Username string `json:"username,omitempty" yaml:"username,omitempty"`
|
||||
Password string `json:"password,omitempty" yaml:"password,omitempty"`
|
||||
DarkMode bool `json:"darkMode,omitempty" yaml:"darkMode,omitempty"`
|
||||
Port int `json:"port,omitempty" yaml:"port,omitempty" env:"API_PORT"`
|
||||
Username string `json:"username,omitempty" yaml:"username,omitempty" env:"API_USERNAME"`
|
||||
Password string `json:"password,omitempty" yaml:"password,omitempty" env:"API_PASSWORD"`
|
||||
DarkMode bool `json:"darkMode,omitempty" yaml:"darkMode,omitempty" env:"API_DARK_MODE"`
|
||||
Metrics Metrics `json:"metrics,omitempty" yaml:"metrics,omitempty" env:"API_METRICS"`
|
||||
}
|
||||
|
||||
// Metrics configuration
|
||||
type Metrics struct {
|
||||
Enabled bool `json:"enabled,omitempty" yaml:"enabled,omitempty" env:"API_METRICS_ENABLED"`
|
||||
ScrapeInterval time.Duration `json:"scrapeInterval,omitempty" yaml:"scrapeInterval,omitempty" env:"API_METRICS_SCRAPE_INTERVAL"`
|
||||
QueryLogLimit int `json:"queryLogLimit,omitempty" yaml:"queryLogLimit,omitempty" env:"API_METRICS_QUERY_LOG_LIMIT"`
|
||||
}
|
||||
|
||||
// Mask maks username and password
|
||||
func (a *API) Mask() {
|
||||
a.Username = mask(a.Username)
|
||||
a.Password = mask(a.Password)
|
||||
}
|
||||
|
||||
// UniqueReplicas get unique replication instances
|
||||
func (cfg *Config) UniqueReplicas() []AdGuardInstance {
|
||||
dedup := make(map[string]AdGuardInstance)
|
||||
if cfg.Replica != nil && cfg.Replica.URL != "" {
|
||||
if cfg.Replica.APIPath == "" {
|
||||
cfg.Replica.APIPath = DefaultAPIPath
|
||||
}
|
||||
dedup[cfg.Replica.Key()] = *cfg.Replica
|
||||
}
|
||||
for _, replica := range cfg.Replicas {
|
||||
if replica.APIPath == "" {
|
||||
replica.APIPath = DefaultAPIPath
|
||||
}
|
||||
if replica.URL != "" {
|
||||
dedup[replica.Key()] = replica
|
||||
}
|
||||
@@ -45,9 +70,6 @@ func (cfg *Config) UniqueReplicas() []AdGuardInstance {
|
||||
|
||||
var r []AdGuardInstance
|
||||
for _, replica := range dedup {
|
||||
if replica.APIPath == "" {
|
||||
replica.APIPath = DefaultAPIPath
|
||||
}
|
||||
r = append(r, replica)
|
||||
}
|
||||
return r
|
||||
@@ -55,6 +77,11 @@ func (cfg *Config) UniqueReplicas() []AdGuardInstance {
|
||||
|
||||
// Log the current config
|
||||
func (cfg *Config) Log(l *zap.SugaredLogger) {
|
||||
c := cfg.mask()
|
||||
l.With("config", c).Debug("Using config")
|
||||
}
|
||||
|
||||
func (cfg *Config) mask() *Config {
|
||||
c := cfg.DeepCopy()
|
||||
c.Origin.Mask()
|
||||
if c.Replica != nil {
|
||||
@@ -67,21 +94,39 @@ func (cfg *Config) Log(l *zap.SugaredLogger) {
|
||||
for i := range c.Replicas {
|
||||
c.Replicas[i].Mask()
|
||||
}
|
||||
l.With("config", c).Debug("Using config")
|
||||
c.API.Mask()
|
||||
return c
|
||||
}
|
||||
|
||||
func (cfg *Config) Init() error {
|
||||
if err := cfg.Origin.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range cfg.Replicas {
|
||||
replica := &cfg.Replicas[i]
|
||||
if err := replica.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AdGuardInstance AdguardHome config instance
|
||||
// +k8s:deepcopy-gen=true
|
||||
type AdGuardInstance struct {
|
||||
URL string `json:"url" yaml:"url"`
|
||||
APIPath string `json:"apiPath,omitempty" yaml:"apiPath,omitempty"`
|
||||
Username string `json:"username,omitempty" yaml:"username,omitempty"`
|
||||
Password string `json:"password,omitempty" yaml:"password,omitempty"`
|
||||
Cookie string `json:"cookie,omitempty" yaml:"cookie,omitempty"`
|
||||
InsecureSkipVerify bool `json:"insecureSkipVerify" yaml:"insecureSkipVerify"`
|
||||
AutoSetup bool `json:"autoSetup" yaml:"autoSetup"`
|
||||
InterfaceName string `json:"interfaceName" yaml:"interfaceName"`
|
||||
DHCPServerEnabled *bool `json:"dhcpServerEnabled,omitempty" yaml:"dhcpServerEnabled,omitempty"`
|
||||
URL string `json:"url" yaml:"url" env:"URL"`
|
||||
WebURL string `json:"webURL" yaml:"webURL" env:"WEB_URL"`
|
||||
APIPath string `json:"apiPath,omitempty" yaml:"apiPath,omitempty" env:"API_PATH"`
|
||||
Username string `json:"username,omitempty" yaml:"username,omitempty" env:"USERNAME"`
|
||||
Password string `json:"password,omitempty" yaml:"password,omitempty" env:"PASSWORD"`
|
||||
Cookie string `json:"cookie,omitempty" yaml:"cookie,omitempty" env:"COOKIE"`
|
||||
InsecureSkipVerify bool `json:"insecureSkipVerify" yaml:"insecureSkipVerify" env:"INSECURE_SKIP_VERIFY"`
|
||||
AutoSetup bool `json:"autoSetup" yaml:"autoSetup" env:"AUTO_SETUP"`
|
||||
InterfaceName string `json:"interfaceName,omitempty" yaml:"interfaceName,omitempty" env:"INTERFACE_NAME"`
|
||||
DHCPServerEnabled *bool `json:"dhcpServerEnabled,omitempty" yaml:"dhcpServerEnabled,omitempty" env:"DHCP_SERVER_ENABLED"`
|
||||
|
||||
Host string `json:"-" yaml:"-"`
|
||||
WebHost string `json:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
// Key AdGuardInstance key
|
||||
@@ -95,11 +140,32 @@ func (i *AdGuardInstance) Mask() {
|
||||
i.Password = mask(i.Password)
|
||||
}
|
||||
|
||||
func (i *AdGuardInstance) Init() error {
|
||||
u, err := url.Parse(i.URL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
i.Host = u.Host
|
||||
|
||||
if i.WebURL == "" {
|
||||
i.WebHost = i.Host
|
||||
i.WebURL = i.URL
|
||||
} else {
|
||||
u, err := url.Parse(i.WebURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
i.WebHost = u.Host
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func mask(s string) string {
|
||||
if s == "" {
|
||||
return "***"
|
||||
}
|
||||
return fmt.Sprintf("%v***%v", string(s[0]), string(s[len(s)-1]))
|
||||
mask := strings.Repeat("*", len(s)-2)
|
||||
return fmt.Sprintf("%v%s%v", string(s[0]), mask, string(s[len(s)-1]))
|
||||
}
|
||||
|
||||
// Protection API struct
|
||||
|
||||
13
pkg/types/types_suite_test.go
Normal file
13
pkg/types/types_suite_test.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package types_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestSync(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Types Suite")
|
||||
}
|
||||
99
pkg/types/types_test.go
Normal file
99
pkg/types/types_test.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Types", func() {
|
||||
Context("AdGuardInstance", func() {
|
||||
var inst AdGuardInstance
|
||||
|
||||
BeforeEach(func() {
|
||||
inst = AdGuardInstance{}
|
||||
})
|
||||
Context("Instance Init", func() {
|
||||
BeforeEach(func() {
|
||||
inst.URL = "https://localhost:3000"
|
||||
})
|
||||
It("should correctly set Host and WebHost if only URL is set", func() {
|
||||
err := inst.Init()
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
Ω(inst.Host).Should(Equal("localhost:3000"))
|
||||
Ω(inst.WebHost).Should(Equal("localhost:3000"))
|
||||
Ω(inst.URL).Should(Equal("https://localhost:3000"))
|
||||
Ω(inst.WebURL).Should(Equal("https://localhost:3000"))
|
||||
})
|
||||
It("should correctly set Host and WebHost if URL and WebURL are set", func() {
|
||||
inst.WebURL = "https://127.0.0.1:4000"
|
||||
err := inst.Init()
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
Ω(inst.Host).Should(Equal("localhost:3000"))
|
||||
Ω(inst.WebHost).Should(Equal("127.0.0.1:4000"))
|
||||
Ω(inst.WebURL).Should(Equal(inst.WebURL))
|
||||
Ω(inst.URL).Should(Equal("https://localhost:3000"))
|
||||
Ω(inst.WebURL).Should(Equal("https://127.0.0.1:4000"))
|
||||
})
|
||||
})
|
||||
})
|
||||
Context("Config", func() {
|
||||
Context("init", func() {
|
||||
cfg := Config{
|
||||
Replicas: []AdGuardInstance{
|
||||
{URL: "https://localhost:3000"},
|
||||
},
|
||||
}
|
||||
err := cfg.Init()
|
||||
Ω(err).ShouldNot(HaveOccurred())
|
||||
Ω(cfg.Replicas[0].Host).Should(Equal("localhost:3000"))
|
||||
Ω(cfg.Replicas[0].WebHost).Should(Equal("localhost:3000"))
|
||||
Ω(cfg.Replicas[0].URL).Should(Equal("https://localhost:3000"))
|
||||
Ω(cfg.Replicas[0].WebURL).Should(Equal("https://localhost:3000"))
|
||||
})
|
||||
Context("UniqueReplicas", func() {
|
||||
It("should return unique replicas in the array", func() {
|
||||
cfg := Config{
|
||||
Replicas: []AdGuardInstance{
|
||||
{URL: "a"},
|
||||
{URL: "a", APIPath: DefaultAPIPath},
|
||||
{URL: "a", APIPath: "foo"},
|
||||
{URL: "b", APIPath: DefaultAPIPath},
|
||||
},
|
||||
Replica: &AdGuardInstance{URL: "b"},
|
||||
}
|
||||
replicas := cfg.UniqueReplicas()
|
||||
Ω(replicas).Should(HaveLen(3))
|
||||
})
|
||||
})
|
||||
Context("mask", func() {
|
||||
It("should mask all names and passwords", func() {
|
||||
cfg := Config{
|
||||
Replicas: []AdGuardInstance{
|
||||
{URL: "a", Username: "user", Password: "pass"},
|
||||
},
|
||||
Replica: &AdGuardInstance{URL: "a", Username: "user", Password: "pass"},
|
||||
API: API{Username: "user", Password: "pass"},
|
||||
}
|
||||
masked := cfg.mask()
|
||||
Ω(masked.Replicas[0].Username).Should(Equal("u**r"))
|
||||
Ω(masked.Replicas[0].Password).Should(Equal("p**s"))
|
||||
Ω(masked.Replica.Username).Should(Equal("u**r"))
|
||||
Ω(masked.Replica.Password).Should(Equal("p**s"))
|
||||
Ω(masked.API.Username).Should(Equal("u**r"))
|
||||
Ω(masked.API.Password).Should(Equal("p**s"))
|
||||
})
|
||||
})
|
||||
})
|
||||
Context("Feature", func() {
|
||||
Context("LogDisabled", func() {
|
||||
It("should log all features", func() {
|
||||
f := NewFeatures(false)
|
||||
Ω(f.collectDisabled()).Should(HaveLen(11))
|
||||
})
|
||||
It("should log no features", func() {
|
||||
f := NewFeatures(true)
|
||||
Ω(f.collectDisabled()).Should(BeEmpty())
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
2
testdata/.gitignore
vendored
Normal file
2
testdata/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
docker-compose.yaml
|
||||
!config_test_*.yaml
|
||||
37
testdata/config_test_replica.yaml
vendored
Normal file
37
testdata/config_test_replica.yaml
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
origin:
|
||||
url: https://origin-file:443
|
||||
webURL: https://origin-file:443
|
||||
apiPath: /control
|
||||
username: foo
|
||||
password: '*********'
|
||||
insecureSkipVerify: true
|
||||
autoSetup: false
|
||||
replica:
|
||||
url: https://replica-file:443
|
||||
webURL: https://replica-file:443
|
||||
apiPath: /control
|
||||
username: bar
|
||||
password: '*********'
|
||||
insecureSkipVerify: false
|
||||
autoSetup: false
|
||||
interfaceName: eth3
|
||||
dhcpServerEnabled: false
|
||||
cron: '*/15 * * * *'
|
||||
runOnStart: true
|
||||
printConfigOnly: true
|
||||
api:
|
||||
port: 9090
|
||||
features:
|
||||
dns:
|
||||
accessLists: true
|
||||
serverConfig: false
|
||||
rewrites: true
|
||||
dhcp:
|
||||
serverConfig: true
|
||||
staticLeases: true
|
||||
generalSettings: true
|
||||
queryLogConfig: true
|
||||
statsConfig: true
|
||||
clientSettings: true
|
||||
services: true
|
||||
filters: true
|
||||
37
testdata/config_test_replicas.yaml
vendored
Normal file
37
testdata/config_test_replicas.yaml
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
origin:
|
||||
url: https://origin-file:443
|
||||
webURL: https://origin-file:443
|
||||
apiPath: /control
|
||||
username: foo
|
||||
password: '*********'
|
||||
insecureSkipVerify: true
|
||||
autoSetup: false
|
||||
replicas:
|
||||
- url: https://replica-file:443
|
||||
webURL: https://replica-file:443
|
||||
apiPath: /control
|
||||
username: bar
|
||||
password: '*********'
|
||||
insecureSkipVerify: false
|
||||
autoSetup: false
|
||||
interfaceName: eth3
|
||||
dhcpServerEnabled: false
|
||||
cron: '*/15 * * * *'
|
||||
runOnStart: true
|
||||
printConfigOnly: true
|
||||
api:
|
||||
port: 9090
|
||||
features:
|
||||
dns:
|
||||
accessLists: true
|
||||
serverConfig: false
|
||||
rewrites: true
|
||||
dhcp:
|
||||
serverConfig: true
|
||||
staticLeases: true
|
||||
generalSettings: true
|
||||
queryLogConfig: true
|
||||
statsConfig: true
|
||||
clientSettings: true
|
||||
services: true
|
||||
filters: true
|
||||
47
testdata/config_test_replicas_and_replica.yaml
vendored
Normal file
47
testdata/config_test_replicas_and_replica.yaml
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
origin:
|
||||
url: https://origin-file:443
|
||||
webURL: https://origin-file:443
|
||||
apiPath: /control
|
||||
username: foo
|
||||
password: '*********'
|
||||
insecureSkipVerify: true
|
||||
autoSetup: false
|
||||
replica:
|
||||
url: https://replica-file:443
|
||||
webURL: https://replica-file:443
|
||||
apiPath: /control
|
||||
username: bar
|
||||
password: '*********'
|
||||
insecureSkipVerify: false
|
||||
autoSetup: false
|
||||
interfaceName: eth3
|
||||
dhcpServerEnabled: false
|
||||
replicas:
|
||||
- url: https://replicas-file:443
|
||||
webURL: https://replicas-file:443
|
||||
apiPath: /control
|
||||
username: bar
|
||||
password: '*********'
|
||||
insecureSkipVerify: false
|
||||
autoSetup: false
|
||||
interfaceName: eth3
|
||||
dhcpServerEnabled: false
|
||||
cron: '*/15 * * * *'
|
||||
runOnStart: true
|
||||
printConfigOnly: true
|
||||
api:
|
||||
port: 9090
|
||||
features:
|
||||
dns:
|
||||
accessLists: true
|
||||
serverConfig: false
|
||||
rewrites: true
|
||||
dhcp:
|
||||
serverConfig: true
|
||||
staticLeases: true
|
||||
generalSettings: true
|
||||
queryLogConfig: true
|
||||
statsConfig: true
|
||||
clientSettings: true
|
||||
services: true
|
||||
filters: true
|
||||
2
testdata/e2e/bin/install-chart.sh
vendored
2
testdata/e2e/bin/install-chart.sh
vendored
@@ -6,4 +6,4 @@ kubectl config set-context --current --namespace=agh-e2e
|
||||
if [[ $(helm list --no-headers -n agh-e2e | grep agh-e2e | wc -l) == "1" ]]; then
|
||||
helm delete agh-e2e -n agh-e2e --wait
|
||||
fi
|
||||
helm install agh-e2e testdata/e2e -n agh-e2e --create-namespace
|
||||
helm install agh-e2e testdata/e2e -n agh-e2e --create-namespace --set mode=${1}
|
||||
|
||||
11
testdata/e2e/bin/show-sync-metrics.sh
vendored
Executable file
11
testdata/e2e/bin/show-sync-metrics.sh
vendored
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "wait another scrape interval (30s)"
|
||||
|
||||
sleep 30
|
||||
|
||||
echo "## Pod adguardhome-sync metrics" >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
curl http://localhost:9090/metrics -s >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
14
testdata/e2e/bin/wait-for-sync.sh
vendored
14
testdata/e2e/bin/wait-for-sync.sh
vendored
@@ -1,4 +1,14 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
kubectl wait --for=jsonpath='{.status.phase}'=Succeeded pod/adguardhome-sync --timeout=1m
|
||||
kubectl wait --for=jsonpath='{.status.phase}'=Running pod/adguardhome-sync --timeout=1m
|
||||
|
||||
kubectl port-forward pod/adguardhome-sync 9090:9090 &
|
||||
|
||||
for i in {1..6}; do
|
||||
sleep 10
|
||||
RUNNING=$(curl http://localhost:9090/api/v1/status -s | jq -r .syncRunning)
|
||||
echo "SyncRunning = ${RUNNING}"
|
||||
if [[ "${RUNNING}" == "false" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
|
||||
21
testdata/e2e/templates/configmap-sync-env.yaml
vendored
Normal file
21
testdata/e2e/templates/configmap-sync-env.yaml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{{- if eq .Values.mode "env" }}
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: sync-conf
|
||||
namespace: {{ .Release.Namespace }}
|
||||
data:
|
||||
API_PORT: '9090'
|
||||
API_METRICS_ENABLED: 'true'
|
||||
API_METRICS_SCRAPE_INTERVAL: '30s'
|
||||
LOG_FORMAT: 'json'
|
||||
ORIGIN_URL: 'http://service-origin.{{ $.Release.Namespace }}.svc.cluster.local:3000'
|
||||
ORIGIN_PASSWORD: 'password'
|
||||
ORIGIN_USERNAME: 'username'
|
||||
{{- range $i,$version := .Values.replica.versions }}
|
||||
REPLICA{{ add 1 $i }}_AUTO_SETUP: 'true'
|
||||
REPLICA{{ add 1 $i }}_URL: 'http://service-replica-{{ $version | toString | replace "." "-" }}.{{ $.Release.Namespace }}.svc.cluster.local:3000'
|
||||
REPLICA{{ add 1 $i }}_PASSWORD: 'password'
|
||||
REPLICA{{ add 1 $i }}_USERNAME: 'username'
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
25
testdata/e2e/templates/configmap-sync-file.yaml
vendored
Normal file
25
testdata/e2e/templates/configmap-sync-file.yaml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
{{- if eq .Values.mode "file" }}
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: sync-conf
|
||||
namespace: {{ .Release.Namespace }}
|
||||
data:
|
||||
config.yaml: |
|
||||
origin:
|
||||
url: 'http://service-origin.{{ $.Release.Namespace }}.svc.cluster.local:3000'
|
||||
username: username
|
||||
password: password
|
||||
api:
|
||||
port: 9090
|
||||
metrics:
|
||||
enabled: true
|
||||
scrapeInterval: 30s
|
||||
replicas:
|
||||
{{- range $i,$version := .Values.replica.versions }}
|
||||
- url: 'http://service-replica-{{ $version | toString | replace "." "-" }}.{{ $.Release.Namespace }}.svc.cluster.local:3000'
|
||||
username: username
|
||||
password: password
|
||||
autoSetup: true
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
17
testdata/e2e/templates/configmap-sync.yaml
vendored
17
testdata/e2e/templates/configmap-sync.yaml
vendored
@@ -1,17 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: sync-conf
|
||||
namespace: {{ .Release.Namespace }}
|
||||
data:
|
||||
API_PORT: '0'
|
||||
LOG_LEVEL: 'info'
|
||||
ORIGIN_URL: 'http://service-origin.{{ $.Release.Namespace }}.svc.cluster.local:3000'
|
||||
ORIGIN_PASSWORD: 'password'
|
||||
ORIGIN_USERNAME: 'username'
|
||||
{{ range $i,$version := .Values.replica.versions }}
|
||||
REPLICA{{ $i }}_AUTOSETUP: 'true'
|
||||
REPLICA{{ $i }}_URL: 'http://service-replica-{{ $version | toString | replace "." "-" }}.{{ $.Release.Namespace }}.svc.cluster.local:3000'
|
||||
REPLICA{{ $i }}_PASSWORD: 'password'
|
||||
REPLICA{{ $i }}_USERNAME: 'username'
|
||||
{{- end }}
|
||||
@@ -1,3 +1,4 @@
|
||||
{{- if eq .Values.mode "env" }}
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
@@ -7,7 +8,7 @@ spec:
|
||||
serviceAccountName: agh-e2e
|
||||
initContainers:
|
||||
- name: wait-for-others
|
||||
image: bitnami/kubectl:1.24
|
||||
image: {{ .Values.kubectl.repository }}:{{ .Values.kubectl.tag }}
|
||||
command:
|
||||
- /bin/bash
|
||||
- -c
|
||||
@@ -19,7 +20,11 @@ spec:
|
||||
command:
|
||||
- /opt/go/adguardhome-sync
|
||||
- run
|
||||
env:
|
||||
- name: LOG_LEVEL
|
||||
value: 'debug'
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: sync-conf
|
||||
restartPolicy: Never
|
||||
{{- end }}
|
||||
36
testdata/e2e/templates/pod-adguardhome-sync-file.yaml
vendored
Normal file
36
testdata/e2e/templates/pod-adguardhome-sync-file.yaml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
{{- if eq .Values.mode "file" }}
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: adguardhome-sync
|
||||
namespace: {{ $.Release.Namespace }}
|
||||
spec:
|
||||
serviceAccountName: agh-e2e
|
||||
initContainers:
|
||||
- name: wait-for-others
|
||||
image: {{ .Values.kubectl.repository }}:{{ .Values.kubectl.tag }}
|
||||
command:
|
||||
- /bin/bash
|
||||
- -c
|
||||
- |
|
||||
{{- .Files.Get "bin/wait-for-agh-pods.sh" | nindent 10}}
|
||||
containers:
|
||||
- name: adguardhome-sync
|
||||
image: localhost:5001/adguardhome-sync:e2e
|
||||
command:
|
||||
- /opt/go/adguardhome-sync
|
||||
- run
|
||||
- '--config'
|
||||
- /etc/go/adguardhome-sync/config.yaml
|
||||
env:
|
||||
- name: LOG_LEVEL
|
||||
value: 'debug'
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /etc/go/adguardhome-sync/
|
||||
volumes:
|
||||
- name: config
|
||||
configMap:
|
||||
name: sync-conf
|
||||
restartPolicy: Never
|
||||
{{- end }}
|
||||
7
testdata/e2e/values.yaml
vendored
7
testdata/e2e/values.yaml
vendored
@@ -1,4 +1,11 @@
|
||||
replica:
|
||||
versions:
|
||||
- v0.107.40
|
||||
- v0.107.43
|
||||
- latest
|
||||
|
||||
mode: env
|
||||
|
||||
kubectl:
|
||||
repository: bitnami/kubectl
|
||||
tag: 1.27
|
||||
|
||||
14
tools.go
Normal file
14
tools.go
Normal file
@@ -0,0 +1,14 @@
|
||||
//go:build tools
|
||||
// +build tools
|
||||
|
||||
package tools
|
||||
|
||||
import (
|
||||
_ "github.com/bakito/semver"
|
||||
_ "github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen"
|
||||
_ "github.com/golangci/golangci-lint/cmd/golangci-lint"
|
||||
_ "github.com/goreleaser/goreleaser"
|
||||
_ "github.com/onsi/ginkgo/v2/ginkgo"
|
||||
_ "go.uber.org/mock/mockgen"
|
||||
_ "k8s.io/code-generator/cmd/deepcopy-gen"
|
||||
)
|
||||
Reference in New Issue
Block a user