Compare commits
205 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
012350a8fd | ||
|
|
fefdda0015 | ||
|
|
55d63df17b | ||
|
|
37ee52aa8f | ||
|
|
da289017a5 | ||
|
|
0fb6f38bab | ||
|
|
014e9c8a26 | ||
|
|
d07c005191 | ||
|
|
a2b9930f92 | ||
|
|
8ac7b9addd | ||
|
|
8115f52d8a | ||
|
|
1581b82ae5 | ||
|
|
9a466cb86b | ||
|
|
fda2ac0a5a | ||
|
|
c1975eebdb | ||
|
|
ab891449cf | ||
|
|
e4f235586e | ||
|
|
49e79274bb | ||
|
|
4d077eba3c | ||
|
|
ad24071c9c | ||
|
|
d99af0c43d | ||
|
|
0f86e61cdd | ||
|
|
dea2d04a63 | ||
|
|
a34eb1ae57 | ||
|
|
6f426a870f | ||
|
|
0e129d4b10 | ||
|
|
2d65e7dc9e | ||
|
|
6e47c07aa3 | ||
|
|
dea159d766 | ||
|
|
e4221a9ca7 | ||
|
|
240a0285c2 | ||
|
|
bc69d0388c | ||
|
|
5043f157fa | ||
|
|
2b4877f122 | ||
|
|
53b9cf704c | ||
|
|
69dd7650e1 | ||
|
|
06b93cbdac | ||
|
|
dd21859cd1 | ||
|
|
9a19b33116 | ||
|
|
5385d0aaed | ||
|
|
02547975e9 | ||
|
|
598324b7aa | ||
|
|
49cd71daf7 | ||
|
|
2f2cd0af58 | ||
|
|
607238230d | ||
|
|
ed952dd891 | ||
|
|
50de6b1d71 | ||
|
|
4db293ccf2 | ||
|
|
3211406ef2 | ||
|
|
c93084e623 | ||
|
|
009715ccea | ||
|
|
c22f38fff2 | ||
|
|
e2c6ef40ec | ||
|
|
b44e2f8d8d | ||
|
|
c20bec7a13 | ||
|
|
f21874c546 | ||
|
|
fce8aea40b | ||
|
|
1dab955843 | ||
|
|
92c4d1f41a | ||
|
|
89e4f2f0aa | ||
|
|
cf0458b4a9 | ||
|
|
c524a8ce2b | ||
|
|
5df430e694 | ||
|
|
faaefd726a | ||
|
|
19451db485 | ||
|
|
abe38e7a2c | ||
|
|
495e0d261d | ||
|
|
cfcffab9d1 | ||
|
|
94ed2c6245 | ||
|
|
e3f01f75a2 | ||
|
|
5865a8160e | ||
|
|
619dd5fcd9 | ||
|
|
9310ddddaf | ||
|
|
25265a5f51 | ||
|
|
bdc8024ba9 | ||
|
|
116fdf5c11 | ||
|
|
49f301589d | ||
|
|
6d08d42626 | ||
|
|
3c7243fcba | ||
|
|
f6bb8757a4 | ||
|
|
2cd21c8331 | ||
|
|
edac7a3b4d | ||
|
|
64b73ba7cf | ||
|
|
e41e5dbaa9 | ||
|
|
00c77a77c9 | ||
|
|
378aa54500 | ||
|
|
3bc7874412 | ||
|
|
a4864b2b7a | ||
|
|
2b27ce88fe | ||
|
|
c32694ff5c | ||
|
|
97ae38c3fa | ||
|
|
9edec9cf04 | ||
|
|
dcaad44315 | ||
|
|
ddc8e9f31d | ||
|
|
6b07040ad7 | ||
|
|
ec3209bdb3 | ||
|
|
883a270f56 | ||
|
|
cbcc85dc93 | ||
|
|
aecd921c82 | ||
|
|
549fcde1e7 | ||
|
|
25430ebb10 | ||
|
|
e216c051d4 | ||
|
|
debeff618d | ||
|
|
30706e5a30 | ||
|
|
5957bd0fde | ||
|
|
425dfc5e50 | ||
|
|
6436dd9998 | ||
|
|
e75600c878 | ||
|
|
7545af2c15 | ||
|
|
5c65877bb3 | ||
|
|
09acb664c5 | ||
|
|
77d2dd96e1 | ||
|
|
2443ca3b0a | ||
|
|
c3f78c7b63 | ||
|
|
db1a3b2d47 | ||
|
|
65fb377f8e | ||
|
|
15035f5199 | ||
|
|
337d39076c | ||
|
|
3cd0463054 | ||
|
|
1556ce4830 | ||
|
|
7065f0797a | ||
|
|
afe9894632 | ||
|
|
35f95fd1fb | ||
|
|
a66e41b8f3 | ||
|
|
29a8202ce7 | ||
|
|
91cf1a1e6f | ||
|
|
16ed9c28bf | ||
|
|
578bc69498 | ||
|
|
f17f049f62 | ||
|
|
662b177acf | ||
|
|
31d91adb42 | ||
|
|
630d9c8eef | ||
|
|
97d3b0f2a9 | ||
|
|
0206e6173f | ||
|
|
ff104f543d | ||
|
|
ec3b5d7956 | ||
|
|
389cf12c1f | ||
|
|
39e81fb74b | ||
|
|
8818c584b8 | ||
|
|
f2891135f8 | ||
|
|
5a764f7fdf | ||
|
|
c129df4049 | ||
|
|
b93cbda566 | ||
|
|
f55137852d | ||
|
|
e9aa9d7420 | ||
|
|
113070b14e | ||
|
|
e66f4e5a83 | ||
|
|
3b33623a46 | ||
|
|
8d85b58ce4 | ||
|
|
dddd5b2e43 | ||
|
|
ff8e2d60d0 | ||
|
|
8c7bd73e8e | ||
|
|
0dc24effa9 | ||
|
|
809d7b2ad8 | ||
|
|
1119c96d5f | ||
|
|
e71add0c82 | ||
|
|
a4095d6833 | ||
|
|
5038326e36 | ||
|
|
c8800b9589 | ||
|
|
339ea56d18 | ||
|
|
76f6097847 | ||
|
|
71ff6dc3c5 | ||
|
|
25966fb186 | ||
|
|
488d2acb01 | ||
|
|
467c7ddeb7 | ||
|
|
bbb885e155 | ||
|
|
7a946b2096 | ||
|
|
08f8a9970e | ||
|
|
ec458fd04a | ||
|
|
2703cad2be | ||
|
|
79bea82c78 | ||
|
|
93e735306e | ||
|
|
bf940aae0f | ||
|
|
6bb795d622 | ||
|
|
afde0d7f3a | ||
|
|
d6d810fd42 |
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
github: [bakito]
|
||||||
27
.github/ISSUE_TEMPLATE/bug_report.md
vendored
27
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,27 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Create a report to help us improve
|
|
||||||
title: ''
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Describe the bug**
|
|
||||||
A clear and concise description of what the bug is.
|
|
||||||
|
|
||||||
**To Reproduce**
|
|
||||||
Steps to reproduce the behavior:
|
|
||||||
1. What version of AdGuardHome sync used?
|
|
||||||
2. What version of AdGuardHome us used?
|
|
||||||
3. How does the configuration look?
|
|
||||||
4. What is the error message?
|
|
||||||
|
|
||||||
**Expected behavior**
|
|
||||||
A clear and concise description of what you expected to happen.
|
|
||||||
|
|
||||||
**Log Files**
|
|
||||||
If applicable, add log files or json responses from AdGuardHome to help explain your problem.
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context about the problem here.
|
|
||||||
60
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal file
60
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
name: Bug report
|
||||||
|
description: Create a report to help us improve
|
||||||
|
labels: ['bug']
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thanks for taking the time to fill out this issue report! If you have usage questions, please try the [FAQ](https://github.com/bakito/adguardhome-sync/wiki/FAQ) first.
|
||||||
|
- type: textarea
|
||||||
|
id: what-happened
|
||||||
|
attributes:
|
||||||
|
label: What happened?
|
||||||
|
description: Also tell us, what did you expect to happen?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: adguardhome-sync-version
|
||||||
|
attributes:
|
||||||
|
label: AdguardHome-Sync Version
|
||||||
|
description: What version of adguardhome-sync was running when you discovered this issue?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: adguardhome-version
|
||||||
|
attributes:
|
||||||
|
label: AdguardHome Version
|
||||||
|
description: What version of adguardhome was running when you discovered this issue?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: config
|
||||||
|
attributes:
|
||||||
|
label: Configuration
|
||||||
|
description: |
|
||||||
|
- How did you configure adguardhome-sync?
|
||||||
|
- Please provide your configuration
|
||||||
|
render: shell
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: logs
|
||||||
|
attributes:
|
||||||
|
label: Relevant log output
|
||||||
|
description: |
|
||||||
|
Please copy and paste any relevant **debug** log output. This will be automatically formatted into code, so no need for backticks.
|
||||||
|
Enable debug logs by defining the following environment variable `LOG_LEVEL=debug`.
|
||||||
|
|
||||||
|
Please also check adguardhome logs and paste any relevant logs/errors to this issue.
|
||||||
|
render: shell
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Anything else?
|
||||||
|
description: |
|
||||||
|
Links? References? Anything that will provide more context about the issue you are encountering!
|
||||||
|
|
||||||
|
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
10
.github/ISSUE_TEMPLATE/custom.md
vendored
10
.github/ISSUE_TEMPLATE/custom.md
vendored
@@ -1,10 +0,0 @@
|
|||||||
---
|
|
||||||
name: Custom issue template
|
|
||||||
about: Describe this issue template's purpose here.
|
|
||||||
title: ''
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
4
.github/dependabot.yml
vendored
4
.github/dependabot.yml
vendored
@@ -9,3 +9,7 @@ updates:
|
|||||||
directory: "/" # Location of package manifests
|
directory: "/" # Location of package manifests
|
||||||
schedule:
|
schedule:
|
||||||
interval: "weekly"
|
interval: "weekly"
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
|||||||
8
.github/workflows/codeql-analysis.yml
vendored
8
.github/workflows/codeql-analysis.yml
vendored
@@ -38,11 +38,11 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v1
|
uses: github/codeql-action/init@v3
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
@@ -53,7 +53,7 @@ jobs:
|
|||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
# 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)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@v1
|
uses: github/codeql-action/autobuild@v3
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
# 📚 https://git.io/JvXDl
|
# 📚 https://git.io/JvXDl
|
||||||
@@ -67,4 +67,4 @@ jobs:
|
|||||||
# make release
|
# make release
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v1
|
uses: github/codeql-action/analyze@v3
|
||||||
|
|||||||
35
.github/workflows/e2e.yaml
vendored
Normal file
35
.github/workflows/e2e.yaml
vendored
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
name: e2e tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
e2e:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install kind with registry
|
||||||
|
uses: bakito/kind-with-registry-action@main
|
||||||
|
|
||||||
|
- name: Build image
|
||||||
|
run: ./testdata/e2e/bin/build-image.sh
|
||||||
|
|
||||||
|
- name: Install Helm Chart
|
||||||
|
run: ./testdata/e2e/bin/install-chart.sh
|
||||||
|
- name: Wait for sync to finish
|
||||||
|
run: ./testdata/e2e/bin/wait-for-sync.sh
|
||||||
|
- name: Show origin Logs
|
||||||
|
run: ./testdata/e2e/bin/show-origin-logs.sh
|
||||||
|
- name: Show Replica Logs
|
||||||
|
run: ./testdata/e2e/bin/show-replica-logs.sh
|
||||||
|
- name: Show Sync Logs
|
||||||
|
run: ./testdata/e2e/bin/show-sync-logs.sh
|
||||||
|
- name: Read latest replica config
|
||||||
|
run: ./testdata/e2e/bin/read-latest-replica-config.sh
|
||||||
40
.github/workflows/go.yml
vendored
40
.github/workflows/go.yml
vendored
@@ -11,24 +11,30 @@ jobs:
|
|||||||
name: lint
|
name: lint
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: golangci-lint
|
|
||||||
uses: golangci/golangci-lint-action@v2
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
version: latest
|
go-version-file: "go.mod"
|
||||||
|
|
||||||
|
- name: golangci-lint
|
||||||
|
uses: golangci/golangci-lint-action@v3
|
||||||
|
with:
|
||||||
|
skip-pkg-cache: true
|
||||||
|
|
||||||
test:
|
test:
|
||||||
name: test
|
name: test
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Set up Go 1.x
|
|
||||||
uses: actions/setup-go@v2
|
|
||||||
with:
|
|
||||||
go-version: ^1.17
|
|
||||||
|
|
||||||
- name: Check out code into the Go module directory
|
- name: Check out code into the Go module directory
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version-file: "go.mod"
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: make test-ci
|
run: make test-ci
|
||||||
@@ -43,16 +49,16 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Set up Go 1.x
|
|
||||||
uses: actions/setup-go@v2
|
|
||||||
with:
|
|
||||||
go-version: ^1.17
|
|
||||||
|
|
||||||
- name: Check out code into the Go module directory
|
- name: Check out code into the Go module directory
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version-file: "go.mod"
|
||||||
|
|
||||||
- name: Run GoReleaser
|
- name: Run GoReleaser
|
||||||
uses: goreleaser/goreleaser-action@v2
|
uses: goreleaser/goreleaser-action@v5
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
args: --skip-publish --snapshot --rm-dist
|
args: --skip-publish --snapshot --rm-dist
|
||||||
|
|||||||
57
.github/workflows/publish.yml
vendored
57
.github/workflows/publish.yml
vendored
@@ -1,60 +1,83 @@
|
|||||||
name: docker-image
|
name: docker-image
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
workflow_dispatch: # allows manual triggering
|
||||||
branches:
|
schedule:
|
||||||
- main
|
- cron: '0 0 * * *'
|
||||||
release:
|
release:
|
||||||
types:
|
types:
|
||||||
- published
|
- published
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
main:
|
images:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
build:
|
||||||
|
- fromImage: scratch
|
||||||
|
tagPrefix: ""
|
||||||
|
- fromImage: alpine:latest
|
||||||
|
tagPrefix: "alpine-"
|
||||||
steps:
|
steps:
|
||||||
- name: Get current date
|
- name: Get current date
|
||||||
id: date
|
run: echo "curr_date=$(date --utc +%Y-%m-%dT%H:%M:%SZ)" >> $GITHUB_ENV
|
||||||
run: echo "::set-output name=date::$(date --utc +%Y-%m-%dT%H:%M:%SZ)"
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v1
|
uses: docker/setup-qemu-action@v3
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v1
|
uses: docker/setup-buildx-action@v3
|
||||||
- name: Login to Quay
|
- name: Login to Quay
|
||||||
uses: docker/login-action@v1
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: quay.io
|
registry: quay.io
|
||||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||||
- name: Login to ghcr.io
|
- name: Login to ghcr.io
|
||||||
uses: docker/login-action@v1
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Modify Dockerfile
|
||||||
|
run: |
|
||||||
|
sed -i -e "s|FROM scratch|FROM ${{ matrix.build.fromImage }}|g" Dockerfile
|
||||||
|
|
||||||
- name: Build and push ${{github.event.release.tag_name }}
|
- name: Build and push ${{github.event.release.tag_name }}
|
||||||
id: docker_build_release
|
id: docker_build_release
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v5
|
||||||
if: ${{ github.event.release.tag_name != '' }}
|
if: ${{ github.event.release.tag_name != '' }}
|
||||||
with:
|
with:
|
||||||
|
context: .
|
||||||
pull: true
|
pull: true
|
||||||
push: true
|
push: true
|
||||||
tags: quay.io/bakito/adguardhome-sync:latest,quay.io/bakito/adguardhome-sync:${{ github.event.release.tag_name }},ghcr.io/bakito/adguardhome-sync:latest,ghcr.io/bakito/adguardhome-sync:${{ github.event.release.tag_name }}
|
tags: quay.io/bakito/adguardhome-sync:${{ matrix.build.tagPrefix }}latest,quay.io/bakito/adguardhome-sync:${{ matrix.build.tagPrefix }}${{ github.event.release.tag_name }},ghcr.io/bakito/adguardhome-sync:${{ matrix.build.tagPrefix }}latest,ghcr.io/bakito/adguardhome-sync:${{ matrix.build.tagPrefix }}${{ github.event.release.tag_name }}
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||||
|
provenance: false
|
||||||
build-args: |
|
build-args: |
|
||||||
VERSION=${{ github.event.release.tag_name }}
|
VERSION=${{ github.event.release.tag_name }}
|
||||||
BUILD=${{ steps.date.outputs.date }}
|
BUILD=${{ env.curr_date }}
|
||||||
|
|
||||||
|
- name: Check for commits in the last 24 hours
|
||||||
|
run: echo "NEW_COMMIT_COUNT=$(git log --oneline --since '24 hours ago' | wc -l)" >> $GITHUB_ENV
|
||||||
|
if: ${{ github.event.release.tag_name == '' }}
|
||||||
|
|
||||||
- name: Build and push main
|
- name: Build and push main
|
||||||
id: docker_build_main
|
id: docker_build_main
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v5
|
||||||
if: ${{ github.event.release.tag_name == '' }}
|
if: ${{ github.event.release.tag_name == '' && env.NEW_COMMIT_COUNT > 0 }}
|
||||||
with:
|
with:
|
||||||
|
context: .
|
||||||
pull: true
|
pull: true
|
||||||
push: true
|
push: true
|
||||||
tags: quay.io/bakito/adguardhome-sync:main,ghcr.io/bakito/adguardhome-sync:main
|
tags: quay.io/bakito/adguardhome-sync:${{ matrix.build.tagPrefix }}main,ghcr.io/bakito/adguardhome-sync:${{ matrix.build.tagPrefix }}main
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||||
|
provenance: false
|
||||||
build-args: |
|
build-args: |
|
||||||
VERSION=main
|
VERSION=main
|
||||||
BUILD=${{ steps.date.outputs.date }}
|
BUILD=${{ env.curr_date }}
|
||||||
|
|
||||||
- name: Image digest
|
- name: Image digest
|
||||||
run: echo ${{ steps.docker_build.outputs.digest }}
|
run: echo ${{ steps.docker_build.outputs.digest }}
|
||||||
|
|
||||||
|
|||||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -1,6 +1,12 @@
|
|||||||
|
.vscode
|
||||||
.idea
|
.idea
|
||||||
coverage.out
|
.run
|
||||||
|
coverage.out*
|
||||||
dist
|
dist
|
||||||
adguardhome-sync
|
adguardhome-sync
|
||||||
main
|
main
|
||||||
.adguardhome-sync.yaml
|
.adguardhome-sync.yaml
|
||||||
|
tmp
|
||||||
|
bin
|
||||||
|
config.yaml
|
||||||
|
*.log
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
run:
|
run:
|
||||||
timeout: 1m
|
timeout: 5m
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
enable:
|
enable:
|
||||||
- asciicheck
|
- asciicheck
|
||||||
- bodyclose
|
- bodyclose
|
||||||
- deadcode
|
|
||||||
- depguard
|
|
||||||
- dogsled
|
- dogsled
|
||||||
- durationcheck
|
- durationcheck
|
||||||
- errcheck
|
- errcheck
|
||||||
@@ -25,15 +23,10 @@ linters:
|
|||||||
- misspell
|
- misspell
|
||||||
- nakedret
|
- nakedret
|
||||||
- nolintlint
|
- nolintlint
|
||||||
- revive
|
|
||||||
- staticcheck
|
- staticcheck
|
||||||
- structcheck
|
|
||||||
- typecheck
|
|
||||||
- unconvert
|
- unconvert
|
||||||
- unparam
|
- unparam
|
||||||
- unused
|
- unused
|
||||||
- varcheck
|
|
||||||
- wastedassign
|
|
||||||
linters-settings:
|
linters-settings:
|
||||||
gosec:
|
gosec:
|
||||||
# Exclude generated files
|
# Exclude generated files
|
||||||
@@ -41,3 +34,4 @@ linters-settings:
|
|||||||
gofmt:
|
gofmt:
|
||||||
# simplify code: gofmt with `-s` option, true by default
|
# simplify code: gofmt with `-s` option, true by default
|
||||||
simplify: true
|
simplify: true
|
||||||
|
|
||||||
|
|||||||
@@ -28,11 +28,9 @@ builds:
|
|||||||
- goos: windows
|
- goos: windows
|
||||||
goarch: arm64
|
goarch: arm64
|
||||||
hooks:
|
hooks:
|
||||||
post: upx {{ .Path }}
|
post:
|
||||||
archives:
|
# don't upx windows binaries as they make trouble with virus scanners
|
||||||
- replacements:
|
- bash -c 'if [[ "{{ .Path }}" != *.exe ]]; then upx {{ .Path }}; fi'
|
||||||
386: i386
|
|
||||||
amd64: x86_64
|
|
||||||
checksum:
|
checksum:
|
||||||
name_template: 'checksums.txt'
|
name_template: 'checksums.txt'
|
||||||
snapshot:
|
snapshot:
|
||||||
@@ -44,4 +42,4 @@ changelog:
|
|||||||
- '^docs:'
|
- '^docs:'
|
||||||
- '^test:'
|
- '^test:'
|
||||||
release:
|
release:
|
||||||
prerelease: auto
|
prerelease: auto
|
||||||
|
|||||||
3
.oapi-codegen.yaml
Normal file
3
.oapi-codegen.yaml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
output-options:
|
||||||
|
client-type-name: AdguardHomeClient
|
||||||
|
response-type-suffix: Resp
|
||||||
12
Dockerfile
12
Dockerfile
@@ -1,14 +1,15 @@
|
|||||||
FROM golang:1.17 as builder
|
FROM golang:1.21-bullseye as builder
|
||||||
|
|
||||||
WORKDIR /go/src/app
|
WORKDIR /go/src/app
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y upx
|
RUN apt-get update && \
|
||||||
|
apt-get install -y upx ca-certificates tzdata && \
|
||||||
|
apt-get upgrade -y # upgrade to get latest ca-certs
|
||||||
|
|
||||||
ARG VERSION=main
|
ARG VERSION=main
|
||||||
ARG BUILD="N/A"
|
ARG BUILD="N/A"
|
||||||
|
|
||||||
ENV GOPROXY=https://goproxy.io \
|
ENV GO111MODULE=on \
|
||||||
GO111MODULE=on \
|
|
||||||
CGO_ENABLED=0 \
|
CGO_ENABLED=0 \
|
||||||
GOOS=linux
|
GOOS=linux
|
||||||
|
|
||||||
@@ -25,6 +26,7 @@ LABEL maintainer="bakito <github@bakito.ch>"
|
|||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
ENTRYPOINT ["/opt/go/adguardhome-sync"]
|
ENTRYPOINT ["/opt/go/adguardhome-sync"]
|
||||||
CMD ["run", "--config", "/config/adguardhome-sync.yaml"]
|
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 /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
|
USER 1001
|
||||||
|
|||||||
142
Makefile
142
Makefile
@@ -1,36 +1,140 @@
|
|||||||
# Run go fmt against code
|
# Run go lint against code
|
||||||
fmt:
|
lint: golangci-lint
|
||||||
golangci-lint run --fix
|
$(GOLANGCI_LINT) run --fix
|
||||||
|
|
||||||
# Run go mod tidy
|
# Run go mod tidy
|
||||||
tidy:
|
tidy:
|
||||||
go mod tidy
|
go mod tidy
|
||||||
|
|
||||||
|
generate: deepcopy-gen
|
||||||
|
@mkdir -p ./tmp
|
||||||
|
@touch ./tmp/deepcopy-gen-boilerplate.go.txt
|
||||||
|
$(DEEPCOPY_GEN) -h ./tmp/deepcopy-gen-boilerplate.go.txt -i ./pkg/types
|
||||||
|
|
||||||
# Run tests
|
# Run tests
|
||||||
test: test-ci fmt
|
test: generate lint test-ci
|
||||||
|
|
||||||
# Run ci tests
|
# Run ci tests
|
||||||
test-ci: mocks tidy
|
test-ci: mocks tidy
|
||||||
go test ./... -coverprofile=coverage.out
|
go test ./... -coverprofile=coverage.out.tmp
|
||||||
|
cat coverage.out.tmp | grep -v "_generated.go" > coverage.out
|
||||||
go tool cover -func=coverage.out
|
go tool cover -func=coverage.out
|
||||||
|
|
||||||
mocks: mockgen
|
mocks: mockgen
|
||||||
mockgen -package client -destination pkg/mocks/client/mock.go github.com/bakito/adguardhome-sync/pkg/client Client
|
$(MOCKGEN) -package client -destination pkg/mocks/client/mock.go github.com/bakito/adguardhome-sync/pkg/client Client
|
||||||
|
|
||||||
release: semver
|
release: semver goreleaser
|
||||||
@version=$$(semver); \
|
@version=$$($(LOCALBIN)/semver); \
|
||||||
git tag -s $$version -m"Release $$version"
|
git tag -s $$version -m"Release $$version"
|
||||||
goreleaser --rm-dist
|
$(GORELEASER) --clean
|
||||||
|
|
||||||
test-release:
|
test-release: goreleaser
|
||||||
goreleaser --skip-publish --snapshot --rm-dist
|
$(GORELEASER) --skip-publish --snapshot --clean
|
||||||
|
|
||||||
semver:
|
## toolbox - start
|
||||||
ifeq (, $(shell which semver))
|
## Current working directory
|
||||||
$(shell go install github.com/bakito/semver@latest)
|
LOCALDIR ?= $(shell which cygpath > /dev/null 2>&1 && cygpath -m $$(pwd) || pwd)
|
||||||
endif
|
## Location to install dependencies to
|
||||||
|
LOCALBIN ?= $(LOCALDIR)/bin
|
||||||
|
$(LOCALBIN):
|
||||||
|
mkdir -p $(LOCALBIN)
|
||||||
|
|
||||||
mockgen:
|
## Tool Binaries
|
||||||
ifeq (, $(shell which mockgen))
|
SEMVER ?= $(LOCALBIN)/semver
|
||||||
$(shell go install github.com/golang/mock/mockgen@v1.5)
|
OAPI_CODEGEN ?= $(LOCALBIN)/oapi-codegen
|
||||||
endif
|
MOCKGEN ?= $(LOCALBIN)/mockgen
|
||||||
|
GOLANGCI_LINT ?= $(LOCALBIN)/golangci-lint
|
||||||
|
GORELEASER ?= $(LOCALBIN)/goreleaser
|
||||||
|
DEEPCOPY_GEN ?= $(LOCALBIN)/deepcopy-gen
|
||||||
|
|
||||||
|
## Tool Versions
|
||||||
|
SEMVER_VERSION ?= v1.1.3
|
||||||
|
OAPI_CODEGEN_VERSION ?= v2.0.0
|
||||||
|
MOCKGEN_VERSION ?= v1.6.0
|
||||||
|
GOLANGCI_LINT_VERSION ?= v1.55.2
|
||||||
|
GORELEASER_VERSION ?= v1.23.0
|
||||||
|
DEEPCOPY_GEN_VERSION ?= v0.29.0
|
||||||
|
|
||||||
|
## Tool Installer
|
||||||
|
.PHONY: semver
|
||||||
|
semver: $(SEMVER) ## Download semver locally if necessary.
|
||||||
|
$(SEMVER): $(LOCALBIN)
|
||||||
|
test -s $(LOCALBIN)/semver || GOBIN=$(LOCALBIN) go install github.com/bakito/semver@$(SEMVER_VERSION)
|
||||||
|
.PHONY: oapi-codegen
|
||||||
|
oapi-codegen: $(OAPI_CODEGEN) ## Download oapi-codegen locally if necessary.
|
||||||
|
$(OAPI_CODEGEN): $(LOCALBIN)
|
||||||
|
test -s $(LOCALBIN)/oapi-codegen || GOBIN=$(LOCALBIN) go install github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen@$(OAPI_CODEGEN_VERSION)
|
||||||
|
.PHONY: mockgen
|
||||||
|
mockgen: $(MOCKGEN) ## Download mockgen locally if necessary.
|
||||||
|
$(MOCKGEN): $(LOCALBIN)
|
||||||
|
test -s $(LOCALBIN)/mockgen || GOBIN=$(LOCALBIN) go install github.com/golang/mock/mockgen@$(MOCKGEN_VERSION)
|
||||||
|
.PHONY: golangci-lint
|
||||||
|
golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary.
|
||||||
|
$(GOLANGCI_LINT): $(LOCALBIN)
|
||||||
|
test -s $(LOCALBIN)/golangci-lint || GOBIN=$(LOCALBIN) go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION)
|
||||||
|
.PHONY: goreleaser
|
||||||
|
goreleaser: $(GORELEASER) ## Download goreleaser locally if necessary.
|
||||||
|
$(GORELEASER): $(LOCALBIN)
|
||||||
|
test -s $(LOCALBIN)/goreleaser || GOBIN=$(LOCALBIN) go install github.com/goreleaser/goreleaser@$(GORELEASER_VERSION)
|
||||||
|
.PHONY: deepcopy-gen
|
||||||
|
deepcopy-gen: $(DEEPCOPY_GEN) ## Download deepcopy-gen locally if necessary.
|
||||||
|
$(DEEPCOPY_GEN): $(LOCALBIN)
|
||||||
|
test -s $(LOCALBIN)/deepcopy-gen || GOBIN=$(LOCALBIN) go install k8s.io/code-generator/cmd/deepcopy-gen@$(DEEPCOPY_GEN_VERSION)
|
||||||
|
|
||||||
|
## Update Tools
|
||||||
|
.PHONY: update-toolbox-tools
|
||||||
|
update-toolbox-tools:
|
||||||
|
@rm -f \
|
||||||
|
$(LOCALBIN)/semver \
|
||||||
|
$(LOCALBIN)/oapi-codegen \
|
||||||
|
$(LOCALBIN)/mockgen \
|
||||||
|
$(LOCALBIN)/golangci-lint \
|
||||||
|
$(LOCALBIN)/goreleaser \
|
||||||
|
$(LOCALBIN)/deepcopy-gen
|
||||||
|
toolbox makefile -f $(LOCALDIR)/Makefile \
|
||||||
|
github.com/bakito/semver \
|
||||||
|
github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen \
|
||||||
|
github.com/golang/mock/mockgen \
|
||||||
|
github.com/golangci/golangci-lint/cmd/golangci-lint \
|
||||||
|
github.com/goreleaser/goreleaser \
|
||||||
|
k8s.io/code-generator/cmd/deepcopy-gen@github.com/kubernetes/code-generator
|
||||||
|
## toolbox - end
|
||||||
|
|
||||||
|
start-replica:
|
||||||
|
docker run --pull always --name adguardhome-replica -p 9091:3000 --rm adguard/adguardhome:latest
|
||||||
|
# docker run --pull always --name adguardhome-replica -p 9090:80 -p 9091:3000 --rm adguard/adguardhome:v0.107.13
|
||||||
|
|
||||||
|
copy-replica-config:
|
||||||
|
docker cp adguardhome-replica:/opt/adguardhome/conf/AdGuardHome.yaml tmp/AdGuardHome.yaml
|
||||||
|
|
||||||
|
start-replica2:
|
||||||
|
docker run --pull always --name adguardhome-replica2 -p 9093:3000 --rm adguard/adguardhome:latest
|
||||||
|
# docker run --pull always --name adguardhome-replica -p 9090:80 -p 9091:3000 --rm adguard/adguardhome:v0.107.13
|
||||||
|
|
||||||
|
check_defined = \
|
||||||
|
$(strip $(foreach 1,$1, \
|
||||||
|
$(call __check_defined,$1,$(strip $(value 2)))))
|
||||||
|
__check_defined = \
|
||||||
|
$(if $(value $1),, \
|
||||||
|
$(error Undefined $1$(if $2, ($2))))
|
||||||
|
|
||||||
|
build-image:
|
||||||
|
$(call check_defined, AGH_SYNC_VERSION)
|
||||||
|
docker build --build-arg VERSION=${AGH_SYNC_VERSION} --build-arg BUILD=$(shell date -u +'%Y-%m-%dT%H:%M:%S.%3NZ') --name adgardhome-replica -t ghcr.io/bakito/adguardhome-sync:${AGH_SYNC_VERSION} .
|
||||||
|
|
||||||
|
kind-create:
|
||||||
|
kind delete cluster
|
||||||
|
kind create cluster
|
||||||
|
|
||||||
|
kind-test:
|
||||||
|
@./testdata/e2e/bin/install-chart.sh
|
||||||
|
|
||||||
|
model: oapi-codegen
|
||||||
|
@mkdir -p tmp
|
||||||
|
go run openapi/main.go v0.107.43
|
||||||
|
$(OAPI_CODEGEN) -package model -generate types,client -config .oapi-codegen.yaml tmp/schema.yaml > pkg/client/model/model_generated.go
|
||||||
|
|
||||||
|
model-diff:
|
||||||
|
go run openapi/main.go v0.107.43
|
||||||
|
go run openapi/main.go
|
||||||
|
diff tmp/schema.yaml tmp/schema-master.yaml
|
||||||
|
|||||||
137
README.md
137
README.md
@@ -1,10 +1,14 @@
|
|||||||
[](https://github.com/bakito/adguardhome-sync/actions/workflows/go.yml)
|
[](https://github.com/bakito/adguardhome-sync/actions/workflows/go.yml)
|
||||||
[](https://goreportcard.com/report/github.com/bakito/adguardhome-sync)
|
[](https://goreportcard.com/report/github.com/bakito/adguardhome-sync)
|
||||||
[](https://coveralls.io/github/bakito/adguardhome-sync?branch=main)
|
[](https://coveralls.io/github/bakito/adguardhome-sync?branch=main)
|
||||||
|
|
||||||
# AdGuardHome sync
|
# AdGuardHome sync
|
||||||
|
|
||||||
Synchronize [AdGuardHome](https://github.com/AdguardTeam/AdGuardHome) config to a replica instance.
|
Synchronize [AdGuardHome](https://github.com/AdguardTeam/AdGuardHome) config to replica instances.
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
Please check the wiki for [FAQ](https://github.com/bakito/adguardhome-sync/wiki/FAQ).
|
||||||
|
|
||||||
## Current sync features
|
## Current sync features
|
||||||
|
|
||||||
@@ -37,16 +41,60 @@ go install github.com/bakito/adguardhome-sync@latest
|
|||||||
|
|
||||||
Both the origin instance must be initially setup via the AdguardHome installation wizard.
|
Both the origin instance must be initially setup via the AdguardHome installation wizard.
|
||||||
|
|
||||||
## Run
|
## Username / Password vs. Cookie
|
||||||
|
|
||||||
|
Some instances of AdGuard Home do not support basic authentication. For instance, many routers with built-in Adguard
|
||||||
|
Home support do not. If this is the case, a valid cookie may be provided instead. If the router protects the AdGuard
|
||||||
|
instance behind its own authentication, the cookie from an authenticated request may allow the sync to succeed.
|
||||||
|
|
||||||
|
- This has been tested successfully against GL.Inet routers with AdGuard Home.
|
||||||
|
- Note: due to the short validity of cookies, this approach is likely only suitable for one-time syncs
|
||||||
|
|
||||||
|
## Run Linux/Mac
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
||||||
|
export LOG_LEVEL=info
|
||||||
export ORIGIN_URL=https://192.168.1.2:3000
|
export ORIGIN_URL=https://192.168.1.2:3000
|
||||||
export ORIGIN_USERNAME=username
|
export ORIGIN_USERNAME=username
|
||||||
export ORIGIN_PASSWORD=password
|
export ORIGIN_PASSWORD=password
|
||||||
|
# export ORIGIN_COOKIE=Origin-Cookie-Name=CCCOOOKKKIIIEEE
|
||||||
export REPLICA_URL=http://192.168.1.3
|
export REPLICA_URL=http://192.168.1.3
|
||||||
export REPLICA_USERNAME=username
|
export REPLICA_USERNAME=username
|
||||||
export REPLICA_PASSWORD=password
|
export REPLICA_PASSWORD=password
|
||||||
|
# export REPLICA_COOKIE=Replica-Cookie-Name=CCCOOOKKKIIIEEE
|
||||||
|
|
||||||
|
# run once
|
||||||
|
adguardhome-sync run
|
||||||
|
|
||||||
|
# run as daemon
|
||||||
|
adguardhome-sync run --cron "*/10 * * * *"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run Windows
|
||||||
|
|
||||||
|
```bash
|
||||||
|
@ECHO OFF
|
||||||
|
@TITLE AdGuardHome-Sync
|
||||||
|
|
||||||
|
REM set LOG_LEVEL=debug
|
||||||
|
set LOG_LEVEL=info
|
||||||
|
REM set LOG_LEVEL=warn
|
||||||
|
REM set LOG_LEVEL=error
|
||||||
|
|
||||||
|
set ORIGIN_URL=http://192.168.1.2:3000
|
||||||
|
set ORIGIN_USERNAME=username
|
||||||
|
set ORIGIN_PASSWORD=password
|
||||||
|
# set ORIGIN_COOKIE=Origin-Cookie-Name=CCCOOOKKKIIIEEE
|
||||||
|
|
||||||
|
set REPLICA_URL=http://192.168.2.2:3000
|
||||||
|
set REPLICA_USERNAME=username
|
||||||
|
set REPLICA_PASSWORD=password
|
||||||
|
# set REPLICA_COOKIE=Replica-Cookie-Name=CCCOOOKKKIIIEEE
|
||||||
|
|
||||||
|
set FEATURES_DHCP=false
|
||||||
|
set FEATURES_DHCP_SERVERCONFIG=false
|
||||||
|
set FEATURES_DHCP_STATICLEASES=false
|
||||||
|
|
||||||
# run once
|
# run once
|
||||||
adguardhome-sync run
|
adguardhome-sync run
|
||||||
@@ -63,7 +111,7 @@ docker run -d \
|
|||||||
-p 8080:8080 \
|
-p 8080:8080 \
|
||||||
-v /path/to/appdata/config/adguardhome-sync.yaml:/config/adguardhome-sync.yaml \
|
-v /path/to/appdata/config/adguardhome-sync.yaml:/config/adguardhome-sync.yaml \
|
||||||
--restart unless-stopped \
|
--restart unless-stopped \
|
||||||
quay.io/bakito/adguardhome-sync:latest
|
ghcr.io/bakito/adguardhome-sync:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
## docker compose
|
## docker compose
|
||||||
@@ -75,8 +123,9 @@ docker run -d \
|
|||||||
version: "2.1"
|
version: "2.1"
|
||||||
services:
|
services:
|
||||||
adguardhome-sync:
|
adguardhome-sync:
|
||||||
image: quay.io/bakito/adguardhome-sync
|
image: ghcr.io/bakito/adguardhome-sync
|
||||||
container_name: adguardhome-sync
|
container_name: adguardhome-sync
|
||||||
|
command: run --config /config/adguardhome-sync.yaml
|
||||||
volumes:
|
volumes:
|
||||||
- /path/to/appdata/config/adguardhome-sync.yaml:/config/adguardhome-sync.yaml
|
- /path/to/appdata/config/adguardhome-sync.yaml:/config/adguardhome-sync.yaml
|
||||||
ports:
|
ports:
|
||||||
@@ -91,35 +140,46 @@ services:
|
|||||||
version: "2.1"
|
version: "2.1"
|
||||||
services:
|
services:
|
||||||
adguardhome-sync:
|
adguardhome-sync:
|
||||||
image: quay.io/bakito/adguardhome-sync
|
image: ghcr.io/bakito/adguardhome-sync
|
||||||
container_name: adguardhome-sync
|
container_name: adguardhome-sync
|
||||||
command: run
|
command: run
|
||||||
environment:
|
environment:
|
||||||
- ORIGIN_URL=https://192.168.1.2:3000
|
LOG_LEVEL: "info"
|
||||||
- ORIGIN_USERNAME=username
|
ORIGIN_URL: "https://192.168.1.2:3000"
|
||||||
- ORIGIN_PASSWORD=password
|
# ORIGIN_WEB_URL: "https://some-other.url" # used in the web interface (default: <origin-url>
|
||||||
- REPLICA_URL=http://192.168.1.3
|
|
||||||
- REPLICA_USERNAME=username
|
ORIGIN_USERNAME: "username"
|
||||||
- REPLICA_PASSWORD=password
|
ORIGIN_PASSWORD: "password"
|
||||||
- REPLICA1_URL=http://192.168.1.4
|
REPLICA_URL: "http://192.168.1.3"
|
||||||
- REPLICA1_USERNAME=username
|
REPLICA_USERNAME: "username"
|
||||||
- REPLICA1_PASSWORD=password
|
REPLICA_PASSWORD: "password"
|
||||||
- REPLICA1_APIPATH=/some/path/control
|
REPLICA1_URL: "http://192.168.1.4"
|
||||||
# - REPLICA1_AUTOSETUP=true # if true, AdGuardHome is automatically initialized.
|
REPLICA1_USERNAME: "username"
|
||||||
- CRON=*/10 * * * * # run every 10 minutes
|
REPLICA1_PASSWORD: "password"
|
||||||
- RUNONSTART=true
|
REPLICA1_API_PATH: "/some/path/control"
|
||||||
|
# REPLICA1_WEB_URL: "https://some-other.url" # used in the web interface (default: <replica-url>
|
||||||
|
# REPLICA1_AUTO_SETUP: true # if true, AdGuardHome is automatically initialized.
|
||||||
|
# REPLICA1_INTERFACE_NAME: 'ens18' # use custom dhcp interface name
|
||||||
|
# REPLICA1_DHCP_SERVER_ENABLED: true/false (optional) enables/disables the dhcp server on the replica
|
||||||
|
CRON: "*/10 * * * *" # run every 10 minutes
|
||||||
|
RUNONSTART: true
|
||||||
|
# CONTINUE_ON_ERROR: false # If enabled, the synchronisation task will not fail on single errors, but will log the errors and continue
|
||||||
|
|
||||||
|
# Configure the sync API server, disabled if api port is 0
|
||||||
|
API_PORT: 8080
|
||||||
|
|
||||||
# Configure sync features; by default all features are enabled.
|
# Configure sync features; by default all features are enabled.
|
||||||
# - FEATURES_GENERALSETTINGS=true
|
# FEATURES_GENERAL_SETTINGS: true
|
||||||
# - FEATURES_QUERYLOGCONFIG=true
|
# FEATURES_QUERY_LOG_CONFIG: true
|
||||||
# - FEATURES_STATSCONFIG=true
|
# FEATURES_STATS_CONFIG: true
|
||||||
# - FEATURES_CLIENTSETTINGS=true
|
# FEATURES_CLIENT_SETTINGS: true
|
||||||
# - FEATURES_SERVICES=true
|
# FEATURES_SERVICES: true
|
||||||
# - FEATURES_FILTERS=true
|
# FEATURES_FILTERS: true
|
||||||
# - FEATURES_DHCP_SERVERCONFIG=true
|
# FEATURES_DHCP_SERVER_CONFIG: true
|
||||||
# - FEATURES_DHCP_STATICLEASES=true
|
# FEATURES_DHCP_STATIC_LEASES: true
|
||||||
# - FEATURES_DNS_SERVERCONFIG=true
|
# FEATURES_DNS_SERVER_CONFIG: true
|
||||||
# - FEATURES_DNS_ACCESSLISTS=true
|
# FEATURES_DNS_ACCESS_LISTS: true
|
||||||
# - FEATURES_DNS_REWRITES=true
|
# FEATURES_DNS_REWRITES: true
|
||||||
ports:
|
ports:
|
||||||
- 8080:8080
|
- 8080:8080
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@@ -136,6 +196,9 @@ cron: "*/10 * * * *"
|
|||||||
# runs the synchronisation on startup
|
# runs the synchronisation on startup
|
||||||
runOnStart: true
|
runOnStart: true
|
||||||
|
|
||||||
|
# If enabled, the synchronisation task will not fail on single errors, but will log the errors and continue
|
||||||
|
continueOnError: false
|
||||||
|
|
||||||
origin:
|
origin:
|
||||||
# url of the origin instance
|
# url of the origin instance
|
||||||
url: https://192.168.1.2:3000
|
url: https://192.168.1.2:3000
|
||||||
@@ -143,6 +206,7 @@ origin:
|
|||||||
# insecureSkipVerify: true # disable tls check
|
# insecureSkipVerify: true # disable tls check
|
||||||
username: username
|
username: username
|
||||||
password: password
|
password: password
|
||||||
|
# cookie: Origin-Cookie-Name=CCCOOOKKKIIIEEE
|
||||||
|
|
||||||
# replica instance (optional, if only one)
|
# replica instance (optional, if only one)
|
||||||
replica:
|
replica:
|
||||||
@@ -150,6 +214,8 @@ replica:
|
|||||||
url: http://192.168.1.3
|
url: http://192.168.1.3
|
||||||
username: username
|
username: username
|
||||||
password: password
|
password: password
|
||||||
|
# cookie: Replica-Cookie-Name=CCCOOOKKKIIIEEE
|
||||||
|
# webURL: "https://some-other.url" # used in the web interface (default: <origin-url>
|
||||||
|
|
||||||
# replicas instances (optional, if more than one)
|
# replicas instances (optional, if more than one)
|
||||||
replicas:
|
replicas:
|
||||||
@@ -157,18 +223,23 @@ replicas:
|
|||||||
- url: http://192.168.1.3
|
- url: http://192.168.1.3
|
||||||
username: username
|
username: username
|
||||||
password: password
|
password: password
|
||||||
|
# cookie: Replica1-Cookie-Name=CCCOOOKKKIIIEEE
|
||||||
- url: http://192.168.1.4
|
- url: http://192.168.1.4
|
||||||
username: username
|
username: username
|
||||||
password: password
|
password: password
|
||||||
# autoSetup: true # if true, AdGuardHome is automatically initialized.
|
# cookie: Replica2-Cookie-Name=CCCOOOKKKIIIEEE
|
||||||
|
# autoSetup: true # if true, AdGuardHome is automatically initialized.
|
||||||
|
# webURL: "https://some-other.url" # used in the web interface (default: <replica-url>
|
||||||
|
|
||||||
# Configure the sync API server, disabled if api port is 0
|
# Configure the sync API server, disabled if api port is 0
|
||||||
api:
|
api:
|
||||||
# Port, default 8080
|
# Port, default 8080
|
||||||
port: 8080
|
port: 8080
|
||||||
# if username and password are defined, basic auth is applied to the sync API
|
# if username and password are defined, basic auth is applied to the sync API
|
||||||
username: username
|
username: username
|
||||||
password: password
|
password: password
|
||||||
|
# enable api dark mode
|
||||||
|
darkMode: true
|
||||||
|
|
||||||
# Configure sync features; by default all features are enabled.
|
# Configure sync features; by default all features are enabled.
|
||||||
features:
|
features:
|
||||||
@@ -196,4 +267,4 @@ The following log levels are supported (default: info)
|
|||||||
- debug
|
- debug
|
||||||
- info
|
- info
|
||||||
- warn
|
- warn
|
||||||
- error
|
- error
|
||||||
|
|||||||
189
cmd/root.go
189
cmd/root.go
@@ -4,53 +4,58 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/log"
|
"github.com/bakito/adguardhome-sync/pkg/log"
|
||||||
"github.com/bakito/adguardhome-sync/pkg/types"
|
"github.com/bakito/adguardhome-sync/pkg/types"
|
||||||
|
"github.com/bakito/adguardhome-sync/pkg/utils"
|
||||||
|
"github.com/bakito/adguardhome-sync/version"
|
||||||
"github.com/mitchellh/go-homedir"
|
"github.com/mitchellh/go-homedir"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
configCron = "cron"
|
configCron = "CRON"
|
||||||
configRunOnStart = "runOnStart"
|
configRunOnStart = "RUN_ON_START"
|
||||||
|
configPrintConfigOnly = "PRINT_CONFIG_ONLY"
|
||||||
|
configContinueOnError = "CONTINUE_ON_ERROR"
|
||||||
|
|
||||||
configAPIPort = "api.port"
|
configAPIPort = "API.PORT"
|
||||||
configAPIUsername = "api.username"
|
configAPIUsername = "API.USERNAME"
|
||||||
configAPIPassword = "api.password"
|
configAPIPassword = "API.PASSWORD"
|
||||||
|
configAPIDarkMode = "API.DARK_MODE"
|
||||||
|
|
||||||
configFeatureDHCPServerConfig = "features.dhcp.serverConfig"
|
configFeatureDHCPServerConfig = "FEATURES.DHCP.SERVER_CONFIG"
|
||||||
configFeatureDHCPStaticLeases = "features.dhcp.staticLeases"
|
configFeatureDHCPStaticLeases = "FEATURES.DHCP.STATIC_LEASES"
|
||||||
configFeatureDNServerConfig = "features.dns.serverConfig"
|
configFeatureDNServerConfig = "FEATURES.DNS.SERVER_CONFIG"
|
||||||
configFeatureDNSPAccessLists = "features.dns.accessLists"
|
configFeatureDNSPAccessLists = "FEATURES.DNS.ACCESS_LISTS"
|
||||||
configFeatureDNSRewrites = "features.dns.rewrites"
|
configFeatureDNSRewrites = "FEATURES.DNS.rewrites"
|
||||||
configFeatureGeneralSettings = "features.generalSettings"
|
configFeatureGeneralSettings = "FEATURES.GENERAL_SETTINGS"
|
||||||
configFeatureQueryLogConfig = "features.queryLogConfig"
|
configFeatureQueryLogConfig = "FEATURES.QUERY_LOG_CONFIG"
|
||||||
configFeatureStatsConfig = "features.statsConfig"
|
configFeatureStatsConfig = "FEATURES.STATS_CONFIG"
|
||||||
configFeatureClientSettings = "features.clientSettings"
|
configFeatureClientSettings = "FEATURES.CLIENT_SETTINGS"
|
||||||
configFeatureServices = "features.services"
|
configFeatureServices = "FEATURES.SERVICES"
|
||||||
configFeatureFilters = "features.filters"
|
configFeatureFilters = "FEATURES.FILTERS"
|
||||||
|
|
||||||
configOriginURL = "origin.url"
|
configOriginURL = "ORIGIN.URL"
|
||||||
configOriginAPIPath = "origin.apiPath"
|
configOriginWebURL = "ORIGIN.WEB_URL"
|
||||||
configOriginUsername = "origin.username"
|
configOriginAPIPath = "ORIGIN.API_PATH"
|
||||||
configOriginPassword = "origin.password"
|
configOriginUsername = "ORIGIN.USERNAME"
|
||||||
configOriginInsecureSkipVerify = "origin.insecureSkipVerify"
|
configOriginPassword = "ORIGIN.PASSWORD"
|
||||||
|
configOriginCookie = "ORIGIN.COOKIE"
|
||||||
|
configOriginInsecureSkipVerify = "ORIGIN.INSECURE_SKIP_VERIFY"
|
||||||
|
|
||||||
configReplicaURL = "replica.url"
|
configReplicaURL = "REPLICA.URL"
|
||||||
configReplicaAPIPath = "replica.apiPath"
|
configReplicaWebURL = "REPLICA.WEB_URL"
|
||||||
configReplicaUsername = "replica.username"
|
configReplicaAPIPath = "REPLICA.API_PATH"
|
||||||
configReplicaPassword = "replica.password"
|
configReplicaUsername = "REPLICA.USERNAME"
|
||||||
configReplicaInsecureSkipVerify = "replica.insecureSkipVerify"
|
configReplicaPassword = "REPLICA.PASSWORD"
|
||||||
configReplicaAutoSetup = "replica.autoSetup"
|
configReplicaCookie = "REPLICA.COOKIE"
|
||||||
|
configReplicaInsecureSkipVerify = "REPLICA.INSECURE_SKIP_VERIFY"
|
||||||
envReplicasUsernameFormat = "REPLICA%s_USERNAME" // #nosec G101
|
configReplicaAutoSetup = "REPLICA.AUTO_SETUP"
|
||||||
envReplicasPasswordFormat = "REPLICA%s_PASSWORD" // #nosec G101
|
configReplicaInterfaceName = "REPLICA.INTERFACE_NAME"
|
||||||
envReplicasAPIPathFormat = "REPLICA%s_APIPATH"
|
|
||||||
envReplicasInsecureSkipVerifyFormat = "REPLICA%s_INSECURESKIPVERIFY"
|
|
||||||
envReplicasAutoSetup = "REPLICA%s_AUTOSETUP"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -61,8 +66,9 @@ var (
|
|||||||
|
|
||||||
// rootCmd represents the base command when called without any subcommands
|
// rootCmd represents the base command when called without any subcommands
|
||||||
var rootCmd = &cobra.Command{
|
var rootCmd = &cobra.Command{
|
||||||
Use: "adguardhome-sync",
|
Use: "adguardhome-sync",
|
||||||
Short: "Synchronize config from one AdGuardHome instance to another",
|
Short: "Synchronize config from one AdGuardHome instance to another",
|
||||||
|
Version: version.Version,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||||
@@ -122,26 +128,129 @@ func getConfig() (*types.Config, error) {
|
|||||||
if err := viper.Unmarshal(cfg); err != nil {
|
if err := viper.Unmarshal(cfg); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if cfg.Replica != nil &&
|
||||||
|
cfg.Replica.URL == "" &&
|
||||||
|
cfg.Replica.Username == "" {
|
||||||
|
cfg.Replica = nil
|
||||||
|
}
|
||||||
|
|
||||||
if len(cfg.Replicas) == 0 {
|
if len(cfg.Replicas) == 0 {
|
||||||
cfg.Replicas = append(cfg.Replicas, collectEnvReplicas()...)
|
cfg.Replicas = append(cfg.Replicas, collectEnvReplicas()...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleDeprecatedEnvVars(cfg)
|
||||||
|
|
||||||
return cfg, nil
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleDeprecatedEnvVars(cfg *types.Config) {
|
||||||
|
value := checkDeprecatedEnvVar("RUNONSTART", "RUN_ON_START")
|
||||||
|
if value != "" {
|
||||||
|
cfg.RunOnStart, _ = strconv.ParseBool(value)
|
||||||
|
}
|
||||||
|
value = checkDeprecatedEnvVar("API_DARKMODE", "API_DARK_MODE")
|
||||||
|
if value != "" {
|
||||||
|
cfg.API.DarkMode, _ = strconv.ParseBool(value)
|
||||||
|
}
|
||||||
|
value = checkDeprecatedEnvVar("FEATURES_GENERALSETTINGS", "FEATURES_GENERAL_SETTINGS")
|
||||||
|
if value != "" {
|
||||||
|
cfg.Features.GeneralSettings, _ = strconv.ParseBool(value)
|
||||||
|
}
|
||||||
|
value = checkDeprecatedEnvVar("FEATURES_QUERYLOGCONFIG", "FEATURES_QUERY_LOG_CONFIG")
|
||||||
|
if value != "" {
|
||||||
|
cfg.Features.QueryLogConfig, _ = strconv.ParseBool(value)
|
||||||
|
}
|
||||||
|
value = checkDeprecatedEnvVar("FEATURES_STATSCONFIG", "FEATURES_STATS_CONFIG")
|
||||||
|
if value != "" {
|
||||||
|
cfg.Features.StatsConfig, _ = strconv.ParseBool(value)
|
||||||
|
}
|
||||||
|
value = checkDeprecatedEnvVar("FEATURES_CLIENTSETTINGS", "FEATURES_CLIENT_SETTINGS")
|
||||||
|
if value != "" {
|
||||||
|
cfg.Features.ClientSettings, _ = strconv.ParseBool(value)
|
||||||
|
}
|
||||||
|
value = checkDeprecatedEnvVar("FEATURES_DHCP_SERVERCONFIG", "FEATURES_DHCP_SERVER_CONFIG")
|
||||||
|
if value != "" {
|
||||||
|
cfg.Features.DHCP.ServerConfig, _ = strconv.ParseBool(value)
|
||||||
|
}
|
||||||
|
value = checkDeprecatedEnvVar("FEATURES_DHCP_STATICLEASES", "FEATURES_DHCP_STATIC_LEASES")
|
||||||
|
if value != "" {
|
||||||
|
cfg.Features.DHCP.StaticLeases, _ = strconv.ParseBool(value)
|
||||||
|
}
|
||||||
|
value = checkDeprecatedEnvVar("FEATURES_DNS_ACCESSLISTS", "FEATURES_DNS_ACCESS_LISTS")
|
||||||
|
if value != "" {
|
||||||
|
cfg.Features.DNS.AccessLists, _ = strconv.ParseBool(value)
|
||||||
|
}
|
||||||
|
value = checkDeprecatedEnvVar("FEATURES_DNS_SERVERCONFIG", "FEATURES_DNS_SERVER_CONFIG")
|
||||||
|
if value != "" {
|
||||||
|
cfg.Features.DNS.ServerConfig, _ = strconv.ParseBool(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.Replica != nil {
|
||||||
|
value = checkDeprecatedEnvVar("REPLICA_WEBURL", "REPLICA_WEB_URL")
|
||||||
|
if value != "" {
|
||||||
|
cfg.Replica.WebURL = value
|
||||||
|
}
|
||||||
|
value = checkDeprecatedEnvVar("REPLICA_AUTOSETUP", "REPLICA_AUTO_SETUP")
|
||||||
|
if value != "" {
|
||||||
|
cfg.Replica.AutoSetup, _ = strconv.ParseBool(value)
|
||||||
|
}
|
||||||
|
value = checkDeprecatedEnvVar("REPLICA_INTERFACENAME", "REPLICA_INTERFACE_NAME")
|
||||||
|
if value != "" {
|
||||||
|
cfg.Replica.InterfaceName = value
|
||||||
|
}
|
||||||
|
value = checkDeprecatedEnvVar("REPLICA_DHCPSERVERENABLED", "REPLICA_DHCP_SERVER_ENABLED")
|
||||||
|
if value != "" {
|
||||||
|
if b, err := strconv.ParseBool(value); err != nil {
|
||||||
|
cfg.Replica.DHCPServerEnabled = utils.Ptr(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkDeprecatedEnvVar(oldName string, newName string) string {
|
||||||
|
old, oldOK := os.LookupEnv(oldName)
|
||||||
|
if oldOK {
|
||||||
|
logger.With("deprecated", oldName, "replacement", newName).
|
||||||
|
Warn("Deprecated env variable is used, please use the correct one")
|
||||||
|
}
|
||||||
|
new, newOK := os.LookupEnv(newName)
|
||||||
|
if newOK {
|
||||||
|
return new
|
||||||
|
}
|
||||||
|
return old
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkDeprecatedReplicaEnvVar(oldPattern, newPattern, replicaID string) string {
|
||||||
|
return checkDeprecatedEnvVar(fmt.Sprintf(oldPattern, replicaID), fmt.Sprintf(newPattern, replicaID))
|
||||||
|
}
|
||||||
|
|
||||||
// Manually collect replicas from env.
|
// Manually collect replicas from env.
|
||||||
func collectEnvReplicas() []types.AdGuardInstance {
|
func collectEnvReplicas() []types.AdGuardInstance {
|
||||||
var replicas []types.AdGuardInstance
|
var replicas []types.AdGuardInstance
|
||||||
for _, v := range os.Environ() {
|
for _, v := range os.Environ() {
|
||||||
if envReplicasURLPattern.MatchString(v) {
|
if envReplicasURLPattern.MatchString(v) {
|
||||||
sm := envReplicasURLPattern.FindStringSubmatch(v)
|
sm := envReplicasURLPattern.FindStringSubmatch(v)
|
||||||
|
index := sm[1]
|
||||||
re := types.AdGuardInstance{
|
re := types.AdGuardInstance{
|
||||||
URL: sm[2],
|
URL: sm[2],
|
||||||
Username: os.Getenv(fmt.Sprintf(envReplicasUsernameFormat, sm[1])),
|
WebURL: os.Getenv(fmt.Sprintf("REPLICA%s_WEB_URL", index)),
|
||||||
Password: os.Getenv(fmt.Sprintf(envReplicasPasswordFormat, sm[1])),
|
APIPath: checkDeprecatedReplicaEnvVar("REPLICA%s_APIPATH", "REPLICA%s_API_PATH", index),
|
||||||
APIPath: os.Getenv(fmt.Sprintf(envReplicasAPIPathFormat, sm[1])),
|
Username: os.Getenv(fmt.Sprintf("REPLICA%s_USERNAME", index)),
|
||||||
InsecureSkipVerify: strings.EqualFold(os.Getenv(fmt.Sprintf(envReplicasInsecureSkipVerifyFormat, sm[1])), "true"),
|
Password: os.Getenv(fmt.Sprintf("REPLICA%s_PASSWORD", index)),
|
||||||
AutoSetup: strings.EqualFold(os.Getenv(fmt.Sprintf(envReplicasAutoSetup, sm[1])), "true"),
|
Cookie: os.Getenv(fmt.Sprintf("REPLICA%s_COOKIE", index)),
|
||||||
|
InsecureSkipVerify: strings.EqualFold(checkDeprecatedReplicaEnvVar("REPLICA%s_INSECURESKIPVERIFY", "REPLICA%s_INSECURE_SKIP_VERIFY", index), "true"),
|
||||||
|
AutoSetup: strings.EqualFold(checkDeprecatedReplicaEnvVar("REPLICA%s_AUTOSETUP", "REPLICA%s_AUTO_SETUP", index), "true"),
|
||||||
|
InterfaceName: checkDeprecatedReplicaEnvVar("REPLICA%s_INTERFACENAME", "REPLICA%s_INTERFACE_NAME", index),
|
||||||
|
}
|
||||||
|
|
||||||
|
dhcpEnabled := checkDeprecatedReplicaEnvVar("REPLICA%s_DHCPSERVERENABLED", "REPLICA%s_DHCP_SERVER_ENABLED", index)
|
||||||
|
if strings.EqualFold(dhcpEnabled, "true") {
|
||||||
|
re.DHCPServerEnabled = utils.Ptr(true)
|
||||||
|
} else if strings.EqualFold(dhcpEnabled, "false") {
|
||||||
|
re.DHCPServerEnabled = utils.Ptr(false)
|
||||||
|
}
|
||||||
|
if re.APIPath == "" {
|
||||||
|
re.APIPath = "/control"
|
||||||
}
|
}
|
||||||
replicas = append(replicas, re)
|
replicas = append(replicas, re)
|
||||||
}
|
}
|
||||||
|
|||||||
112
cmd/root_test.go
112
cmd/root_test.go
@@ -1,6 +1,7 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/types"
|
"github.com/bakito/adguardhome-sync/pkg/types"
|
||||||
@@ -9,6 +10,22 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var envVars = []string{
|
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_GENERALSETTINGS",
|
||||||
"FEATURES_QUERYLOGCONFIG",
|
"FEATURES_QUERYLOGCONFIG",
|
||||||
"FEATURES_STATSCONFIG",
|
"FEATURES_STATSCONFIG",
|
||||||
@@ -20,28 +37,89 @@ var envVars = []string{
|
|||||||
"FEATURES_DNS_SERVERCONFIG",
|
"FEATURES_DNS_SERVERCONFIG",
|
||||||
"FEATURES_DNS_ACCESSLISTS",
|
"FEATURES_DNS_ACCESSLISTS",
|
||||||
"FEATURES_DNS_REWRITES",
|
"FEATURES_DNS_REWRITES",
|
||||||
|
"REPLICA1_INTERFACENAME",
|
||||||
|
"REPLICA1_DHCPSERVERENABLED",
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ = Describe("Run", func() {
|
var _ = Describe("Run", func() {
|
||||||
BeforeEach(func() {
|
Context("deprecated", func() {
|
||||||
for _, envVar := range envVars {
|
BeforeEach(func() {
|
||||||
Ω(os.Unsetenv(envVar)).ShouldNot(HaveOccurred())
|
for _, envVar := range deprecatedEnvVars {
|
||||||
}
|
|
||||||
initConfig()
|
|
||||||
})
|
|
||||||
Context("getConfig", func() {
|
|
||||||
It("features should be true by default", func() {
|
|
||||||
cfg, err := getConfig()
|
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
|
||||||
verifyFeatures(cfg, true)
|
|
||||||
})
|
|
||||||
It("features should be false", func() {
|
|
||||||
for _, envVar := range envVars {
|
|
||||||
Ω(os.Setenv(envVar, "false")).ShouldNot(HaveOccurred())
|
Ω(os.Setenv(envVar, "false")).ShouldNot(HaveOccurred())
|
||||||
}
|
}
|
||||||
cfg, err := getConfig()
|
initConfig()
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
})
|
||||||
verifyFeatures(cfg, false)
|
AfterEach(func() {
|
||||||
|
for _, envVar := range deprecatedEnvVars {
|
||||||
|
Ω(os.Unsetenv(envVar)).ShouldNot(HaveOccurred())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
Context("getConfig", func() {
|
||||||
|
It("features should be false", func() {
|
||||||
|
cfg, err := getConfig()
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
verifyFeatures(cfg, false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("current", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
for _, envVar := range envVars {
|
||||||
|
Ω(os.Unsetenv(envVar)).ShouldNot(HaveOccurred())
|
||||||
|
}
|
||||||
|
initConfig()
|
||||||
|
})
|
||||||
|
AfterEach(func() {
|
||||||
|
for _, envVar := range envVars {
|
||||||
|
Ω(os.Unsetenv(envVar)).ShouldNot(HaveOccurred())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
Context("getConfig", func() {
|
||||||
|
It("features should be true by default", func() {
|
||||||
|
cfg, err := getConfig()
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
verifyFeatures(cfg, true)
|
||||||
|
})
|
||||||
|
It("features should be true by default", func() {
|
||||||
|
cfg, err := getConfig()
|
||||||
|
Ω(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()
|
||||||
|
Ω(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 := getConfig()
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(cfg.Replicas[0].InterfaceName).Should(Equal("eth0"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("dhcp server", func() {
|
||||||
|
It("should enable the dhcp server of replica 1", func() {
|
||||||
|
Ω(os.Setenv("REPLICA1_URL", "https://foo.bar")).ShouldNot(HaveOccurred())
|
||||||
|
Ω(os.Setenv(fmt.Sprintf("REPLICA%s_DHCPSERVERENABLED", "1"), "true")).ShouldNot(HaveOccurred())
|
||||||
|
cfg, err := getConfig()
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(cfg.Replicas[0].DHCPServerEnabled).ShouldNot(BeNil())
|
||||||
|
Ω(*cfg.Replicas[0].DHCPServerEnabled).Should(BeTrue())
|
||||||
|
})
|
||||||
|
It("should disable the dhcp server of replica 1", func() {
|
||||||
|
Ω(os.Setenv("REPLICA1_URL", "https://foo.bar")).ShouldNot(HaveOccurred())
|
||||||
|
Ω(os.Setenv(fmt.Sprintf("REPLICA%s_DHCPSERVERENABLED", "1"), "false")).ShouldNot(HaveOccurred())
|
||||||
|
cfg, err := getConfig()
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(cfg.Replicas[0].DHCPServerEnabled).ShouldNot(BeNil())
|
||||||
|
Ω(*cfg.Replicas[0].DHCPServerEnabled).Should(BeFalse())
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
38
cmd/run.go
38
cmd/run.go
@@ -5,6 +5,7 @@ import (
|
|||||||
"github.com/bakito/adguardhome-sync/pkg/sync"
|
"github.com/bakito/adguardhome-sync/pkg/sync"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// runCmd represents the run command
|
// runCmd represents the run command
|
||||||
@@ -20,6 +21,22 @@ var doCmd = &cobra.Command{
|
|||||||
return 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)
|
return sync.Sync(cfg)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -30,12 +47,21 @@ func init() {
|
|||||||
_ = viper.BindPFlag(configCron, doCmd.PersistentFlags().Lookup("cron"))
|
_ = viper.BindPFlag(configCron, doCmd.PersistentFlags().Lookup("cron"))
|
||||||
doCmd.PersistentFlags().Bool("runOnStart", true, "Run the sync job on start.")
|
doCmd.PersistentFlags().Bool("runOnStart", true, "Run the sync job on start.")
|
||||||
_ = viper.BindPFlag(configRunOnStart, doCmd.PersistentFlags().Lookup("runOnStart"))
|
_ = viper.BindPFlag(configRunOnStart, doCmd.PersistentFlags().Lookup("runOnStart"))
|
||||||
|
doCmd.PersistentFlags().Bool("printConfigOnly", false, "Prints the configuration only and exists. "+
|
||||||
|
"Can be used to debug the config E.g: when having authentication issues.")
|
||||||
|
_ = viper.BindPFlag(configPrintConfigOnly, doCmd.PersistentFlags().Lookup("printConfigOnly"))
|
||||||
|
doCmd.PersistentFlags().Bool("continueOnError", false, "If enabled, the synchronisation task "+
|
||||||
|
"will not fail on single errors, but will log the errors and continue.")
|
||||||
|
_ = viper.BindPFlag(configContinueOnError, doCmd.PersistentFlags().Lookup("continueOnError"))
|
||||||
|
|
||||||
doCmd.PersistentFlags().Int("api-port", 8080, "Sync API Port, the API endpoint will be started to enable remote triggering; if 0 port API is disabled.")
|
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"))
|
_ = viper.BindPFlag(configAPIPort, doCmd.PersistentFlags().Lookup("api-port"))
|
||||||
doCmd.PersistentFlags().String("api-username", "", "Sync API username")
|
doCmd.PersistentFlags().String("api-username", "", "Sync API username")
|
||||||
_ = viper.BindPFlag(configAPIUsername, doCmd.PersistentFlags().Lookup("api-username"))
|
_ = viper.BindPFlag(configAPIUsername, doCmd.PersistentFlags().Lookup("api-username"))
|
||||||
doCmd.PersistentFlags().String("api-password", "", "Sync API password")
|
doCmd.PersistentFlags().String("api-password", "", "Sync API password")
|
||||||
_ = viper.BindPFlag(configAPIPassword, doCmd.PersistentFlags().Lookup("api-password"))
|
_ = viper.BindPFlag(configAPIPassword, doCmd.PersistentFlags().Lookup("api-password"))
|
||||||
|
doCmd.PersistentFlags().String("api-darkMode", "", "API UI in dark mode")
|
||||||
|
_ = viper.BindPFlag(configAPIDarkMode, doCmd.PersistentFlags().Lookup("api-darkMode"))
|
||||||
|
|
||||||
doCmd.PersistentFlags().Bool("feature-dhcp-server-config", true, "Enable DHCP server config feature")
|
doCmd.PersistentFlags().Bool("feature-dhcp-server-config", true, "Enable DHCP server config feature")
|
||||||
_ = viper.BindPFlag(configFeatureDHCPServerConfig, doCmd.PersistentFlags().Lookup("feature-dhcp-server-config"))
|
_ = viper.BindPFlag(configFeatureDHCPServerConfig, doCmd.PersistentFlags().Lookup("feature-dhcp-server-config"))
|
||||||
@@ -64,25 +90,35 @@ func init() {
|
|||||||
|
|
||||||
doCmd.PersistentFlags().String("origin-url", "", "Origin instance url")
|
doCmd.PersistentFlags().String("origin-url", "", "Origin instance url")
|
||||||
_ = viper.BindPFlag(configOriginURL, doCmd.PersistentFlags().Lookup("origin-url"))
|
_ = viper.BindPFlag(configOriginURL, doCmd.PersistentFlags().Lookup("origin-url"))
|
||||||
|
doCmd.PersistentFlags().String("origin-web-url", "", "Origin instance web url used in the web interface (default: <origin-url>)")
|
||||||
|
_ = viper.BindPFlag(configOriginWebURL, doCmd.PersistentFlags().Lookup("origin-web-url"))
|
||||||
doCmd.PersistentFlags().String("origin-api-path", "/control", "Origin instance API path")
|
doCmd.PersistentFlags().String("origin-api-path", "/control", "Origin instance API path")
|
||||||
_ = viper.BindPFlag(configOriginAPIPath, doCmd.PersistentFlags().Lookup("origin-api-path"))
|
_ = viper.BindPFlag(configOriginAPIPath, doCmd.PersistentFlags().Lookup("origin-api-path"))
|
||||||
doCmd.PersistentFlags().String("origin-username", "", "Origin instance username")
|
doCmd.PersistentFlags().String("origin-username", "", "Origin instance username")
|
||||||
_ = viper.BindPFlag(configOriginUsername, doCmd.PersistentFlags().Lookup("origin-username"))
|
_ = viper.BindPFlag(configOriginUsername, doCmd.PersistentFlags().Lookup("origin-username"))
|
||||||
doCmd.PersistentFlags().String("origin-password", "", "Origin instance password")
|
doCmd.PersistentFlags().String("origin-password", "", "Origin instance password")
|
||||||
_ = viper.BindPFlag(configOriginPassword, doCmd.PersistentFlags().Lookup("origin-password"))
|
_ = viper.BindPFlag(configOriginPassword, doCmd.PersistentFlags().Lookup("origin-password"))
|
||||||
doCmd.PersistentFlags().String("origin-insecure-skip-verify", "", "Enable Origin instance InsecureSkipVerify")
|
doCmd.PersistentFlags().String("origin-cookie", "", "If Set, uses a cookie for authentication")
|
||||||
|
_ = viper.BindPFlag(configOriginCookie, doCmd.PersistentFlags().Lookup("origin-cookie"))
|
||||||
|
doCmd.PersistentFlags().Bool("origin-insecure-skip-verify", false, "Enable Origin instance InsecureSkipVerify")
|
||||||
_ = viper.BindPFlag(configOriginInsecureSkipVerify, doCmd.PersistentFlags().Lookup("origin-insecure-skip-verify"))
|
_ = viper.BindPFlag(configOriginInsecureSkipVerify, doCmd.PersistentFlags().Lookup("origin-insecure-skip-verify"))
|
||||||
|
|
||||||
doCmd.PersistentFlags().String("replica-url", "", "Replica instance url")
|
doCmd.PersistentFlags().String("replica-url", "", "Replica instance url")
|
||||||
_ = viper.BindPFlag(configReplicaURL, doCmd.PersistentFlags().Lookup("replica-url"))
|
_ = viper.BindPFlag(configReplicaURL, doCmd.PersistentFlags().Lookup("replica-url"))
|
||||||
|
doCmd.PersistentFlags().String("replica-web-url", "", "Replica instance web url used in the web interface (default: <replica-url>)")
|
||||||
|
_ = viper.BindPFlag(configReplicaWebURL, doCmd.PersistentFlags().Lookup("replica-web-url"))
|
||||||
doCmd.PersistentFlags().String("replica-api-path", "/control", "Replica instance API path")
|
doCmd.PersistentFlags().String("replica-api-path", "/control", "Replica instance API path")
|
||||||
_ = viper.BindPFlag(configReplicaAPIPath, doCmd.PersistentFlags().Lookup("replica-api-path"))
|
_ = viper.BindPFlag(configReplicaAPIPath, doCmd.PersistentFlags().Lookup("replica-api-path"))
|
||||||
doCmd.PersistentFlags().String("replica-username", "", "Replica instance username")
|
doCmd.PersistentFlags().String("replica-username", "", "Replica instance username")
|
||||||
_ = viper.BindPFlag(configReplicaUsername, doCmd.PersistentFlags().Lookup("replica-username"))
|
_ = viper.BindPFlag(configReplicaUsername, doCmd.PersistentFlags().Lookup("replica-username"))
|
||||||
doCmd.PersistentFlags().String("replica-password", "", "Replica instance password")
|
doCmd.PersistentFlags().String("replica-password", "", "Replica instance password")
|
||||||
_ = viper.BindPFlag(configReplicaPassword, doCmd.PersistentFlags().Lookup("replica-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")
|
doCmd.PersistentFlags().Bool("replica-insecure-skip-verify", false, "Enable Replica instance InsecureSkipVerify")
|
||||||
_ = viper.BindPFlag(configReplicaInsecureSkipVerify, doCmd.PersistentFlags().Lookup("replica-insecure-skip-verify"))
|
_ = 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.")
|
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"))
|
_ = viper.BindPFlag(configReplicaAutoSetup, doCmd.PersistentFlags().Lookup("replica-auto-setup"))
|
||||||
|
doCmd.PersistentFlags().String("replica-interface-name", "", "Optional change the interface name of the replica if it differs from the master")
|
||||||
|
_ = viper.BindPFlag(configReplicaInterfaceName, doCmd.PersistentFlags().Lookup("replica-interface-name"))
|
||||||
}
|
}
|
||||||
|
|||||||
90
go.mod
90
go.mod
@@ -1,38 +1,80 @@
|
|||||||
module github.com/bakito/adguardhome-sync
|
module github.com/bakito/adguardhome-sync
|
||||||
|
|
||||||
go 1.17
|
go 1.21
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/go-resty/resty/v2 v2.7.0
|
github.com/gin-gonic/gin v1.9.1
|
||||||
|
github.com/go-resty/resty/v2 v2.9.1
|
||||||
github.com/golang/mock v1.6.0
|
github.com/golang/mock v1.6.0
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.5.0
|
||||||
|
github.com/jinzhu/copier v0.4.0
|
||||||
github.com/mitchellh/go-homedir v1.1.0
|
github.com/mitchellh/go-homedir v1.1.0
|
||||||
github.com/onsi/ginkgo/v2 v2.1.0
|
github.com/oapi-codegen/runtime v1.1.1
|
||||||
github.com/onsi/gomega v1.18.0
|
github.com/onsi/ginkgo/v2 v2.13.2
|
||||||
|
github.com/onsi/gomega v1.30.0
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/spf13/cobra v1.3.0
|
github.com/spf13/cobra v1.8.0
|
||||||
github.com/spf13/viper v1.10.1
|
github.com/spf13/viper v1.18.2
|
||||||
go.uber.org/zap v1.20.0
|
go.uber.org/zap v1.26.0
|
||||||
golang.org/x/mod v0.5.1
|
golang.org/x/mod v0.14.0
|
||||||
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
|
k8s.io/apimachinery v0.29.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/fsnotify/fsnotify v1.5.1 // indirect
|
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
|
||||||
|
github.com/bytedance/sonic v1.10.2 // indirect
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
||||||
|
github.com/chenzhuoyu/iasm v0.9.1 // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||||
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
|
github.com/go-logr/logr v1.3.0 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/go-playground/validator/v10 v10.16.0 // indirect
|
||||||
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
|
github.com/google/go-cmp v0.6.0 // indirect
|
||||||
|
github.com/google/gofuzz v1.2.0 // indirect
|
||||||
|
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect
|
||||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/magiconair/properties v1.8.5 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.4.3 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
|
||||||
github.com/pelletier/go-toml v1.9.4 // indirect
|
github.com/leodido/go-urn v1.2.4 // indirect
|
||||||
github.com/spf13/afero v1.6.0 // indirect
|
github.com/magiconair/properties v1.8.7 // indirect
|
||||||
github.com/spf13/cast v1.4.1 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.1.1 // indirect
|
||||||
|
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||||
|
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||||
|
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||||
|
github.com/spf13/afero v1.11.0 // indirect
|
||||||
|
github.com/spf13/cast v1.6.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/subosito/gotenv v1.2.0 // indirect
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
go.uber.org/atomic v1.7.0 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
go.uber.org/multierr v1.6.0 // indirect
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
golang.org/x/net v0.0.0-20211029224645-99673261e6eb // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
|
golang.org/x/arch v0.7.0 // indirect
|
||||||
golang.org/x/text v0.3.7 // indirect
|
golang.org/x/crypto v0.18.0 // indirect
|
||||||
gopkg.in/ini.v1 v1.66.2 // indirect
|
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc // indirect
|
||||||
|
golang.org/x/net v0.19.0 // indirect
|
||||||
|
golang.org/x/sys v0.16.0 // indirect
|
||||||
|
golang.org/x/text v0.14.0 // indirect
|
||||||
|
golang.org/x/tools v0.16.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.32.0 // indirect
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
|
||||||
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
k8s.io/klog/v2 v2.110.1 // indirect
|
||||||
|
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
|
||||||
|
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||||
|
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
63
openapi/main.go
Normal file
63
openapi/main.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
version := "master"
|
||||||
|
fileName := "schema-master.yaml"
|
||||||
|
if len(os.Args) > 1 {
|
||||||
|
version = os.Args[1]
|
||||||
|
fileName = "schema.yaml"
|
||||||
|
}
|
||||||
|
log.Printf("Patching schema version %s\n", version)
|
||||||
|
|
||||||
|
resp, err := http.Get(fmt.Sprintf("https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/%s/openapi/openapi.yaml", version))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
defer func() { _ = resp.Body.Close() }()
|
||||||
|
data, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
schema := make(map[string]interface{})
|
||||||
|
err = yaml.Unmarshal(data, &schema)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if requestBodies, ok, _ := unstructured.NestedMap(schema, "components", "requestBodies"); ok {
|
||||||
|
for k := range requestBodies {
|
||||||
|
_ = unstructured.SetNestedField(schema, k+"Body", "components", "requestBodies", k, "x-go-name")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if dnsInfo, ok, _ := unstructured.NestedMap(schema,
|
||||||
|
"paths", "/dns_info", "get", "responses", "200", "content", "application/json", "schema"); ok {
|
||||||
|
if allOf, ok, _ := unstructured.NestedSlice(dnsInfo, "allOf"); ok && len(allOf) == 2 {
|
||||||
|
delete(dnsInfo, "allOf")
|
||||||
|
if err := unstructured.SetNestedMap(schema, allOf[0].(map[string]interface{}),
|
||||||
|
"paths", "/dns_info", "get", "responses", "200", "content", "application/json", "schema"); err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b, err := yaml.Marshal(&schema)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
log.Printf("Writing schema file tmp/%s", fileName)
|
||||||
|
err = os.WriteFile("tmp/"+fileName, b, 0o600)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
101
pkg/client/client-methods.go
Normal file
101
pkg/client/client-methods.go
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (cl *client) doGet(req *resty.Request, url string) error {
|
||||||
|
rl := cl.log.With("method", "GET", "path", url)
|
||||||
|
if cl.client.UserInfo != nil {
|
||||||
|
rl = rl.With("username", cl.client.UserInfo.Username)
|
||||||
|
}
|
||||||
|
req.ForceContentType("application/json")
|
||||||
|
rl.Debug("do get")
|
||||||
|
resp, err := req.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
if resp != nil && resp.StatusCode() == http.StatusFound {
|
||||||
|
loc := resp.Header().Get("Location")
|
||||||
|
if loc == "/install.html" || loc == "/control/install.html" {
|
||||||
|
return ErrSetupNeeded
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rl.With("status", resp.StatusCode(), "body", string(resp.Body()), "error", err).Debug("error in do get")
|
||||||
|
return detailedError(resp, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkAuthenticationIssue(resp, rl)
|
||||||
|
|
||||||
|
rl.With(
|
||||||
|
"status", resp.StatusCode(),
|
||||||
|
"body", string(resp.Body()),
|
||||||
|
"content-type", resp.Header()["Content-Type"],
|
||||||
|
).Debug("got response")
|
||||||
|
if resp.StatusCode() != http.StatusOK {
|
||||||
|
return detailedError(resp, nil)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) doPost(req *resty.Request, url string) error {
|
||||||
|
rl := cl.log.With("method", "POST", "path", url)
|
||||||
|
if cl.client.UserInfo != nil {
|
||||||
|
rl = rl.With("username", cl.client.UserInfo.Username)
|
||||||
|
}
|
||||||
|
b, _ := json.Marshal(req.Body)
|
||||||
|
rl.With("body", string(b)).Debug("do post")
|
||||||
|
resp, err := req.Post(url)
|
||||||
|
if err != nil {
|
||||||
|
rl.With("status", resp.StatusCode(), "body", string(resp.Body()), "error", err).Debug("error in do post")
|
||||||
|
return detailedError(resp, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkAuthenticationIssue(resp, rl)
|
||||||
|
|
||||||
|
rl.With(
|
||||||
|
"status", resp.StatusCode(),
|
||||||
|
"body", string(resp.Body()),
|
||||||
|
"content-type", contentType(resp),
|
||||||
|
).Debug("got response")
|
||||||
|
if resp.StatusCode() != http.StatusOK {
|
||||||
|
return detailedError(resp, nil)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) doPut(req *resty.Request, url string) error {
|
||||||
|
rl := cl.log.With("method", "PUT", "path", url)
|
||||||
|
if cl.client.UserInfo != nil {
|
||||||
|
rl = rl.With("username", cl.client.UserInfo.Username)
|
||||||
|
}
|
||||||
|
b, _ := json.Marshal(req.Body)
|
||||||
|
rl.With("body", string(b)).Debug("do put")
|
||||||
|
resp, err := req.Put(url)
|
||||||
|
if err != nil {
|
||||||
|
rl.With("status", resp.StatusCode(), "body", string(resp.Body()), "error", err).Debug("error in do put")
|
||||||
|
return detailedError(resp, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkAuthenticationIssue(resp, rl)
|
||||||
|
|
||||||
|
rl.With(
|
||||||
|
"status", resp.StatusCode(),
|
||||||
|
"body", string(resp.Body()),
|
||||||
|
"content-type", contentType(resp),
|
||||||
|
).Debug("got response")
|
||||||
|
if resp.StatusCode() != http.StatusOK {
|
||||||
|
return detailedError(resp, nil)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkAuthenticationIssue(resp *resty.Response, rl *zap.SugaredLogger) {
|
||||||
|
if resp != nil && (resp.StatusCode() == http.StatusUnauthorized || resp.StatusCode() == http.StatusForbidden) {
|
||||||
|
rl.With("status", resp.StatusCode()).Error("there seems to be an authentication issue - " +
|
||||||
|
"please check https://github.com/bakito/adguardhome-sync/wiki/FAQ")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,20 +6,38 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/pkg/client/model"
|
||||||
"github.com/bakito/adguardhome-sync/pkg/log"
|
"github.com/bakito/adguardhome-sync/pkg/log"
|
||||||
"github.com/bakito/adguardhome-sync/pkg/types"
|
"github.com/bakito/adguardhome-sync/pkg/types"
|
||||||
|
"github.com/bakito/adguardhome-sync/pkg/utils"
|
||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const envRedirectPolicyNoOfRedirects = "REDIRECT_POLICY_NO_OF_REDIRECTS"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
l = log.GetLogger("client")
|
l = log.GetLogger("client")
|
||||||
// ErrSetupNeeded custom error
|
// ErrSetupNeeded custom error
|
||||||
ErrSetupNeeded = errors.New("setup needed")
|
ErrSetupNeeded = errors.New("setup needed")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func detailedError(resp *resty.Response, err error) error {
|
||||||
|
e := resp.Status()
|
||||||
|
if len(resp.Body()) > 0 {
|
||||||
|
e += fmt.Sprintf("(%s)", string(resp.Body()))
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
e += fmt.Sprintf(": %s", err.Error())
|
||||||
|
}
|
||||||
|
return errors.New(e)
|
||||||
|
}
|
||||||
|
|
||||||
// New create a new client
|
// New create a new client
|
||||||
func New(config types.AdGuardInstance) (Client, error) {
|
func New(config types.AdGuardInstance) (Client, error) {
|
||||||
var apiURL string
|
var apiURL string
|
||||||
@@ -40,139 +58,118 @@ func New(config types.AdGuardInstance) (Client, error) {
|
|||||||
cl.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
|
cl.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.Username != "" && config.Password != "" {
|
cookieParts := strings.Split(config.Cookie, "=")
|
||||||
|
if len(cookieParts) == 2 {
|
||||||
|
cl.SetCookie(&http.Cookie{
|
||||||
|
Name: cookieParts[0],
|
||||||
|
Value: cookieParts[1],
|
||||||
|
})
|
||||||
|
} else if config.Username != "" && config.Password != "" {
|
||||||
cl = cl.SetBasicAuth(config.Username, config.Password)
|
cl = cl.SetBasicAuth(config.Username, config.Password)
|
||||||
}
|
}
|
||||||
|
|
||||||
// no redirect
|
if v, ok := os.LookupEnv(envRedirectPolicyNoOfRedirects); ok {
|
||||||
cl.SetRedirectPolicy(resty.NoRedirectPolicy())
|
nbr, err := strconv.Atoi(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing env var %q value must be an integer", envRedirectPolicyNoOfRedirects)
|
||||||
|
}
|
||||||
|
cl.SetRedirectPolicy(resty.FlexibleRedirectPolicy(nbr))
|
||||||
|
} else {
|
||||||
|
// no redirect
|
||||||
|
cl.SetRedirectPolicy(resty.NoRedirectPolicy())
|
||||||
|
}
|
||||||
|
|
||||||
return &client{
|
return &client{
|
||||||
host: u.Host,
|
host: config.Host,
|
||||||
client: cl,
|
client: cl,
|
||||||
log: l.With("host", u.Host),
|
log: l.With("host", config.Host),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client AdguardHome API client interface
|
// Client AdguardHome API client interface
|
||||||
type Client interface {
|
type Client interface {
|
||||||
Host() string
|
Host() string
|
||||||
|
Status() (*model.ServerStatus, error)
|
||||||
Status() (*types.Status, error)
|
|
||||||
ToggleProtection(enable bool) error
|
ToggleProtection(enable bool) error
|
||||||
RewriteList() (*types.RewriteEntries, error)
|
RewriteList() (*model.RewriteEntries, error)
|
||||||
AddRewriteEntries(e ...types.RewriteEntry) error
|
AddRewriteEntries(e ...model.RewriteEntry) error
|
||||||
DeleteRewriteEntries(e ...types.RewriteEntry) error
|
DeleteRewriteEntries(e ...model.RewriteEntry) error
|
||||||
|
Filtering() (*model.FilterStatus, error)
|
||||||
Filtering() (*types.FilteringStatus, error)
|
ToggleFiltering(enabled bool, interval int) error
|
||||||
ToggleFiltering(enabled bool, interval float64) error
|
AddFilter(whitelist bool, f model.Filter) error
|
||||||
AddFilters(whitelist bool, e ...types.Filter) error
|
DeleteFilter(whitelist bool, f model.Filter) error
|
||||||
DeleteFilters(whitelist bool, e ...types.Filter) error
|
UpdateFilter(whitelist bool, f model.Filter) error
|
||||||
UpdateFilters(whitelist bool, e ...types.Filter) error
|
|
||||||
RefreshFilters(whitelist bool) error
|
RefreshFilters(whitelist bool) error
|
||||||
SetCustomRules(rules types.UserRules) error
|
SetCustomRules(rules *[]string) error
|
||||||
|
|
||||||
SafeBrowsing() (bool, error)
|
SafeBrowsing() (bool, error)
|
||||||
ToggleSafeBrowsing(enable bool) error
|
ToggleSafeBrowsing(enable bool) error
|
||||||
Parental() (bool, error)
|
Parental() (bool, error)
|
||||||
ToggleParental(enable bool) error
|
ToggleParental(enable bool) error
|
||||||
SafeSearch() (bool, error)
|
SafeSearchConfig() (*model.SafeSearchConfig, error)
|
||||||
ToggleSafeSearch(enable bool) error
|
SetSafeSearchConfig(settings *model.SafeSearchConfig) error
|
||||||
|
ProfileInfo() (*model.ProfileInfo, error)
|
||||||
Services() (types.Services, error)
|
SetProfileInfo(settings *model.ProfileInfo) error
|
||||||
SetServices(services types.Services) error
|
BlockedServices() (*model.BlockedServicesArray, error)
|
||||||
|
BlockedServicesSchedule() (*model.BlockedServicesSchedule, error)
|
||||||
Clients() (*types.Clients, error)
|
SetBlockedServices(services *model.BlockedServicesArray) error
|
||||||
AddClients(client ...types.Client) error
|
SetBlockedServicesSchedule(schedule *model.BlockedServicesSchedule) error
|
||||||
UpdateClients(client ...types.Client) error
|
Clients() (*model.Clients, error)
|
||||||
DeleteClients(client ...types.Client) error
|
AddClient(client *model.Client) error
|
||||||
|
UpdateClient(client *model.Client) error
|
||||||
QueryLogConfig() (*types.QueryLogConfig, error)
|
DeleteClient(client *model.Client) error
|
||||||
SetQueryLogConfig(enabled bool, interval float64, anonymizeClientIP bool) error
|
QueryLogConfig() (*model.QueryLogConfig, error)
|
||||||
StatsConfig() (*types.IntervalConfig, error)
|
SetQueryLogConfig(*model.QueryLogConfig) error
|
||||||
SetStatsConfig(interval float64) error
|
StatsConfig() (*model.StatsConfig, error)
|
||||||
|
SetStatsConfig(sc *model.StatsConfig) error
|
||||||
Setup() error
|
Setup() error
|
||||||
|
AccessList() (*model.AccessList, error)
|
||||||
AccessList() (*types.AccessList, error)
|
SetAccessList(*model.AccessList) error
|
||||||
SetAccessList(*types.AccessList) error
|
DNSConfig() (*model.DNSConfig, error)
|
||||||
|
SetDNSConfig(*model.DNSConfig) error
|
||||||
DNSConfig() (*types.DNSConfig, error)
|
DhcpConfig() (*model.DhcpStatus, error)
|
||||||
SetDNSConfig(*types.DNSConfig) error
|
SetDhcpConfig(*model.DhcpStatus) error
|
||||||
|
AddDHCPStaticLease(lease model.DhcpStaticLease) error
|
||||||
DHCPServerConfig() (*types.DHCPServerConfig, error)
|
DeleteDHCPStaticLease(lease model.DhcpStaticLease) error
|
||||||
SetDHCPServerConfig(*types.DHCPServerConfig) error
|
|
||||||
AddDHCPStaticLeases(leases ...types.Lease) error
|
|
||||||
DeleteDHCPStaticLeases(leases ...types.Lease) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type client struct {
|
type client struct {
|
||||||
client *resty.Client
|
client *resty.Client
|
||||||
log *zap.SugaredLogger
|
log *zap.SugaredLogger
|
||||||
host string
|
host string
|
||||||
|
version string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *client) Host() string {
|
func (cl *client) Host() string {
|
||||||
return cl.host
|
return cl.host
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *client) doGet(req *resty.Request, url string) error {
|
func contentType(resp *resty.Response) string {
|
||||||
rl := cl.log.With("method", "GET", "path", url)
|
if ct, ok := resp.Header()["Content-Type"]; ok {
|
||||||
if cl.client.UserInfo != nil {
|
if len(ct) != 1 {
|
||||||
rl = rl.With("username", cl.client.UserInfo.Username)
|
return fmt.Sprintf("%v", ct)
|
||||||
}
|
|
||||||
rl.Debug("do get")
|
|
||||||
resp, err := req.Get(url)
|
|
||||||
if err != nil {
|
|
||||||
if resp != nil && resp.StatusCode() == http.StatusFound {
|
|
||||||
loc := resp.Header().Get("Location")
|
|
||||||
if loc == "/install.html" {
|
|
||||||
return ErrSetupNeeded
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
rl.With("status", resp.StatusCode(), "body", string(resp.Body()), "error", err).Debug("error in do get")
|
return ct[0]
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
rl.With("status", resp.StatusCode(), "body", string(resp.Body())).Debug("got response")
|
return ""
|
||||||
if resp.StatusCode() != http.StatusOK {
|
|
||||||
return errors.New(resp.Status())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *client) doPost(req *resty.Request, url string) error {
|
func (cl *client) Status() (*model.ServerStatus, error) {
|
||||||
rl := cl.log.With("method", "POST", "path", url)
|
status := &model.ServerStatus{}
|
||||||
if cl.client.UserInfo != nil {
|
|
||||||
rl = rl.With("username", cl.client.UserInfo.Username)
|
|
||||||
}
|
|
||||||
rl.Debug("do post")
|
|
||||||
resp, err := req.Post(url)
|
|
||||||
if err != nil {
|
|
||||||
rl.With("status", resp.StatusCode(), "body", string(resp.Body()), "error", err).Debug("error in do post")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
rl.With("status", resp.StatusCode(), "body", string(resp.Body())).Debug("got response")
|
|
||||||
if resp.StatusCode() != http.StatusOK {
|
|
||||||
return errors.New(resp.Status())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) Status() (*types.Status, error) {
|
|
||||||
status := &types.Status{}
|
|
||||||
err := cl.doGet(cl.client.R().EnableTrace().SetResult(status), "status")
|
err := cl.doGet(cl.client.R().EnableTrace().SetResult(status), "status")
|
||||||
|
cl.version = status.Version
|
||||||
return status, err
|
return status, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *client) RewriteList() (*types.RewriteEntries, error) {
|
func (cl *client) RewriteList() (*model.RewriteEntries, error) {
|
||||||
rewrites := &types.RewriteEntries{}
|
rewrites := &model.RewriteEntries{}
|
||||||
err := cl.doGet(cl.client.R().EnableTrace().SetResult(&rewrites), "/rewrite/list")
|
err := cl.doGet(cl.client.R().EnableTrace().SetResult(&rewrites), "/rewrite/list")
|
||||||
return rewrites, err
|
return rewrites, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *client) AddRewriteEntries(entries ...types.RewriteEntry) error {
|
func (cl *client) AddRewriteEntries(entries ...model.RewriteEntry) error {
|
||||||
for i := range entries {
|
for i := range entries {
|
||||||
e := entries[i]
|
e := entries[i]
|
||||||
cl.log.With("domain", e.Domain, "answer", e.Answer).Info("Add rewrite entry")
|
cl.log.With("domain", e.Domain, "answer", e.Answer).Info("Add DNS rewrite entry")
|
||||||
err := cl.doPost(cl.client.R().EnableTrace().SetBody(&e), "/rewrite/add")
|
err := cl.doPost(cl.client.R().EnableTrace().SetBody(&e), "/rewrite/add")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -181,10 +178,10 @@ func (cl *client) AddRewriteEntries(entries ...types.RewriteEntry) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *client) DeleteRewriteEntries(entries ...types.RewriteEntry) error {
|
func (cl *client) DeleteRewriteEntries(entries ...model.RewriteEntry) error {
|
||||||
for i := range entries {
|
for i := range entries {
|
||||||
e := entries[i]
|
e := entries[i]
|
||||||
cl.log.With("domain", e.Domain, "answer", e.Answer).Info("Delete rewrite entry")
|
cl.log.With("domain", e.Domain, "answer", e.Answer).Info("Delete DNS rewrite entry")
|
||||||
err := cl.doPost(cl.client.R().EnableTrace().SetBody(&e), "/rewrite/delete")
|
err := cl.doPost(cl.client.R().EnableTrace().SetBody(&e), "/rewrite/delete")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -209,16 +206,8 @@ func (cl *client) ToggleParental(enable bool) error {
|
|||||||
return cl.toggleBool("parental", enable)
|
return cl.toggleBool("parental", enable)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *client) SafeSearch() (bool, error) {
|
|
||||||
return cl.toggleStatus("safesearch")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) ToggleSafeSearch(enable bool) error {
|
|
||||||
return cl.toggleBool("safesearch", enable)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *client) toggleStatus(mode string) (bool, error) {
|
func (cl *client) toggleStatus(mode string) (bool, error) {
|
||||||
fs := &types.EnableConfig{}
|
fs := &model.EnableConfig{}
|
||||||
err := cl.doGet(cl.client.R().EnableTrace().SetResult(fs), fmt.Sprintf("/%s/status", mode))
|
err := cl.doGet(cl.client.R().EnableTrace().SetResult(fs), fmt.Sprintf("/%s/status", mode))
|
||||||
return fs.Enabled, err
|
return fs.Enabled, err
|
||||||
}
|
}
|
||||||
@@ -234,51 +223,36 @@ func (cl *client) toggleBool(mode string, enable bool) error {
|
|||||||
return cl.doPost(cl.client.R().EnableTrace(), fmt.Sprintf("/%s/%s", mode, target))
|
return cl.doPost(cl.client.R().EnableTrace(), fmt.Sprintf("/%s/%s", mode, target))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *client) Filtering() (*types.FilteringStatus, error) {
|
func (cl *client) Filtering() (*model.FilterStatus, error) {
|
||||||
f := &types.FilteringStatus{}
|
f := &model.FilterStatus{}
|
||||||
err := cl.doGet(cl.client.R().EnableTrace().SetResult(f), "/filtering/status")
|
err := cl.doGet(cl.client.R().EnableTrace().SetResult(f), "/filtering/status")
|
||||||
return f, err
|
return f, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *client) AddFilters(whitelist bool, filters ...types.Filter) error {
|
func (cl *client) AddFilter(whitelist bool, f model.Filter) error {
|
||||||
for _, f := range filters {
|
cl.log.With("url", f.Url, "whitelist", whitelist, "enabled", f.Enabled).Info("Add filter")
|
||||||
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)}
|
||||||
ff := &types.Filter{Name: f.Name, URL: f.URL, Whitelist: whitelist}
|
return cl.doPost(cl.client.R().EnableTrace().SetBody(ff), "/filtering/add_url")
|
||||||
err := cl.doPost(cl.client.R().EnableTrace().SetBody(ff), "/filtering/add_url")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *client) DeleteFilters(whitelist bool, filters ...types.Filter) error {
|
func (cl *client) DeleteFilter(whitelist bool, f model.Filter) error {
|
||||||
for _, f := range filters {
|
cl.log.With("url", f.Url, "whitelist", whitelist, "enabled", f.Enabled).Info("Delete filter")
|
||||||
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)}
|
||||||
ff := &types.Filter{URL: f.URL, Whitelist: whitelist}
|
return cl.doPost(cl.client.R().EnableTrace().SetBody(ff), "/filtering/remove_url")
|
||||||
err := cl.doPost(cl.client.R().EnableTrace().SetBody(ff), "/filtering/remove_url")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *client) UpdateFilters(whitelist bool, filters ...types.Filter) error {
|
func (cl *client) UpdateFilter(whitelist bool, f model.Filter) error {
|
||||||
for _, f := range filters {
|
cl.log.With("url", f.Url, "whitelist", whitelist, "enabled", f.Enabled).Info("Update filter")
|
||||||
cl.log.With("url", f.URL, "whitelist", whitelist, "enabled", f.Enabled).Info("Update filter")
|
fu := &model.FilterSetUrl{
|
||||||
fu := &types.FilterUpdate{Whitelist: whitelist, URL: f.URL, Data: types.Filter{ID: f.ID, Name: f.Name, URL: f.URL, Whitelist: whitelist, Enabled: f.Enabled}}
|
Whitelist: utils.Ptr(whitelist), Url: utils.Ptr(f.Url),
|
||||||
err := cl.doPost(cl.client.R().EnableTrace().SetBody(fu), "/filtering/set_url")
|
Data: &model.FilterSetUrlData{Name: f.Name, Url: f.Url, Enabled: f.Enabled},
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
return cl.doPost(cl.client.R().EnableTrace().SetBody(fu), "/filtering/set_url")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *client) RefreshFilters(whitelist bool) error {
|
func (cl *client) RefreshFilters(whitelist bool) error {
|
||||||
cl.log.With("whitelist", whitelist).Info("Refresh filter")
|
cl.log.With("whitelist", whitelist).Info("Refresh filter")
|
||||||
return cl.doPost(cl.client.R().EnableTrace().SetBody(&types.RefreshFilter{Whitelist: whitelist}), "/filtering/refresh")
|
return cl.doPost(cl.client.R().EnableTrace().SetBody(&model.FilterRefreshRequest{Whitelist: utils.Ptr(whitelist)}), "/filtering/refresh")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *client) ToggleProtection(enable bool) error {
|
func (cl *client) ToggleProtection(enable bool) error {
|
||||||
@@ -286,95 +260,82 @@ func (cl *client) ToggleProtection(enable bool) error {
|
|||||||
return cl.doPost(cl.client.R().EnableTrace().SetBody(&types.Protection{ProtectionEnabled: enable}), "/dns_config")
|
return cl.doPost(cl.client.R().EnableTrace().SetBody(&types.Protection{ProtectionEnabled: enable}), "/dns_config")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *client) SetCustomRules(rules types.UserRules) error {
|
func (cl *client) SetCustomRules(rules *[]string) error {
|
||||||
cl.log.With("rules", len(rules)).Info("Set user rules")
|
cl.log.With("rules", len(*rules)).Info("Set user rules")
|
||||||
return cl.doPost(cl.client.R().EnableTrace().SetBody(rules.String()), "/filtering/set_rules")
|
return cl.doPost(cl.client.R().EnableTrace().SetBody(&model.SetRulesRequest{Rules: rules}), "/filtering/set_rules")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *client) ToggleFiltering(enabled bool, interval float64) error {
|
func (cl *client) ToggleFiltering(enabled bool, interval int) error {
|
||||||
cl.log.With("enabled", enabled, "interval", interval).Info("Toggle filtering")
|
cl.log.With("enabled", enabled, "interval", interval).Info("Toggle filtering")
|
||||||
return cl.doPost(cl.client.R().EnableTrace().SetBody(&types.FilteringConfig{
|
return cl.doPost(cl.client.R().EnableTrace().SetBody(&model.FilterConfig{
|
||||||
EnableConfig: types.EnableConfig{Enabled: enabled},
|
Enabled: utils.Ptr(enabled),
|
||||||
IntervalConfig: types.IntervalConfig{Interval: interval},
|
Interval: utils.Ptr(interval),
|
||||||
}), "/filtering/config")
|
}), "/filtering/config")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *client) Services() (types.Services, error) {
|
func (cl *client) BlockedServices() (*model.BlockedServicesArray, error) {
|
||||||
svcs := types.Services{}
|
svcs := &model.BlockedServicesArray{}
|
||||||
err := cl.doGet(cl.client.R().EnableTrace().SetResult(&svcs), "/blocked_services/list")
|
err := cl.doGet(cl.client.R().EnableTrace().SetResult(svcs), "/blocked_services/list")
|
||||||
return svcs, err
|
return svcs, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *client) SetServices(services types.Services) error {
|
func (cl *client) SetBlockedServices(services *model.BlockedServicesArray) error {
|
||||||
cl.log.With("services", len(services)).Info("Set services")
|
cl.log.With("services", model.ArrayString(services)).Info("Set blocked services")
|
||||||
return cl.doPost(cl.client.R().EnableTrace().SetBody(&services), "/blocked_services/set")
|
return cl.doPost(cl.client.R().EnableTrace().SetBody(services), "/blocked_services/set")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *client) Clients() (*types.Clients, error) {
|
func (cl *client) BlockedServicesSchedule() (*model.BlockedServicesSchedule, error) {
|
||||||
clients := &types.Clients{}
|
sched := &model.BlockedServicesSchedule{}
|
||||||
|
err := cl.doGet(cl.client.R().EnableTrace().SetResult(sched), "/blocked_services/get")
|
||||||
|
return sched, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) SetBlockedServicesSchedule(schedule *model.BlockedServicesSchedule) error {
|
||||||
|
cl.log.With("services", schedule.ServicesString()).Info("Set blocked services schedule")
|
||||||
|
return cl.doPut(cl.client.R().EnableTrace().SetBody(schedule), "/blocked_services/update")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) Clients() (*model.Clients, error) {
|
||||||
|
clients := &model.Clients{}
|
||||||
err := cl.doGet(cl.client.R().EnableTrace().SetResult(clients), "/clients")
|
err := cl.doGet(cl.client.R().EnableTrace().SetResult(clients), "/clients")
|
||||||
return clients, err
|
return clients, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *client) AddClients(clients ...types.Client) error {
|
func (cl *client) AddClient(client *model.Client) error {
|
||||||
for i := range clients {
|
cl.log.With("name", *client.Name).Info("Add client settings")
|
||||||
client := clients[i]
|
return cl.doPost(cl.client.R().EnableTrace().SetBody(client), "/clients/add")
|
||||||
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) UpdateClients(clients ...types.Client) error {
|
func (cl *client) UpdateClient(client *model.Client) error {
|
||||||
for _, client := range clients {
|
cl.log.With("name", *client.Name).Info("Update client settings")
|
||||||
cl.log.With("name", client.Name).Info("Update client")
|
return cl.doPost(cl.client.R().EnableTrace().SetBody(&model.ClientUpdate{Name: client.Name, Data: client}), "/clients/update")
|
||||||
err := cl.doPost(cl.client.R().EnableTrace().SetBody(&types.ClientUpdate{Name: client.Name, Data: client}), "/clients/update")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *client) DeleteClients(clients ...types.Client) error {
|
func (cl *client) DeleteClient(client *model.Client) error {
|
||||||
for i := range clients {
|
cl.log.With("name", *client.Name).Info("Delete client settings")
|
||||||
client := clients[i]
|
return cl.doPost(cl.client.R().EnableTrace().SetBody(client), "/clients/delete")
|
||||||
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) QueryLogConfig() (*types.QueryLogConfig, error) {
|
func (cl *client) QueryLogConfig() (*model.QueryLogConfig, error) {
|
||||||
qlc := &types.QueryLogConfig{}
|
qlc := &model.QueryLogConfig{}
|
||||||
err := cl.doGet(cl.client.R().EnableTrace().SetResult(qlc), "/querylog_info")
|
err := cl.doGet(cl.client.R().EnableTrace().SetResult(qlc), "/querylog_info")
|
||||||
return qlc, err
|
return qlc, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *client) SetQueryLogConfig(enabled bool, interval float64, anonymizeClientIP bool) error {
|
func (cl *client) SetQueryLogConfig(qlc *model.QueryLogConfig) error {
|
||||||
cl.log.With("enabled", enabled, "interval", interval, "anonymizeClientIP", anonymizeClientIP).Info("Set query log config")
|
cl.log.With("enabled", *qlc.Enabled, "interval", *qlc.Interval, "anonymizeClientIP", *qlc.AnonymizeClientIp).Info("Set query log config")
|
||||||
return cl.doPost(cl.client.R().EnableTrace().SetBody(&types.QueryLogConfig{
|
return cl.doPost(cl.client.R().EnableTrace().SetBody(qlc), "/querylog_config")
|
||||||
EnableConfig: types.EnableConfig{Enabled: enabled},
|
|
||||||
IntervalConfig: types.IntervalConfig{Interval: interval},
|
|
||||||
AnonymizeClientIP: anonymizeClientIP,
|
|
||||||
}), "/querylog_config")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *client) StatsConfig() (*types.IntervalConfig, error) {
|
func (cl *client) StatsConfig() (*model.StatsConfig, error) {
|
||||||
stats := &types.IntervalConfig{}
|
stats := &model.StatsConfig{}
|
||||||
err := cl.doGet(cl.client.R().EnableTrace().SetResult(stats), "/stats_info")
|
err := cl.doGet(cl.client.R().EnableTrace().SetResult(stats), "/stats_info")
|
||||||
return stats, err
|
return stats, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *client) SetStatsConfig(interval float64) error {
|
func (cl *client) SetStatsConfig(sc *model.StatsConfig) error {
|
||||||
cl.log.With("interval", interval).Info("Set stats config")
|
cl.log.With("interval", *sc.Interval).Info("Set stats config")
|
||||||
return cl.doPost(cl.client.R().EnableTrace().SetBody(&types.IntervalConfig{Interval: interval}), "/stats_config")
|
return cl.doPost(cl.client.R().EnableTrace().SetBody(sc), "/stats_config")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *client) Setup() error {
|
func (cl *client) Setup() error {
|
||||||
@@ -403,57 +364,75 @@ func (cl *client) Setup() error {
|
|||||||
return cl.doPost(req, "/install/configure")
|
return cl.doPost(req, "/install/configure")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *client) AccessList() (*types.AccessList, error) {
|
func (cl *client) AccessList() (*model.AccessList, error) {
|
||||||
al := &types.AccessList{}
|
al := &model.AccessList{}
|
||||||
err := cl.doGet(cl.client.R().EnableTrace().SetResult(al), "/access/list")
|
err := cl.doGet(cl.client.R().EnableTrace().SetResult(al), "/access/list")
|
||||||
return al, err
|
return al, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *client) SetAccessList(list *types.AccessList) error {
|
func (cl *client) SetAccessList(list *model.AccessList) error {
|
||||||
cl.log.Info("Set access list")
|
cl.log.Info("Set access list")
|
||||||
return cl.doPost(cl.client.R().EnableTrace().SetBody(list), "/access/set")
|
return cl.doPost(cl.client.R().EnableTrace().SetBody(list), "/access/set")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *client) DNSConfig() (*types.DNSConfig, error) {
|
func (cl *client) DNSConfig() (*model.DNSConfig, error) {
|
||||||
cfg := &types.DNSConfig{}
|
cfg := &model.DNSConfig{}
|
||||||
err := cl.doGet(cl.client.R().EnableTrace().SetResult(cfg), "/dns_info")
|
err := cl.doGet(cl.client.R().EnableTrace().SetResult(cfg), "/dns_info")
|
||||||
return cfg, err
|
return cfg, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *client) SetDNSConfig(config *types.DNSConfig) error {
|
func (cl *client) SetDNSConfig(config *model.DNSConfig) error {
|
||||||
cl.log.Info("Set dns config list")
|
cl.log.Info("Set dns config list")
|
||||||
return cl.doPost(cl.client.R().EnableTrace().SetBody(config), "/dns_config")
|
return cl.doPost(cl.client.R().EnableTrace().SetBody(config), "/dns_config")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *client) DHCPServerConfig() (*types.DHCPServerConfig, error) {
|
func (cl *client) DhcpConfig() (*model.DhcpStatus, error) {
|
||||||
cfg := &types.DHCPServerConfig{}
|
cfg := &model.DhcpStatus{}
|
||||||
err := cl.doGet(cl.client.R().EnableTrace().SetResult(cfg), "/dhcp/status")
|
err := cl.doGet(cl.client.R().EnableTrace().SetResult(cfg), "/dhcp/status")
|
||||||
return cfg, err
|
return cfg, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *client) SetDHCPServerConfig(config *types.DHCPServerConfig) error {
|
func (cl *client) SetDhcpConfig(config *model.DhcpStatus) error {
|
||||||
cl.log.Info("Set dhcp server config")
|
cl.log.Info("Set dhcp server config")
|
||||||
return cl.doPost(cl.client.R().EnableTrace().SetBody(config), "/dhcp/set_config")
|
return cl.doPost(cl.client.R().EnableTrace().SetBody(config), "/dhcp/set_config")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *client) AddDHCPStaticLeases(leases ...types.Lease) error {
|
func (cl *client) AddDHCPStaticLease(l model.DhcpStaticLease) error {
|
||||||
for _, l := range leases {
|
cl.log.With("mac", l.Mac, "ip", l.Ip, "hostname", l.Hostname).Info("Add static dhcp lease")
|
||||||
cl.log.With("mac", l.HWAddr, "ip", l.IP, "hostname", l.Hostname).Info("Add static dhcp lease")
|
err := cl.doPost(cl.client.R().EnableTrace().SetBody(l), "/dhcp/add_static_lease")
|
||||||
err := cl.doPost(cl.client.R().EnableTrace().SetBody(l), "/dhcp/add_static_lease")
|
if err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *client) DeleteDHCPStaticLeases(leases ...types.Lease) error {
|
func (cl *client) DeleteDHCPStaticLease(l model.DhcpStaticLease) error {
|
||||||
for _, l := range leases {
|
cl.log.With("mac", l.Mac, "ip", l.Ip, "hostname", l.Hostname).Info("Delete static dhcp lease")
|
||||||
cl.log.With("mac", l.HWAddr, "ip", l.IP, "hostname", l.Hostname).Info("Delete static dhcp lease")
|
err := cl.doPost(cl.client.R().EnableTrace().SetBody(l), "/dhcp/remove_static_lease")
|
||||||
err := cl.doPost(cl.client.R().EnableTrace().SetBody(l), "/dhcp/remove_static_lease")
|
if err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cl *client) SafeSearchConfig() (*model.SafeSearchConfig, error) {
|
||||||
|
sss := &model.SafeSearchConfig{}
|
||||||
|
err := cl.doGet(cl.client.R().EnableTrace().SetResult(sss), "/safesearch/status")
|
||||||
|
return sss, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) SetSafeSearchConfig(settings *model.SafeSearchConfig) error {
|
||||||
|
cl.log.With("enabled", *settings.Enabled).Info("Set safesearch settings")
|
||||||
|
return cl.doPut(cl.client.R().EnableTrace().SetBody(settings), "/safesearch/settings")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) ProfileInfo() (*model.ProfileInfo, error) {
|
||||||
|
p := &model.ProfileInfo{}
|
||||||
|
err := cl.doGet(cl.client.R().EnableTrace().SetResult(p), "/profile")
|
||||||
|
return p, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *client) SetProfileInfo(profile *model.ProfileInfo) error {
|
||||||
|
cl.log.With("language", profile.Language, "theme", profile.Theme).Info("Set profile")
|
||||||
|
return cl.doPut(cl.client.R().EnableTrace().SetBody(profile), "/profile/update")
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,13 +2,16 @@ package client_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/client"
|
"github.com/bakito/adguardhome-sync/pkg/client"
|
||||||
|
"github.com/bakito/adguardhome-sync/pkg/client/model"
|
||||||
"github.com/bakito/adguardhome-sync/pkg/types"
|
"github.com/bakito/adguardhome-sync/pkg/types"
|
||||||
|
"github.com/bakito/adguardhome-sync/pkg/utils"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
@@ -32,19 +35,22 @@ var _ = Describe("Client", func() {
|
|||||||
|
|
||||||
Context("Host", func() {
|
Context("Host", func() {
|
||||||
It("should read the current 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 := cl.Host()
|
||||||
Ω(host).Should(Equal("foo.bar:3000"))
|
Ω(host).Should(Equal("foo.bar:3000"))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Context("Filtering", func() {
|
Context("Filter", func() {
|
||||||
It("should read filtering status", func() {
|
It("should read filter status", func() {
|
||||||
ts, cl = ClientGet("filtering-status.json", "/filtering/status")
|
ts, cl = ClientGet("filtering-status.json", "/filtering/status")
|
||||||
fs, err := cl.Filtering()
|
fs, err := cl.Filtering()
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
Ω(fs.Enabled).Should(BeTrue())
|
Ω(*fs.Enabled).Should(BeTrue())
|
||||||
Ω(fs.Filters).Should(HaveLen(2))
|
Ω(*fs.Filters).Should(HaveLen(2))
|
||||||
})
|
})
|
||||||
It("should enable protection", func() {
|
It("should enable protection", func() {
|
||||||
ts, cl = ClientPost("/filtering/config", `{"enabled":true,"interval":123}`)
|
ts, cl = ClientPost("/filtering/config", `{"enabled":true,"interval":123}`)
|
||||||
@@ -63,35 +69,32 @@ var _ = Describe("Client", func() {
|
|||||||
})
|
})
|
||||||
It("should add Filters", func() {
|
It("should add Filters", func() {
|
||||||
ts, cl = ClientPost("/filtering/add_url",
|
ts, cl = ClientPost("/filtering/add_url",
|
||||||
`{"id":0,"enabled":false,"url":"foo","name":"","rules_count":0,"whitelist":true}`,
|
`{"name":"","url":"foo","whitelist":true}`,
|
||||||
`{"id":0,"enabled":false,"url":"bar","name":"","rules_count":0,"whitelist":true}`,
|
`{"name":"","url":"bar","whitelist":true}`,
|
||||||
)
|
)
|
||||||
err := cl.AddFilters(true, types.Filter{URL: "foo"}, types.Filter{URL: "bar"})
|
err := cl.AddFilter(true, model.Filter{Url: "foo"})
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
err = cl.AddFilter(true, model.Filter{Url: "bar"})
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
It("should update Filters", func() {
|
It("should update Filters", func() {
|
||||||
ts, cl = ClientPost("/filtering/set_url",
|
ts, cl = ClientPost("/filtering/set_url",
|
||||||
`{"url":"foo","data":{"id":0,"enabled":false,"url":"foo","name":"","rules_count":0,"whitelist":true},"whitelist":true}`,
|
`{"data":{"enabled":false,"name":"","url":"foo"},"url":"foo","whitelist":true}`,
|
||||||
`{"url":"bar","data":{"id":0,"enabled":false,"url":"bar","name":"","rules_count":0,"whitelist":true},"whitelist":true}`,
|
`{"data":{"enabled":false,"name":"","url":"bar"},"url":"bar","whitelist":true}`,
|
||||||
)
|
)
|
||||||
err := cl.UpdateFilters(true, types.Filter{URL: "foo"}, types.Filter{URL: "bar"})
|
err := cl.UpdateFilter(true, model.Filter{Url: "foo"})
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
err = cl.UpdateFilter(true, model.Filter{Url: "bar"})
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
It("should delete Filters", func() {
|
It("should delete Filters", func() {
|
||||||
ts, cl = ClientPost("/filtering/remove_url",
|
ts, cl = ClientPost("/filtering/remove_url",
|
||||||
`{"id":0,"enabled":false,"url":"foo","name":"","rules_count":0,"whitelist":true}`,
|
`{"url":"foo","whitelist":true}`,
|
||||||
`{"id":0,"enabled":false,"url":"bar","name":"","rules_count":0,"whitelist":true}`,
|
`{"url":"bar","whitelist":true}`,
|
||||||
)
|
)
|
||||||
err := cl.DeleteFilters(true, types.Filter{URL: "foo"}, types.Filter{URL: "bar"})
|
err := cl.DeleteFilter(true, model.Filter{Url: "foo"})
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
err = cl.DeleteFilter(true, model.Filter{Url: "bar"})
|
||||||
})
|
|
||||||
|
|
||||||
Context("CustomRules", func() {
|
|
||||||
It("should set SetCustomRules", func() {
|
|
||||||
ts, cl = ClientPost("/filtering/set_rules", `foo
|
|
||||||
bar`)
|
|
||||||
err := cl.SetCustomRules([]string{"foo", "bar"})
|
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -101,8 +104,8 @@ bar`)
|
|||||||
ts, cl = ClientGet("status.json", "/status")
|
ts, cl = ClientGet("status.json", "/status")
|
||||||
fs, err := cl.Status()
|
fs, err := cl.Status()
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
Ω(fs.DNSAddresses).Should(HaveLen(1))
|
Ω(fs.DnsAddresses).Should(HaveLen(1))
|
||||||
Ω(fs.DNSAddresses[0]).Should(Equal("192.168.1.2"))
|
Ω(fs.DnsAddresses[0]).Should(Equal("192.168.1.2"))
|
||||||
Ω(fs.Version).Should(Equal("v0.105.2"))
|
Ω(fs.Version).Should(Equal("v0.105.2"))
|
||||||
})
|
})
|
||||||
It("should return ErrSetupNeeded", func() {
|
It("should return ErrSetupNeeded", func() {
|
||||||
@@ -134,13 +137,19 @@ bar`)
|
|||||||
Ω(*rwl).Should(HaveLen(2))
|
Ω(*rwl).Should(HaveLen(2))
|
||||||
})
|
})
|
||||||
It("should add RewriteList", func() {
|
It("should add RewriteList", func() {
|
||||||
ts, cl = ClientPost("/rewrite/add", `{"domain":"foo","answer":"foo"}`, `{"domain":"bar","answer":"bar"}`)
|
ts, cl = ClientPost("/rewrite/add", `{"answer":"foo","domain":"foo"}`, `{"answer":"bar","domain":"bar"}`)
|
||||||
err := cl.AddRewriteEntries(types.RewriteEntry{Answer: "foo", Domain: "foo"}, types.RewriteEntry{Answer: "bar", Domain: "bar"})
|
err := cl.AddRewriteEntries(
|
||||||
|
model.RewriteEntry{Answer: utils.Ptr("foo"), Domain: utils.Ptr("foo")},
|
||||||
|
model.RewriteEntry{Answer: utils.Ptr("bar"), Domain: utils.Ptr("bar")},
|
||||||
|
)
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
It("should delete RewriteList", func() {
|
It("should delete RewriteList", func() {
|
||||||
ts, cl = ClientPost("/rewrite/delete", `{"domain":"foo","answer":"foo"}`, `{"domain":"bar","answer":"bar"}`)
|
ts, cl = ClientPost("/rewrite/delete", `{"answer":"foo","domain":"foo"}`, `{"answer":"bar","domain":"bar"}`)
|
||||||
err := cl.DeleteRewriteEntries(types.RewriteEntry{Answer: "foo", Domain: "foo"}, types.RewriteEntry{Answer: "bar", Domain: "bar"})
|
err := cl.DeleteRewriteEntries(
|
||||||
|
model.RewriteEntry{Answer: utils.Ptr("foo"), Domain: utils.Ptr("foo")},
|
||||||
|
model.RewriteEntry{Answer: utils.Ptr("bar"), Domain: utils.Ptr("bar")},
|
||||||
|
)
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -164,21 +173,22 @@ bar`)
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Context("SafeSearch", func() {
|
Context("SafeSearchConfig", func() {
|
||||||
It("should read safesearch status", func() {
|
It("should read safesearch status", func() {
|
||||||
ts, cl = ClientGet("safesearch-status.json", "/safesearch/status")
|
ts, cl = ClientGet("safesearch-status.json", "/safesearch/status")
|
||||||
ss, err := cl.SafeSearch()
|
ss, err := cl.SafeSearchConfig()
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
Ω(ss).Should(BeTrue())
|
Ω(ss.Enabled).ShouldNot(BeNil())
|
||||||
|
Ω(*ss.Enabled).Should(BeTrue())
|
||||||
})
|
})
|
||||||
It("should enable safesearch", func() {
|
It("should enable safesearch", func() {
|
||||||
ts, cl = ClientPost("/safesearch/enable", "")
|
ts, cl = ClientPut("/safesearch/settings", `{"enabled":true}`)
|
||||||
err := cl.ToggleSafeSearch(true)
|
err := cl.SetSafeSearchConfig(&model.SafeSearchConfig{Enabled: utils.Ptr(true)})
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
It("should disable safesearch", func() {
|
It("should disable safesearch", func() {
|
||||||
ts, cl = ClientPost("/safesearch/disable", "")
|
ts, cl = ClientPut("/safesearch/settings", `{"enabled":false}`)
|
||||||
err := cl.ToggleSafeSearch(false)
|
err := cl.SetSafeSearchConfig(&model.SafeSearchConfig{Enabled: utils.Ptr(false)})
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -215,16 +225,39 @@ bar`)
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Context("Services", func() {
|
Context("BlockedServices", func() {
|
||||||
It("should read Services", func() {
|
It("should read BlockedServices", func() {
|
||||||
ts, cl = ClientGet("blockedservices-list.json", "/blocked_services/list")
|
ts, cl = ClientGet("blockedservices-list.json", "/blocked_services/list")
|
||||||
s, err := cl.Services()
|
s, err := cl.BlockedServices()
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
Ω(s).Should(HaveLen(2))
|
Ω(*s).Should(HaveLen(2))
|
||||||
})
|
})
|
||||||
It("should set Services", func() {
|
It("should set BlockedServices", func() {
|
||||||
ts, cl = ClientPost("/blocked_services/set", `["foo","bar"]`)
|
ts, cl = ClientPost("/blocked_services/set", `["bar","foo"]`)
|
||||||
err := cl.SetServices([]string{"foo", "bar"})
|
err := cl.SetBlockedServices(&model.BlockedServicesArray{"foo", "bar"})
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("BlockedServicesSchedule", func() {
|
||||||
|
It("should read BlockedServicesSchedule", func() {
|
||||||
|
ts, cl = ClientGet("blockedservicesschedule-get.json", "/blocked_services/get")
|
||||||
|
s, err := cl.BlockedServicesSchedule()
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(*s.Ids).Should(HaveLen(3))
|
||||||
|
})
|
||||||
|
It("should set BlockedServicesSchedule", func() {
|
||||||
|
ts, cl = ClientPost("/blocked_services/update",
|
||||||
|
`{"ids":["bar","foo"],"schedule":{"mon":{"end":99,"start":1}}}`)
|
||||||
|
err := cl.SetBlockedServicesSchedule(&model.BlockedServicesSchedule{
|
||||||
|
Ids: utils.Ptr([]string{"foo", "bar"}),
|
||||||
|
Schedule: &model.Schedule{
|
||||||
|
Mon: &model.DayRange{
|
||||||
|
Start: utils.Ptr(float32(1.0)),
|
||||||
|
End: utils.Ptr(float32(99.0)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -234,27 +267,27 @@ bar`)
|
|||||||
ts, cl = ClientGet("clients.json", "/clients")
|
ts, cl = ClientGet("clients.json", "/clients")
|
||||||
c, err := cl.Clients()
|
c, err := cl.Clients()
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
Ω(c.Clients).Should(HaveLen(2))
|
Ω(*c.Clients).Should(HaveLen(2))
|
||||||
})
|
})
|
||||||
It("should add Clients", func() {
|
It("should add Clients", func() {
|
||||||
ts, cl = ClientPost("/clients/add",
|
ts, cl = ClientPost("/clients/add",
|
||||||
`{"ids":["id"],"use_global_settings":false,"use_global_blocked_services":false,"name":"foo","filtering_enabled":false,"parental_enabled":false,"safesearch_enabled":false,"safebrowsing_enabled":false,"disallowed":false,"disallowed_rule":""}`,
|
`{"ids":["id"],"name":"foo"}`,
|
||||||
)
|
)
|
||||||
err := cl.AddClients(types.Client{Name: "foo", Ids: []string{"id"}})
|
err := cl.AddClient(&model.Client{Name: utils.Ptr("foo"), Ids: utils.Ptr([]string{"id"})})
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
It("should update Clients", func() {
|
It("should update Clients", func() {
|
||||||
ts, cl = ClientPost("/clients/update",
|
ts, cl = ClientPost("/clients/update",
|
||||||
`{"name":"foo","data":{"ids":["id"],"use_global_settings":false,"use_global_blocked_services":false,"name":"foo","filtering_enabled":false,"parental_enabled":false,"safesearch_enabled":false,"safebrowsing_enabled":false,"disallowed":false,"disallowed_rule":""}}`,
|
`{"data":{"ids":["id"],"name":"foo"},"name":"foo"}`,
|
||||||
)
|
)
|
||||||
err := cl.UpdateClients(types.Client{Name: "foo", Ids: []string{"id"}})
|
err := cl.UpdateClient(&model.Client{Name: utils.Ptr("foo"), Ids: utils.Ptr([]string{"id"})})
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
It("should delete Clients", func() {
|
It("should delete Clients", func() {
|
||||||
ts, cl = ClientPost("/clients/delete",
|
ts, cl = ClientPost("/clients/delete",
|
||||||
`{"ids":["id"],"use_global_settings":false,"use_global_blocked_services":false,"name":"foo","filtering_enabled":false,"parental_enabled":false,"safesearch_enabled":false,"safebrowsing_enabled":false,"disallowed":false,"disallowed_rule":""}`,
|
`{"ids":["id"],"name":"foo"}`,
|
||||||
)
|
)
|
||||||
err := cl.DeleteClients(types.Client{Name: "foo", Ids: []string{"id"}})
|
err := cl.DeleteClient(&model.Client{Name: utils.Ptr("foo"), Ids: utils.Ptr([]string{"id"})})
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -264,12 +297,16 @@ bar`)
|
|||||||
ts, cl = ClientGet("querylog_info.json", "/querylog_info")
|
ts, cl = ClientGet("querylog_info.json", "/querylog_info")
|
||||||
qlc, err := cl.QueryLogConfig()
|
qlc, err := cl.QueryLogConfig()
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
Ω(qlc.Enabled).Should(BeTrue())
|
Ω(qlc.Enabled).ShouldNot(BeNil())
|
||||||
Ω(qlc.Interval).Should(Equal(90.0))
|
Ω(*qlc.Enabled).Should(BeTrue())
|
||||||
|
Ω(qlc.Interval).ShouldNot(BeNil())
|
||||||
|
Ω(*qlc.Interval).Should(Equal(model.QueryLogConfigInterval(90)))
|
||||||
})
|
})
|
||||||
It("should set QueryLogConfig", func() {
|
It("should set QueryLogConfig", func() {
|
||||||
ts, cl = ClientPost("/querylog_config", `{"enabled":true,"interval":123,"anonymize_client_ip":true}`)
|
ts, cl = ClientPost("/querylog_config", `{"anonymize_client_ip":true,"enabled":true,"interval":123}`)
|
||||||
err := cl.SetQueryLogConfig(true, 123, true)
|
|
||||||
|
var interval model.QueryLogConfigInterval = 123
|
||||||
|
err := cl.SetQueryLogConfig(&model.QueryLogConfig{AnonymizeClientIp: utils.Ptr(true), Interval: &interval, Enabled: utils.Ptr(true)})
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -278,11 +315,14 @@ bar`)
|
|||||||
ts, cl = ClientGet("stats_info.json", "/stats_info")
|
ts, cl = ClientGet("stats_info.json", "/stats_info")
|
||||||
sc, err := cl.StatsConfig()
|
sc, err := cl.StatsConfig()
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
Ω(sc.Interval).Should(Equal(1.0))
|
Ω(sc.Interval).ShouldNot(BeNil())
|
||||||
|
Ω(*sc.Interval).Should(Equal(model.StatsConfigInterval(1)))
|
||||||
})
|
})
|
||||||
It("should set StatsConfig", func() {
|
It("should set StatsConfig", func() {
|
||||||
ts, cl = ClientPost("/stats_config", `{"interval":123}`)
|
ts, cl = ClientPost("/stats_config", `{"interval":123}`)
|
||||||
err := cl.SetStatsConfig(123.0)
|
|
||||||
|
var interval model.StatsConfigInterval = 123
|
||||||
|
err := cl.SetStatsConfig(&model.StatsConfig{Interval: &interval})
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -307,7 +347,8 @@ bar`)
|
|||||||
|
|
||||||
Context("doPost", func() {
|
Context("doPost", func() {
|
||||||
It("should return an error on status code != 200", func() {
|
It("should return an error on status code != 200", func() {
|
||||||
err := cl.SetStatsConfig(123)
|
var interval model.StatsConfigInterval = 123
|
||||||
|
err := cl.SetStatsConfig(&model.StatsConfig{Interval: &interval})
|
||||||
Ω(err).Should(HaveOccurred())
|
Ω(err).Should(HaveOccurred())
|
||||||
Ω(err.Error()).Should(Equal("401 Unauthorized"))
|
Ω(err.Error()).Should(Equal("401 Unauthorized"))
|
||||||
})
|
})
|
||||||
@@ -318,7 +359,7 @@ bar`)
|
|||||||
func ClientGet(file string, path string) (*httptest.Server, client.Client) {
|
func ClientGet(file string, path string) (*httptest.Server, client.Client) {
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
Ω(r.URL.Path).Should(Equal(types.DefaultAPIPath + path))
|
Ω(r.URL.Path).Should(Equal(types.DefaultAPIPath + path))
|
||||||
b, err := ioutil.ReadFile(filepath.Join("../../testdata", file))
|
b, err := os.ReadFile(filepath.Join("../../testdata", file))
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
_, err = w.Write(b)
|
_, err = w.Write(b)
|
||||||
@@ -333,7 +374,22 @@ func ClientPost(path string, content ...string) (*httptest.Server, client.Client
|
|||||||
index := 0
|
index := 0
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
Ω(r.URL.Path).Should(Equal(types.DefaultAPIPath + path))
|
Ω(r.URL.Path).Should(Equal(types.DefaultAPIPath + path))
|
||||||
body, err := ioutil.ReadAll(r.Body)
|
body, err := io.ReadAll(r.Body)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
Ω(body).Should(Equal([]byte(content[index])))
|
||||||
|
index++
|
||||||
|
}))
|
||||||
|
|
||||||
|
cl, err := client.New(types.AdGuardInstance{URL: ts.URL, Username: username, Password: password})
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
return ts, cl
|
||||||
|
}
|
||||||
|
|
||||||
|
func ClientPut(path string, content ...string) (*httptest.Server, client.Client) {
|
||||||
|
index := 0
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
Ω(r.URL.Path).Should(Equal(types.DefaultAPIPath + path))
|
||||||
|
body, err := io.ReadAll(r.Body)
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
Ω(body).Should(Equal([]byte(content[index])))
|
Ω(body).Should(Equal([]byte(content[index])))
|
||||||
index++
|
index++
|
||||||
|
|||||||
144
pkg/client/model/client/client.go
Normal file
144
pkg/client/model/client/client.go
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/pkg/client/model"
|
||||||
|
"github.com/bakito/adguardhome-sync/pkg/log"
|
||||||
|
"github.com/bakito/adguardhome-sync/pkg/types"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
var l = log.GetLogger("client")
|
||||||
|
|
||||||
|
// New create a new api client
|
||||||
|
func New(config types.AdGuardInstance) (Client, error) {
|
||||||
|
var apiURL string
|
||||||
|
if config.APIPath == "" {
|
||||||
|
apiURL = fmt.Sprintf("%s/control", config.URL)
|
||||||
|
} else {
|
||||||
|
apiURL = fmt.Sprintf("%s/%s", config.URL, config.APIPath)
|
||||||
|
}
|
||||||
|
u, err := url.Parse(apiURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
u.Path = path.Clean(u.Path)
|
||||||
|
|
||||||
|
httpClient := &http.Client{}
|
||||||
|
if config.InsecureSkipVerify {
|
||||||
|
// #nosec G402 has to be explicitly enabled
|
||||||
|
httpClient.Transport = &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aghClient, err := model.NewClient(u.String(), func(client *model.AdguardHomeClient) error {
|
||||||
|
client.Client = httpClient
|
||||||
|
client.RequestEditors = append(client.RequestEditors, func(ctx context.Context, req *http.Request) error {
|
||||||
|
if config.Username != "" && config.Password != "" {
|
||||||
|
req.Header.Add("Authorization", "Basic "+basicAuth(config.Username, config.Password))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &apiClient{
|
||||||
|
host: u.Host,
|
||||||
|
client: aghClient,
|
||||||
|
log: l.With("host", u.Host),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func basicAuth(username, password string) string {
|
||||||
|
auth := username + ":" + password
|
||||||
|
return base64.StdEncoding.EncodeToString([]byte(auth))
|
||||||
|
}
|
||||||
|
|
||||||
|
type apiClient struct {
|
||||||
|
host string
|
||||||
|
client *model.AdguardHomeClient
|
||||||
|
log *zap.SugaredLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a apiClient) Host(context.Context) string {
|
||||||
|
return a.host
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a apiClient) GetServerStatus(ctx context.Context) (*model.ServerStatus, error) {
|
||||||
|
sr, err := read(ctx, a.client.Status, model.ParseStatusResp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return sr.JSON200, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a apiClient) GetFilteringStatus(ctx context.Context) (*model.FilterStatus, error) {
|
||||||
|
sr, err := read(ctx, a.client.FilteringStatus, model.ParseFilteringStatusResp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return sr.JSON200, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a apiClient) SetFilteringConfig(ctx context.Context, config model.FilterConfig) error {
|
||||||
|
return write(ctx, config, a.client.FilteringConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func write[B interface{}](
|
||||||
|
ctx context.Context,
|
||||||
|
body B,
|
||||||
|
req func(ctx context.Context, body B, reqEditors ...model.RequestEditorFn) (*http.Response, error),
|
||||||
|
) error {
|
||||||
|
resp, err := req(ctx, body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return detailedError(resp)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func read[I interface{}](
|
||||||
|
ctx context.Context,
|
||||||
|
req func(ctx context.Context, reqEditors ...model.RequestEditorFn) (*http.Response, error),
|
||||||
|
parse func(rsp *http.Response) (*I, error),
|
||||||
|
) (*I, error) {
|
||||||
|
resp, err := req(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, detailedError(resp)
|
||||||
|
}
|
||||||
|
return parse(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func detailedError(resp *http.Response) error {
|
||||||
|
e := resp.Status
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(body) > 0 {
|
||||||
|
e += fmt.Sprintf("(%s)", string(body))
|
||||||
|
}
|
||||||
|
return errors.New(e)
|
||||||
|
}
|
||||||
15
pkg/client/model/client/interface.go
Normal file
15
pkg/client/model/client/interface.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/pkg/client/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client interface {
|
||||||
|
Host(ctx context.Context) string
|
||||||
|
GetServerStatus(ctx context.Context) (*model.ServerStatus, error)
|
||||||
|
|
||||||
|
GetFilteringStatus(ctx context.Context) (*model.FilterStatus, error)
|
||||||
|
SetFilteringConfig(ctx context.Context, config model.FilterConfig) error
|
||||||
|
}
|
||||||
27
pkg/client/model/client/resty.go
Normal file
27
pkg/client/model/client/resty.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/pkg/client/model"
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ model.HttpRequestDoer = &adapter{}
|
||||||
|
|
||||||
|
func RestyAdapter(r *resty.Client) model.HttpRequestDoer {
|
||||||
|
return &adapter{
|
||||||
|
client: r,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type adapter struct {
|
||||||
|
client *resty.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a adapter) Do(req *http.Request) (*http.Response, error) {
|
||||||
|
r, err := a.client.R().
|
||||||
|
SetHeaderMultiValues(req.Header).
|
||||||
|
Execute(req.Method, req.URL.String())
|
||||||
|
return r.RawResponse, err
|
||||||
|
}
|
||||||
388
pkg/client/model/model-functions.go
Normal file
388
pkg/client/model/model-functions.go
Normal file
@@ -0,0 +1,388 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/pkg/utils"
|
||||||
|
"github.com/jinzhu/copier"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Clone the config
|
||||||
|
func (c *DhcpStatus) Clone() *DhcpStatus {
|
||||||
|
clone := &DhcpStatus{}
|
||||||
|
_ = copier.Copy(clone, c)
|
||||||
|
return clone
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equals dhcp server config equal check
|
||||||
|
func (c *DhcpStatus) Equals(o *DhcpStatus) bool {
|
||||||
|
return utils.JsonEquals(c, o)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DhcpStatus) HasConfig() bool {
|
||||||
|
return (c.V4 != nil && c.V4.isValid()) || (c.V6 != nil && c.V6.isValid())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j DhcpConfigV4) isValid() bool {
|
||||||
|
return j.GatewayIp != nil && j.SubnetMask != nil && j.RangeStart != nil && j.RangeEnd != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j DhcpConfigV6) isValid() bool {
|
||||||
|
return j.RangeStart != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type DhcpStaticLeases []DhcpStaticLease
|
||||||
|
|
||||||
|
// MergeDhcpStaticLeases the leases
|
||||||
|
func MergeDhcpStaticLeases(l *[]DhcpStaticLease, other *[]DhcpStaticLease) (DhcpStaticLeases, DhcpStaticLeases) {
|
||||||
|
var thisLeases []DhcpStaticLease
|
||||||
|
var otherLeases []DhcpStaticLease
|
||||||
|
|
||||||
|
if l != nil {
|
||||||
|
thisLeases = *l
|
||||||
|
}
|
||||||
|
if other != nil {
|
||||||
|
otherLeases = *other
|
||||||
|
}
|
||||||
|
current := make(map[string]DhcpStaticLease)
|
||||||
|
|
||||||
|
var adds DhcpStaticLeases
|
||||||
|
var removes DhcpStaticLeases
|
||||||
|
for _, le := range thisLeases {
|
||||||
|
current[le.Mac] = le
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, le := range otherLeases {
|
||||||
|
if _, ok := current[le.Mac]; ok {
|
||||||
|
delete(current, le.Mac)
|
||||||
|
} else {
|
||||||
|
adds = append(adds, le)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rr := range current {
|
||||||
|
removes = append(removes, rr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return adds, removes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equals dns config equal check
|
||||||
|
func (c *DNSConfig) Equals(o *DNSConfig) bool {
|
||||||
|
cc := c.Clone()
|
||||||
|
oo := o.Clone()
|
||||||
|
cc.Sort()
|
||||||
|
oo.Sort()
|
||||||
|
|
||||||
|
return utils.JsonEquals(cc, oo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *DNSConfig) Clone() *DNSConfig {
|
||||||
|
return utils.Clone(c, &DNSConfig{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort sort dns config
|
||||||
|
func (c *DNSConfig) Sort() {
|
||||||
|
if c.UpstreamDns != nil {
|
||||||
|
sort.Strings(*c.UpstreamDns)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.UpstreamDns != nil {
|
||||||
|
sort.Strings(*c.BootstrapDns)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.UpstreamDns != nil {
|
||||||
|
sort.Strings(*c.LocalPtrUpstreams)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equals access list equal check
|
||||||
|
func (al *AccessList) Equals(o *AccessList) bool {
|
||||||
|
return EqualsStringSlice(al.AllowedClients, o.AllowedClients, true) &&
|
||||||
|
EqualsStringSlice(al.DisallowedClients, o.DisallowedClients, true) &&
|
||||||
|
EqualsStringSlice(al.BlockedHosts, o.BlockedHosts, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func EqualsStringSlice(a *[]string, b *[]string, sortIt bool) bool {
|
||||||
|
if a == nil && b == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if a == nil || b == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
aa := *a
|
||||||
|
bb := *b
|
||||||
|
if sortIt {
|
||||||
|
sort.Strings(aa)
|
||||||
|
sort.Strings(bb)
|
||||||
|
}
|
||||||
|
if len(aa) != len(bb) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i, v := range aa {
|
||||||
|
if v != bb[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort clients
|
||||||
|
func (cl *Client) Sort() {
|
||||||
|
if cl.Ids != nil {
|
||||||
|
sort.Strings(*cl.Ids)
|
||||||
|
}
|
||||||
|
if cl.Tags != nil {
|
||||||
|
sort.Strings(*cl.Tags)
|
||||||
|
}
|
||||||
|
if cl.BlockedServices != nil {
|
||||||
|
sort.Strings(*cl.BlockedServices)
|
||||||
|
}
|
||||||
|
if cl.Upstreams != nil {
|
||||||
|
sort.Strings(*cl.Upstreams)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equals Clients equal check
|
||||||
|
func (cl *Client) Equals(o *Client) bool {
|
||||||
|
cl.Sort()
|
||||||
|
o.Sort()
|
||||||
|
|
||||||
|
return utils.JsonEquals(cl, o)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add ac client
|
||||||
|
func (clients *Clients) Add(cl Client) {
|
||||||
|
if clients.Clients == nil {
|
||||||
|
clients.Clients = &ClientsArray{cl}
|
||||||
|
} else {
|
||||||
|
a := append(*clients.Clients, cl)
|
||||||
|
clients.Clients = &a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge merge Clients
|
||||||
|
func (clients *Clients) Merge(other *Clients) ([]*Client, []*Client, []*Client) {
|
||||||
|
current := make(map[string]*Client)
|
||||||
|
if clients.Clients != nil {
|
||||||
|
cc := *clients.Clients
|
||||||
|
for i := range cc {
|
||||||
|
client := cc[i]
|
||||||
|
current[*client.Name] = &client
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := make(map[string]*Client)
|
||||||
|
if other.Clients != nil {
|
||||||
|
oc := *other.Clients
|
||||||
|
for i := range oc {
|
||||||
|
client := oc[i]
|
||||||
|
expected[*client.Name] = &client
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var adds []*Client
|
||||||
|
var removes []*Client
|
||||||
|
var updates []*Client
|
||||||
|
|
||||||
|
for _, cl := range expected {
|
||||||
|
if oc, ok := current[*cl.Name]; ok {
|
||||||
|
if !cl.Equals(oc) {
|
||||||
|
updates = append(updates, cl)
|
||||||
|
}
|
||||||
|
delete(current, *cl.Name)
|
||||||
|
} else {
|
||||||
|
adds = append(adds, cl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rr := range current {
|
||||||
|
removes = append(removes, rr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return adds, updates, removes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key RewriteEntry key
|
||||||
|
func (re *RewriteEntry) Key() string {
|
||||||
|
var d string
|
||||||
|
var a string
|
||||||
|
if re.Domain != nil {
|
||||||
|
d = *re.Domain
|
||||||
|
}
|
||||||
|
if re.Answer != nil {
|
||||||
|
a = *re.Answer
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s#%s", d, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RewriteEntries list of RewriteEntry
|
||||||
|
type RewriteEntries []RewriteEntry
|
||||||
|
|
||||||
|
// Merge RewriteEntries
|
||||||
|
func (rwe *RewriteEntries) Merge(other *RewriteEntries) (RewriteEntries, RewriteEntries, RewriteEntries) {
|
||||||
|
current := make(map[string]RewriteEntry)
|
||||||
|
|
||||||
|
var adds RewriteEntries
|
||||||
|
var removes RewriteEntries
|
||||||
|
var duplicates RewriteEntries
|
||||||
|
processed := make(map[string]bool)
|
||||||
|
for _, rr := range *rwe {
|
||||||
|
if _, ok := processed[rr.Key()]; !ok {
|
||||||
|
current[rr.Key()] = rr
|
||||||
|
processed[rr.Key()] = true
|
||||||
|
} else {
|
||||||
|
// remove duplicate
|
||||||
|
removes = append(removes, rr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rr := range *other {
|
||||||
|
if _, ok := current[rr.Key()]; ok {
|
||||||
|
delete(current, rr.Key())
|
||||||
|
} else {
|
||||||
|
if _, ok := processed[rr.Key()]; !ok {
|
||||||
|
adds = append(adds, rr)
|
||||||
|
processed[rr.Key()] = true
|
||||||
|
} else {
|
||||||
|
// skip duplicate
|
||||||
|
duplicates = append(duplicates, rr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rr := range current {
|
||||||
|
removes = append(removes, rr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return adds, removes, duplicates
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeFilters(this *[]Filter, other *[]Filter) ([]Filter, []Filter, []Filter) {
|
||||||
|
if this == nil && other == nil {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
current := make(map[string]*Filter)
|
||||||
|
|
||||||
|
var adds []Filter
|
||||||
|
var updates []Filter
|
||||||
|
var removes []Filter
|
||||||
|
if this != nil {
|
||||||
|
for i := range *this {
|
||||||
|
fi := (*this)[i]
|
||||||
|
current[fi.Url] = &fi
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if other != nil {
|
||||||
|
for i := range *other {
|
||||||
|
rr := (*other)[i]
|
||||||
|
if c, ok := current[rr.Url]; ok {
|
||||||
|
if !c.Equals(&rr) {
|
||||||
|
updates = append(updates, rr)
|
||||||
|
}
|
||||||
|
delete(current, rr.Url)
|
||||||
|
} else {
|
||||||
|
adds = append(adds, rr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rr := range current {
|
||||||
|
removes = append(removes, *rr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return adds, updates, removes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equals Filter equal check
|
||||||
|
func (f *Filter) Equals(o *Filter) bool {
|
||||||
|
return f.Enabled == o.Enabled && f.Url == o.Url && f.Name == o.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equals QueryLogConfig equal check
|
||||||
|
func (qlc *QueryLogConfig) Equals(o *QueryLogConfig) bool {
|
||||||
|
return ptrEquals(qlc.Enabled, o.Enabled) &&
|
||||||
|
ptrEquals(qlc.AnonymizeClientIp, o.AnonymizeClientIp) &&
|
||||||
|
qlc.Interval.Equals(o.Interval)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equals QueryLogConfigInterval equal check
|
||||||
|
func (qlc *QueryLogConfigInterval) Equals(o *QueryLogConfigInterval) bool {
|
||||||
|
return ptrEquals(qlc, o)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ptrEquals[T comparable](a *T, b *T) bool {
|
||||||
|
if a == nil && b == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
var aa T
|
||||||
|
if a != nil {
|
||||||
|
aa = *a
|
||||||
|
}
|
||||||
|
var bb T
|
||||||
|
if b != nil {
|
||||||
|
bb = *b
|
||||||
|
}
|
||||||
|
|
||||||
|
return aa == bb
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnableConfig API struct
|
||||||
|
type EnableConfig struct {
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ssc *SafeSearchConfig) Equals(o *SafeSearchConfig) bool {
|
||||||
|
return ptrEquals(ssc.Enabled, o.Enabled) &&
|
||||||
|
ptrEquals(ssc.Bing, o.Bing) &&
|
||||||
|
ptrEquals(ssc.Duckduckgo, o.Duckduckgo) &&
|
||||||
|
ptrEquals(ssc.Google, o.Google) &&
|
||||||
|
ptrEquals(ssc.Pixabay, o.Pixabay) &&
|
||||||
|
ptrEquals(ssc.Yandex, o.Yandex) &&
|
||||||
|
ptrEquals(ssc.Youtube, o.Youtube)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pi *ProfileInfo) Equals(o *ProfileInfo) bool {
|
||||||
|
return pi.Language == o.Language &&
|
||||||
|
pi.Theme == o.Theme
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pi *ProfileInfo) ShouldSyncFor(o *ProfileInfo) *ProfileInfo {
|
||||||
|
if pi.Equals(o) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
merged := &ProfileInfo{Name: pi.Name, Language: pi.Language, Theme: pi.Theme}
|
||||||
|
if o.Language != "" {
|
||||||
|
merged.Language = o.Language
|
||||||
|
}
|
||||||
|
if o.Theme != "" {
|
||||||
|
merged.Theme = o.Theme
|
||||||
|
}
|
||||||
|
if merged.Name == "" || merged.Language == "" || merged.Theme == "" || merged.Equals(pi) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return merged
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bss *BlockedServicesSchedule) Equals(o *BlockedServicesSchedule) bool {
|
||||||
|
return utils.JsonEquals(bss, o)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bss *BlockedServicesSchedule) ServicesString() string {
|
||||||
|
return ArrayString(bss.Ids)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ArrayString(a *[]string) string {
|
||||||
|
if a == nil {
|
||||||
|
return "[]"
|
||||||
|
}
|
||||||
|
sorted := *a
|
||||||
|
sort.Strings(sorted)
|
||||||
|
return fmt.Sprintf("[%s]", strings.Join(sorted, ","))
|
||||||
|
}
|
||||||
10592
pkg/client/model/model_generated.go
Normal file
10592
pkg/client/model/model_generated.go
Normal file
File diff suppressed because it is too large
Load Diff
13
pkg/client/model/model_suite_test.go
Normal file
13
pkg/client/model/model_suite_test.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package model_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTypes(t *testing.T) {
|
||||||
|
RegisterFailHandler(Fail)
|
||||||
|
RunSpecs(t, "Model Suite")
|
||||||
|
}
|
||||||
430
pkg/client/model/model_test.go
Normal file
430
pkg/client/model/model_test.go
Normal file
@@ -0,0 +1,430 @@
|
|||||||
|
package model_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/bakito/adguardhome-sync/pkg/client/model"
|
||||||
|
"github.com/bakito/adguardhome-sync/pkg/types"
|
||||||
|
"github.com/bakito/adguardhome-sync/pkg/utils"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("Types", func() {
|
||||||
|
var (
|
||||||
|
url string
|
||||||
|
apiPath string
|
||||||
|
)
|
||||||
|
BeforeEach(func() {
|
||||||
|
url = "https://" + uuid.NewString()
|
||||||
|
apiPath = "/" + uuid.NewString()
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("FilteringStatus", func() {
|
||||||
|
It("should correctly parse json", func() {
|
||||||
|
b, err := os.ReadFile("../../../testdata/filtering-status.json")
|
||||||
|
fs := &model.FilterStatus{}
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
err = json.Unmarshal(b, fs)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("Filters", func() {
|
||||||
|
Context("Merge", func() {
|
||||||
|
var (
|
||||||
|
originFilters []model.Filter
|
||||||
|
replicaFilters []model.Filter
|
||||||
|
)
|
||||||
|
BeforeEach(func() {
|
||||||
|
originFilters = []model.Filter{}
|
||||||
|
replicaFilters = []model.Filter{}
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should add a missing filter", func() {
|
||||||
|
originFilters = append(originFilters, model.Filter{Url: url})
|
||||||
|
a, u, d := model.MergeFilters(&replicaFilters, &originFilters)
|
||||||
|
Ω(a).Should(HaveLen(1))
|
||||||
|
Ω(u).Should(BeEmpty())
|
||||||
|
Ω(d).Should(BeEmpty())
|
||||||
|
|
||||||
|
Ω(a[0].Url).Should(Equal(url))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should remove additional filter", func() {
|
||||||
|
replicaFilters = append(replicaFilters, model.Filter{Url: url})
|
||||||
|
a, u, d := model.MergeFilters(&replicaFilters, &originFilters)
|
||||||
|
Ω(a).Should(BeEmpty())
|
||||||
|
Ω(u).Should(BeEmpty())
|
||||||
|
Ω(d).Should(HaveLen(1))
|
||||||
|
|
||||||
|
Ω(d[0].Url).Should(Equal(url))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should update existing filter when enabled differs", func() {
|
||||||
|
enabled := true
|
||||||
|
originFilters = append(originFilters, model.Filter{Url: url, Enabled: enabled})
|
||||||
|
replicaFilters = append(replicaFilters, model.Filter{Url: url, Enabled: !enabled})
|
||||||
|
a, u, d := model.MergeFilters(&replicaFilters, &originFilters)
|
||||||
|
Ω(a).Should(BeEmpty())
|
||||||
|
Ω(u).Should(HaveLen(1))
|
||||||
|
Ω(d).Should(BeEmpty())
|
||||||
|
|
||||||
|
Ω(u[0].Enabled).Should(Equal(enabled))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should update existing filter when name differs", func() {
|
||||||
|
name1 := uuid.NewString()
|
||||||
|
name2 := uuid.NewString()
|
||||||
|
originFilters = append(originFilters, model.Filter{Url: url, Name: name1})
|
||||||
|
replicaFilters = append(replicaFilters, model.Filter{Url: url, Name: name2})
|
||||||
|
a, u, d := model.MergeFilters(&replicaFilters, &originFilters)
|
||||||
|
Ω(a).Should(BeEmpty())
|
||||||
|
Ω(u).Should(HaveLen(1))
|
||||||
|
Ω(d).Should(BeEmpty())
|
||||||
|
|
||||||
|
Ω(u[0].Name).Should(Equal(name1))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should have no changes", func() {
|
||||||
|
originFilters = append(originFilters, model.Filter{Url: url})
|
||||||
|
replicaFilters = append(replicaFilters, model.Filter{Url: url})
|
||||||
|
a, u, d := model.MergeFilters(&replicaFilters, &originFilters)
|
||||||
|
Ω(a).Should(BeEmpty())
|
||||||
|
Ω(u).Should(BeEmpty())
|
||||||
|
Ω(d).Should(BeEmpty())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("AdGuardInstance", func() {
|
||||||
|
It("should build a key with url and api apiPath", func() {
|
||||||
|
i := &types.AdGuardInstance{URL: url, APIPath: apiPath}
|
||||||
|
Ω(i.Key()).Should(Equal(url + "#" + apiPath))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("RewriteEntry", func() {
|
||||||
|
It("should build a key with url and api apiPath", func() {
|
||||||
|
domain := uuid.NewString()
|
||||||
|
answer := uuid.NewString()
|
||||||
|
re := &model.RewriteEntry{Domain: utils.Ptr(domain), Answer: utils.Ptr(answer)}
|
||||||
|
Ω(re.Key()).Should(Equal(domain + "#" + answer))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("QueryLogConfig", func() {
|
||||||
|
Context("Equal", func() {
|
||||||
|
var (
|
||||||
|
a *model.QueryLogConfig
|
||||||
|
b *model.QueryLogConfig
|
||||||
|
)
|
||||||
|
BeforeEach(func() {
|
||||||
|
a = &model.QueryLogConfig{}
|
||||||
|
b = &model.QueryLogConfig{}
|
||||||
|
})
|
||||||
|
It("should be equal", func() {
|
||||||
|
a.Enabled = utils.Ptr(true)
|
||||||
|
var interval model.QueryLogConfigInterval = 1
|
||||||
|
a.Interval = &interval
|
||||||
|
a.AnonymizeClientIp = utils.Ptr(true)
|
||||||
|
b.Enabled = utils.Ptr(true)
|
||||||
|
b.Interval = &interval
|
||||||
|
b.AnonymizeClientIp = utils.Ptr(true)
|
||||||
|
Ω(a.Equals(b)).Should(BeTrue())
|
||||||
|
})
|
||||||
|
It("should not be equal when enabled differs", func() {
|
||||||
|
a.Enabled = utils.Ptr(true)
|
||||||
|
b.Enabled = utils.Ptr(false)
|
||||||
|
Ω(a.Equals(b)).ShouldNot(BeTrue())
|
||||||
|
})
|
||||||
|
It("should not be equal when interval differs", func() {
|
||||||
|
var interval1 model.QueryLogConfigInterval = 1
|
||||||
|
var interval2 model.QueryLogConfigInterval = 2
|
||||||
|
a.Interval = &interval1
|
||||||
|
b.Interval = &interval2
|
||||||
|
Ω(a.Equals(b)).ShouldNot(BeTrue())
|
||||||
|
})
|
||||||
|
It("should not be equal when anonymizeClientIP differs", func() {
|
||||||
|
a.AnonymizeClientIp = utils.Ptr(true)
|
||||||
|
b.AnonymizeClientIp = utils.Ptr(false)
|
||||||
|
Ω(a.Equals(b)).ShouldNot(BeTrue())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("RewriteEntries", func() {
|
||||||
|
Context("Merge", func() {
|
||||||
|
var (
|
||||||
|
originRE model.RewriteEntries
|
||||||
|
replicaRE model.RewriteEntries
|
||||||
|
domain string
|
||||||
|
)
|
||||||
|
BeforeEach(func() {
|
||||||
|
originRE = model.RewriteEntries{}
|
||||||
|
replicaRE = model.RewriteEntries{}
|
||||||
|
domain = uuid.NewString()
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should add a missing rewrite entry", func() {
|
||||||
|
originRE = append(originRE, model.RewriteEntry{Domain: utils.Ptr(domain)})
|
||||||
|
a, r, d := replicaRE.Merge(&originRE)
|
||||||
|
Ω(a).Should(HaveLen(1))
|
||||||
|
Ω(r).Should(BeEmpty())
|
||||||
|
Ω(d).Should(BeEmpty())
|
||||||
|
|
||||||
|
Ω(*a[0].Domain).Should(Equal(domain))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should remove additional rewrite entry", func() {
|
||||||
|
replicaRE = append(replicaRE, model.RewriteEntry{Domain: utils.Ptr(domain)})
|
||||||
|
a, r, d := replicaRE.Merge(&originRE)
|
||||||
|
Ω(a).Should(BeEmpty())
|
||||||
|
Ω(r).Should(HaveLen(1))
|
||||||
|
Ω(d).Should(BeEmpty())
|
||||||
|
|
||||||
|
Ω(*r[0].Domain).Should(Equal(domain))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should have no changes", func() {
|
||||||
|
originRE = append(originRE, model.RewriteEntry{Domain: utils.Ptr(domain)})
|
||||||
|
replicaRE = append(replicaRE, model.RewriteEntry{Domain: utils.Ptr(domain)})
|
||||||
|
a, r, d := replicaRE.Merge(&originRE)
|
||||||
|
Ω(a).Should(BeEmpty())
|
||||||
|
Ω(r).Should(BeEmpty())
|
||||||
|
Ω(d).Should(BeEmpty())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should remove target duplicate", func() {
|
||||||
|
originRE = append(originRE, model.RewriteEntry{Domain: utils.Ptr(domain)})
|
||||||
|
replicaRE = append(replicaRE, model.RewriteEntry{Domain: utils.Ptr(domain)})
|
||||||
|
replicaRE = append(replicaRE, model.RewriteEntry{Domain: utils.Ptr(domain)})
|
||||||
|
a, r, d := replicaRE.Merge(&originRE)
|
||||||
|
Ω(a).Should(BeEmpty())
|
||||||
|
Ω(r).Should(HaveLen(1))
|
||||||
|
Ω(d).Should(BeEmpty())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should remove target duplicate", func() {
|
||||||
|
originRE = append(originRE, model.RewriteEntry{Domain: utils.Ptr(domain)})
|
||||||
|
originRE = append(originRE, model.RewriteEntry{Domain: utils.Ptr(domain)})
|
||||||
|
replicaRE = append(replicaRE, model.RewriteEntry{Domain: utils.Ptr(domain)})
|
||||||
|
a, r, d := replicaRE.Merge(&originRE)
|
||||||
|
Ω(a).Should(BeEmpty())
|
||||||
|
Ω(r).Should(BeEmpty())
|
||||||
|
Ω(d).Should(HaveLen(1))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("Config", func() {
|
||||||
|
var cfg *types.Config
|
||||||
|
BeforeEach(func() {
|
||||||
|
cfg = &types.Config{}
|
||||||
|
})
|
||||||
|
Context("UniqueReplicas", func() {
|
||||||
|
It("should be empty if noting defined", func() {
|
||||||
|
r := cfg.UniqueReplicas()
|
||||||
|
Ω(r).Should(BeEmpty())
|
||||||
|
})
|
||||||
|
It("should be empty if replica url is not set", func() {
|
||||||
|
cfg.Replica = &types.AdGuardInstance{URL: ""}
|
||||||
|
r := cfg.UniqueReplicas()
|
||||||
|
Ω(r).Should(BeEmpty())
|
||||||
|
})
|
||||||
|
It("should be empty if replicas url is not set", func() {
|
||||||
|
cfg.Replicas = []types.AdGuardInstance{{URL: ""}}
|
||||||
|
r := cfg.UniqueReplicas()
|
||||||
|
Ω(r).Should(BeEmpty())
|
||||||
|
})
|
||||||
|
It("should return only one replica if same url and apiPath", func() {
|
||||||
|
cfg.Replica = &types.AdGuardInstance{URL: url, APIPath: apiPath}
|
||||||
|
cfg.Replicas = []types.AdGuardInstance{{URL: url, APIPath: apiPath}, {URL: url, APIPath: apiPath}}
|
||||||
|
r := cfg.UniqueReplicas()
|
||||||
|
Ω(r).Should(HaveLen(1))
|
||||||
|
})
|
||||||
|
It("should return 3 one replicas if urls are different", func() {
|
||||||
|
cfg.Replica = &types.AdGuardInstance{URL: url, APIPath: apiPath}
|
||||||
|
cfg.Replicas = []types.AdGuardInstance{{URL: url + "1", APIPath: apiPath}, {URL: url, APIPath: apiPath + "1"}}
|
||||||
|
r := cfg.UniqueReplicas()
|
||||||
|
Ω(r).Should(HaveLen(3))
|
||||||
|
})
|
||||||
|
It("should set default api apiPath if not set", func() {
|
||||||
|
cfg.Replica = &types.AdGuardInstance{URL: url}
|
||||||
|
cfg.Replicas = []types.AdGuardInstance{{URL: url + "1"}}
|
||||||
|
r := cfg.UniqueReplicas()
|
||||||
|
Ω(r).Should(HaveLen(2))
|
||||||
|
Ω(r[0].APIPath).Should(Equal(types.DefaultAPIPath))
|
||||||
|
Ω(r[1].APIPath).Should(Equal(types.DefaultAPIPath))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("Clients", func() {
|
||||||
|
Context("Merge", func() {
|
||||||
|
var (
|
||||||
|
originClients *model.Clients
|
||||||
|
replicaClients model.Clients
|
||||||
|
name string
|
||||||
|
)
|
||||||
|
BeforeEach(func() {
|
||||||
|
originClients = &model.Clients{}
|
||||||
|
replicaClients = model.Clients{}
|
||||||
|
name = uuid.NewString()
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should add a missing client", func() {
|
||||||
|
originClients.Add(model.Client{Name: utils.Ptr(name)})
|
||||||
|
a, u, d := replicaClients.Merge(originClients)
|
||||||
|
Ω(a).Should(HaveLen(1))
|
||||||
|
Ω(u).Should(BeEmpty())
|
||||||
|
Ω(d).Should(BeEmpty())
|
||||||
|
|
||||||
|
Ω(*a[0].Name).Should(Equal(name))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should remove additional client", func() {
|
||||||
|
replicaClients.Add(model.Client{Name: utils.Ptr(name)})
|
||||||
|
a, u, d := replicaClients.Merge(originClients)
|
||||||
|
Ω(a).Should(BeEmpty())
|
||||||
|
Ω(u).Should(BeEmpty())
|
||||||
|
Ω(d).Should(HaveLen(1))
|
||||||
|
|
||||||
|
Ω(*d[0].Name).Should(Equal(name))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should update existing client when name differs", func() {
|
||||||
|
disallowed := true
|
||||||
|
originClients.Add(model.Client{Name: utils.Ptr(name), FilteringEnabled: utils.Ptr(disallowed)})
|
||||||
|
replicaClients.Add(model.Client{Name: utils.Ptr(name), FilteringEnabled: utils.Ptr(!disallowed)})
|
||||||
|
a, u, d := replicaClients.Merge(originClients)
|
||||||
|
Ω(a).Should(BeEmpty())
|
||||||
|
Ω(u).Should(HaveLen(1))
|
||||||
|
Ω(d).Should(BeEmpty())
|
||||||
|
|
||||||
|
Ω(*u[0].FilteringEnabled).Should(Equal(disallowed))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("BlockedServices", func() {
|
||||||
|
Context("Equals", func() {
|
||||||
|
It("should be equal", func() {
|
||||||
|
s1 := &model.BlockedServicesArray{"a", "b"}
|
||||||
|
s2 := &model.BlockedServicesArray{"b", "a"}
|
||||||
|
Ω(model.EqualsStringSlice(s1, s2, true)).Should(BeTrue())
|
||||||
|
})
|
||||||
|
It("should not be equal different values", func() {
|
||||||
|
s1 := &model.BlockedServicesArray{"a", "b"}
|
||||||
|
s2 := &model.BlockedServicesArray{"B", "a"}
|
||||||
|
Ω(model.EqualsStringSlice(s1, s2, true)).ShouldNot(BeTrue())
|
||||||
|
})
|
||||||
|
It("should not be equal different length", func() {
|
||||||
|
s1 := &model.BlockedServicesArray{"a", "b"}
|
||||||
|
s2 := &model.BlockedServicesArray{"b", "a", "c"}
|
||||||
|
Ω(model.EqualsStringSlice(s1, s2, true)).ShouldNot(BeTrue())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("DNSConfig", func() {
|
||||||
|
Context("Equals", func() {
|
||||||
|
It("should be equal", func() {
|
||||||
|
dc1 := &model.DNSConfig{LocalPtrUpstreams: utils.Ptr([]string{"a"})}
|
||||||
|
dc2 := &model.DNSConfig{LocalPtrUpstreams: utils.Ptr([]string{"a"})}
|
||||||
|
Ω(dc1.Equals(dc2)).Should(BeTrue())
|
||||||
|
})
|
||||||
|
It("should not be equal", func() {
|
||||||
|
dc1 := &model.DNSConfig{LocalPtrUpstreams: utils.Ptr([]string{"a"})}
|
||||||
|
dc2 := &model.DNSConfig{LocalPtrUpstreams: utils.Ptr([]string{"b"})}
|
||||||
|
Ω(dc1.Equals(dc2)).ShouldNot(BeTrue())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("DHCPServerConfig", func() {
|
||||||
|
Context("Equals", func() {
|
||||||
|
It("should be equal", func() {
|
||||||
|
dc1 := &model.DhcpStatus{
|
||||||
|
V4: &model.DhcpConfigV4{
|
||||||
|
GatewayIp: utils.Ptr("1.2.3.4"),
|
||||||
|
LeaseDuration: utils.Ptr(123),
|
||||||
|
RangeStart: utils.Ptr("1.2.3.5"),
|
||||||
|
RangeEnd: utils.Ptr("1.2.3.6"),
|
||||||
|
SubnetMask: utils.Ptr("255.255.255.0"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
dc2 := &model.DhcpStatus{
|
||||||
|
V4: &model.DhcpConfigV4{
|
||||||
|
GatewayIp: utils.Ptr("1.2.3.4"),
|
||||||
|
LeaseDuration: utils.Ptr(123),
|
||||||
|
RangeStart: utils.Ptr("1.2.3.5"),
|
||||||
|
RangeEnd: utils.Ptr("1.2.3.6"),
|
||||||
|
SubnetMask: utils.Ptr("255.255.255.0"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
Ω(dc1.Equals(dc2)).Should(BeTrue())
|
||||||
|
})
|
||||||
|
It("should not be equal", func() {
|
||||||
|
dc1 := &model.DhcpStatus{
|
||||||
|
V4: &model.DhcpConfigV4{
|
||||||
|
GatewayIp: utils.Ptr("1.2.3.3"),
|
||||||
|
LeaseDuration: utils.Ptr(123),
|
||||||
|
RangeStart: utils.Ptr("1.2.3.5"),
|
||||||
|
RangeEnd: utils.Ptr("1.2.3.6"),
|
||||||
|
SubnetMask: utils.Ptr("255.255.255.0"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
dc2 := &model.DhcpStatus{
|
||||||
|
V4: &model.DhcpConfigV4{
|
||||||
|
GatewayIp: utils.Ptr("1.2.3.4"),
|
||||||
|
LeaseDuration: utils.Ptr(123),
|
||||||
|
RangeStart: utils.Ptr("1.2.3.5"),
|
||||||
|
RangeEnd: utils.Ptr("1.2.3.6"),
|
||||||
|
SubnetMask: utils.Ptr("255.255.255.0"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
Ω(dc1.Equals(dc2)).ShouldNot(BeTrue())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("Clone", func() {
|
||||||
|
It("clone should be equal", func() {
|
||||||
|
dc1 := &model.DhcpStatus{
|
||||||
|
V4: &model.DhcpConfigV4{
|
||||||
|
GatewayIp: utils.Ptr("1.2.3.4"),
|
||||||
|
LeaseDuration: utils.Ptr(123),
|
||||||
|
RangeStart: utils.Ptr("1.2.3.5"),
|
||||||
|
RangeEnd: utils.Ptr("1.2.3.6"),
|
||||||
|
SubnetMask: utils.Ptr("255.255.255.0"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
Ω(dc1.Clone().Equals(dc1)).Should(BeTrue())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("HasConfig", func() {
|
||||||
|
It("should not have a config", func() {
|
||||||
|
dc1 := &model.DhcpStatus{
|
||||||
|
V4: &model.DhcpConfigV4{},
|
||||||
|
V6: &model.DhcpConfigV6{},
|
||||||
|
}
|
||||||
|
Ω(dc1.HasConfig()).Should(BeFalse())
|
||||||
|
})
|
||||||
|
It("should not have a v4 config", func() {
|
||||||
|
dc1 := &model.DhcpStatus{
|
||||||
|
V4: &model.DhcpConfigV4{
|
||||||
|
GatewayIp: utils.Ptr("1.2.3.4"),
|
||||||
|
LeaseDuration: utils.Ptr(123),
|
||||||
|
RangeStart: utils.Ptr("1.2.3.5"),
|
||||||
|
RangeEnd: utils.Ptr("1.2.3.6"),
|
||||||
|
SubnetMask: utils.Ptr("255.255.255.0"),
|
||||||
|
},
|
||||||
|
V6: &model.DhcpConfigV6{},
|
||||||
|
}
|
||||||
|
Ω(dc1.HasConfig()).Should(BeTrue())
|
||||||
|
})
|
||||||
|
It("should not have a v6 config", func() {
|
||||||
|
dc1 := &model.DhcpStatus{
|
||||||
|
V4: &model.DhcpConfigV4{},
|
||||||
|
V6: &model.DhcpConfigV6{
|
||||||
|
RangeStart: utils.Ptr("1.2.3.5"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
Ω(dc1.HasConfig()).Should(BeTrue())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -7,7 +7,7 @@ package client
|
|||||||
import (
|
import (
|
||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
|
|
||||||
types "github.com/bakito/adguardhome-sync/pkg/types"
|
model "github.com/bakito/adguardhome-sync/pkg/client/model"
|
||||||
gomock "github.com/golang/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -35,10 +35,10 @@ func (m *MockClient) EXPECT() *MockClientMockRecorder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AccessList mocks base method.
|
// AccessList mocks base method.
|
||||||
func (m *MockClient) AccessList() (*types.AccessList, error) {
|
func (m *MockClient) AccessList() (*model.AccessList, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "AccessList")
|
ret := m.ctrl.Call(m, "AccessList")
|
||||||
ret0, _ := ret[0].(*types.AccessList)
|
ret0, _ := ret[0].(*model.AccessList)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
@@ -49,63 +49,50 @@ func (mr *MockClientMockRecorder) AccessList() *gomock.Call {
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AccessList", reflect.TypeOf((*MockClient)(nil).AccessList))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AccessList", reflect.TypeOf((*MockClient)(nil).AccessList))
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddClients mocks base method.
|
// AddClient mocks base method.
|
||||||
func (m *MockClient) AddClients(arg0 ...types.Client) error {
|
func (m *MockClient) AddClient(arg0 *model.Client) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
varargs := []interface{}{}
|
ret := m.ctrl.Call(m, "AddClient", arg0)
|
||||||
for _, a := range arg0 {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "AddClients", varargs...)
|
|
||||||
ret0, _ := ret[0].(error)
|
ret0, _ := ret[0].(error)
|
||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddClients indicates an expected call of AddClients.
|
// AddClient indicates an expected call of AddClient.
|
||||||
func (mr *MockClientMockRecorder) AddClients(arg0 ...interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) AddClient(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
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.
|
// AddDHCPStaticLease mocks base method.
|
||||||
func (m *MockClient) AddDHCPStaticLeases(arg0 ...types.Lease) error {
|
func (m *MockClient) AddDHCPStaticLease(arg0 model.DhcpStaticLease) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
varargs := []interface{}{}
|
ret := m.ctrl.Call(m, "AddDHCPStaticLease", arg0)
|
||||||
for _, a := range arg0 {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "AddDHCPStaticLeases", varargs...)
|
|
||||||
ret0, _ := ret[0].(error)
|
ret0, _ := ret[0].(error)
|
||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddDHCPStaticLeases indicates an expected call of AddDHCPStaticLeases.
|
// AddDHCPStaticLease indicates an expected call of AddDHCPStaticLease.
|
||||||
func (mr *MockClientMockRecorder) AddDHCPStaticLeases(arg0 ...interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) AddDHCPStaticLease(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
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.
|
// AddFilter mocks base method.
|
||||||
func (m *MockClient) AddFilters(arg0 bool, arg1 ...types.Filter) error {
|
func (m *MockClient) AddFilter(arg0 bool, arg1 model.Filter) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
varargs := []interface{}{arg0}
|
ret := m.ctrl.Call(m, "AddFilter", arg0, arg1)
|
||||||
for _, a := range arg1 {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "AddFilters", varargs...)
|
|
||||||
ret0, _ := ret[0].(error)
|
ret0, _ := ret[0].(error)
|
||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddFilters indicates an expected call of AddFilters.
|
// AddFilter indicates an expected call of AddFilter.
|
||||||
func (mr *MockClientMockRecorder) AddFilters(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) AddFilter(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
varargs := append([]interface{}{arg0}, arg1...)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddFilter", reflect.TypeOf((*MockClient)(nil).AddFilter), arg0, arg1)
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddFilters", reflect.TypeOf((*MockClient)(nil).AddFilters), varargs...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddRewriteEntries mocks base method.
|
// AddRewriteEntries mocks base method.
|
||||||
func (m *MockClient) AddRewriteEntries(arg0 ...types.RewriteEntry) error {
|
func (m *MockClient) AddRewriteEntries(arg0 ...model.RewriteEntry) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
varargs := []interface{}{}
|
varargs := []interface{}{}
|
||||||
for _, a := range arg0 {
|
for _, a := range arg0 {
|
||||||
@@ -122,11 +109,41 @@ func (mr *MockClientMockRecorder) AddRewriteEntries(arg0 ...interface{}) *gomock
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRewriteEntries", reflect.TypeOf((*MockClient)(nil).AddRewriteEntries), arg0...)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRewriteEntries", reflect.TypeOf((*MockClient)(nil).AddRewriteEntries), arg0...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BlockedServices mocks base method.
|
||||||
|
func (m *MockClient) BlockedServices() (*[]string, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "BlockedServices")
|
||||||
|
ret0, _ := ret[0].(*[]string)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlockedServices indicates an expected call of BlockedServices.
|
||||||
|
func (mr *MockClientMockRecorder) BlockedServices() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BlockedServices", reflect.TypeOf((*MockClient)(nil).BlockedServices))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlockedServicesSchedule mocks base method.
|
||||||
|
func (m *MockClient) BlockedServicesSchedule() (*model.BlockedServicesSchedule, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "BlockedServicesSchedule")
|
||||||
|
ret0, _ := ret[0].(*model.BlockedServicesSchedule)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlockedServicesSchedule indicates an expected call of BlockedServicesSchedule.
|
||||||
|
func (mr *MockClientMockRecorder) BlockedServicesSchedule() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BlockedServicesSchedule", reflect.TypeOf((*MockClient)(nil).BlockedServicesSchedule))
|
||||||
|
}
|
||||||
|
|
||||||
// Clients mocks base method.
|
// Clients mocks base method.
|
||||||
func (m *MockClient) Clients() (*types.Clients, error) {
|
func (m *MockClient) Clients() (*model.Clients, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Clients")
|
ret := m.ctrl.Call(m, "Clients")
|
||||||
ret0, _ := ret[0].(*types.Clients)
|
ret0, _ := ret[0].(*model.Clients)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
@@ -137,26 +154,11 @@ func (mr *MockClientMockRecorder) Clients() *gomock.Call {
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Clients", reflect.TypeOf((*MockClient)(nil).Clients))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Clients", reflect.TypeOf((*MockClient)(nil).Clients))
|
||||||
}
|
}
|
||||||
|
|
||||||
// DHCPServerConfig mocks base method.
|
|
||||||
func (m *MockClient) DHCPServerConfig() (*types.DHCPServerConfig, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "DHCPServerConfig")
|
|
||||||
ret0, _ := ret[0].(*types.DHCPServerConfig)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// DHCPServerConfig indicates an expected call of DHCPServerConfig.
|
|
||||||
func (mr *MockClientMockRecorder) DHCPServerConfig() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DHCPServerConfig", reflect.TypeOf((*MockClient)(nil).DHCPServerConfig))
|
|
||||||
}
|
|
||||||
|
|
||||||
// DNSConfig mocks base method.
|
// DNSConfig mocks base method.
|
||||||
func (m *MockClient) DNSConfig() (*types.DNSConfig, error) {
|
func (m *MockClient) DNSConfig() (*model.DNSConfig, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "DNSConfig")
|
ret := m.ctrl.Call(m, "DNSConfig")
|
||||||
ret0, _ := ret[0].(*types.DNSConfig)
|
ret0, _ := ret[0].(*model.DNSConfig)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
@@ -167,63 +169,50 @@ func (mr *MockClientMockRecorder) DNSConfig() *gomock.Call {
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DNSConfig", reflect.TypeOf((*MockClient)(nil).DNSConfig))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DNSConfig", reflect.TypeOf((*MockClient)(nil).DNSConfig))
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteClients mocks base method.
|
// DeleteClient mocks base method.
|
||||||
func (m *MockClient) DeleteClients(arg0 ...types.Client) error {
|
func (m *MockClient) DeleteClient(arg0 *model.Client) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
varargs := []interface{}{}
|
ret := m.ctrl.Call(m, "DeleteClient", arg0)
|
||||||
for _, a := range arg0 {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "DeleteClients", varargs...)
|
|
||||||
ret0, _ := ret[0].(error)
|
ret0, _ := ret[0].(error)
|
||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteClients indicates an expected call of DeleteClients.
|
// DeleteClient indicates an expected call of DeleteClient.
|
||||||
func (mr *MockClientMockRecorder) DeleteClients(arg0 ...interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) DeleteClient(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
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.
|
// DeleteDHCPStaticLease mocks base method.
|
||||||
func (m *MockClient) DeleteDHCPStaticLeases(arg0 ...types.Lease) error {
|
func (m *MockClient) DeleteDHCPStaticLease(arg0 model.DhcpStaticLease) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
varargs := []interface{}{}
|
ret := m.ctrl.Call(m, "DeleteDHCPStaticLease", arg0)
|
||||||
for _, a := range arg0 {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "DeleteDHCPStaticLeases", varargs...)
|
|
||||||
ret0, _ := ret[0].(error)
|
ret0, _ := ret[0].(error)
|
||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteDHCPStaticLeases indicates an expected call of DeleteDHCPStaticLeases.
|
// DeleteDHCPStaticLease indicates an expected call of DeleteDHCPStaticLease.
|
||||||
func (mr *MockClientMockRecorder) DeleteDHCPStaticLeases(arg0 ...interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) DeleteDHCPStaticLease(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
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.
|
// DeleteFilter mocks base method.
|
||||||
func (m *MockClient) DeleteFilters(arg0 bool, arg1 ...types.Filter) error {
|
func (m *MockClient) DeleteFilter(arg0 bool, arg1 model.Filter) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
varargs := []interface{}{arg0}
|
ret := m.ctrl.Call(m, "DeleteFilter", arg0, arg1)
|
||||||
for _, a := range arg1 {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "DeleteFilters", varargs...)
|
|
||||||
ret0, _ := ret[0].(error)
|
ret0, _ := ret[0].(error)
|
||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteFilters indicates an expected call of DeleteFilters.
|
// DeleteFilter indicates an expected call of DeleteFilter.
|
||||||
func (mr *MockClientMockRecorder) DeleteFilters(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) DeleteFilter(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
varargs := append([]interface{}{arg0}, arg1...)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteFilter", reflect.TypeOf((*MockClient)(nil).DeleteFilter), arg0, arg1)
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteFilters", reflect.TypeOf((*MockClient)(nil).DeleteFilters), varargs...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteRewriteEntries mocks base method.
|
// DeleteRewriteEntries mocks base method.
|
||||||
func (m *MockClient) DeleteRewriteEntries(arg0 ...types.RewriteEntry) error {
|
func (m *MockClient) DeleteRewriteEntries(arg0 ...model.RewriteEntry) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
varargs := []interface{}{}
|
varargs := []interface{}{}
|
||||||
for _, a := range arg0 {
|
for _, a := range arg0 {
|
||||||
@@ -240,11 +229,26 @@ func (mr *MockClientMockRecorder) DeleteRewriteEntries(arg0 ...interface{}) *gom
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteRewriteEntries", reflect.TypeOf((*MockClient)(nil).DeleteRewriteEntries), arg0...)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteRewriteEntries", reflect.TypeOf((*MockClient)(nil).DeleteRewriteEntries), arg0...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DhcpConfig mocks base method.
|
||||||
|
func (m *MockClient) DhcpConfig() (*model.DhcpStatus, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "DhcpConfig")
|
||||||
|
ret0, _ := ret[0].(*model.DhcpStatus)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// DhcpConfig indicates an expected call of DhcpConfig.
|
||||||
|
func (mr *MockClientMockRecorder) DhcpConfig() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DhcpConfig", reflect.TypeOf((*MockClient)(nil).DhcpConfig))
|
||||||
|
}
|
||||||
|
|
||||||
// Filtering mocks base method.
|
// Filtering mocks base method.
|
||||||
func (m *MockClient) Filtering() (*types.FilteringStatus, error) {
|
func (m *MockClient) Filtering() (*model.FilterStatus, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Filtering")
|
ret := m.ctrl.Call(m, "Filtering")
|
||||||
ret0, _ := ret[0].(*types.FilteringStatus)
|
ret0, _ := ret[0].(*model.FilterStatus)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
@@ -284,11 +288,26 @@ func (mr *MockClientMockRecorder) Parental() *gomock.Call {
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Parental", reflect.TypeOf((*MockClient)(nil).Parental))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Parental", reflect.TypeOf((*MockClient)(nil).Parental))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ProfileInfo mocks base method.
|
||||||
|
func (m *MockClient) ProfileInfo() (*model.ProfileInfo, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "ProfileInfo")
|
||||||
|
ret0, _ := ret[0].(*model.ProfileInfo)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProfileInfo indicates an expected call of ProfileInfo.
|
||||||
|
func (mr *MockClientMockRecorder) ProfileInfo() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ProfileInfo", reflect.TypeOf((*MockClient)(nil).ProfileInfo))
|
||||||
|
}
|
||||||
|
|
||||||
// QueryLogConfig mocks base method.
|
// QueryLogConfig mocks base method.
|
||||||
func (m *MockClient) QueryLogConfig() (*types.QueryLogConfig, error) {
|
func (m *MockClient) QueryLogConfig() (*model.QueryLogConfig, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "QueryLogConfig")
|
ret := m.ctrl.Call(m, "QueryLogConfig")
|
||||||
ret0, _ := ret[0].(*types.QueryLogConfig)
|
ret0, _ := ret[0].(*model.QueryLogConfig)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
@@ -314,10 +333,10 @@ func (mr *MockClientMockRecorder) RefreshFilters(arg0 interface{}) *gomock.Call
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RewriteList mocks base method.
|
// RewriteList mocks base method.
|
||||||
func (m *MockClient) RewriteList() (*types.RewriteEntries, error) {
|
func (m *MockClient) RewriteList() (*model.RewriteEntries, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "RewriteList")
|
ret := m.ctrl.Call(m, "RewriteList")
|
||||||
ret0, _ := ret[0].(*types.RewriteEntries)
|
ret0, _ := ret[0].(*model.RewriteEntries)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
@@ -343,38 +362,23 @@ func (mr *MockClientMockRecorder) SafeBrowsing() *gomock.Call {
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SafeBrowsing", reflect.TypeOf((*MockClient)(nil).SafeBrowsing))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SafeBrowsing", reflect.TypeOf((*MockClient)(nil).SafeBrowsing))
|
||||||
}
|
}
|
||||||
|
|
||||||
// SafeSearch mocks base method.
|
// SafeSearchConfig mocks base method.
|
||||||
func (m *MockClient) SafeSearch() (bool, error) {
|
func (m *MockClient) SafeSearchConfig() (*model.SafeSearchConfig, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "SafeSearch")
|
ret := m.ctrl.Call(m, "SafeSearchConfig")
|
||||||
ret0, _ := ret[0].(bool)
|
ret0, _ := ret[0].(*model.SafeSearchConfig)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// SafeSearch indicates an expected call of SafeSearch.
|
// SafeSearchConfig indicates an expected call of SafeSearchConfig.
|
||||||
func (mr *MockClientMockRecorder) SafeSearch() *gomock.Call {
|
func (mr *MockClientMockRecorder) SafeSearchConfig() *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SafeSearch", reflect.TypeOf((*MockClient)(nil).SafeSearch))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SafeSearchConfig", reflect.TypeOf((*MockClient)(nil).SafeSearchConfig))
|
||||||
}
|
|
||||||
|
|
||||||
// Services mocks base method.
|
|
||||||
func (m *MockClient) Services() (types.Services, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "Services")
|
|
||||||
ret0, _ := ret[0].(types.Services)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Services indicates an expected call of Services.
|
|
||||||
func (mr *MockClientMockRecorder) Services() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Services", reflect.TypeOf((*MockClient)(nil).Services))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetAccessList mocks base method.
|
// SetAccessList mocks base method.
|
||||||
func (m *MockClient) SetAccessList(arg0 *types.AccessList) error {
|
func (m *MockClient) SetAccessList(arg0 *model.AccessList) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "SetAccessList", arg0)
|
ret := m.ctrl.Call(m, "SetAccessList", arg0)
|
||||||
ret0, _ := ret[0].(error)
|
ret0, _ := ret[0].(error)
|
||||||
@@ -387,8 +391,36 @@ func (mr *MockClientMockRecorder) SetAccessList(arg0 interface{}) *gomock.Call {
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetAccessList", reflect.TypeOf((*MockClient)(nil).SetAccessList), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetAccessList", reflect.TypeOf((*MockClient)(nil).SetAccessList), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetBlockedServices mocks base method.
|
||||||
|
func (m *MockClient) SetBlockedServices(arg0 *[]string) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "SetBlockedServices", arg0)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBlockedServices indicates an expected call of SetBlockedServices.
|
||||||
|
func (mr *MockClientMockRecorder) SetBlockedServices(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBlockedServices", reflect.TypeOf((*MockClient)(nil).SetBlockedServices), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBlockedServicesSchedule mocks base method.
|
||||||
|
func (m *MockClient) SetBlockedServicesSchedule(arg0 *model.BlockedServicesSchedule) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "SetBlockedServicesSchedule", arg0)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBlockedServicesSchedule indicates an expected call of SetBlockedServicesSchedule.
|
||||||
|
func (mr *MockClientMockRecorder) SetBlockedServicesSchedule(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBlockedServicesSchedule", reflect.TypeOf((*MockClient)(nil).SetBlockedServicesSchedule), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
// SetCustomRules mocks base method.
|
// SetCustomRules mocks base method.
|
||||||
func (m *MockClient) SetCustomRules(arg0 types.UserRules) error {
|
func (m *MockClient) SetCustomRules(arg0 *[]string) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "SetCustomRules", arg0)
|
ret := m.ctrl.Call(m, "SetCustomRules", arg0)
|
||||||
ret0, _ := ret[0].(error)
|
ret0, _ := ret[0].(error)
|
||||||
@@ -401,22 +433,8 @@ func (mr *MockClientMockRecorder) SetCustomRules(arg0 interface{}) *gomock.Call
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetCustomRules", reflect.TypeOf((*MockClient)(nil).SetCustomRules), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetCustomRules", reflect.TypeOf((*MockClient)(nil).SetCustomRules), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDHCPServerConfig mocks base method.
|
|
||||||
func (m *MockClient) SetDHCPServerConfig(arg0 *types.DHCPServerConfig) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "SetDHCPServerConfig", arg0)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDHCPServerConfig indicates an expected call of SetDHCPServerConfig.
|
|
||||||
func (mr *MockClientMockRecorder) SetDHCPServerConfig(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDHCPServerConfig", reflect.TypeOf((*MockClient)(nil).SetDHCPServerConfig), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDNSConfig mocks base method.
|
// SetDNSConfig mocks base method.
|
||||||
func (m *MockClient) SetDNSConfig(arg0 *types.DNSConfig) error {
|
func (m *MockClient) SetDNSConfig(arg0 *model.DNSConfig) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "SetDNSConfig", arg0)
|
ret := m.ctrl.Call(m, "SetDNSConfig", arg0)
|
||||||
ret0, _ := ret[0].(error)
|
ret0, _ := ret[0].(error)
|
||||||
@@ -429,36 +447,64 @@ func (mr *MockClientMockRecorder) SetDNSConfig(arg0 interface{}) *gomock.Call {
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDNSConfig", reflect.TypeOf((*MockClient)(nil).SetDNSConfig), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDNSConfig", reflect.TypeOf((*MockClient)(nil).SetDNSConfig), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetQueryLogConfig mocks base method.
|
// SetDhcpConfig mocks base method.
|
||||||
func (m *MockClient) SetQueryLogConfig(arg0 bool, arg1 float64, arg2 bool) error {
|
func (m *MockClient) SetDhcpConfig(arg0 *model.DhcpStatus) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "SetQueryLogConfig", arg0, arg1, arg2)
|
ret := m.ctrl.Call(m, "SetDhcpConfig", arg0)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDhcpConfig indicates an expected call of SetDhcpConfig.
|
||||||
|
func (mr *MockClientMockRecorder) SetDhcpConfig(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDhcpConfig", reflect.TypeOf((*MockClient)(nil).SetDhcpConfig), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetProfileInfo mocks base method.
|
||||||
|
func (m *MockClient) SetProfileInfo(arg0 *model.ProfileInfo) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "SetProfileInfo", arg0)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetProfileInfo indicates an expected call of SetProfileInfo.
|
||||||
|
func (mr *MockClientMockRecorder) SetProfileInfo(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetProfileInfo", reflect.TypeOf((*MockClient)(nil).SetProfileInfo), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetQueryLogConfig mocks base method.
|
||||||
|
func (m *MockClient) SetQueryLogConfig(arg0 *model.QueryLogConfig) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "SetQueryLogConfig", arg0)
|
||||||
ret0, _ := ret[0].(error)
|
ret0, _ := ret[0].(error)
|
||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetQueryLogConfig indicates an expected call of SetQueryLogConfig.
|
// SetQueryLogConfig indicates an expected call of SetQueryLogConfig.
|
||||||
func (mr *MockClientMockRecorder) SetQueryLogConfig(arg0, arg1, arg2 interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) SetQueryLogConfig(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetQueryLogConfig", reflect.TypeOf((*MockClient)(nil).SetQueryLogConfig), arg0, arg1, arg2)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetQueryLogConfig", reflect.TypeOf((*MockClient)(nil).SetQueryLogConfig), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetServices mocks base method.
|
// SetSafeSearchConfig mocks base method.
|
||||||
func (m *MockClient) SetServices(arg0 types.Services) error {
|
func (m *MockClient) SetSafeSearchConfig(arg0 *model.SafeSearchConfig) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "SetServices", arg0)
|
ret := m.ctrl.Call(m, "SetSafeSearchConfig", arg0)
|
||||||
ret0, _ := ret[0].(error)
|
ret0, _ := ret[0].(error)
|
||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetServices indicates an expected call of SetServices.
|
// SetSafeSearchConfig indicates an expected call of SetSafeSearchConfig.
|
||||||
func (mr *MockClientMockRecorder) SetServices(arg0 interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) SetSafeSearchConfig(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetServices", reflect.TypeOf((*MockClient)(nil).SetServices), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSafeSearchConfig", reflect.TypeOf((*MockClient)(nil).SetSafeSearchConfig), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetStatsConfig mocks base method.
|
// SetStatsConfig mocks base method.
|
||||||
func (m *MockClient) SetStatsConfig(arg0 float64) error {
|
func (m *MockClient) SetStatsConfig(arg0 *model.StatsConfig) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "SetStatsConfig", arg0)
|
ret := m.ctrl.Call(m, "SetStatsConfig", arg0)
|
||||||
ret0, _ := ret[0].(error)
|
ret0, _ := ret[0].(error)
|
||||||
@@ -486,10 +532,10 @@ func (mr *MockClientMockRecorder) Setup() *gomock.Call {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// StatsConfig mocks base method.
|
// StatsConfig mocks base method.
|
||||||
func (m *MockClient) StatsConfig() (*types.IntervalConfig, error) {
|
func (m *MockClient) StatsConfig() (*model.StatsConfig, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "StatsConfig")
|
ret := m.ctrl.Call(m, "StatsConfig")
|
||||||
ret0, _ := ret[0].(*types.IntervalConfig)
|
ret0, _ := ret[0].(*model.StatsConfig)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
@@ -501,10 +547,10 @@ func (mr *MockClientMockRecorder) StatsConfig() *gomock.Call {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Status mocks base method.
|
// Status mocks base method.
|
||||||
func (m *MockClient) Status() (*types.Status, error) {
|
func (m *MockClient) Status() (*model.ServerStatus, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Status")
|
ret := m.ctrl.Call(m, "Status")
|
||||||
ret0, _ := ret[0].(*types.Status)
|
ret0, _ := ret[0].(*model.ServerStatus)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
@@ -516,7 +562,7 @@ func (mr *MockClientMockRecorder) Status() *gomock.Call {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ToggleFiltering mocks base method.
|
// ToggleFiltering mocks base method.
|
||||||
func (m *MockClient) ToggleFiltering(arg0 bool, arg1 float64) error {
|
func (m *MockClient) ToggleFiltering(arg0 bool, arg1 int) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "ToggleFiltering", arg0, arg1)
|
ret := m.ctrl.Call(m, "ToggleFiltering", arg0, arg1)
|
||||||
ret0, _ := ret[0].(error)
|
ret0, _ := ret[0].(error)
|
||||||
@@ -571,53 +617,30 @@ func (mr *MockClientMockRecorder) ToggleSafeBrowsing(arg0 interface{}) *gomock.C
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleSafeBrowsing", reflect.TypeOf((*MockClient)(nil).ToggleSafeBrowsing), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleSafeBrowsing", reflect.TypeOf((*MockClient)(nil).ToggleSafeBrowsing), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToggleSafeSearch mocks base method.
|
// UpdateClient mocks base method.
|
||||||
func (m *MockClient) ToggleSafeSearch(arg0 bool) error {
|
func (m *MockClient) UpdateClient(arg0 *model.Client) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "ToggleSafeSearch", arg0)
|
ret := m.ctrl.Call(m, "UpdateClient", arg0)
|
||||||
ret0, _ := ret[0].(error)
|
ret0, _ := ret[0].(error)
|
||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToggleSafeSearch indicates an expected call of ToggleSafeSearch.
|
// UpdateClient indicates an expected call of UpdateClient.
|
||||||
func (mr *MockClientMockRecorder) ToggleSafeSearch(arg0 interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) UpdateClient(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleSafeSearch", reflect.TypeOf((*MockClient)(nil).ToggleSafeSearch), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateClient", reflect.TypeOf((*MockClient)(nil).UpdateClient), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateClients mocks base method.
|
// UpdateFilter mocks base method.
|
||||||
func (m *MockClient) UpdateClients(arg0 ...types.Client) error {
|
func (m *MockClient) UpdateFilter(arg0 bool, arg1 model.Filter) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
varargs := []interface{}{}
|
ret := m.ctrl.Call(m, "UpdateFilter", arg0, arg1)
|
||||||
for _, a := range arg0 {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "UpdateClients", varargs...)
|
|
||||||
ret0, _ := ret[0].(error)
|
ret0, _ := ret[0].(error)
|
||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateClients indicates an expected call of UpdateClients.
|
// UpdateFilter indicates an expected call of UpdateFilter.
|
||||||
func (mr *MockClientMockRecorder) UpdateClients(arg0 ...interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) UpdateFilter(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
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, "UpdateFilter", reflect.TypeOf((*MockClient)(nil).UpdateFilter), arg0, arg1)
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateFilters mocks base method.
|
|
||||||
func (m *MockClient) UpdateFilters(arg0 bool, arg1 ...types.Filter) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []interface{}{arg0}
|
|
||||||
for _, a := range arg1 {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "UpdateFilters", varargs...)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateFilters indicates an expected call of UpdateFilters.
|
|
||||||
func (mr *MockClientMockRecorder) UpdateFilters(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]interface{}{arg0}, arg1...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateFilters", reflect.TypeOf((*MockClient)(nil).UpdateFilters), varargs...)
|
|
||||||
}
|
}
|
||||||
|
|||||||
283
pkg/sync/action-general.go
Normal file
283
pkg/sync/action-general.go
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
package sync
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/bakito/adguardhome-sync/pkg/client"
|
||||||
|
"github.com/bakito/adguardhome-sync/pkg/client/model"
|
||||||
|
"github.com/bakito/adguardhome-sync/pkg/utils"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
actionProfileInfo = func(ac *actionContext) error {
|
||||||
|
if pro, err := ac.client.ProfileInfo(); err != nil {
|
||||||
|
return err
|
||||||
|
} else if merged := pro.ShouldSyncFor(ac.origin.profileInfo); merged != nil {
|
||||||
|
return ac.client.SetProfileInfo(merged)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
actionProtection = func(ac *actionContext) error {
|
||||||
|
if ac.origin.status.ProtectionEnabled != ac.replicaStatus.ProtectionEnabled {
|
||||||
|
return ac.client.ToggleProtection(ac.origin.status.ProtectionEnabled)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
actionParental = func(ac *actionContext) error {
|
||||||
|
if rp, err := ac.client.Parental(); err != nil {
|
||||||
|
return err
|
||||||
|
} else if ac.origin.parental != rp {
|
||||||
|
return ac.client.ToggleParental(ac.origin.parental)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
actionSafeSearchConfig = func(ac *actionContext) error {
|
||||||
|
if ssc, err := ac.client.SafeSearchConfig(); err != nil {
|
||||||
|
return err
|
||||||
|
} else if !ac.origin.safeSearch.Equals(ssc) {
|
||||||
|
return ac.client.SetSafeSearchConfig(ac.origin.safeSearch)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
actionSafeBrowsing = func(ac *actionContext) error {
|
||||||
|
if rs, err := ac.client.SafeBrowsing(); err != nil {
|
||||||
|
return err
|
||||||
|
} else if ac.origin.safeBrowsing != rs {
|
||||||
|
if err = ac.client.ToggleSafeBrowsing(ac.origin.safeBrowsing); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
actionQueryLogConfig = func(ac *actionContext) error {
|
||||||
|
qlc, err := ac.client.QueryLogConfig()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !ac.origin.queryLogConfig.Equals(qlc) {
|
||||||
|
return ac.client.SetQueryLogConfig(ac.origin.queryLogConfig)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
actionStatsConfig = func(ac *actionContext) error {
|
||||||
|
sc, err := ac.client.StatsConfig()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if ac.origin.statsConfig.Interval != sc.Interval {
|
||||||
|
return ac.client.SetStatsConfig(ac.origin.statsConfig)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
actionDNSRewrites = func(ac *actionContext) error {
|
||||||
|
replicaRewrites, err := ac.client.RewriteList()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
a, r, d := replicaRewrites.Merge(ac.origin.rewrites)
|
||||||
|
|
||||||
|
if err = ac.client.DeleteRewriteEntries(r...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = ac.client.AddRewriteEntries(a...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, dupl := range d {
|
||||||
|
ac.rl.With("domain", dupl.Domain, "answer", dupl.Answer).Warn("Skipping duplicated rewrite from source")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
actionFilters = func(ac *actionContext) error {
|
||||||
|
rf, err := ac.client.Filtering()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = syncFilterType(ac.rl, ac.origin.filters.Filters, rf.Filters, false, ac.client, ac.continueOnError); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = syncFilterType(ac.rl, ac.origin.filters.WhitelistFilters, rf.WhitelistFilters, true, ac.client, ac.continueOnError); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if utils.PtrToString(ac.origin.filters.UserRules) != utils.PtrToString(rf.UserRules) {
|
||||||
|
return ac.client.SetCustomRules(ac.origin.filters.UserRules)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ac.origin.filters.Enabled != rf.Enabled || ac.origin.filters.Interval != rf.Interval {
|
||||||
|
return ac.client.ToggleFiltering(*ac.origin.filters.Enabled, *ac.origin.filters.Interval)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
actionBlockedServices = func(ac *actionContext) error {
|
||||||
|
rs, err := ac.client.BlockedServices()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !model.EqualsStringSlice(ac.origin.blockedServices, rs, true) {
|
||||||
|
return ac.client.SetBlockedServices(ac.origin.blockedServices)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
actionBlockedServicesSchedule = func(ac *actionContext) error {
|
||||||
|
rbss, err := ac.client.BlockedServicesSchedule()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ac.origin.blockedServicesSchedule.Equals(rbss) {
|
||||||
|
return ac.client.SetBlockedServicesSchedule(ac.origin.blockedServicesSchedule)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
actionClientSettings = func(ac *actionContext) error {
|
||||||
|
rc, err := ac.client.Clients()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
a, u, r := rc.Merge(ac.origin.clients)
|
||||||
|
|
||||||
|
for _, client := range r {
|
||||||
|
if err := ac.client.DeleteClient(client); err != nil {
|
||||||
|
ac.rl.With("client-name", client.Name, "error", err).Error("error deleting client setting")
|
||||||
|
if !ac.continueOnError {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, client := range a {
|
||||||
|
if err := ac.client.AddClient(client); err != nil {
|
||||||
|
ac.rl.With("client-name", client.Name, "error", err).Error("error adding client setting")
|
||||||
|
if !ac.continueOnError {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, client := range u {
|
||||||
|
if err := ac.client.UpdateClient(client); err != nil {
|
||||||
|
ac.rl.With("client-name", client.Name, "error", err).Error("error updating client setting")
|
||||||
|
if !ac.continueOnError {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
actionDNSAccessLists = func(ac *actionContext) error {
|
||||||
|
al, err := ac.client.AccessList()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !al.Equals(ac.origin.accessList) {
|
||||||
|
return ac.client.SetAccessList(ac.origin.accessList)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
actionDNSServerConfig = func(ac *actionContext) error {
|
||||||
|
dc, err := ac.client.DNSConfig()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !dc.Equals(ac.origin.dnsConfig) {
|
||||||
|
if err = ac.client.SetDNSConfig(ac.origin.dnsConfig); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
actionDHCPServerConfig = func(ac *actionContext) error {
|
||||||
|
if ac.origin.dhcpServerConfig.HasConfig() {
|
||||||
|
sc, err := ac.client.DhcpConfig()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
origClone := ac.origin.dhcpServerConfig.Clone()
|
||||||
|
if ac.replica.InterfaceName != "" {
|
||||||
|
// overwrite interface name
|
||||||
|
origClone.InterfaceName = utils.Ptr(ac.replica.InterfaceName)
|
||||||
|
}
|
||||||
|
if ac.replica.DHCPServerEnabled != nil {
|
||||||
|
// overwrite dhcp enabled
|
||||||
|
origClone.Enabled = ac.replica.DHCPServerEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
if !sc.Equals(origClone) {
|
||||||
|
return ac.client.SetDhcpConfig(origClone)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
actionDHCPStaticLeases = func(ac *actionContext) error {
|
||||||
|
sc, err := ac.client.DhcpConfig()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
a, r := model.MergeDhcpStaticLeases(sc.StaticLeases, ac.origin.dhcpServerConfig.StaticLeases)
|
||||||
|
|
||||||
|
for _, lease := range r {
|
||||||
|
if err := ac.client.DeleteDHCPStaticLease(lease); err != nil {
|
||||||
|
ac.rl.With("hostname", lease.Hostname, "error", err).Error("error deleting dhcp static lease")
|
||||||
|
if !ac.continueOnError {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, lease := range a {
|
||||||
|
if err := ac.client.AddDHCPStaticLease(lease); err != nil {
|
||||||
|
ac.rl.With("hostname", lease.Hostname, "error", err).Error("error adding dhcp static lease")
|
||||||
|
if !ac.continueOnError {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func syncFilterType(rl *zap.SugaredLogger, of *[]model.Filter, rFilters *[]model.Filter, whitelist bool, replica client.Client, continueOnError bool) error {
|
||||||
|
fa, fu, fd := model.MergeFilters(rFilters, of)
|
||||||
|
|
||||||
|
for _, f := range fd {
|
||||||
|
if err := replica.DeleteFilter(whitelist, f); err != nil {
|
||||||
|
rl.With("filter", f.Name, "url", f.Url, "whitelist", whitelist, "error", err).Error("error deleting filter")
|
||||||
|
if !continueOnError {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range fa {
|
||||||
|
if err := replica.AddFilter(whitelist, f); err != nil {
|
||||||
|
rl.With("filter", f.Name, "url", f.Url, "whitelist", whitelist, "error", err).Error("error adding filter")
|
||||||
|
if !continueOnError {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range fu {
|
||||||
|
if err := replica.UpdateFilter(whitelist, f); err != nil {
|
||||||
|
rl.With("filter", f.Name, "url", f.Url, "whitelist", whitelist, "error", err).Error("error updating filter")
|
||||||
|
if !continueOnError {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(fa) > 0 || len(fu) > 0 {
|
||||||
|
if err := replica.RefreshFilters(whitelist); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
104
pkg/sync/action.go
Normal file
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
|
||||||
|
}
|
||||||
104
pkg/sync/http.go
104
pkg/sync/http.go
@@ -2,11 +2,10 @@ package sync
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
// import embed for html page
|
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"html/template"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@@ -16,6 +15,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/log"
|
"github.com/bakito/adguardhome-sync/pkg/log"
|
||||||
|
"github.com/bakito/adguardhome-sync/version"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -25,77 +26,57 @@ var (
|
|||||||
favicon []byte
|
favicon []byte
|
||||||
)
|
)
|
||||||
|
|
||||||
func (w *worker) handleSync(rw http.ResponseWriter, req *http.Request) {
|
func (w *worker) handleSync(c *gin.Context) {
|
||||||
switch req.Method {
|
l.With("remote-addr", c.Request.RemoteAddr).Info("Starting sync from API")
|
||||||
case http.MethodPost:
|
w.sync()
|
||||||
l.With("remote-addr", req.RemoteAddr).Info("Starting sync from API")
|
|
||||||
w.sync()
|
|
||||||
default:
|
|
||||||
http.Error(rw, "only POST allowed", http.StatusBadRequest)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *worker) handleRoot(rw http.ResponseWriter, _ *http.Request) {
|
func (w *worker) handleRoot(c *gin.Context) {
|
||||||
rw.Header().Set("Content-Type", "text/html")
|
c.HTML(http.StatusOK, "index.html", map[string]interface{}{
|
||||||
_, _ = rw.Write(index)
|
"DarkMode": w.cfg.API.DarkMode,
|
||||||
|
"Version": version.Version,
|
||||||
|
"Build": version.Build,
|
||||||
|
"SyncStatus": w.status(),
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *worker) handleFavicon(rw http.ResponseWriter, _ *http.Request) {
|
func (w *worker) handleFavicon(c *gin.Context) {
|
||||||
rw.Header().Set("Content-Type", "image/x-icon")
|
c.Data(http.StatusOK, "image/x-icon", favicon)
|
||||||
_, _ = rw.Write(favicon)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *worker) handleLogs(rw http.ResponseWriter, _ *http.Request) {
|
func (w *worker) handleLogs(c *gin.Context) {
|
||||||
_, _ = rw.Write([]byte(strings.Join(log.Logs(), "")))
|
c.Data(http.StatusOK, "text/plain", []byte(strings.Join(log.Logs(), "")))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *worker) basicAuth(h http.HandlerFunc) http.HandlerFunc {
|
func (w *worker) handleStatus(c *gin.Context) {
|
||||||
return func(rw http.ResponseWriter, r *http.Request) {
|
c.JSON(http.StatusOK, w.status())
|
||||||
rw.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
|
||||||
|
|
||||||
username, password, authOK := r.BasicAuth()
|
|
||||||
if !authOK {
|
|
||||||
http.Error(rw, "Not authorized", 401)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if username != w.cfg.API.Username || password != w.cfg.API.Password {
|
|
||||||
http.Error(rw, "Not authorized", 401)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
h.ServeHTTP(rw, r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func use(h http.HandlerFunc, middleware ...func(http.HandlerFunc) http.HandlerFunc) http.HandlerFunc {
|
|
||||||
for _, m := range middleware {
|
|
||||||
h = m(h)
|
|
||||||
}
|
|
||||||
|
|
||||||
return h
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *worker) listenAndServe() {
|
func (w *worker) listenAndServe() {
|
||||||
l.With("port", w.cfg.API.Port).Info("Starting API server")
|
l.With("port", w.cfg.API.Port).Info("Starting API server")
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
mux := http.NewServeMux()
|
|
||||||
httpServer := &http.Server{
|
|
||||||
Addr: fmt.Sprintf(":%d", w.cfg.API.Port),
|
|
||||||
Handler: mux,
|
|
||||||
BaseContext: func(_ net.Listener) context.Context { return ctx },
|
|
||||||
}
|
|
||||||
|
|
||||||
var mw []func(http.HandlerFunc) http.HandlerFunc
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
r := gin.New()
|
||||||
|
r.Use(gin.Recovery())
|
||||||
if w.cfg.API.Username != "" && w.cfg.API.Password != "" {
|
if w.cfg.API.Username != "" && w.cfg.API.Password != "" {
|
||||||
mw = append(mw, w.basicAuth)
|
r.Use(gin.BasicAuth(map[string]string{w.cfg.API.Username: w.cfg.API.Password}))
|
||||||
|
}
|
||||||
|
httpServer := &http.Server{
|
||||||
|
Addr: fmt.Sprintf(":%d", w.cfg.API.Port),
|
||||||
|
Handler: r,
|
||||||
|
BaseContext: func(_ net.Listener) context.Context { return ctx },
|
||||||
|
ReadHeaderTimeout: 1 * time.Second,
|
||||||
}
|
}
|
||||||
|
|
||||||
mux.HandleFunc("/api/v1/sync", use(w.handleSync, mw...))
|
r.SetHTMLTemplate(template.Must(template.New("index.html").Parse(string(index))))
|
||||||
mux.HandleFunc("/api/v1/logs", use(w.handleLogs, mw...))
|
r.POST("/api/v1/sync", w.handleSync)
|
||||||
mux.HandleFunc("/favicon.ico", use(w.handleFavicon, mw...))
|
r.GET("/api/v1/logs", w.handleLogs)
|
||||||
mux.HandleFunc("/", use(w.handleRoot, mw...))
|
r.GET("/api/v1/status", w.handleStatus)
|
||||||
|
r.GET("/favicon.ico", w.handleFavicon)
|
||||||
|
r.GET("/", w.handleRoot)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
if err := httpServer.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
|
if err := httpServer.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
|
||||||
@@ -140,3 +121,16 @@ func (w *worker) listenAndServe() {
|
|||||||
|
|
||||||
defer os.Exit(0)
|
defer os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type syncStatus struct {
|
||||||
|
Origin replicaStatus `json:"origin"`
|
||||||
|
Replicas []replicaStatus `json:"replicas"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type replicaStatus struct {
|
||||||
|
Host string `json:"host"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
ProtectionEnabled *bool `json:"protection_enabled"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
<html>
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>AdGuardHome sync</title>
|
<title>AdGuardHome sync</title>
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css" rel="stylesheet"
|
|
||||||
integrity="sha384-KyZXEAg3QhqLMpG8r+8fhAXLRk2vvoC2f3B09zVXn8CA5QIVfZOJ3BCsw2P0p/We" crossorigin="anonymous">
|
|
||||||
<script type="text/javascript" src="https://code.jquery.com/jquery-3.6.0.min.js">
|
<script type="text/javascript" src="https://code.jquery.com/jquery-3.6.0.min.js">
|
||||||
</script>
|
</script>
|
||||||
|
{{- if .DarkMode }}
|
||||||
|
<link rel="stylesheet" href="https://bootswatch.com/5/darkly/bootstrap.min.css"
|
||||||
|
crossorigin="anonymous">
|
||||||
|
{{- else }}
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||||
|
integrity="sha384-KyZXEAg3QhqLMpG8r+8fhAXLRk2vvoC2f3B09zVXn8CA5QIVfZOJ3BCsw2P0p/We"
|
||||||
|
crossOrigin="anonymous">
|
||||||
|
{{- end }}
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
$("#showLogs").click(function () {
|
$("#showLogs").click(function () {
|
||||||
@@ -12,20 +18,35 @@
|
|||||||
$('#logs').html(data);
|
$('#logs').html(data);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
$.get("api/v1/status", {}, function (status) {
|
||||||
|
$('#origin').removeClass(function (index, className) {
|
||||||
|
return (className.match(/(^|\s)btn-\S+/g) || []).join(' ');
|
||||||
|
}).addClass("btn-" + status.origin.status).attr('title', status.origin.error);
|
||||||
|
status.replicas.forEach(function (replica, i) {
|
||||||
|
$('#replica_' + i).removeClass(function (index, className) {
|
||||||
|
return (className.match(/(^|\s)btn-\S+/g) || []).join(' ');
|
||||||
|
}).addClass("btn-" + replica.status).attr('title', replica.error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
$("#sync").click(function () {
|
$("#sync").click(function () {
|
||||||
$.post("api/v1/sync", {}, function (data) {
|
$.post("api/v1/sync", {}, function (data) {
|
||||||
});
|
});
|
||||||
|
$("#showLogs").click();
|
||||||
});
|
});
|
||||||
$("#showLogs").click()
|
$("#showLogs").click();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<link rel="shortcut icon" href="favicon.ico">
|
<link rel="shortcut icon" href="favicon.ico">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container-fluid px-4">
|
<div class="container-fluid px-4">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<h1 class="display-4">AdGuardHome sync</h1>
|
<p class="h1">
|
||||||
|
AdGuardHome sync
|
||||||
|
<p class="h6">{{ .Version }} ({{ .Build }})</p>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
@@ -34,6 +55,18 @@
|
|||||||
<input class="btn btn-secondary" type="button" id="showLogs" value="Update Logs"/>
|
<input class="btn btn-secondary" type="button" id="showLogs" value="Update Logs"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col col-md-auto">
|
||||||
|
<div class="float-right">
|
||||||
|
<a href="{{ .SyncStatus.Origin.URL }}" target="_blank" class="btn btn-{{ .SyncStatus.Origin.Status }}"
|
||||||
|
type="button" id="origin"
|
||||||
|
{{ if .SyncStatus.Origin.Error }} title="{{ .SyncStatus.Origin.Error }}" {{ end }}>Origin {{ .SyncStatus.Origin.Host }}</a>
|
||||||
|
{{ range $i, $r := .SyncStatus.Replicas }}
|
||||||
|
<a href="{{ $r.URL }}" target="_blank" class="btn btn-{{ $r.Status }}"
|
||||||
|
type="button" id="replica_{{ $i }}"
|
||||||
|
{{ if $r.Error }} title="{{ $r.Error }}" {{ end }} >Replica {{ $r.Host }}</a>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mt-3">
|
<div class="row mt-3">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
@@ -42,4 +75,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
431
pkg/sync/sync.go
431
pkg/sync/sync.go
@@ -3,18 +3,21 @@ package sync
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
"sort"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/client"
|
"github.com/bakito/adguardhome-sync/pkg/client"
|
||||||
|
"github.com/bakito/adguardhome-sync/pkg/client/model"
|
||||||
"github.com/bakito/adguardhome-sync/pkg/log"
|
"github.com/bakito/adguardhome-sync/pkg/log"
|
||||||
"github.com/bakito/adguardhome-sync/pkg/types"
|
"github.com/bakito/adguardhome-sync/pkg/types"
|
||||||
|
"github.com/bakito/adguardhome-sync/pkg/utils"
|
||||||
|
"github.com/bakito/adguardhome-sync/pkg/versions"
|
||||||
"github.com/bakito/adguardhome-sync/version"
|
"github.com/bakito/adguardhome-sync/version"
|
||||||
"github.com/robfig/cron/v3"
|
"github.com/robfig/cron/v3"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"golang.org/x/mod/semver"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const minAghVersion = "v0.107.0"
|
|
||||||
|
|
||||||
var l = log.GetLogger("sync")
|
var l = log.GetLogger("sync")
|
||||||
|
|
||||||
// Sync config from origin to replica
|
// Sync config from origin to replica
|
||||||
@@ -27,7 +30,13 @@ func Sync(cfg *types.Config) error {
|
|||||||
return fmt.Errorf("no replicas configured")
|
return fmt.Errorf("no replicas configured")
|
||||||
}
|
}
|
||||||
|
|
||||||
l.With("version", version.Version, "build", version.Build).Info("AdGuardHome sync")
|
l.With(
|
||||||
|
"version", version.Version,
|
||||||
|
"build", version.Build,
|
||||||
|
"os", runtime.GOOS,
|
||||||
|
"arch", runtime.GOARCH,
|
||||||
|
).Info("AdGuardHome sync")
|
||||||
|
cfg.Log(l)
|
||||||
cfg.Features.LogDisabled(l)
|
cfg.Features.LogDisabled(l)
|
||||||
cfg.Origin.AutoSetup = false
|
cfg.Origin.AutoSetup = false
|
||||||
|
|
||||||
@@ -40,7 +49,13 @@ func Sync(cfg *types.Config) error {
|
|||||||
if cfg.Cron != "" {
|
if cfg.Cron != "" {
|
||||||
w.cron = cron.New()
|
w.cron = cron.New()
|
||||||
cl := l.With("cron", cfg.Cron)
|
cl := l.With("cron", cfg.Cron)
|
||||||
_, err := w.cron.AddFunc(cfg.Cron, func() {
|
sched, err := cron.ParseStandard(cfg.Cron)
|
||||||
|
if err != nil {
|
||||||
|
cl.With("error", err).Error("Error parsing cron expression")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cl = cl.With("next-execution", sched.Next(time.Now()))
|
||||||
|
_, err = w.cron.AddFunc(cfg.Cron, func() {
|
||||||
w.sync()
|
w.sync()
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -75,6 +90,55 @@ type worker struct {
|
|||||||
running bool
|
running bool
|
||||||
cron *cron.Cron
|
cron *cron.Cron
|
||||||
createClient func(instance types.AdGuardInstance) (client.Client, error)
|
createClient func(instance types.AdGuardInstance) (client.Client, error)
|
||||||
|
actions []syncAction
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *worker) status() *syncStatus {
|
||||||
|
syncStatus := &syncStatus{
|
||||||
|
Origin: w.getStatus(w.cfg.Origin),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, replica := range w.cfg.Replicas {
|
||||||
|
st := w.getStatus(replica)
|
||||||
|
if w.running {
|
||||||
|
st.Status = "info"
|
||||||
|
}
|
||||||
|
syncStatus.Replicas = append(syncStatus.Replicas, st)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(syncStatus.Replicas, func(i, j int) bool {
|
||||||
|
return syncStatus.Replicas[i].Host < syncStatus.Replicas[j].Host
|
||||||
|
})
|
||||||
|
|
||||||
|
return syncStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *worker) getStatus(inst types.AdGuardInstance) (st replicaStatus) {
|
||||||
|
st = replicaStatus{Host: inst.WebHost, URL: inst.WebURL}
|
||||||
|
|
||||||
|
oc, err := w.createClient(inst)
|
||||||
|
if err != nil {
|
||||||
|
l.With("error", err, "url", w.cfg.Origin.URL).Error("Error creating origin client")
|
||||||
|
st.Status = "danger"
|
||||||
|
st.Error = err.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sl := l.With("from", inst.WebHost)
|
||||||
|
status, err := oc.Status()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, client.ErrSetupNeeded) {
|
||||||
|
st.Status = "warning"
|
||||||
|
st.Error = err.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sl.With("error", err).Error("Error getting origin status")
|
||||||
|
st.Status = "danger"
|
||||||
|
st.Error = err.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
st.Status = "success"
|
||||||
|
st.ProtectionEnabled = utils.Ptr(status.ProtectionEnabled)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *worker) sync() {
|
func (w *worker) sync() {
|
||||||
@@ -100,8 +164,16 @@ func (w *worker) sync() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if semver.Compare(o.status.Version, minAghVersion) == -1 {
|
if versions.IsNewerThan(versions.MinAgh, o.status.Version) {
|
||||||
sl.With("error", err, "version", o.status.Version).Errorf("Origin AdGuard Home version must be >= %s", minAghVersion)
|
sl.With("error", err, "version", o.status.Version).Errorf("Origin AdGuard Home version must be >= %s", versions.MinAgh)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sl.With("version", o.status.Version).Info("Connected to origin")
|
||||||
|
|
||||||
|
o.profileInfo, err = oc.ProfileInfo()
|
||||||
|
if err != nil {
|
||||||
|
sl.With("error", err).Error("Error getting profileInfo info")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +182,7 @@ func (w *worker) sync() {
|
|||||||
sl.With("error", err).Error("Error getting parental status")
|
sl.With("error", err).Error("Error getting parental status")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
o.safeSearch, err = oc.SafeSearch()
|
o.safeSearch, err = oc.SafeSearchConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sl.With("error", err).Error("Error getting safe search status")
|
sl.With("error", err).Error("Error getting safe search status")
|
||||||
return
|
return
|
||||||
@@ -127,15 +199,21 @@ func (w *worker) sync() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
o.services, err = oc.Services()
|
o.blockedServices, err = oc.BlockedServices()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sl.With("error", err).Error("Error getting origin services")
|
sl.With("error", err).Error("Error getting origin blocked services")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
o.blockedServicesSchedule, err = oc.BlockedServicesSchedule()
|
||||||
|
if err != nil {
|
||||||
|
sl.With("error", err).Error("Error getting origin blocked services schedule")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
o.filters, err = oc.Filtering()
|
o.filters, err = oc.Filtering()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sl.With("error", err).Error("Error getting origin filters")
|
sl.With("error", err).Error("Error getting origin actionFilters")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
o.clients, err = oc.Clients()
|
o.clients, err = oc.Clients()
|
||||||
@@ -166,12 +244,16 @@ func (w *worker) sync() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
o.dhcpServerConfig, err = oc.DHCPServerConfig()
|
if w.cfg.Features.DHCP.ServerConfig || w.cfg.Features.DHCP.StaticLeases {
|
||||||
if err != nil {
|
o.dhcpServerConfig, err = oc.DhcpConfig()
|
||||||
sl.With("error", err).Error("Error getting dhcp server config")
|
if err != nil {
|
||||||
return
|
sl.With("error", err).Error("Error getting dhcp server config")
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
w.actions = setupActions(w.cfg)
|
||||||
|
|
||||||
replicas := w.cfg.UniqueReplicas()
|
replicas := w.cfg.UniqueReplicas()
|
||||||
for _, replica := range replicas {
|
for _, replica := range replicas {
|
||||||
w.syncTo(sl, o, replica)
|
w.syncTo(sl, o, replica)
|
||||||
@@ -188,69 +270,44 @@ func (w *worker) syncTo(l *zap.SugaredLogger, o *origin, replica types.AdGuardIn
|
|||||||
rl := l.With("to", rc.Host())
|
rl := l.With("to", rc.Host())
|
||||||
rl.Info("Start sync")
|
rl.Info("Start sync")
|
||||||
|
|
||||||
rs, err := w.statusWithSetup(rl, replica, rc)
|
replicaStatus, err := w.statusWithSetup(rl, replica, rc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rl.With("error", err).Error("Error getting replica status")
|
rl.With("error", err).Error("Error getting replica status")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if semver.Compare(rs.Version, minAghVersion) == -1 {
|
rl.With("version", replicaStatus.Version).Info("Connected to replica")
|
||||||
rl.With("error", err, "version", rs.Version).Errorf("Replica AdGuard Home version must be >= %s", minAghVersion)
|
|
||||||
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if o.status.Version != rs.Version {
|
if o.status.Version != replicaStatus.Version {
|
||||||
rl.With("originVersion", o.status.Version, "replicaVersion", rs.Version).Warn("Versions do not match")
|
rl.With("originVersion", o.status.Version, "replicaVersion", replicaStatus.Version).Warn("Versions do not match")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = w.syncGeneralSettings(o, rs, rc)
|
ac := &actionContext{
|
||||||
if err != nil {
|
continueOnError: w.cfg.ContinueOnError,
|
||||||
rl.With("error", err).Error("Error syncing general settings")
|
rl: rl,
|
||||||
return
|
origin: o,
|
||||||
|
replicaStatus: replicaStatus,
|
||||||
|
client: rc,
|
||||||
|
replica: replica,
|
||||||
}
|
}
|
||||||
|
for _, action := range w.actions {
|
||||||
err = w.syncConfigs(o, rc)
|
if err := action.sync(ac); err != nil {
|
||||||
if err != nil {
|
rl.With("error", err).Errorf("Error syncing %s", action.name())
|
||||||
rl.With("error", err).Error("Error syncing configs")
|
if !w.cfg.ContinueOnError {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
err = w.syncRewrites(rl, o.rewrites, rc)
|
|
||||||
if err != nil {
|
|
||||||
rl.With("error", err).Error("Error syncing rewrites")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = w.syncFilters(o.filters, rc)
|
|
||||||
if err != nil {
|
|
||||||
rl.With("error", err).Error("Error syncing filters")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = w.syncServices(o.services, rc)
|
|
||||||
if err != nil {
|
|
||||||
rl.With("error", err).Error("Error syncing services")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = w.syncClients(o.clients, rc); err != nil {
|
|
||||||
rl.With("error", err).Error("Error syncing clients")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = w.syncDNS(o.accessList, o.dnsConfig, rc); err != nil {
|
|
||||||
rl.With("error", err).Error("Error syncing dns")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = w.syncDHCPServer(o.dhcpServerConfig, rc); err != nil {
|
|
||||||
rl.With("error", err).Error("Error syncing dns")
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rl.Info("Sync done")
|
rl.Info("Sync done")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *worker) statusWithSetup(rl *zap.SugaredLogger, replica types.AdGuardInstance, rc client.Client) (*types.Status, error) {
|
func (w *worker) statusWithSetup(rl *zap.SugaredLogger, replica types.AdGuardInstance, rc client.Client) (*model.ServerStatus, error) {
|
||||||
rs, err := rc.Status()
|
rs, err := rc.Status()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if replica.AutoSetup && errors.Is(err, client.ErrSetupNeeded) {
|
if replica.AutoSetup && errors.Is(err, client.ErrSetupNeeded) {
|
||||||
@@ -265,240 +322,20 @@ func (w *worker) statusWithSetup(rl *zap.SugaredLogger, replica types.AdGuardIns
|
|||||||
return rs, err
|
return rs, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *worker) syncServices(os types.Services, replica client.Client) error {
|
|
||||||
if w.cfg.Features.Services {
|
|
||||||
rs, err := replica.Services()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !os.Equals(rs) {
|
|
||||||
if err := replica.SetServices(os); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *worker) syncFilters(of *types.FilteringStatus, replica client.Client) error {
|
|
||||||
if w.cfg.Features.Filters {
|
|
||||||
rf, err := replica.Filtering()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = w.syncFilterType(of.Filters, rf.Filters, false, replica); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = w.syncFilterType(of.WhitelistFilters, rf.WhitelistFilters, true, replica); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if of.UserRules.String() != rf.UserRules.String() {
|
|
||||||
return replica.SetCustomRules(of.UserRules)
|
|
||||||
}
|
|
||||||
|
|
||||||
if of.Enabled != rf.Enabled || of.Interval != rf.Interval {
|
|
||||||
if err = replica.ToggleFiltering(of.Enabled, of.Interval); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *worker) syncFilterType(of types.Filters, rFilters types.Filters, whitelist bool, replica client.Client) error {
|
|
||||||
fa, fu, fd := rFilters.Merge(of)
|
|
||||||
|
|
||||||
if err := replica.AddFilters(whitelist, fa...); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := replica.UpdateFilters(whitelist, fu...); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(fa) > 0 || len(fu) > 0 {
|
|
||||||
if err := replica.RefreshFilters(whitelist); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := replica.DeleteFilters(whitelist, fd...); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *worker) syncRewrites(rl *zap.SugaredLogger, or *types.RewriteEntries, replica client.Client) error {
|
|
||||||
if w.cfg.Features.DNS.Rewrites {
|
|
||||||
replicaRewrites, err := replica.RewriteList()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
a, r, d := replicaRewrites.Merge(or)
|
|
||||||
|
|
||||||
if err = replica.AddRewriteEntries(a...); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = replica.DeleteRewriteEntries(r...); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, dupl := range d {
|
|
||||||
rl.With("domain", dupl.Domain, "answer", dupl.Answer).Warn("Skipping duplicated rewrite from source")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *worker) syncClients(oc *types.Clients, replica client.Client) error {
|
|
||||||
if w.cfg.Features.ClientSettings {
|
|
||||||
rc, err := replica.Clients()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
a, u, r := rc.Merge(oc)
|
|
||||||
|
|
||||||
if err = replica.AddClients(a...); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = replica.UpdateClients(u...); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = replica.DeleteClients(r...); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *worker) syncGeneralSettings(o *origin, rs *types.Status, replica client.Client) error {
|
|
||||||
if w.cfg.Features.GeneralSettings {
|
|
||||||
if o.status.ProtectionEnabled != rs.ProtectionEnabled {
|
|
||||||
if err := replica.ToggleProtection(o.status.ProtectionEnabled); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if rp, err := replica.Parental(); err != nil {
|
|
||||||
return err
|
|
||||||
} else if o.parental != rp {
|
|
||||||
if err = replica.ToggleParental(o.parental); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if rs, err := replica.SafeSearch(); err != nil {
|
|
||||||
return err
|
|
||||||
} else if o.safeSearch != rs {
|
|
||||||
if err = replica.ToggleSafeSearch(o.safeSearch); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if rs, err := replica.SafeBrowsing(); err != nil {
|
|
||||||
return err
|
|
||||||
} else if o.safeBrowsing != rs {
|
|
||||||
if err = replica.ToggleSafeBrowsing(o.safeBrowsing); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *worker) syncConfigs(o *origin, rc client.Client) error {
|
|
||||||
if w.cfg.Features.QueryLogConfig {
|
|
||||||
qlc, err := rc.QueryLogConfig()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !o.queryLogConfig.Equals(qlc) {
|
|
||||||
if err = rc.SetQueryLogConfig(o.queryLogConfig.Enabled, o.queryLogConfig.Interval, o.queryLogConfig.AnonymizeClientIP); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if w.cfg.Features.StatsConfig {
|
|
||||||
sc, err := rc.StatsConfig()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if o.statsConfig.Interval != sc.Interval {
|
|
||||||
if err = rc.SetStatsConfig(o.statsConfig.Interval); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *worker) syncDNS(oal *types.AccessList, odc *types.DNSConfig, rc client.Client) error {
|
|
||||||
if w.cfg.Features.DNS.AccessLists {
|
|
||||||
al, err := rc.AccessList()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !al.Equals(oal) {
|
|
||||||
if err = rc.SetAccessList(oal); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if w.cfg.Features.DNS.ServerConfig {
|
|
||||||
dc, err := rc.DNSConfig()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !dc.Equals(odc) {
|
|
||||||
if err = rc.SetDNSConfig(odc); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *worker) syncDHCPServer(osc *types.DHCPServerConfig, rc client.Client) error {
|
|
||||||
sc, err := rc.DHCPServerConfig()
|
|
||||||
if w.cfg.Features.DHCP.ServerConfig {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !sc.Equals(osc) {
|
|
||||||
if err = rc.SetDHCPServerConfig(osc); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if w.cfg.Features.DHCP.StaticLeases {
|
|
||||||
a, r := sc.StaticLeases.Merge(osc.StaticLeases)
|
|
||||||
|
|
||||||
if err = rc.AddDHCPStaticLeases(a...); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = rc.DeleteDHCPStaticLeases(r...); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type origin struct {
|
type origin struct {
|
||||||
status *types.Status
|
status *model.ServerStatus
|
||||||
rewrites *types.RewriteEntries
|
rewrites *model.RewriteEntries
|
||||||
services types.Services
|
blockedServices *model.BlockedServicesArray
|
||||||
filters *types.FilteringStatus
|
blockedServicesSchedule *model.BlockedServicesSchedule
|
||||||
clients *types.Clients
|
filters *model.FilterStatus
|
||||||
queryLogConfig *types.QueryLogConfig
|
clients *model.Clients
|
||||||
statsConfig *types.IntervalConfig
|
queryLogConfig *model.QueryLogConfig
|
||||||
accessList *types.AccessList
|
statsConfig *model.StatsConfig
|
||||||
dnsConfig *types.DNSConfig
|
accessList *model.AccessList
|
||||||
dhcpServerConfig *types.DHCPServerConfig
|
dnsConfig *model.DNSConfig
|
||||||
parental bool
|
dhcpServerConfig *model.DhcpStatus
|
||||||
safeSearch bool
|
parental bool
|
||||||
safeBrowsing bool
|
safeSearch *model.SafeSearchConfig
|
||||||
|
profileInfo *model.ProfileInfo
|
||||||
|
safeBrowsing bool
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,11 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/client"
|
"github.com/bakito/adguardhome-sync/pkg/client"
|
||||||
|
"github.com/bakito/adguardhome-sync/pkg/client/model"
|
||||||
clientmock "github.com/bakito/adguardhome-sync/pkg/mocks/client"
|
clientmock "github.com/bakito/adguardhome-sync/pkg/mocks/client"
|
||||||
"github.com/bakito/adguardhome-sync/pkg/types"
|
"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"
|
gm "github.com/golang/mock/gomock"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
@@ -18,6 +21,7 @@ var _ = Describe("Sync", func() {
|
|||||||
cl *clientmock.MockClient
|
cl *clientmock.MockClient
|
||||||
w *worker
|
w *worker
|
||||||
te error
|
te error
|
||||||
|
ac *actionContext
|
||||||
)
|
)
|
||||||
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
@@ -45,251 +49,290 @@ var _ = Describe("Sync", func() {
|
|||||||
StatsConfig: true,
|
StatsConfig: true,
|
||||||
QueryLogConfig: true,
|
QueryLogConfig: true,
|
||||||
},
|
},
|
||||||
|
Replicas: []types.AdGuardInstance{
|
||||||
|
{},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
te = errors.New(uuid.NewString())
|
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() {
|
AfterEach(func() {
|
||||||
defer mockCtrl.Finish()
|
defer mockCtrl.Finish()
|
||||||
})
|
})
|
||||||
|
|
||||||
Context("worker", func() {
|
Context("worker", func() {
|
||||||
Context("syncRewrites", func() {
|
Context("actionDNSRewrites", func() {
|
||||||
var (
|
var (
|
||||||
domain string
|
domain string
|
||||||
answer string
|
answer string
|
||||||
reO types.RewriteEntries
|
reO model.RewriteEntries
|
||||||
reR types.RewriteEntries
|
reR model.RewriteEntries
|
||||||
)
|
)
|
||||||
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
domain = uuid.NewString()
|
domain = uuid.NewString()
|
||||||
answer = uuid.NewString()
|
answer = uuid.NewString()
|
||||||
reO = []types.RewriteEntry{{Domain: domain, Answer: answer}}
|
reO = model.RewriteEntries{{Domain: utils.Ptr(domain), Answer: utils.Ptr(answer)}}
|
||||||
reR = []types.RewriteEntry{{Domain: domain, Answer: answer}}
|
reR = model.RewriteEntries{{Domain: utils.Ptr(domain), Answer: utils.Ptr(answer)}}
|
||||||
})
|
})
|
||||||
It("should have no changes (empty slices)", func() {
|
It("should have no changes (empty slices)", func() {
|
||||||
|
ac.origin.rewrites = &reO
|
||||||
cl.EXPECT().RewriteList().Return(&reR, nil)
|
cl.EXPECT().RewriteList().Return(&reR, nil)
|
||||||
cl.EXPECT().AddRewriteEntries()
|
cl.EXPECT().AddRewriteEntries()
|
||||||
cl.EXPECT().DeleteRewriteEntries()
|
cl.EXPECT().DeleteRewriteEntries()
|
||||||
err := w.syncRewrites(l, &reO, cl)
|
err := actionDNSRewrites(ac)
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
It("should add one rewrite entry", func() {
|
It("should add one rewrite entry", func() {
|
||||||
reR = []types.RewriteEntry{}
|
reR = []model.RewriteEntry{}
|
||||||
|
ac.origin.rewrites = &reO
|
||||||
cl.EXPECT().RewriteList().Return(&reR, nil)
|
cl.EXPECT().RewriteList().Return(&reR, nil)
|
||||||
cl.EXPECT().AddRewriteEntries(reO[0])
|
cl.EXPECT().AddRewriteEntries(reO[0])
|
||||||
cl.EXPECT().DeleteRewriteEntries()
|
cl.EXPECT().DeleteRewriteEntries()
|
||||||
err := w.syncRewrites(l, &reO, cl)
|
err := actionDNSRewrites(ac)
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
It("should remove one rewrite entry", func() {
|
It("should remove one rewrite entry", func() {
|
||||||
reO = []types.RewriteEntry{}
|
reO = []model.RewriteEntry{}
|
||||||
|
ac.origin.rewrites = &reO
|
||||||
cl.EXPECT().RewriteList().Return(&reR, nil)
|
cl.EXPECT().RewriteList().Return(&reR, nil)
|
||||||
cl.EXPECT().AddRewriteEntries()
|
cl.EXPECT().AddRewriteEntries()
|
||||||
cl.EXPECT().DeleteRewriteEntries(reR[0])
|
cl.EXPECT().DeleteRewriteEntries(reR[0])
|
||||||
err := w.syncRewrites(l, &reO, cl)
|
err := actionDNSRewrites(ac)
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
It("should remove one rewrite entry", func() {
|
It("should remove one rewrite entry", func() {
|
||||||
reO = []types.RewriteEntry{}
|
reO = []model.RewriteEntry{}
|
||||||
|
ac.origin.rewrites = &reO
|
||||||
cl.EXPECT().RewriteList().Return(&reR, nil)
|
cl.EXPECT().RewriteList().Return(&reR, nil)
|
||||||
cl.EXPECT().AddRewriteEntries()
|
cl.EXPECT().AddRewriteEntries()
|
||||||
cl.EXPECT().DeleteRewriteEntries(reR[0])
|
cl.EXPECT().DeleteRewriteEntries(reR[0])
|
||||||
err := w.syncRewrites(l, &reO, cl)
|
err := actionDNSRewrites(ac)
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
It("should return error when error on RewriteList()", func() {
|
It("should return error when error on RewriteList()", func() {
|
||||||
|
ac.origin.rewrites = &reO
|
||||||
cl.EXPECT().RewriteList().Return(nil, te)
|
cl.EXPECT().RewriteList().Return(nil, te)
|
||||||
err := w.syncRewrites(l, &reO, cl)
|
err := actionDNSRewrites(ac)
|
||||||
Ω(err).Should(HaveOccurred())
|
Ω(err).Should(HaveOccurred())
|
||||||
})
|
})
|
||||||
It("should return error when error on AddRewriteEntries()", func() {
|
It("should return error when error on AddRewriteEntries()", func() {
|
||||||
|
ac.origin.rewrites = &reO
|
||||||
cl.EXPECT().RewriteList().Return(&reR, nil)
|
cl.EXPECT().RewriteList().Return(&reR, nil)
|
||||||
|
cl.EXPECT().DeleteRewriteEntries()
|
||||||
cl.EXPECT().AddRewriteEntries().Return(te)
|
cl.EXPECT().AddRewriteEntries().Return(te)
|
||||||
err := w.syncRewrites(l, &reO, cl)
|
err := actionDNSRewrites(ac)
|
||||||
Ω(err).Should(HaveOccurred())
|
Ω(err).Should(HaveOccurred())
|
||||||
})
|
})
|
||||||
It("should return error when error on DeleteRewriteEntries()", func() {
|
It("should return error when error on DeleteRewriteEntries()", func() {
|
||||||
|
ac.origin.rewrites = &reO
|
||||||
cl.EXPECT().RewriteList().Return(&reR, nil)
|
cl.EXPECT().RewriteList().Return(&reR, nil)
|
||||||
cl.EXPECT().AddRewriteEntries()
|
|
||||||
cl.EXPECT().DeleteRewriteEntries().Return(te)
|
cl.EXPECT().DeleteRewriteEntries().Return(te)
|
||||||
err := w.syncRewrites(l, &reO, cl)
|
err := actionDNSRewrites(ac)
|
||||||
Ω(err).Should(HaveOccurred())
|
Ω(err).Should(HaveOccurred())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
Context("syncClients", func() {
|
Context("actionClientSettings", func() {
|
||||||
var (
|
var (
|
||||||
clO *types.Clients
|
clR *model.Clients
|
||||||
clR *types.Clients
|
|
||||||
name string
|
name string
|
||||||
)
|
)
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
name = uuid.NewString()
|
name = uuid.NewString()
|
||||||
clO = &types.Clients{Clients: []types.Client{{Name: name}}}
|
ac.origin.clients = &model.Clients{Clients: &model.ClientsArray{{Name: utils.Ptr(name)}}}
|
||||||
clR = &types.Clients{Clients: []types.Client{{Name: name}}}
|
clR = &model.Clients{Clients: &model.ClientsArray{{Name: utils.Ptr(name)}}}
|
||||||
})
|
})
|
||||||
It("should have no changes (empty slices)", func() {
|
It("should have no changes (empty slices)", func() {
|
||||||
cl.EXPECT().Clients().Return(clR, nil)
|
cl.EXPECT().Clients().Return(clR, nil)
|
||||||
cl.EXPECT().AddClients()
|
err := actionClientSettings(ac)
|
||||||
cl.EXPECT().UpdateClients()
|
|
||||||
cl.EXPECT().DeleteClients()
|
|
||||||
err := w.syncClients(clO, cl)
|
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
It("should add one client", func() {
|
It("should add one client", func() {
|
||||||
clR.Clients = []types.Client{}
|
clR.Clients = &model.ClientsArray{}
|
||||||
cl.EXPECT().Clients().Return(clR, nil)
|
cl.EXPECT().Clients().Return(clR, nil)
|
||||||
cl.EXPECT().AddClients(clO.Clients[0])
|
cl.EXPECT().AddClient(&(*ac.origin.clients.Clients)[0])
|
||||||
cl.EXPECT().UpdateClients()
|
err := actionClientSettings(ac)
|
||||||
cl.EXPECT().DeleteClients()
|
|
||||||
err := w.syncClients(clO, cl)
|
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
It("should update one client", func() {
|
It("should update one client", func() {
|
||||||
clR.Clients[0].Disallowed = true
|
(*clR.Clients)[0].FilteringEnabled = utils.Ptr(true)
|
||||||
cl.EXPECT().Clients().Return(clR, nil)
|
cl.EXPECT().Clients().Return(clR, nil)
|
||||||
cl.EXPECT().AddClients()
|
cl.EXPECT().UpdateClient(&(*ac.origin.clients.Clients)[0])
|
||||||
cl.EXPECT().UpdateClients(clO.Clients[0])
|
err := actionClientSettings(ac)
|
||||||
cl.EXPECT().DeleteClients()
|
|
||||||
err := w.syncClients(clO, cl)
|
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
It("should delete one client", func() {
|
It("should delete one client", func() {
|
||||||
clO.Clients = []types.Client{}
|
ac.origin.clients.Clients = &model.ClientsArray{}
|
||||||
cl.EXPECT().Clients().Return(clR, nil)
|
cl.EXPECT().Clients().Return(clR, nil)
|
||||||
cl.EXPECT().AddClients()
|
cl.EXPECT().DeleteClient(&(*clR.Clients)[0])
|
||||||
cl.EXPECT().UpdateClients()
|
err := actionClientSettings(ac)
|
||||||
cl.EXPECT().DeleteClients(clR.Clients[0])
|
|
||||||
err := w.syncClients(clO, cl)
|
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
It("should return error when error on Clients()", func() {
|
It("should return error when error on Clients()", func() {
|
||||||
cl.EXPECT().Clients().Return(nil, te)
|
cl.EXPECT().Clients().Return(nil, te)
|
||||||
err := w.syncClients(clO, cl)
|
err := actionClientSettings(ac)
|
||||||
Ω(err).Should(HaveOccurred())
|
|
||||||
})
|
|
||||||
It("should return error when error on AddClients()", func() {
|
|
||||||
cl.EXPECT().Clients().Return(clR, nil)
|
|
||||||
cl.EXPECT().AddClients().Return(te)
|
|
||||||
err := w.syncClients(clO, cl)
|
|
||||||
Ω(err).Should(HaveOccurred())
|
|
||||||
})
|
|
||||||
It("should return error when error on UpdateClients()", func() {
|
|
||||||
cl.EXPECT().Clients().Return(clR, nil)
|
|
||||||
cl.EXPECT().AddClients()
|
|
||||||
cl.EXPECT().UpdateClients().Return(te)
|
|
||||||
err := w.syncClients(clO, cl)
|
|
||||||
Ω(err).Should(HaveOccurred())
|
|
||||||
})
|
|
||||||
It("should return error when error on DeleteClients()", func() {
|
|
||||||
cl.EXPECT().Clients().Return(clR, nil)
|
|
||||||
cl.EXPECT().AddClients()
|
|
||||||
cl.EXPECT().UpdateClients()
|
|
||||||
cl.EXPECT().DeleteClients().Return(te)
|
|
||||||
err := w.syncClients(clO, cl)
|
|
||||||
Ω(err).Should(HaveOccurred())
|
Ω(err).Should(HaveOccurred())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
Context("syncGeneralSettings", func() {
|
Context("actionParental", func() {
|
||||||
var (
|
|
||||||
o *origin
|
|
||||||
rs *types.Status
|
|
||||||
)
|
|
||||||
BeforeEach(func() {
|
|
||||||
o = &origin{
|
|
||||||
status: &types.Status{},
|
|
||||||
}
|
|
||||||
rs = &types.Status{}
|
|
||||||
})
|
|
||||||
It("should have no changes", func() {
|
It("should have no changes", func() {
|
||||||
cl.EXPECT().Parental()
|
cl.EXPECT().Parental()
|
||||||
cl.EXPECT().SafeSearch()
|
err := actionParental(ac)
|
||||||
cl.EXPECT().SafeBrowsing()
|
|
||||||
err := w.syncGeneralSettings(o, rs, cl)
|
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
|
||||||
})
|
|
||||||
It("should have protection enabled changes", func() {
|
|
||||||
o.status.ProtectionEnabled = true
|
|
||||||
cl.EXPECT().ToggleProtection(true)
|
|
||||||
cl.EXPECT().Parental()
|
|
||||||
cl.EXPECT().SafeSearch()
|
|
||||||
cl.EXPECT().SafeBrowsing()
|
|
||||||
err := w.syncGeneralSettings(o, rs, cl)
|
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
It("should have parental enabled changes", func() {
|
It("should have parental enabled changes", func() {
|
||||||
o.parental = true
|
ac.origin.parental = true
|
||||||
cl.EXPECT().Parental()
|
cl.EXPECT().Parental()
|
||||||
cl.EXPECT().ToggleParental(true)
|
cl.EXPECT().ToggleParental(true)
|
||||||
cl.EXPECT().SafeSearch()
|
err := actionParental(ac)
|
||||||
cl.EXPECT().SafeBrowsing()
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
err := w.syncGeneralSettings(o, rs, cl)
|
})
|
||||||
|
})
|
||||||
|
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())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
It("should have safeSearch enabled changes", func() {
|
It("should have safeSearch enabled changes", func() {
|
||||||
o.safeSearch = true
|
ac.origin.safeSearch = &model.SafeSearchConfig{Enabled: utils.Ptr(true)}
|
||||||
cl.EXPECT().Parental()
|
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil)
|
||||||
cl.EXPECT().SafeSearch()
|
cl.EXPECT().SetSafeSearchConfig(ac.origin.safeSearch)
|
||||||
cl.EXPECT().ToggleSafeSearch(true)
|
err := actionSafeSearchConfig(ac)
|
||||||
cl.EXPECT().SafeBrowsing()
|
|
||||||
err := w.syncGeneralSettings(o, rs, cl)
|
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
|
It("should have Duckduckgo safeSearch enabled changed", func() {
|
||||||
|
ac.origin.safeSearch = &model.SafeSearchConfig{Duckduckgo: utils.Ptr(true)}
|
||||||
|
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{Google: utils.Ptr(true)}, nil)
|
||||||
|
cl.EXPECT().SetSafeSearchConfig(ac.origin.safeSearch)
|
||||||
|
err := actionSafeSearchConfig(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("actionProfileInfo", func() {
|
||||||
|
It("should have no changes", func() {
|
||||||
|
cl.EXPECT().ProfileInfo().Return(ac.origin.profileInfo, nil)
|
||||||
|
err := actionProfileInfo(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
It("should have profileInfo language changed", func() {
|
||||||
|
ac.origin.profileInfo.Language = "de"
|
||||||
|
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{Name: "replica", Language: "en"}, nil)
|
||||||
|
cl.EXPECT().SetProfileInfo(&model.ProfileInfo{
|
||||||
|
Language: "de",
|
||||||
|
Name: "replica",
|
||||||
|
Theme: "auto",
|
||||||
|
})
|
||||||
|
err := actionProfileInfo(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
It("should not sync profileInfo if language is not set", func() {
|
||||||
|
ac.origin.profileInfo.Language = ""
|
||||||
|
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{Name: "replica", Language: "en", Theme: "auto"}, nil)
|
||||||
|
cl.EXPECT().SetProfileInfo(ac.origin.profileInfo).Times(0)
|
||||||
|
err := actionProfileInfo(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
It("should not sync profileInfo if theme is not set", func() {
|
||||||
|
ac.origin.profileInfo.Theme = ""
|
||||||
|
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{Name: "replica", Language: "en", Theme: "auto"}, nil)
|
||||||
|
cl.EXPECT().SetProfileInfo(ac.origin.profileInfo).Times(0)
|
||||||
|
err := actionProfileInfo(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("actionSafeBrowsing", func() {
|
||||||
|
It("should have no changes", func() {
|
||||||
|
cl.EXPECT().SafeBrowsing()
|
||||||
|
err := actionSafeBrowsing(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
It("should have safeBrowsing enabled changes", func() {
|
It("should have safeBrowsing enabled changes", func() {
|
||||||
o.safeBrowsing = true
|
ac.origin.safeBrowsing = true
|
||||||
cl.EXPECT().Parental()
|
|
||||||
cl.EXPECT().SafeSearch()
|
|
||||||
cl.EXPECT().SafeBrowsing()
|
cl.EXPECT().SafeBrowsing()
|
||||||
cl.EXPECT().ToggleSafeBrowsing(true)
|
cl.EXPECT().ToggleSafeBrowsing(true)
|
||||||
err := w.syncGeneralSettings(o, rs, cl)
|
err := actionSafeBrowsing(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("actionQueryLogConfig", func() {
|
||||||
|
var qlc *model.QueryLogConfig
|
||||||
|
BeforeEach(func() {
|
||||||
|
qlc = &model.QueryLogConfig{}
|
||||||
|
})
|
||||||
|
It("should have no changes", func() {
|
||||||
|
cl.EXPECT().QueryLogConfig().Return(qlc, nil)
|
||||||
|
err := actionQueryLogConfig(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
It("should have QueryLogConfig changes", func() {
|
||||||
|
var interval model.QueryLogConfigInterval = 123
|
||||||
|
ac.origin.queryLogConfig.Interval = &interval
|
||||||
|
cl.EXPECT().QueryLogConfig().Return(qlc, nil)
|
||||||
|
cl.EXPECT().SetQueryLogConfig(&model.QueryLogConfig{AnonymizeClientIp: nil, Interval: &interval, Enabled: nil})
|
||||||
|
err := actionQueryLogConfig(ac)
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
Context("syncConfigs", func() {
|
Context("syncConfigs", func() {
|
||||||
var (
|
var sc *model.StatsConfig
|
||||||
o *origin
|
|
||||||
qlc *types.QueryLogConfig
|
|
||||||
sc *types.IntervalConfig
|
|
||||||
)
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
o = &origin{
|
sc = &model.StatsConfig{}
|
||||||
queryLogConfig: &types.QueryLogConfig{},
|
|
||||||
statsConfig: &types.IntervalConfig{},
|
|
||||||
}
|
|
||||||
qlc = &types.QueryLogConfig{}
|
|
||||||
sc = &types.IntervalConfig{}
|
|
||||||
})
|
})
|
||||||
It("should have no changes", func() {
|
It("should have no changes", func() {
|
||||||
cl.EXPECT().QueryLogConfig().Return(qlc, nil)
|
|
||||||
cl.EXPECT().StatsConfig().Return(sc, nil)
|
cl.EXPECT().StatsConfig().Return(sc, nil)
|
||||||
err := w.syncConfigs(o, cl)
|
err := actionStatsConfig(ac)
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
|
||||||
})
|
|
||||||
It("should have QueryLogConfig changes", func() {
|
|
||||||
o.queryLogConfig.Interval = 123
|
|
||||||
cl.EXPECT().QueryLogConfig().Return(qlc, nil)
|
|
||||||
cl.EXPECT().SetQueryLogConfig(false, 123.0, false)
|
|
||||||
cl.EXPECT().StatsConfig().Return(sc, nil)
|
|
||||||
err := w.syncConfigs(o, cl)
|
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
It("should have StatsConfig changes", func() {
|
It("should have StatsConfig changes", func() {
|
||||||
o.statsConfig.Interval = 123
|
var interval model.StatsConfigInterval = 123
|
||||||
cl.EXPECT().QueryLogConfig().Return(qlc, nil)
|
ac.origin.statsConfig.Interval = &interval
|
||||||
cl.EXPECT().StatsConfig().Return(sc, nil)
|
cl.EXPECT().StatsConfig().Return(sc, nil)
|
||||||
cl.EXPECT().SetStatsConfig(123.0)
|
cl.EXPECT().SetStatsConfig(&model.StatsConfig{Interval: &interval})
|
||||||
err := w.syncConfigs(o, cl)
|
err := actionStatsConfig(ac)
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
Context("statusWithSetup", func() {
|
Context("statusWithSetup", func() {
|
||||||
var (
|
var (
|
||||||
status *types.Status
|
status *model.ServerStatus
|
||||||
inst types.AdGuardInstance
|
inst types.AdGuardInstance
|
||||||
)
|
)
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
status = &types.Status{}
|
status = &model.ServerStatus{}
|
||||||
inst = types.AdGuardInstance{
|
inst = types.AdGuardInstance{
|
||||||
AutoSetup: true,
|
AutoSetup: true,
|
||||||
}
|
}
|
||||||
@@ -316,110 +359,199 @@ var _ = Describe("Sync", func() {
|
|||||||
Ω(st).Should(BeNil())
|
Ω(st).Should(BeNil())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
Context("syncServices", func() {
|
Context("actionBlockedServices", func() {
|
||||||
var (
|
var rbs *model.BlockedServicesArray
|
||||||
os types.Services
|
|
||||||
rs types.Services
|
|
||||||
)
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
os = []string{"foo"}
|
ac.origin.blockedServices = &model.BlockedServicesArray{"foo"}
|
||||||
rs = []string{"foo"}
|
rbs = &model.BlockedServicesArray{"foo"}
|
||||||
})
|
})
|
||||||
It("should have no changes", func() {
|
It("should have no changes", func() {
|
||||||
cl.EXPECT().Services().Return(rs, nil)
|
cl.EXPECT().BlockedServices().Return(rbs, nil)
|
||||||
err := w.syncServices(os, cl)
|
err := actionBlockedServices(ac)
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
It("should have services changes", func() {
|
It("should have blockedServices changes", func() {
|
||||||
os = []string{"bar"}
|
ac.origin.blockedServices = &model.BlockedServicesArray{"bar"}
|
||||||
cl.EXPECT().Services().Return(rs, nil)
|
|
||||||
cl.EXPECT().SetServices(os)
|
cl.EXPECT().BlockedServices().Return(rbs, nil)
|
||||||
err := w.syncServices(os, 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())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
Context("syncFilters", func() {
|
Context("syncFilters", func() {
|
||||||
var (
|
var rf *model.FilterStatus
|
||||||
of *types.FilteringStatus
|
|
||||||
rf *types.FilteringStatus
|
|
||||||
)
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
of = &types.FilteringStatus{}
|
ac.origin.filters = &model.FilterStatus{}
|
||||||
rf = &types.FilteringStatus{}
|
rf = &model.FilterStatus{}
|
||||||
})
|
})
|
||||||
It("should have no changes", func() {
|
It("should have no changes", func() {
|
||||||
cl.EXPECT().Filtering().Return(rf, nil)
|
cl.EXPECT().Filtering().Return(rf, nil)
|
||||||
cl.EXPECT().AddFilters(false)
|
err := actionFilters(ac)
|
||||||
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).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
It("should have changes user roles", func() {
|
It("should have changes user roles", func() {
|
||||||
of.UserRules = []string{"foo"}
|
ac.origin.filters.UserRules = utils.Ptr([]string{"foo"})
|
||||||
cl.EXPECT().Filtering().Return(rf, nil)
|
cl.EXPECT().Filtering().Return(rf, nil)
|
||||||
cl.EXPECT().AddFilters(false)
|
cl.EXPECT().SetCustomRules(ac.origin.filters.UserRules)
|
||||||
cl.EXPECT().UpdateFilters(false)
|
err := actionFilters(ac)
|
||||||
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)
|
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
It("should have changed filtering config", func() {
|
It("should have changed filtering config", func() {
|
||||||
of.Enabled = true
|
ac.origin.filters.Enabled = utils.Ptr(true)
|
||||||
of.Interval = 123
|
ac.origin.filters.Interval = utils.Ptr(123)
|
||||||
cl.EXPECT().Filtering().Return(rf, nil)
|
cl.EXPECT().Filtering().Return(rf, nil)
|
||||||
cl.EXPECT().AddFilters(false)
|
cl.EXPECT().ToggleFiltering(*ac.origin.filters.Enabled, *ac.origin.filters.Interval)
|
||||||
cl.EXPECT().UpdateFilters(false)
|
err := actionFilters(ac)
|
||||||
cl.EXPECT().DeleteFilters(false)
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
cl.EXPECT().AddFilters(true)
|
})
|
||||||
cl.EXPECT().UpdateFilters(true)
|
It("should add a filter", func() {
|
||||||
cl.EXPECT().DeleteFilters(true)
|
ac.origin.filters.Filters = utils.Ptr([]model.Filter{{Name: "foo", Url: "https://foo.bar"}})
|
||||||
cl.EXPECT().ToggleFiltering(of.Enabled, of.Interval)
|
cl.EXPECT().Filtering().Return(rf, nil)
|
||||||
err := w.syncFilters(of, cl)
|
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())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Context("syncDNS", func() {
|
Context("actionDNSAccessLists", func() {
|
||||||
var (
|
var ral *model.AccessList
|
||||||
oal *types.AccessList
|
|
||||||
ral *types.AccessList
|
|
||||||
odc *types.DNSConfig
|
|
||||||
rdc *types.DNSConfig
|
|
||||||
)
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
oal = &types.AccessList{}
|
ac.origin.accessList = &model.AccessList{}
|
||||||
ral = &types.AccessList{}
|
ral = &model.AccessList{}
|
||||||
odc = &types.DNSConfig{}
|
|
||||||
rdc = &types.DNSConfig{}
|
|
||||||
})
|
})
|
||||||
It("should have no changes", func() {
|
It("should have no changes", func() {
|
||||||
cl.EXPECT().AccessList().Return(ral, nil)
|
cl.EXPECT().AccessList().Return(ral, nil)
|
||||||
cl.EXPECT().DNSConfig().Return(rdc, nil)
|
err := actionDNSAccessLists(ac)
|
||||||
err := w.syncDNS(oal, odc, cl)
|
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
It("should have access list changes", func() {
|
It("should have access list changes", func() {
|
||||||
ral.BlockedHosts = []string{"foo"}
|
ral.BlockedHosts = utils.Ptr([]string{"foo"})
|
||||||
cl.EXPECT().AccessList().Return(ral, nil)
|
cl.EXPECT().AccessList().Return(ral, nil)
|
||||||
|
cl.EXPECT().SetAccessList(ac.origin.accessList)
|
||||||
|
err := actionDNSAccessLists(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("actionDNSServerConfig", func() {
|
||||||
|
var rdc *model.DNSConfig
|
||||||
|
BeforeEach(func() {
|
||||||
|
ac.origin.dnsConfig = &model.DNSConfig{}
|
||||||
|
rdc = &model.DNSConfig{}
|
||||||
|
})
|
||||||
|
It("should have no changes", func() {
|
||||||
cl.EXPECT().DNSConfig().Return(rdc, nil)
|
cl.EXPECT().DNSConfig().Return(rdc, nil)
|
||||||
cl.EXPECT().SetAccessList(oal)
|
err := actionDNSServerConfig(ac)
|
||||||
err := w.syncDNS(oal, odc, cl)
|
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
It("should have dns config changes", func() {
|
It("should have dns config changes", func() {
|
||||||
rdc.Bootstraps = []string{"foo"}
|
rdc.BootstrapDns = utils.Ptr([]string{"foo"})
|
||||||
cl.EXPECT().AccessList().Return(ral, nil)
|
|
||||||
cl.EXPECT().DNSConfig().Return(rdc, nil)
|
cl.EXPECT().DNSConfig().Return(rdc, nil)
|
||||||
cl.EXPECT().SetDNSConfig(odc)
|
cl.EXPECT().SetDNSConfig(ac.origin.dnsConfig)
|
||||||
err := w.syncDNS(oal, odc, cl)
|
err := actionDNSServerConfig(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("actionDHCPServerConfig", func() {
|
||||||
|
var rsc *model.DhcpStatus
|
||||||
|
BeforeEach(func() {
|
||||||
|
ac.origin.dhcpServerConfig = &model.DhcpStatus{V4: &model.DhcpConfigV4{
|
||||||
|
GatewayIp: utils.Ptr("1.2.3.4"),
|
||||||
|
RangeStart: utils.Ptr("1.2.3.5"),
|
||||||
|
RangeEnd: utils.Ptr("1.2.3.6"),
|
||||||
|
SubnetMask: utils.Ptr("255.255.255.0"),
|
||||||
|
}}
|
||||||
|
rsc = &model.DhcpStatus{}
|
||||||
|
w.cfg.Features.DHCP.StaticLeases = false
|
||||||
|
})
|
||||||
|
It("should have no changes", func() {
|
||||||
|
rsc.V4 = ac.origin.dhcpServerConfig.V4
|
||||||
|
cl.EXPECT().DhcpConfig().Return(rsc, nil)
|
||||||
|
err := actionDHCPServerConfig(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
It("should have changes", func() {
|
||||||
|
rsc.Enabled = utils.Ptr(true)
|
||||||
|
cl.EXPECT().DhcpConfig().Return(rsc, nil)
|
||||||
|
cl.EXPECT().SetDhcpConfig(ac.origin.dhcpServerConfig)
|
||||||
|
err := actionDHCPServerConfig(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
It("should use replica interface name", func() {
|
||||||
|
ac.replica.InterfaceName = "foo"
|
||||||
|
cl.EXPECT().DhcpConfig().Return(rsc, nil)
|
||||||
|
oscClone := ac.origin.dhcpServerConfig.Clone()
|
||||||
|
oscClone.InterfaceName = utils.Ptr("foo")
|
||||||
|
cl.EXPECT().SetDhcpConfig(oscClone)
|
||||||
|
err := actionDHCPServerConfig(ac)
|
||||||
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
It("should enable the target dhcp server", func() {
|
||||||
|
ac.replica.DHCPServerEnabled = utils.Ptr(true)
|
||||||
|
cl.EXPECT().DhcpConfig().Return(rsc, nil)
|
||||||
|
oscClone := ac.origin.dhcpServerConfig.Clone()
|
||||||
|
oscClone.Enabled = utils.Ptr(true)
|
||||||
|
cl.EXPECT().SetDhcpConfig(oscClone)
|
||||||
|
err := actionDHCPServerConfig(ac)
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -428,7 +560,7 @@ var _ = Describe("Sync", func() {
|
|||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
w.cfg = &types.Config{
|
w.cfg = &types.Config{
|
||||||
Origin: types.AdGuardInstance{},
|
Origin: types.AdGuardInstance{},
|
||||||
Replica: types.AdGuardInstance{URL: "foo"},
|
Replica: &types.AdGuardInstance{URL: "foo"},
|
||||||
Features: types.Features{
|
Features: types.Features{
|
||||||
DHCP: types.DHCP{
|
DHCP: types.DHCP{
|
||||||
ServerConfig: true,
|
ServerConfig: true,
|
||||||
@@ -451,76 +583,111 @@ var _ = Describe("Sync", func() {
|
|||||||
It("should have no changes", func() {
|
It("should have no changes", func() {
|
||||||
// origin
|
// origin
|
||||||
cl.EXPECT().Host()
|
cl.EXPECT().Host()
|
||||||
cl.EXPECT().Status().Return(&types.Status{Version: minAghVersion}, nil)
|
cl.EXPECT().Status().Return(&model.ServerStatus{Version: versions.MinAgh}, nil)
|
||||||
|
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{}, nil)
|
||||||
cl.EXPECT().Parental()
|
cl.EXPECT().Parental()
|
||||||
cl.EXPECT().SafeSearch()
|
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil)
|
||||||
cl.EXPECT().SafeBrowsing()
|
cl.EXPECT().SafeBrowsing()
|
||||||
cl.EXPECT().RewriteList().Return(&types.RewriteEntries{}, nil)
|
cl.EXPECT().RewriteList().Return(&model.RewriteEntries{}, nil)
|
||||||
cl.EXPECT().Services()
|
cl.EXPECT().BlockedServices()
|
||||||
cl.EXPECT().Filtering().Return(&types.FilteringStatus{}, nil)
|
cl.EXPECT().BlockedServicesSchedule()
|
||||||
cl.EXPECT().Clients().Return(&types.Clients{}, nil)
|
cl.EXPECT().Filtering().Return(&model.FilterStatus{}, nil)
|
||||||
cl.EXPECT().QueryLogConfig().Return(&types.QueryLogConfig{}, nil)
|
cl.EXPECT().Clients().Return(&model.Clients{}, nil)
|
||||||
cl.EXPECT().StatsConfig().Return(&types.IntervalConfig{}, nil)
|
cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfig{}, nil)
|
||||||
cl.EXPECT().AccessList().Return(&types.AccessList{}, nil)
|
cl.EXPECT().StatsConfig().Return(&model.StatsConfig{}, nil)
|
||||||
cl.EXPECT().DNSConfig().Return(&types.DNSConfig{}, nil)
|
cl.EXPECT().AccessList().Return(&model.AccessList{}, nil)
|
||||||
cl.EXPECT().DHCPServerConfig().Return(&types.DHCPServerConfig{}, nil)
|
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
|
||||||
|
cl.EXPECT().DhcpConfig().Return(&model.DhcpStatus{}, nil)
|
||||||
|
|
||||||
// replica
|
// replica
|
||||||
cl.EXPECT().Host()
|
cl.EXPECT().Host()
|
||||||
cl.EXPECT().Status().Return(&types.Status{Version: minAghVersion}, nil)
|
cl.EXPECT().Status().Return(&model.ServerStatus{Version: versions.MinAgh}, nil)
|
||||||
|
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{}, nil)
|
||||||
cl.EXPECT().Parental()
|
cl.EXPECT().Parental()
|
||||||
cl.EXPECT().SafeSearch()
|
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil)
|
||||||
cl.EXPECT().SafeBrowsing()
|
cl.EXPECT().SafeBrowsing()
|
||||||
cl.EXPECT().QueryLogConfig().Return(&types.QueryLogConfig{}, nil)
|
cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfig{}, nil)
|
||||||
cl.EXPECT().StatsConfig().Return(&types.IntervalConfig{}, nil)
|
cl.EXPECT().StatsConfig().Return(&model.StatsConfig{}, nil)
|
||||||
cl.EXPECT().RewriteList().Return(&types.RewriteEntries{}, nil)
|
cl.EXPECT().RewriteList().Return(&model.RewriteEntries{}, nil)
|
||||||
cl.EXPECT().AddRewriteEntries()
|
cl.EXPECT().AddRewriteEntries()
|
||||||
cl.EXPECT().DeleteRewriteEntries()
|
cl.EXPECT().DeleteRewriteEntries()
|
||||||
cl.EXPECT().Filtering().Return(&types.FilteringStatus{}, nil)
|
cl.EXPECT().Filtering().Return(&model.FilterStatus{}, nil)
|
||||||
cl.EXPECT().AddFilters(false)
|
cl.EXPECT().BlockedServices()
|
||||||
cl.EXPECT().UpdateFilters(false)
|
cl.EXPECT().BlockedServicesSchedule()
|
||||||
cl.EXPECT().DeleteFilters(false)
|
cl.EXPECT().Clients().Return(&model.Clients{}, nil)
|
||||||
cl.EXPECT().AddFilters(true)
|
cl.EXPECT().AccessList().Return(&model.AccessList{}, nil)
|
||||||
cl.EXPECT().UpdateFilters(true)
|
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
|
||||||
cl.EXPECT().DeleteFilters(true)
|
cl.EXPECT().DhcpConfig().Return(&model.DhcpStatus{}, nil)
|
||||||
cl.EXPECT().Services()
|
w.sync()
|
||||||
cl.EXPECT().Clients().Return(&types.Clients{}, nil)
|
})
|
||||||
cl.EXPECT().AddClients()
|
It("should not sync DHCP", func() {
|
||||||
cl.EXPECT().UpdateClients()
|
w.cfg.Features.DHCP.ServerConfig = false
|
||||||
cl.EXPECT().DeleteClients()
|
w.cfg.Features.DHCP.StaticLeases = false
|
||||||
cl.EXPECT().AccessList().Return(&types.AccessList{}, nil)
|
// origin
|
||||||
cl.EXPECT().DNSConfig().Return(&types.DNSConfig{}, nil)
|
cl.EXPECT().Host()
|
||||||
cl.EXPECT().DHCPServerConfig().Return(&types.DHCPServerConfig{}, nil)
|
cl.EXPECT().Status().Return(&model.ServerStatus{Version: versions.MinAgh}, nil)
|
||||||
cl.EXPECT().AddDHCPStaticLeases().Return(nil)
|
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{}, nil)
|
||||||
cl.EXPECT().DeleteDHCPStaticLeases().Return(nil)
|
cl.EXPECT().Parental()
|
||||||
|
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil)
|
||||||
|
cl.EXPECT().SafeBrowsing()
|
||||||
|
cl.EXPECT().RewriteList().Return(&model.RewriteEntries{}, nil)
|
||||||
|
cl.EXPECT().BlockedServices()
|
||||||
|
cl.EXPECT().BlockedServicesSchedule()
|
||||||
|
cl.EXPECT().Filtering().Return(&model.FilterStatus{}, nil)
|
||||||
|
cl.EXPECT().Clients().Return(&model.Clients{}, nil)
|
||||||
|
cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfig{}, nil)
|
||||||
|
cl.EXPECT().StatsConfig().Return(&model.StatsConfig{}, nil)
|
||||||
|
cl.EXPECT().AccessList().Return(&model.AccessList{}, nil)
|
||||||
|
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
|
||||||
|
|
||||||
|
// replica
|
||||||
|
cl.EXPECT().Host()
|
||||||
|
cl.EXPECT().Status().Return(&model.ServerStatus{Version: versions.MinAgh}, nil)
|
||||||
|
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{}, nil)
|
||||||
|
cl.EXPECT().Parental()
|
||||||
|
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil)
|
||||||
|
cl.EXPECT().SafeBrowsing()
|
||||||
|
cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfig{}, nil)
|
||||||
|
cl.EXPECT().StatsConfig().Return(&model.StatsConfig{}, nil)
|
||||||
|
cl.EXPECT().RewriteList().Return(&model.RewriteEntries{}, nil)
|
||||||
|
cl.EXPECT().AddRewriteEntries()
|
||||||
|
cl.EXPECT().DeleteRewriteEntries()
|
||||||
|
cl.EXPECT().Filtering().Return(&model.FilterStatus{}, nil)
|
||||||
|
cl.EXPECT().BlockedServices()
|
||||||
|
cl.EXPECT().BlockedServicesSchedule()
|
||||||
|
cl.EXPECT().Clients().Return(&model.Clients{}, nil)
|
||||||
|
cl.EXPECT().AccessList().Return(&model.AccessList{}, nil)
|
||||||
|
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
|
||||||
w.sync()
|
w.sync()
|
||||||
})
|
})
|
||||||
It("origin version is too small", func() {
|
It("origin version is too small", func() {
|
||||||
// origin
|
// origin
|
||||||
cl.EXPECT().Host()
|
cl.EXPECT().Host()
|
||||||
cl.EXPECT().Status().Return(&types.Status{Version: "v0.106.9"}, nil)
|
cl.EXPECT().Status().Return(&model.ServerStatus{Version: "v0.106.9"}, nil)
|
||||||
w.sync()
|
w.sync()
|
||||||
})
|
})
|
||||||
It("replica version is too small", func() {
|
It("replica version is too small", func() {
|
||||||
// origin
|
// origin
|
||||||
cl.EXPECT().Host()
|
cl.EXPECT().Host()
|
||||||
cl.EXPECT().Status().Return(&types.Status{Version: minAghVersion}, nil)
|
cl.EXPECT().Status().Return(&model.ServerStatus{Version: versions.MinAgh}, nil)
|
||||||
|
cl.EXPECT().ProfileInfo().Return(&model.ProfileInfo{}, nil)
|
||||||
cl.EXPECT().Parental()
|
cl.EXPECT().Parental()
|
||||||
cl.EXPECT().SafeSearch()
|
cl.EXPECT().SafeSearchConfig().Return(&model.SafeSearchConfig{}, nil)
|
||||||
cl.EXPECT().SafeBrowsing()
|
cl.EXPECT().SafeBrowsing()
|
||||||
cl.EXPECT().RewriteList().Return(&types.RewriteEntries{}, nil)
|
cl.EXPECT().RewriteList().Return(&model.RewriteEntries{}, nil)
|
||||||
cl.EXPECT().Services()
|
cl.EXPECT().BlockedServices()
|
||||||
cl.EXPECT().Filtering().Return(&types.FilteringStatus{}, nil)
|
cl.EXPECT().BlockedServicesSchedule()
|
||||||
cl.EXPECT().Clients().Return(&types.Clients{}, nil)
|
cl.EXPECT().Filtering().Return(&model.FilterStatus{}, nil)
|
||||||
cl.EXPECT().QueryLogConfig().Return(&types.QueryLogConfig{}, nil)
|
cl.EXPECT().Clients().Return(&model.Clients{}, nil)
|
||||||
cl.EXPECT().StatsConfig().Return(&types.IntervalConfig{}, nil)
|
cl.EXPECT().QueryLogConfig().Return(&model.QueryLogConfig{}, nil)
|
||||||
cl.EXPECT().AccessList().Return(&types.AccessList{}, nil)
|
cl.EXPECT().StatsConfig().Return(&model.StatsConfig{}, nil)
|
||||||
cl.EXPECT().DNSConfig().Return(&types.DNSConfig{}, nil)
|
cl.EXPECT().AccessList().Return(&model.AccessList{}, nil)
|
||||||
cl.EXPECT().DHCPServerConfig().Return(&types.DHCPServerConfig{}, nil)
|
cl.EXPECT().DNSConfig().Return(&model.DNSConfig{}, nil)
|
||||||
|
cl.EXPECT().DhcpConfig().Return(&model.DhcpStatus{}, nil)
|
||||||
|
|
||||||
// replica
|
// replica
|
||||||
cl.EXPECT().Host()
|
cl.EXPECT().Host()
|
||||||
cl.EXPECT().Status().Return(&types.Status{Version: "v0.106.9"}, nil)
|
cl.EXPECT().Status().Return(&model.ServerStatus{Version: "v0.106.9"}, nil)
|
||||||
w.sync()
|
w.sync()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
58
pkg/types/deepcopy_generated.go
Normal file
58
pkg/types/deepcopy_generated.go
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
//go:build !ignore_autogenerated
|
||||||
|
// +build !ignore_autogenerated
|
||||||
|
|
||||||
|
// Code generated by deepcopy-gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package types
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *AdGuardInstance) DeepCopyInto(out *AdGuardInstance) {
|
||||||
|
*out = *in
|
||||||
|
if in.DHCPServerEnabled != nil {
|
||||||
|
in, out := &in.DHCPServerEnabled, &out.DHCPServerEnabled
|
||||||
|
*out = new(bool)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdGuardInstance.
|
||||||
|
func (in *AdGuardInstance) DeepCopy() *AdGuardInstance {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(AdGuardInstance)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *Config) DeepCopyInto(out *Config) {
|
||||||
|
*out = *in
|
||||||
|
in.Origin.DeepCopyInto(&out.Origin)
|
||||||
|
if in.Replica != nil {
|
||||||
|
in, out := &in.Replica, &out.Replica
|
||||||
|
*out = new(AdGuardInstance)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
if in.Replicas != nil {
|
||||||
|
in, out := &in.Replicas, &out.Replicas
|
||||||
|
*out = make([]AdGuardInstance, len(*in))
|
||||||
|
for i := range *in {
|
||||||
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.API = in.API
|
||||||
|
out.Features = in.Features
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Config.
|
||||||
|
func (in *Config) DeepCopy() *Config {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(Config)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DHCPServerConfig dhcp server config
|
|
||||||
type DHCPServerConfig struct {
|
|
||||||
V4 *V4ServerConfJSON `json:"v4"`
|
|
||||||
V6 *V6ServerConfJSON `json:"v6"`
|
|
||||||
InterfaceName string `json:"interface_name"`
|
|
||||||
Enabled bool `json:"enabled"`
|
|
||||||
|
|
||||||
Leases Leases `json:"leases,omitempty"`
|
|
||||||
StaticLeases Leases `json:"static_leases,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equals dhcp server config equal check
|
|
||||||
func (c *DHCPServerConfig) Equals(o *DHCPServerConfig) bool {
|
|
||||||
a, _ := json.Marshal(c)
|
|
||||||
b, _ := json.Marshal(o)
|
|
||||||
return string(a) == string(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// V4ServerConfJSON v4 server conf
|
|
||||||
type V4ServerConfJSON struct {
|
|
||||||
GatewayIP net.IP `json:"gateway_ip"`
|
|
||||||
SubnetMask net.IP `json:"subnet_mask"`
|
|
||||||
RangeStart net.IP `json:"range_start"`
|
|
||||||
RangeEnd net.IP `json:"range_end"`
|
|
||||||
LeaseDuration uint32 `json:"lease_duration"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// V6ServerConfJSON v6 server conf
|
|
||||||
type V6ServerConfJSON struct {
|
|
||||||
RangeStart net.IP `json:"range_start"`
|
|
||||||
RangeEnd net.IP `json:"range_end"`
|
|
||||||
LeaseDuration uint32 `json:"lease_duration"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Leases slice of leases type
|
|
||||||
type Leases []Lease
|
|
||||||
|
|
||||||
// Merge the leases
|
|
||||||
func (l Leases) Merge(other Leases) ([]Lease, []Lease) {
|
|
||||||
current := make(map[string]Lease)
|
|
||||||
|
|
||||||
var adds Leases
|
|
||||||
var removes Leases
|
|
||||||
for _, le := range l {
|
|
||||||
current[le.HWAddr] = le
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, le := range other {
|
|
||||||
if _, ok := current[le.HWAddr]; ok {
|
|
||||||
delete(current, le.HWAddr)
|
|
||||||
} else {
|
|
||||||
adds = append(adds, le)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, rr := range current {
|
|
||||||
removes = append(removes, rr)
|
|
||||||
}
|
|
||||||
|
|
||||||
return adds, removes
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lease contains the necessary information about a DHCP lease
|
|
||||||
type Lease struct {
|
|
||||||
HWAddr string `json:"mac"`
|
|
||||||
IP net.IP `json:"ip"`
|
|
||||||
Hostname string `json:"hostname"`
|
|
||||||
|
|
||||||
// Lease expiration time
|
|
||||||
// 1: static lease
|
|
||||||
Expiry time.Time `json:"expires"`
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"net"
|
|
||||||
"sort"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DNSConfig dns config
|
|
||||||
type DNSConfig struct {
|
|
||||||
Upstreams []string `json:"upstream_dns,omitempty"`
|
|
||||||
UpstreamsFile string `json:"upstream_dns_file"`
|
|
||||||
Bootstraps []string `json:"bootstrap_dns,omitempty"`
|
|
||||||
|
|
||||||
ProtectionEnabled bool `json:"protection_enabled"`
|
|
||||||
RateLimit uint32 `json:"ratelimit"`
|
|
||||||
BlockingMode string `json:"blocking_mode,omitempty"`
|
|
||||||
BlockingIPv4 net.IP `json:"blocking_ipv4,omitempty"`
|
|
||||||
BlockingIPv6 net.IP `json:"blocking_ipv6"`
|
|
||||||
EDNSCSEnabled bool `json:"edns_cs_enabled"`
|
|
||||||
DNSSECEnabled bool `json:"dnssec_enabled"`
|
|
||||||
DisableIPv6 bool `json:"disable_ipv6"`
|
|
||||||
UpstreamMode string `json:"upstream_mode,omitempty"`
|
|
||||||
CacheSize uint32 `json:"cache_size"`
|
|
||||||
CacheMinTTL uint32 `json:"cache_ttl_min"`
|
|
||||||
CacheMaxTTL uint32 `json:"cache_ttl_max"`
|
|
||||||
ResolveClients bool `json:"resolve_clients"`
|
|
||||||
LocalPTRUpstreams []string `json:"local_ptr_upstreams,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equals dns config equal check
|
|
||||||
func (c *DNSConfig) Equals(o *DNSConfig) bool {
|
|
||||||
c.Sort()
|
|
||||||
o.Sort()
|
|
||||||
|
|
||||||
a, _ := json.Marshal(c)
|
|
||||||
b, _ := json.Marshal(o)
|
|
||||||
return string(a) == string(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort sort dns config
|
|
||||||
func (c *DNSConfig) Sort() {
|
|
||||||
sort.Strings(c.Upstreams)
|
|
||||||
sort.Strings(c.Bootstraps)
|
|
||||||
sort.Strings(c.LocalPTRUpstreams)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AccessList access list
|
|
||||||
type AccessList struct {
|
|
||||||
AllowedClients []string `json:"allowed_clients"`
|
|
||||||
DisallowedClients []string `json:"disallowed_clients"`
|
|
||||||
BlockedHosts []string `json:"blocked_hosts"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equals access list equal check
|
|
||||||
func (al *AccessList) Equals(o *AccessList) bool {
|
|
||||||
return equals(al.AllowedClients, o.AllowedClients) &&
|
|
||||||
equals(al.DisallowedClients, o.DisallowedClients) &&
|
|
||||||
equals(al.BlockedHosts, o.BlockedHosts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort sort access list
|
|
||||||
func (al *AccessList) Sort() {
|
|
||||||
sort.Strings(al.AllowedClients)
|
|
||||||
sort.Strings(al.DisallowedClients)
|
|
||||||
sort.Strings(al.BlockedHosts)
|
|
||||||
}
|
|
||||||
@@ -6,26 +6,26 @@ import (
|
|||||||
|
|
||||||
// Features feature flags
|
// Features feature flags
|
||||||
type Features struct {
|
type Features struct {
|
||||||
DNS DNS `json:"dns" yaml:"dns"`
|
DNS DNS `json:"dns" yaml:"dns" mapstructure:"DNS"`
|
||||||
DHCP DHCP `json:"dhcp" yaml:"dhcp"`
|
DHCP DHCP `json:"dhcp" yaml:"dhcp" mapstructure:"DHCP"`
|
||||||
GeneralSettings bool `json:"generalSettings" yaml:"generalSettings"`
|
GeneralSettings bool `json:"generalSettings" yaml:"generalSettings" mapstructure:"GENERAL_SETTINGS"`
|
||||||
QueryLogConfig bool `json:"queryLogConfig" yaml:"queryLogConfig"`
|
QueryLogConfig bool `json:"queryLogConfig" yaml:"queryLogConfig" mapstructure:"QUERY_LOG_CONFIG"`
|
||||||
StatsConfig bool `json:"statsConfig" yaml:"statsConfig"`
|
StatsConfig bool `json:"statsConfig" yaml:"statsConfig" mapstructure:"STATS_CONFIG"`
|
||||||
ClientSettings bool `json:"clientSettings" yaml:"clientSettings"`
|
ClientSettings bool `json:"clientSettings" yaml:"clientSettings" mapstructure:"CLIENT_SETTINGS"`
|
||||||
Services bool `json:"services" yaml:"services"`
|
Services bool `json:"services" yaml:"services" mapstructure:"SERVICES"`
|
||||||
Filters bool `json:"filters" yaml:"filters"`
|
Filters bool `json:"filters" yaml:"filters" mapstructure:"FILTERS"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DHCP features
|
// DHCP features
|
||||||
type DHCP struct {
|
type DHCP struct {
|
||||||
ServerConfig bool `json:"serverConfig" yaml:"serverConfig"`
|
ServerConfig bool `json:"serverConfig" yaml:"serverConfig" mapstructure:"SERVER_CONFIG"`
|
||||||
StaticLeases bool `json:"staticLeases" yaml:"staticLeases"`
|
StaticLeases bool `json:"staticLeases" yaml:"staticLeases" mapstructure:"STATIC_LEASES"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DNS features
|
// DNS features
|
||||||
type DNS struct {
|
type DNS struct {
|
||||||
AccessLists bool `json:"accessLists" yaml:"accessLists"`
|
AccessLists bool `json:"accessLists" yaml:"accessLists" mapstructure:"ACCESS_LISTS"`
|
||||||
ServerConfig bool `json:"serverConfig" yaml:"serverConfig"`
|
ServerConfig bool `json:"serverConfig" yaml:"serverConfig" mapstructure:"SERVER_CONFIG"`
|
||||||
Rewrites bool `json:"rewrites" yaml:"rewrites"`
|
Rewrites bool `json:"rewrites" yaml:"rewrites"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,13 +39,13 @@ func (f *Features) LogDisabled(l *zap.SugaredLogger) {
|
|||||||
features = append(features, "DHCP.StaticLeases")
|
features = append(features, "DHCP.StaticLeases")
|
||||||
}
|
}
|
||||||
if !f.DNS.AccessLists {
|
if !f.DNS.AccessLists {
|
||||||
features = append(features, "DHCP.AccessLists")
|
features = append(features, "DNS.AccessLists")
|
||||||
}
|
}
|
||||||
if !f.DNS.ServerConfig {
|
if !f.DNS.ServerConfig {
|
||||||
features = append(features, "DHCP.ServerConfig")
|
features = append(features, "DNS.ServerConfig")
|
||||||
}
|
}
|
||||||
if !f.DNS.Rewrites {
|
if !f.DNS.Rewrites {
|
||||||
features = append(features, "DHCP.Rewrites")
|
features = append(features, "DNS.Rewrites")
|
||||||
}
|
}
|
||||||
if !f.GeneralSettings {
|
if !f.GeneralSettings {
|
||||||
features = append(features, "GeneralSettings")
|
features = append(features, "GeneralSettings")
|
||||||
@@ -60,7 +60,7 @@ func (f *Features) LogDisabled(l *zap.SugaredLogger) {
|
|||||||
features = append(features, "ClientSettings")
|
features = append(features, "ClientSettings")
|
||||||
}
|
}
|
||||||
if !f.Services {
|
if !f.Services {
|
||||||
features = append(features, "Services")
|
features = append(features, "BlockedServices")
|
||||||
}
|
}
|
||||||
if !f.Filters {
|
if !f.Filters {
|
||||||
features = append(features, "Filters")
|
features = append(features, "Filters")
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"net/url"
|
||||||
"strings"
|
|
||||||
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -13,28 +13,32 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Config application configuration struct
|
// Config application configuration struct
|
||||||
|
// +k8s:deepcopy-gen=true
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Origin AdGuardInstance `json:"origin" yaml:"origin"`
|
Origin AdGuardInstance `json:"origin" yaml:"origin" mapstructure:"ORIGIN"`
|
||||||
Replica AdGuardInstance `json:"replica,omitempty" yaml:"replica,omitempty"`
|
Replica *AdGuardInstance `json:"replica,omitempty" yaml:"replica,omitempty" mapstructure:"REPLICA"`
|
||||||
Replicas []AdGuardInstance `json:"replicas,omitempty" yaml:"replicas,omitempty"`
|
Replicas []AdGuardInstance `json:"replicas,omitempty" yaml:"replicas,omitempty"`
|
||||||
Cron string `json:"cron,omitempty" yaml:"cron,omitempty"`
|
Cron string `json:"cron,omitempty" yaml:"cron,omitempty" mapstructure:"CRON"`
|
||||||
RunOnStart bool `json:"runOnStart,omitempty" yaml:"runOnStart,omitempty"`
|
RunOnStart bool `json:"runOnStart,omitempty" yaml:"runOnStart,omitempty" mapstructure:"RUN_ON_START"`
|
||||||
API API `json:"api,omitempty" yaml:"api,omitempty"`
|
PrintConfigOnly bool `json:"printConfigOnly,omitempty" yaml:"printConfigOnly,omitempty" mapstructure:"PRINT_CONFIG_ONLY"`
|
||||||
Features Features `json:"features,omitempty" yaml:"features,omitempty"`
|
ContinueOnError bool `json:"continueOnError,omitempty" yaml:"continueOnError,omitempty" mapstructure:"CONTINUE_ON_ERROR"`
|
||||||
|
API API `json:"api,omitempty" yaml:"api,omitempty" mapstructure:"API"`
|
||||||
|
Features Features `json:"features,omitempty" yaml:"features,omitempty" mapstructure:"FEATURES"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// API configuration
|
// API configuration
|
||||||
type API struct {
|
type API struct {
|
||||||
Port int `json:"port,omitempty" yaml:"port,omitempty"`
|
Port int `json:"port,omitempty" yaml:"port,omitempty" mapstructure:"PORT"`
|
||||||
Username string `json:"username,omitempty" yaml:"username,omitempty"`
|
Username string `json:"username,omitempty" yaml:"username,omitempty" mapstructure:"USERNAME"`
|
||||||
Password string `json:"password,omitempty" yaml:"password,omitempty"`
|
Password string `json:"password,omitempty" yaml:"password,omitempty" mapstructure:"PASSWORD"`
|
||||||
|
DarkMode bool `json:"darkMode,omitempty" yaml:"darkMode,omitempty" mapstructure:"DARK_MODE"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UniqueReplicas get unique replication instances
|
// UniqueReplicas get unique replication instances
|
||||||
func (cfg *Config) UniqueReplicas() []AdGuardInstance {
|
func (cfg *Config) UniqueReplicas() []AdGuardInstance {
|
||||||
dedup := make(map[string]AdGuardInstance)
|
dedup := make(map[string]AdGuardInstance)
|
||||||
if cfg.Replica.URL != "" {
|
if cfg.Replica != nil && cfg.Replica.URL != "" {
|
||||||
dedup[cfg.Replica.Key()] = cfg.Replica
|
dedup[cfg.Replica.Key()] = *cfg.Replica
|
||||||
}
|
}
|
||||||
for _, replica := range cfg.Replicas {
|
for _, replica := range cfg.Replicas {
|
||||||
if replica.URL != "" {
|
if replica.URL != "" {
|
||||||
@@ -52,14 +56,52 @@ func (cfg *Config) UniqueReplicas() []AdGuardInstance {
|
|||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Log the current config
|
||||||
|
func (cfg *Config) Log(l *zap.SugaredLogger) {
|
||||||
|
c := cfg.DeepCopy()
|
||||||
|
c.Origin.Mask()
|
||||||
|
if c.Replica != nil {
|
||||||
|
if c.Replica.URL == "" {
|
||||||
|
c.Replica = nil
|
||||||
|
} else {
|
||||||
|
c.Replica.Mask()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := range c.Replicas {
|
||||||
|
c.Replicas[i].Mask()
|
||||||
|
}
|
||||||
|
l.With("config", c).Debug("Using config")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *Config) Init() error {
|
||||||
|
if err := cfg.Origin.Init(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for i := range cfg.Replicas {
|
||||||
|
replica := &cfg.Replicas[i]
|
||||||
|
if err := replica.Init(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// AdGuardInstance AdguardHome config instance
|
// AdGuardInstance AdguardHome config instance
|
||||||
|
// +k8s:deepcopy-gen=true
|
||||||
type AdGuardInstance struct {
|
type AdGuardInstance struct {
|
||||||
URL string `json:"url" yaml:"url"`
|
URL string `json:"url" yaml:"url" mapstructure:"URL"`
|
||||||
APIPath string `json:"apiPath,omitempty" yaml:"apiPath,omitempty"`
|
WebURL string `json:"webURL" yaml:"webURL" mapstructure:"WEB_URL"`
|
||||||
Username string `json:"username,omitempty" yaml:"username,omitempty"`
|
APIPath string `json:"apiPath,omitempty" yaml:"apiPath,omitempty" mapstructure:"API_PATH"`
|
||||||
Password string `json:"password,omitempty" yaml:"password,omitempty"`
|
Username string `json:"username,omitempty" yaml:"username,omitempty" mapstructure:"USERNAME"`
|
||||||
InsecureSkipVerify bool `json:"insecureSkipVerify" yaml:"insecureSkipVerify"`
|
Password string `json:"password,omitempty" yaml:"password,omitempty" mapstructure:"PASSWORD"`
|
||||||
AutoSetup bool `json:"autoSetup" yaml:"autoSetup"`
|
Cookie string `json:"cookie,omitempty" yaml:"cookie,omitempty" mapstructure:"COOKIE"`
|
||||||
|
InsecureSkipVerify bool `json:"insecureSkipVerify" yaml:"insecureSkipVerify" mapstructure:"INSECURE_SKIP_VERIFY"`
|
||||||
|
AutoSetup bool `json:"autoSetup" yaml:"autoSetup" mapstructure:"AUTO_SETUP"`
|
||||||
|
InterfaceName string `json:"interfaceName,omitempty" yaml:"interfaceName,omitempty" mapstructure:"INTERFACE_NAME"`
|
||||||
|
DHCPServerEnabled *bool `json:"dhcpServerEnabled,omitempty" yaml:"dhcpServerEnabled,omitempty" mapstructure:"DHCP_SERVER_ENABLED"`
|
||||||
|
|
||||||
|
Host string `json:"-" yaml:"-"`
|
||||||
|
WebHost string `json:"-" yaml:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Key AdGuardInstance key
|
// Key AdGuardInstance key
|
||||||
@@ -67,295 +109,44 @@ func (i *AdGuardInstance) Key() string {
|
|||||||
return fmt.Sprintf("%s#%s", i.URL, i.APIPath)
|
return fmt.Sprintf("%s#%s", i.URL, i.APIPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mask maks username and password
|
||||||
|
func (i *AdGuardInstance) Mask() {
|
||||||
|
i.Username = mask(i.Username)
|
||||||
|
i.Password = mask(i.Password)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *AdGuardInstance) Init() error {
|
||||||
|
u, err := url.Parse(i.URL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i.Host = u.Host
|
||||||
|
|
||||||
|
if i.WebURL == "" {
|
||||||
|
i.WebHost = i.Host
|
||||||
|
i.WebURL = i.URL
|
||||||
|
} else {
|
||||||
|
u, err := url.Parse(i.WebURL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i.WebHost = u.Host
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mask(s string) string {
|
||||||
|
if s == "" {
|
||||||
|
return "***"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%v***%v", string(s[0]), string(s[len(s)-1]))
|
||||||
|
}
|
||||||
|
|
||||||
// Protection API struct
|
// Protection API struct
|
||||||
type Protection struct {
|
type Protection struct {
|
||||||
ProtectionEnabled bool `json:"protection_enabled"`
|
ProtectionEnabled bool `json:"protection_enabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Status API struct
|
|
||||||
type Status struct {
|
|
||||||
Protection
|
|
||||||
DNSAddresses []string `json:"dns_addresses"`
|
|
||||||
DNSPort int `json:"dns_port"`
|
|
||||||
HTTPPort int `json:"http_port"`
|
|
||||||
DhcpAvailable bool `json:"dhcp_available"`
|
|
||||||
Running bool `json:"running"`
|
|
||||||
Version string `json:"version"`
|
|
||||||
Language string `json:"language"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// RewriteEntries list of RewriteEntry
|
|
||||||
type RewriteEntries []RewriteEntry
|
|
||||||
|
|
||||||
// Merge RewriteEntries
|
|
||||||
func (rwe *RewriteEntries) Merge(other *RewriteEntries) (RewriteEntries, RewriteEntries, RewriteEntries) {
|
|
||||||
current := make(map[string]RewriteEntry)
|
|
||||||
|
|
||||||
var adds RewriteEntries
|
|
||||||
var removes RewriteEntries
|
|
||||||
var duplicates RewriteEntries
|
|
||||||
processed := make(map[string]bool)
|
|
||||||
for _, rr := range *rwe {
|
|
||||||
if _, ok := processed[rr.Key()]; !ok {
|
|
||||||
current[rr.Key()] = rr
|
|
||||||
processed[rr.Key()] = true
|
|
||||||
} else {
|
|
||||||
// remove duplicate
|
|
||||||
removes = append(removes, rr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, rr := range *other {
|
|
||||||
if _, ok := current[rr.Key()]; ok {
|
|
||||||
delete(current, rr.Key())
|
|
||||||
} else {
|
|
||||||
if _, ok := processed[rr.Key()]; !ok {
|
|
||||||
adds = append(adds, rr)
|
|
||||||
processed[rr.Key()] = true
|
|
||||||
} else {
|
|
||||||
// skip duplicate
|
|
||||||
duplicates = append(duplicates, rr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, rr := range current {
|
|
||||||
removes = append(removes, rr)
|
|
||||||
}
|
|
||||||
|
|
||||||
return adds, removes, duplicates
|
|
||||||
}
|
|
||||||
|
|
||||||
// RewriteEntry API struct
|
|
||||||
type RewriteEntry struct {
|
|
||||||
Domain string `json:"domain"`
|
|
||||||
Answer string `json:"answer"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Key RewriteEntry key
|
|
||||||
func (re *RewriteEntry) Key() string {
|
|
||||||
return fmt.Sprintf("%s#%s", re.Domain, re.Answer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filters list of Filter
|
|
||||||
type Filters []Filter
|
|
||||||
|
|
||||||
// Merge merge Filters
|
|
||||||
func (f Filters) Merge(other Filters) (Filters, Filters, Filters) {
|
|
||||||
current := make(map[string]Filter)
|
|
||||||
|
|
||||||
var adds Filters
|
|
||||||
var updates Filters
|
|
||||||
var removes Filters
|
|
||||||
for _, f := range f {
|
|
||||||
current[f.URL] = f
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range other {
|
|
||||||
rr := other[i]
|
|
||||||
if c, ok := current[rr.URL]; ok {
|
|
||||||
if !c.Equals(&rr) {
|
|
||||||
updates = append(updates, rr)
|
|
||||||
}
|
|
||||||
delete(current, rr.URL)
|
|
||||||
} else {
|
|
||||||
adds = append(adds, rr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, rr := range current {
|
|
||||||
removes = append(removes, rr)
|
|
||||||
}
|
|
||||||
|
|
||||||
return adds, updates, removes
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter API struct
|
|
||||||
type Filter struct {
|
|
||||||
ID int `json:"id"`
|
|
||||||
Enabled bool `json:"enabled"`
|
|
||||||
URL string `json:"url"` // needed for add
|
|
||||||
Name string `json:"name"` // needed for add
|
|
||||||
RulesCount int `json:"rules_count"`
|
|
||||||
Whitelist bool `json:"whitelist"` // needed for add
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equals Filter equal check
|
|
||||||
func (f *Filter) Equals(o *Filter) bool {
|
|
||||||
return f.Enabled == o.Enabled && f.URL == o.URL && f.Name == o.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// FilterUpdate API struct
|
|
||||||
type FilterUpdate struct {
|
|
||||||
URL string `json:"url"`
|
|
||||||
Data Filter `json:"data"`
|
|
||||||
Whitelist bool `json:"whitelist"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// FilteringStatus API struct
|
|
||||||
type FilteringStatus struct {
|
|
||||||
FilteringConfig
|
|
||||||
Filters Filters `json:"filters"`
|
|
||||||
WhitelistFilters Filters `json:"whitelist_filters"`
|
|
||||||
UserRules UserRules `json:"user_rules"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// UserRules API struct
|
|
||||||
type UserRules []string
|
|
||||||
|
|
||||||
// String toString of Users
|
|
||||||
func (ur UserRules) String() string {
|
|
||||||
return strings.Join(ur, "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
// EnableConfig API struct
|
|
||||||
type EnableConfig struct {
|
|
||||||
Enabled bool `json:"enabled"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// IntervalConfig API struct
|
|
||||||
type IntervalConfig struct {
|
|
||||||
Interval float64 `json:"interval"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// FilteringConfig API struct
|
|
||||||
type FilteringConfig struct {
|
|
||||||
EnableConfig
|
|
||||||
IntervalConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryLogConfig API struct
|
|
||||||
type QueryLogConfig struct {
|
|
||||||
EnableConfig
|
|
||||||
IntervalConfig
|
|
||||||
AnonymizeClientIP bool `json:"anonymize_client_ip"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equals QueryLogConfig equal check
|
|
||||||
func (qlc *QueryLogConfig) Equals(o *QueryLogConfig) bool {
|
|
||||||
return qlc.Enabled == o.Enabled && qlc.AnonymizeClientIP == o.AnonymizeClientIP && qlc.Interval == o.Interval
|
|
||||||
}
|
|
||||||
|
|
||||||
// RefreshFilter API struct
|
|
||||||
type RefreshFilter struct {
|
|
||||||
Whitelist bool `json:"whitelist"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Services API struct
|
|
||||||
type Services []string
|
|
||||||
|
|
||||||
// Sort sort Services
|
|
||||||
func (s Services) Sort() {
|
|
||||||
sort.Strings(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equals Services equal check
|
|
||||||
func (s Services) Equals(o Services) bool {
|
|
||||||
s.Sort()
|
|
||||||
o.Sort()
|
|
||||||
return equals(s, o)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clients API struct
|
|
||||||
type Clients struct {
|
|
||||||
Clients []Client `json:"clients"`
|
|
||||||
AutoClients []struct {
|
|
||||||
IP string `json:"ip"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Source string `json:"source"`
|
|
||||||
WhoisInfo struct{} `json:"whois_info"`
|
|
||||||
} `json:"auto_clients"`
|
|
||||||
SupportedTags []string `json:"supported_tags"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Client API struct
|
|
||||||
type Client struct {
|
|
||||||
Ids []string `json:"ids,omitempty"`
|
|
||||||
Tags []string `json:"tags,omitempty"`
|
|
||||||
BlockedServices []string `json:"blocked_services,omitempty"`
|
|
||||||
Upstreams []string `json:"upstreams,omitempty"`
|
|
||||||
|
|
||||||
UseGlobalSettings bool `json:"use_global_settings"`
|
|
||||||
UseGlobalBlockedServices bool `json:"use_global_blocked_services"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
FilteringEnabled bool `json:"filtering_enabled"`
|
|
||||||
ParentalEnabled bool `json:"parental_enabled"`
|
|
||||||
SafesearchEnabled bool `json:"safesearch_enabled"`
|
|
||||||
SafebrowsingEnabled bool `json:"safebrowsing_enabled"`
|
|
||||||
Disallowed bool `json:"disallowed"`
|
|
||||||
DisallowedRule string `json:"disallowed_rule"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort sort clients
|
|
||||||
func (cl *Client) Sort() {
|
|
||||||
sort.Strings(cl.Ids)
|
|
||||||
sort.Strings(cl.Tags)
|
|
||||||
sort.Strings(cl.BlockedServices)
|
|
||||||
sort.Strings(cl.Upstreams)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equals Clients equal check
|
|
||||||
func (cl *Client) Equals(o *Client) bool {
|
|
||||||
cl.Sort()
|
|
||||||
o.Sort()
|
|
||||||
|
|
||||||
a, _ := json.Marshal(cl)
|
|
||||||
b, _ := json.Marshal(o)
|
|
||||||
return string(a) == string(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge merge Clients
|
|
||||||
func (clients *Clients) Merge(other *Clients) ([]Client, []Client, []Client) {
|
|
||||||
current := make(map[string]Client)
|
|
||||||
for _, client := range clients.Clients {
|
|
||||||
current[client.Name] = client
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := make(map[string]Client)
|
|
||||||
for _, client := range other.Clients {
|
|
||||||
expected[client.Name] = client
|
|
||||||
}
|
|
||||||
|
|
||||||
var adds []Client
|
|
||||||
var removes []Client
|
|
||||||
var updates []Client
|
|
||||||
|
|
||||||
for _, cl := range expected {
|
|
||||||
if oc, ok := current[cl.Name]; ok {
|
|
||||||
if !cl.Equals(&oc) {
|
|
||||||
updates = append(updates, cl)
|
|
||||||
}
|
|
||||||
delete(current, cl.Name)
|
|
||||||
} else {
|
|
||||||
adds = append(adds, cl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, rr := range current {
|
|
||||||
removes = append(removes, rr)
|
|
||||||
}
|
|
||||||
|
|
||||||
return adds, updates, removes
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClientUpdate API struct
|
|
||||||
type ClientUpdate struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Data Client `json:"data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func equals(a []string, b []string) bool {
|
|
||||||
if len(a) != len(b) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for i, v := range a {
|
|
||||||
if v != b[i] {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// InstallConfig AdguardHome install config
|
// InstallConfig AdguardHome install config
|
||||||
type InstallConfig struct {
|
type InstallConfig struct {
|
||||||
Web InstallPort `json:"web"`
|
Web InstallPort `json:"web"`
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestTypes(t *testing.T) {
|
func TestSync(t *testing.T) {
|
||||||
RegisterFailHandler(Fail)
|
RegisterFailHandler(Fail)
|
||||||
RunSpecs(t, "Types Suite")
|
RunSpecs(t, "Types Suite")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,342 +1,38 @@
|
|||||||
package types_test
|
package types_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"io/ioutil"
|
|
||||||
|
|
||||||
"github.com/bakito/adguardhome-sync/pkg/types"
|
"github.com/bakito/adguardhome-sync/pkg/types"
|
||||||
"github.com/google/uuid"
|
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("Types", func() {
|
var _ = Describe("AdGuardInstance", func() {
|
||||||
var (
|
var inst types.AdGuardInstance
|
||||||
url string
|
|
||||||
apiPath string
|
|
||||||
)
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
url = "https://" + uuid.NewString()
|
inst = types.AdGuardInstance{}
|
||||||
apiPath = "/" + uuid.NewString()
|
|
||||||
})
|
})
|
||||||
|
Context("Init", func() {
|
||||||
Context("FilteringStatus", func() {
|
|
||||||
It("should correctly parse json", func() {
|
|
||||||
b, err := ioutil.ReadFile("../..//testdata/filtering-status.json")
|
|
||||||
fs := &types.FilteringStatus{}
|
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
|
||||||
err = json.Unmarshal(b, fs)
|
|
||||||
Ω(err).ShouldNot(HaveOccurred())
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Context("Filters", func() {
|
|
||||||
Context("Merge", func() {
|
|
||||||
var (
|
|
||||||
originFilters types.Filters
|
|
||||||
replicaFilters types.Filters
|
|
||||||
)
|
|
||||||
BeforeEach(func() {
|
|
||||||
originFilters = types.Filters{}
|
|
||||||
replicaFilters = types.Filters{}
|
|
||||||
})
|
|
||||||
|
|
||||||
It("should add a missing filter", func() {
|
|
||||||
originFilters = append(originFilters, types.Filter{URL: url})
|
|
||||||
a, u, d := replicaFilters.Merge(originFilters)
|
|
||||||
Ω(a).Should(HaveLen(1))
|
|
||||||
Ω(u).Should(BeEmpty())
|
|
||||||
Ω(d).Should(BeEmpty())
|
|
||||||
|
|
||||||
Ω(a[0].URL).Should(Equal(url))
|
|
||||||
})
|
|
||||||
|
|
||||||
It("should remove additional filter", func() {
|
|
||||||
replicaFilters = append(replicaFilters, types.Filter{URL: url})
|
|
||||||
a, u, d := replicaFilters.Merge(originFilters)
|
|
||||||
Ω(a).Should(BeEmpty())
|
|
||||||
Ω(u).Should(BeEmpty())
|
|
||||||
Ω(d).Should(HaveLen(1))
|
|
||||||
|
|
||||||
Ω(d[0].URL).Should(Equal(url))
|
|
||||||
})
|
|
||||||
|
|
||||||
It("should update existing filter when enabled differs", func() {
|
|
||||||
enabled := true
|
|
||||||
originFilters = append(originFilters, types.Filter{URL: url, Enabled: enabled})
|
|
||||||
replicaFilters = append(replicaFilters, types.Filter{URL: url, Enabled: !enabled})
|
|
||||||
a, u, d := replicaFilters.Merge(originFilters)
|
|
||||||
Ω(a).Should(BeEmpty())
|
|
||||||
Ω(u).Should(HaveLen(1))
|
|
||||||
Ω(d).Should(BeEmpty())
|
|
||||||
|
|
||||||
Ω(u[0].Enabled).Should(Equal(enabled))
|
|
||||||
})
|
|
||||||
|
|
||||||
It("should update existing filter when name differs", func() {
|
|
||||||
name1 := uuid.NewString()
|
|
||||||
name2 := uuid.NewString()
|
|
||||||
originFilters = append(originFilters, types.Filter{URL: url, Name: name1})
|
|
||||||
replicaFilters = append(replicaFilters, types.Filter{URL: url, Name: name2})
|
|
||||||
a, u, d := replicaFilters.Merge(originFilters)
|
|
||||||
Ω(a).Should(BeEmpty())
|
|
||||||
Ω(u).Should(HaveLen(1))
|
|
||||||
Ω(d).Should(BeEmpty())
|
|
||||||
|
|
||||||
Ω(u[0].Name).Should(Equal(name1))
|
|
||||||
})
|
|
||||||
|
|
||||||
It("should have no changes", func() {
|
|
||||||
originFilters = append(originFilters, types.Filter{URL: url})
|
|
||||||
replicaFilters = append(replicaFilters, types.Filter{URL: url})
|
|
||||||
a, u, d := replicaFilters.Merge(originFilters)
|
|
||||||
Ω(a).Should(BeEmpty())
|
|
||||||
Ω(u).Should(BeEmpty())
|
|
||||||
Ω(d).Should(BeEmpty())
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
Context("AdGuardInstance", func() {
|
|
||||||
It("should build a key with url and api apiPath", func() {
|
|
||||||
i := &types.AdGuardInstance{URL: url, APIPath: apiPath}
|
|
||||||
Ω(i.Key()).Should(Equal(url + "#" + apiPath))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
Context("RewriteEntry", func() {
|
|
||||||
It("should build a key with url and api apiPath", func() {
|
|
||||||
domain := uuid.NewString()
|
|
||||||
answer := uuid.NewString()
|
|
||||||
re := &types.RewriteEntry{Domain: domain, Answer: answer}
|
|
||||||
Ω(re.Key()).Should(Equal(domain + "#" + answer))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
Context("QueryLogConfig", func() {
|
|
||||||
Context("Equal", func() {
|
|
||||||
var (
|
|
||||||
a *types.QueryLogConfig
|
|
||||||
b *types.QueryLogConfig
|
|
||||||
)
|
|
||||||
BeforeEach(func() {
|
|
||||||
a = &types.QueryLogConfig{}
|
|
||||||
b = &types.QueryLogConfig{}
|
|
||||||
})
|
|
||||||
It("should be equal", func() {
|
|
||||||
a.Enabled = true
|
|
||||||
a.Interval = 1
|
|
||||||
a.AnonymizeClientIP = true
|
|
||||||
b.Enabled = true
|
|
||||||
b.Interval = 1
|
|
||||||
b.AnonymizeClientIP = true
|
|
||||||
Ω(a.Equals(b)).Should(BeTrue())
|
|
||||||
})
|
|
||||||
It("should not be equal when enabled differs", func() {
|
|
||||||
a.Enabled = true
|
|
||||||
b.Enabled = false
|
|
||||||
Ω(a.Equals(b)).ShouldNot(BeTrue())
|
|
||||||
})
|
|
||||||
It("should not be equal when interval differs", func() {
|
|
||||||
a.Interval = 1
|
|
||||||
b.Interval = 2
|
|
||||||
Ω(a.Equals(b)).ShouldNot(BeTrue())
|
|
||||||
})
|
|
||||||
It("should not be equal when anonymizeClientIP differs", func() {
|
|
||||||
a.AnonymizeClientIP = true
|
|
||||||
b.AnonymizeClientIP = false
|
|
||||||
Ω(a.Equals(b)).ShouldNot(BeTrue())
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
Context("RewriteEntries", func() {
|
|
||||||
Context("Merge", func() {
|
|
||||||
var (
|
|
||||||
originRE types.RewriteEntries
|
|
||||||
replicaRE types.RewriteEntries
|
|
||||||
domain string
|
|
||||||
)
|
|
||||||
BeforeEach(func() {
|
|
||||||
originRE = types.RewriteEntries{}
|
|
||||||
replicaRE = types.RewriteEntries{}
|
|
||||||
domain = uuid.NewString()
|
|
||||||
})
|
|
||||||
|
|
||||||
It("should add a missing rewrite entry", func() {
|
|
||||||
originRE = append(originRE, types.RewriteEntry{Domain: domain})
|
|
||||||
a, r, d := replicaRE.Merge(&originRE)
|
|
||||||
Ω(a).Should(HaveLen(1))
|
|
||||||
Ω(r).Should(BeEmpty())
|
|
||||||
Ω(d).Should(BeEmpty())
|
|
||||||
|
|
||||||
Ω(a[0].Domain).Should(Equal(domain))
|
|
||||||
})
|
|
||||||
|
|
||||||
It("should remove additional ewrite entry", func() {
|
|
||||||
replicaRE = append(replicaRE, types.RewriteEntry{Domain: domain})
|
|
||||||
a, r, d := replicaRE.Merge(&originRE)
|
|
||||||
Ω(a).Should(BeEmpty())
|
|
||||||
Ω(r).Should(HaveLen(1))
|
|
||||||
Ω(d).Should(BeEmpty())
|
|
||||||
|
|
||||||
Ω(r[0].Domain).Should(Equal(domain))
|
|
||||||
})
|
|
||||||
|
|
||||||
It("should have no changes", func() {
|
|
||||||
originRE = append(originRE, types.RewriteEntry{Domain: domain})
|
|
||||||
replicaRE = append(replicaRE, types.RewriteEntry{Domain: domain})
|
|
||||||
a, r, d := replicaRE.Merge(&originRE)
|
|
||||||
Ω(a).Should(BeEmpty())
|
|
||||||
Ω(r).Should(BeEmpty())
|
|
||||||
Ω(d).Should(BeEmpty())
|
|
||||||
})
|
|
||||||
|
|
||||||
It("should remove target duplicate", func() {
|
|
||||||
originRE = append(originRE, types.RewriteEntry{Domain: domain})
|
|
||||||
replicaRE = append(replicaRE, types.RewriteEntry{Domain: domain})
|
|
||||||
replicaRE = append(replicaRE, types.RewriteEntry{Domain: domain})
|
|
||||||
a, r, d := replicaRE.Merge(&originRE)
|
|
||||||
Ω(a).Should(BeEmpty())
|
|
||||||
Ω(r).Should(HaveLen(1))
|
|
||||||
Ω(d).Should(BeEmpty())
|
|
||||||
})
|
|
||||||
|
|
||||||
It("should remove target duplicate", func() {
|
|
||||||
originRE = append(originRE, types.RewriteEntry{Domain: domain})
|
|
||||||
originRE = append(originRE, types.RewriteEntry{Domain: domain})
|
|
||||||
replicaRE = append(replicaRE, types.RewriteEntry{Domain: domain})
|
|
||||||
a, r, d := replicaRE.Merge(&originRE)
|
|
||||||
Ω(a).Should(BeEmpty())
|
|
||||||
Ω(r).Should(BeEmpty())
|
|
||||||
Ω(d).Should(HaveLen(1))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
Context("UserRules", func() {
|
|
||||||
It("should join the rules correctly", func() {
|
|
||||||
r1 := uuid.NewString()
|
|
||||||
r2 := uuid.NewString()
|
|
||||||
ur := types.UserRules([]string{r1, r2})
|
|
||||||
Ω(ur.String()).Should(Equal(r1 + "\n" + r2))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
Context("Config", func() {
|
|
||||||
var cfg *types.Config
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
cfg = &types.Config{}
|
inst.URL = "https://localhost:3000"
|
||||||
})
|
})
|
||||||
Context("UniqueReplicas", func() {
|
It("should correctly set Host and WebHost if only URL is set", func() {
|
||||||
It("should be empty if noting defined", func() {
|
err := inst.Init()
|
||||||
r := cfg.UniqueReplicas()
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
Ω(r).Should(BeEmpty())
|
Ω(inst.Host).Should(Equal("localhost:3000"))
|
||||||
})
|
Ω(inst.WebHost).Should(Equal("localhost:3000"))
|
||||||
It("should be empty if replica url is not set", func() {
|
Ω(inst.URL).Should(Equal("https://localhost:3000"))
|
||||||
cfg.Replica = types.AdGuardInstance{URL: ""}
|
Ω(inst.WebURL).Should(Equal("https://localhost:3000"))
|
||||||
r := cfg.UniqueReplicas()
|
|
||||||
Ω(r).Should(BeEmpty())
|
|
||||||
})
|
|
||||||
It("should be empty if replicas url is not set", func() {
|
|
||||||
cfg.Replicas = []types.AdGuardInstance{{URL: ""}}
|
|
||||||
r := cfg.UniqueReplicas()
|
|
||||||
Ω(r).Should(BeEmpty())
|
|
||||||
})
|
|
||||||
It("should return only one replica if same url and apiPath", func() {
|
|
||||||
cfg.Replica = types.AdGuardInstance{URL: url, APIPath: apiPath}
|
|
||||||
cfg.Replicas = []types.AdGuardInstance{{URL: url, APIPath: apiPath}, {URL: url, APIPath: apiPath}}
|
|
||||||
r := cfg.UniqueReplicas()
|
|
||||||
Ω(r).Should(HaveLen(1))
|
|
||||||
})
|
|
||||||
It("should return 3 one replicas if urls are different", func() {
|
|
||||||
cfg.Replica = types.AdGuardInstance{URL: url, APIPath: apiPath}
|
|
||||||
cfg.Replicas = []types.AdGuardInstance{{URL: url + "1", APIPath: apiPath}, {URL: url, APIPath: apiPath + "1"}}
|
|
||||||
r := cfg.UniqueReplicas()
|
|
||||||
Ω(r).Should(HaveLen(3))
|
|
||||||
})
|
|
||||||
It("should set default api apiPath if not set", func() {
|
|
||||||
cfg.Replica = types.AdGuardInstance{URL: url}
|
|
||||||
cfg.Replicas = []types.AdGuardInstance{{URL: url + "1"}}
|
|
||||||
r := cfg.UniqueReplicas()
|
|
||||||
Ω(r).Should(HaveLen(2))
|
|
||||||
Ω(r[0].APIPath).Should(Equal(types.DefaultAPIPath))
|
|
||||||
Ω(r[1].APIPath).Should(Equal(types.DefaultAPIPath))
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
It("should correctly set Host and WebHost if URL and WebURL are set", func() {
|
||||||
|
inst.WebURL = "https://127.0.0.1:4000"
|
||||||
Context("Clients", func() {
|
err := inst.Init()
|
||||||
Context("Merge", func() {
|
Ω(err).ShouldNot(HaveOccurred())
|
||||||
var (
|
Ω(inst.Host).Should(Equal("localhost:3000"))
|
||||||
originClients *types.Clients
|
Ω(inst.WebHost).Should(Equal("127.0.0.1:4000"))
|
||||||
replicaClients types.Clients
|
Ω(inst.WebURL).Should(Equal(inst.WebURL))
|
||||||
name string
|
Ω(inst.URL).Should(Equal("https://localhost:3000"))
|
||||||
)
|
Ω(inst.WebURL).Should(Equal("https://127.0.0.1:4000"))
|
||||||
BeforeEach(func() {
|
|
||||||
originClients = &types.Clients{}
|
|
||||||
replicaClients = types.Clients{}
|
|
||||||
name = uuid.NewString()
|
|
||||||
})
|
|
||||||
|
|
||||||
It("should add a missing client", func() {
|
|
||||||
originClients.Clients = append(originClients.Clients, types.Client{Name: name})
|
|
||||||
a, u, d := replicaClients.Merge(originClients)
|
|
||||||
Ω(a).Should(HaveLen(1))
|
|
||||||
Ω(u).Should(BeEmpty())
|
|
||||||
Ω(d).Should(BeEmpty())
|
|
||||||
|
|
||||||
Ω(a[0].Name).Should(Equal(name))
|
|
||||||
})
|
|
||||||
|
|
||||||
It("should remove additional client", func() {
|
|
||||||
replicaClients.Clients = append(replicaClients.Clients, types.Client{Name: name})
|
|
||||||
a, u, d := replicaClients.Merge(originClients)
|
|
||||||
Ω(a).Should(BeEmpty())
|
|
||||||
Ω(u).Should(BeEmpty())
|
|
||||||
Ω(d).Should(HaveLen(1))
|
|
||||||
|
|
||||||
Ω(d[0].Name).Should(Equal(name))
|
|
||||||
})
|
|
||||||
|
|
||||||
It("should update existing client when name differs", func() {
|
|
||||||
disallowed := true
|
|
||||||
originClients.Clients = append(originClients.Clients, types.Client{Name: name, Disallowed: disallowed})
|
|
||||||
replicaClients.Clients = append(replicaClients.Clients, types.Client{Name: name, Disallowed: !disallowed})
|
|
||||||
a, u, d := replicaClients.Merge(originClients)
|
|
||||||
Ω(a).Should(BeEmpty())
|
|
||||||
Ω(u).Should(HaveLen(1))
|
|
||||||
Ω(d).Should(BeEmpty())
|
|
||||||
|
|
||||||
Ω(u[0].Disallowed).Should(Equal(disallowed))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
Context("Services", func() {
|
|
||||||
Context("Equals", func() {
|
|
||||||
It("should be equal", func() {
|
|
||||||
s1 := types.Services([]string{"a", "b"})
|
|
||||||
s2 := types.Services([]string{"b", "a"})
|
|
||||||
Ω(s1.Equals(s2)).Should(BeTrue())
|
|
||||||
})
|
|
||||||
It("should not be equal different values", func() {
|
|
||||||
s1 := types.Services([]string{"a", "b"})
|
|
||||||
s2 := types.Services([]string{"B", "a"})
|
|
||||||
Ω(s1.Equals(s2)).ShouldNot(BeTrue())
|
|
||||||
})
|
|
||||||
It("should not be equal different length", func() {
|
|
||||||
s1 := types.Services([]string{"a", "b"})
|
|
||||||
s2 := types.Services([]string{"b", "a", "c"})
|
|
||||||
Ω(s1.Equals(s2)).ShouldNot(BeTrue())
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
Context("DNSConfig", func() {
|
|
||||||
Context("Equals", func() {
|
|
||||||
It("should be equal", func() {
|
|
||||||
dc1 := &types.DNSConfig{Upstreams: []string{"a"}}
|
|
||||||
dc2 := &types.DNSConfig{Upstreams: []string{"a"}}
|
|
||||||
Ω(dc1.Equals(dc2)).Should(BeTrue())
|
|
||||||
})
|
|
||||||
It("should not be equal", func() {
|
|
||||||
dc1 := &types.DNSConfig{Upstreams: []string{"a"}}
|
|
||||||
dc2 := &types.DNSConfig{Upstreams: []string{"b"}}
|
|
||||||
Ω(dc1.Equals(dc2)).ShouldNot(BeTrue())
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
15
pkg/utils/clone.go
Normal file
15
pkg/utils/clone.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import "encoding/json"
|
||||||
|
|
||||||
|
func Clone[I interface{}](in I, out I) I {
|
||||||
|
b, _ := json.Marshal(in)
|
||||||
|
_ = json.Unmarshal(b, out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func JsonEquals(a interface{}, b interface{}) bool {
|
||||||
|
ja, _ := json.Marshal(a)
|
||||||
|
jb, _ := json.Marshal(b)
|
||||||
|
return string(ja) == string(jb)
|
||||||
|
}
|
||||||
14
pkg/utils/ptr.go
Normal file
14
pkg/utils/ptr.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func Ptr[I interface{}](i I) *I {
|
||||||
|
return &i
|
||||||
|
}
|
||||||
|
|
||||||
|
func PtrToString[I interface{}](i *I) string {
|
||||||
|
if i == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%v", i)
|
||||||
|
}
|
||||||
23
pkg/versions/versions.go
Normal file
23
pkg/versions/versions.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package versions
|
||||||
|
|
||||||
|
import "golang.org/x/mod/semver"
|
||||||
|
|
||||||
|
const (
|
||||||
|
// MinAgh minimal adguardhome version
|
||||||
|
MinAgh = "v0.107.40"
|
||||||
|
)
|
||||||
|
|
||||||
|
func IsNewerThan(v1 string, v2 string) bool {
|
||||||
|
return semver.Compare(sanitize(v1), sanitize(v2)) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsSame(v1 string, v2 string) bool {
|
||||||
|
return semver.Compare(sanitize(v1), sanitize(v2)) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func sanitize(v string) string {
|
||||||
|
if v == "" || v[0] == 'v' {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return "v" + v
|
||||||
|
}
|
||||||
13
pkg/versions/versions_suite_test.go
Normal file
13
pkg/versions/versions_suite_test.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package versions_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTypes(t *testing.T) {
|
||||||
|
RegisterFailHandler(Fail)
|
||||||
|
RunSpecs(t, "Types Suite")
|
||||||
|
}
|
||||||
24
pkg/versions/versions_test.go
Normal file
24
pkg/versions/versions_test.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package versions_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/bakito/adguardhome-sync/pkg/versions"
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("Versions", func() {
|
||||||
|
Context("IsNewerThan", func() {
|
||||||
|
It("should correctly parse json", func() {
|
||||||
|
Ω(versions.IsNewerThan("v0.106.10", "v0.106.9")).Should(BeTrue())
|
||||||
|
Ω(versions.IsNewerThan("v0.106.9", "v0.106.10")).Should(BeFalse())
|
||||||
|
Ω(versions.IsNewerThan("v0.106.10", "0.106.9")).Should(BeTrue())
|
||||||
|
Ω(versions.IsNewerThan("v0.106.9", "0.106.10")).Should(BeFalse())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("IsSame", func() {
|
||||||
|
It("should be the same version", func() {
|
||||||
|
Ω(versions.IsSame("v0.106.9", "v0.106.9")).Should(BeTrue())
|
||||||
|
Ω(versions.IsSame("0.106.9", "v0.106.9")).Should(BeTrue())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
1
testdata/.gitignore
vendored
Normal file
1
testdata/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
docker-compose.yaml
|
||||||
22
testdata/blockedservicesschedule-get.json
vendored
Normal file
22
testdata/blockedservicesschedule-get.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"schedule": {
|
||||||
|
"time_zone": "Europe/Zurich",
|
||||||
|
"tue": {
|
||||||
|
"start": 0,
|
||||||
|
"end": 86340000
|
||||||
|
},
|
||||||
|
"thu": {
|
||||||
|
"start": 0,
|
||||||
|
"end": 86340000
|
||||||
|
},
|
||||||
|
"sat": {
|
||||||
|
"start": 0,
|
||||||
|
"end": 35940000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ids": [
|
||||||
|
"9gag",
|
||||||
|
"dailymotion",
|
||||||
|
"disneyplus"
|
||||||
|
]
|
||||||
|
}
|
||||||
23
testdata/e2e/.helmignore
vendored
Normal file
23
testdata/e2e/.helmignore
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# Patterns to ignore when building packages.
|
||||||
|
# This supports shell glob matching, relative path matching, and
|
||||||
|
# negation (prefixed with !). Only one pattern per line.
|
||||||
|
.DS_Store
|
||||||
|
# Common VCS dirs
|
||||||
|
.git/
|
||||||
|
.gitignore
|
||||||
|
.bzr/
|
||||||
|
.bzrignore
|
||||||
|
.hg/
|
||||||
|
.hgignore
|
||||||
|
.svn/
|
||||||
|
# Common backup files
|
||||||
|
*.swp
|
||||||
|
*.bak
|
||||||
|
*.tmp
|
||||||
|
*.orig
|
||||||
|
*~
|
||||||
|
# Various IDEs
|
||||||
|
.project
|
||||||
|
.idea/
|
||||||
|
*.tmproj
|
||||||
|
.vscode/
|
||||||
8
testdata/e2e/Chart.yaml
vendored
Normal file
8
testdata/e2e/Chart.yaml
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
apiVersion: v2
|
||||||
|
name: agh-e2e
|
||||||
|
description: adguardhome sync test charts
|
||||||
|
type: application
|
||||||
|
|
||||||
|
version: 0.1.0
|
||||||
|
|
||||||
|
appVersion: "1.16.0"
|
||||||
4
testdata/e2e/bin/build-image.sh
vendored
Executable file
4
testdata/e2e/bin/build-image.sh
vendored
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
docker build -f Dockerfile --build-arg VERSION=e2e-tests -t localhost:5001/adguardhome-sync:e2e .
|
||||||
|
docker push localhost:5001/adguardhome-sync:e2e
|
||||||
9
testdata/e2e/bin/install-chart.sh
vendored
Executable file
9
testdata/e2e/bin/install-chart.sh
vendored
Executable file
@@ -0,0 +1,9 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
kubectl config set-context --current --namespace=agh-e2e
|
||||||
|
|
||||||
|
if [[ $(helm list --no-headers -n agh-e2e | grep agh-e2e | wc -l) == "1" ]]; then
|
||||||
|
helm delete agh-e2e -n agh-e2e --wait
|
||||||
|
fi
|
||||||
|
helm install agh-e2e testdata/e2e -n agh-e2e --create-namespace
|
||||||
6
testdata/e2e/bin/read-latest-replica-config.sh
vendored
Executable file
6
testdata/e2e/bin/read-latest-replica-config.sh
vendored
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
echo "## AdGuardHome.yaml of latest replica" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||||
|
kubectl exec adguardhome-replica-latest -- cat /opt/adguardhome/conf/AdGuardHome.yaml >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||||
7
testdata/e2e/bin/show-origin-logs.sh
vendored
Executable file
7
testdata/e2e/bin/show-origin-logs.sh
vendored
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "## Pod adguardhome-origin logs" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||||
|
kubectl logs adguardhome-origin >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||||
18
testdata/e2e/bin/show-replica-logs.sh
vendored
Executable file
18
testdata/e2e/bin/show-replica-logs.sh
vendored
Executable file
@@ -0,0 +1,18 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
for pod in $(kubectl get pods -l bakito.net/adguardhome-sync=replica -o name); do
|
||||||
|
echo "## Pod ${pod} logs" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||||
|
LOGS=$(kubectl logs ${pod})
|
||||||
|
# ignore certain errors
|
||||||
|
LOGS=$(echo -e "${LOGS}" | grep -v -e "error.* deleting filter .* no such file or directory" )
|
||||||
|
# https://github.com/AdguardTeam/AdGuardHome/issues/4944
|
||||||
|
LOGS=$(echo -e "${LOGS}" | grep -v -e "error.* creating dhcpv4 srv")
|
||||||
|
echo -e "${LOGS}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
ERRORS=$(echo -e "${LOGS}"} | grep '\[error\]' | wc -l)
|
||||||
|
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "Found ${ERRORS} error(s) in ${pod} log" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "----------------------------------------------" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
done
|
||||||
10
testdata/e2e/bin/show-sync-logs.sh
vendored
Executable file
10
testdata/e2e/bin/show-sync-logs.sh
vendored
Executable file
@@ -0,0 +1,10 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "## Pod adguardhome-sync logs" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||||
|
kubectl logs adguardhome-sync >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||||
|
ERRORS=$(kubectl logs adguardhome-sync | grep Error | wc -l)
|
||||||
|
echo "Found ${ERRORS} error(s) in adguardhome-sync log"; >> $GITHUB_STEP_SUMMARY
|
||||||
|
if [[ "${ERRORS}" != "0" ]]; then exit 1; fi
|
||||||
7
testdata/e2e/bin/wait-for-agh-pods.sh
vendored
Executable file
7
testdata/e2e/bin/wait-for-agh-pods.sh
vendored
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "wait for adguardhome pods"
|
||||||
|
for pod in $(kubectl get pods -l bakito.net/adguardhome-sync -o name); do
|
||||||
|
kubectl wait --for condition=Ready ${pod} --timeout=30s
|
||||||
|
done
|
||||||
4
testdata/e2e/bin/wait-for-sync.sh
vendored
Executable file
4
testdata/e2e/bin/wait-for-sync.sh
vendored
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
kubectl wait --for=jsonpath='{.status.phase}'=Succeeded pod/adguardhome-sync --timeout=1m
|
||||||
206
testdata/e2e/resources/AdGuardHome.yaml
vendored
Normal file
206
testdata/e2e/resources/AdGuardHome.yaml
vendored
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
http:
|
||||||
|
pprof:
|
||||||
|
port: 6060
|
||||||
|
enabled: false
|
||||||
|
address: 0.0.0.0:3000
|
||||||
|
session_ttl: 720h
|
||||||
|
users:
|
||||||
|
- name: username
|
||||||
|
password: $2a$10$yrrX.EvDpUUnZxr74u6euOMeF6dPFd/mEyohDq1LkpH76JyeObPBm
|
||||||
|
auth_attempts: 5
|
||||||
|
block_auth_min: 15
|
||||||
|
http_proxy: ""
|
||||||
|
language: en
|
||||||
|
theme: auto
|
||||||
|
dns:
|
||||||
|
bind_hosts:
|
||||||
|
- 0.0.0.0
|
||||||
|
port: 53
|
||||||
|
anonymize_client_ip: false
|
||||||
|
ratelimit: 20
|
||||||
|
ratelimit_whitelist: [ ]
|
||||||
|
refuse_any: true
|
||||||
|
upstream_dns:
|
||||||
|
- https://dns10.quad9.net/dns-query
|
||||||
|
upstream_dns_file: ""
|
||||||
|
bootstrap_dns:
|
||||||
|
- 1.1.1.1:53
|
||||||
|
fallback_dns: [ ]
|
||||||
|
all_servers: false
|
||||||
|
fastest_addr: false
|
||||||
|
fastest_timeout: 1s
|
||||||
|
allowed_clients: [ ]
|
||||||
|
disallowed_clients: [ ]
|
||||||
|
blocked_hosts:
|
||||||
|
- version.bind
|
||||||
|
- id.server
|
||||||
|
- hostname.bind
|
||||||
|
trusted_proxies:
|
||||||
|
- 127.0.0.0/8
|
||||||
|
- ::1/128
|
||||||
|
cache_size: 4194304
|
||||||
|
cache_ttl_min: 0
|
||||||
|
cache_ttl_max: 0
|
||||||
|
cache_optimistic: true
|
||||||
|
bogus_nxdomain: [ ]
|
||||||
|
aaaa_disabled: false
|
||||||
|
enable_dnssec: false
|
||||||
|
edns_client_subnet:
|
||||||
|
custom_ip: ""
|
||||||
|
enabled: false
|
||||||
|
use_custom: false
|
||||||
|
max_goroutines: 300
|
||||||
|
handle_ddr: true
|
||||||
|
ipset: [ ]
|
||||||
|
ipset_file: ""
|
||||||
|
bootstrap_prefer_ipv6: false
|
||||||
|
upstream_timeout: 10s
|
||||||
|
private_networks: [ ]
|
||||||
|
use_private_ptr_resolvers: true
|
||||||
|
local_ptr_upstreams: [ ]
|
||||||
|
use_dns64: false
|
||||||
|
dns64_prefixes: [ ]
|
||||||
|
serve_http3: false
|
||||||
|
use_http3_upstreams: false
|
||||||
|
tls:
|
||||||
|
enabled: false
|
||||||
|
server_name: ""
|
||||||
|
force_https: false
|
||||||
|
port_https: 443
|
||||||
|
port_dns_over_tls: 853
|
||||||
|
port_dns_over_quic: 853
|
||||||
|
port_dnscrypt: 0
|
||||||
|
dnscrypt_config_file: ""
|
||||||
|
allow_unencrypted_doh: false
|
||||||
|
certificate_chain: ""
|
||||||
|
private_key: ""
|
||||||
|
certificate_path: ""
|
||||||
|
private_key_path: ""
|
||||||
|
strict_sni_check: false
|
||||||
|
querylog:
|
||||||
|
ignored: [ ]
|
||||||
|
interval: 6h
|
||||||
|
size_memory: 1000
|
||||||
|
enabled: true
|
||||||
|
file_enabled: true
|
||||||
|
statistics:
|
||||||
|
ignored: [ ]
|
||||||
|
interval: 24h
|
||||||
|
enabled: true
|
||||||
|
filters:
|
||||||
|
- enabled: true
|
||||||
|
url: https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt
|
||||||
|
name: AdGuard DNS filter
|
||||||
|
id: 1
|
||||||
|
- enabled: true
|
||||||
|
url: https://adaway.org/hosts.txt
|
||||||
|
name: AdAway Default Blocklist
|
||||||
|
id: 2
|
||||||
|
whitelist_filters: [ ]
|
||||||
|
user_rules:
|
||||||
|
- '||metrics2.data.hicloud.com^$important'
|
||||||
|
- '||www.curiouscorrespondence.com^$important'
|
||||||
|
- '||bluewizard.com^$important'
|
||||||
|
- '||facebook.com^$important'
|
||||||
|
dhcp:
|
||||||
|
enabled: false
|
||||||
|
interface_name: eth0
|
||||||
|
local_domain_name: lan
|
||||||
|
dhcpv4:
|
||||||
|
gateway_ip: 1.2.3.4
|
||||||
|
subnet_mask: 255.255.0.0
|
||||||
|
range_start: 1.2.3.5
|
||||||
|
range_end: 1.2.3.56
|
||||||
|
lease_duration: 86400
|
||||||
|
icmp_timeout_msec: 1000
|
||||||
|
options: [ ]
|
||||||
|
dhcpv6:
|
||||||
|
range_start: ""
|
||||||
|
lease_duration: 86400
|
||||||
|
ra_slaac_only: false
|
||||||
|
ra_allow_slaac: false
|
||||||
|
filtering:
|
||||||
|
blocking_ipv4: ""
|
||||||
|
blocking_ipv6: ""
|
||||||
|
blocked_services:
|
||||||
|
schedule:
|
||||||
|
time_zone: Europe/Zurich
|
||||||
|
tue:
|
||||||
|
start: 0s
|
||||||
|
end: 23h59m
|
||||||
|
thu:
|
||||||
|
start: 0s
|
||||||
|
end: 23h59m
|
||||||
|
sat:
|
||||||
|
start: 0s
|
||||||
|
end: 9h59m
|
||||||
|
ids:
|
||||||
|
- 9gag
|
||||||
|
- dailymotion
|
||||||
|
- disneyplus
|
||||||
|
protection_disabled_until: null
|
||||||
|
safe_search:
|
||||||
|
enabled: true
|
||||||
|
bing: true
|
||||||
|
duckduckgo: true
|
||||||
|
google: true
|
||||||
|
pixabay: true
|
||||||
|
yandex: true
|
||||||
|
youtube: true
|
||||||
|
blocking_mode: default
|
||||||
|
parental_block_host: family-block.dns.adguard.com
|
||||||
|
safebrowsing_block_host: standard-block.dns.adguard.com
|
||||||
|
rewrites: [ ]
|
||||||
|
safebrowsing_cache_size: 1048576
|
||||||
|
safesearch_cache_size: 1048576
|
||||||
|
parental_cache_size: 1048576
|
||||||
|
cache_time: 30
|
||||||
|
filters_update_interval: 24
|
||||||
|
blocked_response_ttl: 10
|
||||||
|
filtering_enabled: true
|
||||||
|
parental_enabled: true
|
||||||
|
safebrowsing_enabled: true
|
||||||
|
protection_enabled: true
|
||||||
|
clients:
|
||||||
|
runtime_sources:
|
||||||
|
whois: true
|
||||||
|
arp: true
|
||||||
|
rdns: true
|
||||||
|
dhcp: true
|
||||||
|
hosts: true
|
||||||
|
persistent:
|
||||||
|
- name: Device 1
|
||||||
|
tags:
|
||||||
|
- device_1
|
||||||
|
ids:
|
||||||
|
- 2.2.2.2
|
||||||
|
blocked_services:
|
||||||
|
schedule:
|
||||||
|
time_zone: Europe/Zurich
|
||||||
|
ids:
|
||||||
|
- facebook
|
||||||
|
- mail_ru
|
||||||
|
- qq
|
||||||
|
- vk
|
||||||
|
- ok
|
||||||
|
upstreams: [ ]
|
||||||
|
use_global_settings: true
|
||||||
|
filtering_enabled: false
|
||||||
|
parental_enabled: false
|
||||||
|
safebrowsing_enabled: false
|
||||||
|
use_global_blocked_services: false
|
||||||
|
ignore_querylog: false
|
||||||
|
ignore_statistics: false
|
||||||
|
log:
|
||||||
|
file: ""
|
||||||
|
max_backups: 0
|
||||||
|
max_size: 100
|
||||||
|
max_age: 3
|
||||||
|
compress: false
|
||||||
|
local_time: false
|
||||||
|
verbose: false
|
||||||
|
os:
|
||||||
|
group: ""
|
||||||
|
user: ""
|
||||||
|
rlimit_nofile: 0
|
||||||
|
schema_version: 27
|
||||||
4
testdata/e2e/templates/NOTES.txt
vendored
Normal file
4
testdata/e2e/templates/NOTES.txt
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
Installed adguardhome-sync end-2-end test with {{ len .Values.replica.versions }} replica instances.
|
||||||
|
{{- range $_, $version := .Values.replica.versions }}
|
||||||
|
- {{ $version }}
|
||||||
|
{{- end }}
|
||||||
8
testdata/e2e/templates/configmap-origin.yaml
vendored
Normal file
8
testdata/e2e/templates/configmap-origin.yaml
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: origin-conf
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
data:
|
||||||
|
AdGuardHome.yaml: |
|
||||||
|
{{- .Files.Get "resources/AdGuardHome.yaml" | nindent 4 }}
|
||||||
17
testdata/e2e/templates/configmap-sync.yaml
vendored
Normal file
17
testdata/e2e/templates/configmap-sync.yaml
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: sync-conf
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
data:
|
||||||
|
API_PORT: '0'
|
||||||
|
LOG_LEVEL: 'debug'
|
||||||
|
ORIGIN_URL: 'http://service-origin.{{ $.Release.Namespace }}.svc.cluster.local:3000'
|
||||||
|
ORIGIN_PASSWORD: 'password'
|
||||||
|
ORIGIN_USERNAME: 'username'
|
||||||
|
{{ range $i,$version := .Values.replica.versions }}
|
||||||
|
REPLICA{{ $i }}_AUTOSETUP: 'true'
|
||||||
|
REPLICA{{ $i }}_URL: 'http://service-replica-{{ $version | toString | replace "." "-" }}.{{ $.Release.Namespace }}.svc.cluster.local:3000'
|
||||||
|
REPLICA{{ $i }}_PASSWORD: 'password'
|
||||||
|
REPLICA{{ $i }}_USERNAME: 'username'
|
||||||
|
{{- end }}
|
||||||
37
testdata/e2e/templates/pod-adguardhome-origin.yaml
vendored
Normal file
37
testdata/e2e/templates/pod-adguardhome-origin.yaml
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
name: adguardhome-origin
|
||||||
|
namespace: {{ $.Release.Namespace }}
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: adguardhome-origin
|
||||||
|
bakito.net/adguardhome-sync: origin
|
||||||
|
spec:
|
||||||
|
volumes:
|
||||||
|
- name: configmap
|
||||||
|
configMap:
|
||||||
|
name: origin-conf
|
||||||
|
- name: conf
|
||||||
|
emptyDir: { }
|
||||||
|
|
||||||
|
initContainers:
|
||||||
|
- name: init
|
||||||
|
image: busybox
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /opt/adguardhome/configmap
|
||||||
|
name: configmap
|
||||||
|
- mountPath: /opt/adguardhome/conf
|
||||||
|
name: conf
|
||||||
|
command:
|
||||||
|
- cp
|
||||||
|
- /opt/adguardhome/configmap/AdGuardHome.yaml
|
||||||
|
- /opt/adguardhome/conf
|
||||||
|
|
||||||
|
containers:
|
||||||
|
- name: adguardhome
|
||||||
|
image: adguard/adguardhome:latest
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /opt/adguardhome/conf
|
||||||
|
name: conf
|
||||||
|
ports:
|
||||||
|
- containerPort: 3000
|
||||||
17
testdata/e2e/templates/pod-adguardhome-replica.yaml
vendored
Normal file
17
testdata/e2e/templates/pod-adguardhome-replica.yaml
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{{ range $_, $version := .Values.replica.versions }}
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
name: adguardhome-replica-{{ $version | toString | replace "." "-" }}
|
||||||
|
namespace: {{ $.Release.Namespace }}
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: adguardhome-replica-{{ $version | toString | replace "." "-" }}
|
||||||
|
bakito.net/adguardhome-sync: replica
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: adguardhome
|
||||||
|
image: "adguard/adguardhome:{{ $version }}"
|
||||||
|
ports:
|
||||||
|
- containerPort: 3000
|
||||||
|
---
|
||||||
|
{{- end }}
|
||||||
25
testdata/e2e/templates/pod-adguardhome-sync.yaml
vendored
Normal file
25
testdata/e2e/templates/pod-adguardhome-sync.yaml
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
name: adguardhome-sync
|
||||||
|
namespace: {{ $.Release.Namespace }}
|
||||||
|
spec:
|
||||||
|
serviceAccountName: agh-e2e
|
||||||
|
initContainers:
|
||||||
|
- name: wait-for-others
|
||||||
|
image: bitnami/kubectl:1.24
|
||||||
|
command:
|
||||||
|
- /bin/bash
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
{{- .Files.Get "bin/wait-for-agh-pods.sh" | nindent 10}}
|
||||||
|
containers:
|
||||||
|
- name: adguardhome-sync
|
||||||
|
image: localhost:5001/adguardhome-sync:e2e
|
||||||
|
command:
|
||||||
|
- /opt/go/adguardhome-sync
|
||||||
|
- run
|
||||||
|
envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: sync-conf
|
||||||
|
restartPolicy: Never
|
||||||
30
testdata/e2e/templates/rbac.yaml
vendored
Normal file
30
testdata/e2e/templates/rbac.yaml
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: agh-e2e
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
---
|
||||||
|
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: Role
|
||||||
|
metadata:
|
||||||
|
name: agh-e2e
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
rules:
|
||||||
|
- apiGroups: [ "" ]
|
||||||
|
resources: [ "pods" ]
|
||||||
|
verbs: [ "get", "watch", "list" ]
|
||||||
|
---
|
||||||
|
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: RoleBinding
|
||||||
|
metadata:
|
||||||
|
name: agh-e2e
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: agh-e2e
|
||||||
|
roleRef:
|
||||||
|
kind: Role
|
||||||
|
name: agh-e2e
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
12
testdata/e2e/templates/service-origin.yaml
vendored
Normal file
12
testdata/e2e/templates/service-origin.yaml
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: service-origin
|
||||||
|
namespace: {{ $.Release.Namespace }}
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app.kubernetes.io/name: adguardhome-origin
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 3000
|
||||||
|
targetPort: 3000
|
||||||
14
testdata/e2e/templates/service-replica.yaml
vendored
Normal file
14
testdata/e2e/templates/service-replica.yaml
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{{ range $i ,$version := .Values.replica.versions }}
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: service-replica-{{ $version | toString | replace "." "-" }}
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app.kubernetes.io/name: adguardhome-replica-{{ $version | toString | replace "." "-" }}
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 3000
|
||||||
|
targetPort: 3000
|
||||||
|
---
|
||||||
|
{{- end }}
|
||||||
5
testdata/e2e/values.yaml
vendored
Normal file
5
testdata/e2e/values.yaml
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
replica:
|
||||||
|
versions:
|
||||||
|
- v0.107.40
|
||||||
|
- v0.107.43
|
||||||
|
- latest
|
||||||
5
testdata/filtering-status.json
vendored
5
testdata/filtering-status.json
vendored
@@ -7,8 +7,7 @@
|
|||||||
"enabled": true,
|
"enabled": true,
|
||||||
"url": "https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt",
|
"url": "https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt",
|
||||||
"name": "AdGuard DNS filter",
|
"name": "AdGuard DNS filter",
|
||||||
"rules_count": 37330,
|
"rules_count": 37330
|
||||||
"last_updated": ""
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 1616956421,
|
"id": 1616956421,
|
||||||
@@ -24,4 +23,4 @@
|
|||||||
"||metrics2.data.hicloud.com^$important",
|
"||metrics2.data.hicloud.com^$important",
|
||||||
""
|
""
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user