Compare commits

...

63 Commits

Author SHA1 Message Date
bakito
a7d15ce655 add debug logs on client error 2021-08-18 21:36:34 +02:00
dependabot[bot]
8a5c005d91 Bump go.uber.org/zap from 1.18.1 to 1.19.0 (#35)
Bumps [go.uber.org/zap](https://github.com/uber-go/zap) from 1.18.1 to 1.19.0.
- [Release notes](https://github.com/uber-go/zap/releases)
- [Changelog](https://github.com/uber-go/zap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/uber-go/zap/compare/v1.18.1...v1.19.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-08-16 07:14:19 +02:00
Marc Brugger
462f0ef7e8 Update LICENSE 2021-08-12 14:28:20 +02:00
Marc Brugger
e28134f6b4 push images also to ghcr.io (#34)
* push images also to ghcr.io
2021-08-10 21:22:33 +02:00
bakito
d2a6f0aa20 add some styling to api page 2021-08-09 22:54:39 +02:00
bakito
484cf26119 add build time to version 2021-08-09 19:00:34 +02:00
dependabot[bot]
5c2e0b966e Bump github.com/onsi/gomega from 1.14.0 to 1.15.0 (#33)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.14.0 to 1.15.0.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.14.0...v1.15.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-08-09 07:52:00 +02:00
bakito
36a589aa85 enable all test 2021-08-08 11:16:57 +02:00
bakito
4cd7134941 handle dns rewrite duplicateds #23 2021-08-08 11:07:08 +02:00
bakito
aca26f449e remove beta #12 2021-08-08 10:16:04 +02:00
dependabot[bot]
f2de32a2c1 Bump github.com/google/uuid from 1.2.0 to 1.3.0 (#32)
Bumps [github.com/google/uuid](https://github.com/google/uuid) from 1.2.0 to 1.3.0.
- [Release notes](https://github.com/google/uuid/releases)
- [Commits](https://github.com/google/uuid/compare/v1.2.0...v1.3.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-19 09:15:06 +02:00
dependabot[bot]
3da4ced3c6 Bump github.com/onsi/gomega from 1.13.0 to 1.14.0 (#31)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.13.0 to 1.14.0.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.13.0...v1.14.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-12 13:18:44 +02:00
dependabot[bot]
752563eb79 Bump github.com/spf13/cobra from 1.1.3 to 1.2.1 (#30)
Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.1.3 to 1.2.1.
- [Release notes](https://github.com/spf13/cobra/releases)
- [Changelog](https://github.com/spf13/cobra/blob/master/CHANGELOG.md)
- [Commits](https://github.com/spf13/cobra/compare/v1.1.3...v1.2.1)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-05 19:40:58 +02:00
dependabot[bot]
a7873abf16 Bump go.uber.org/zap from 1.17.0 to 1.18.1 (#29)
Bumps [go.uber.org/zap](https://github.com/uber-go/zap) from 1.17.0 to 1.18.1.
- [Release notes](https://github.com/uber-go/zap/releases)
- [Changelog](https://github.com/uber-go/zap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/uber-go/zap/compare/v1.17.0...v1.18.1)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-05 07:14:07 +02:00
dependabot[bot]
7483f994d7 Bump github.com/spf13/viper from 1.8.0 to 1.8.1 (#27)
Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.8.0 to 1.8.1.
- [Release notes](https://github.com/spf13/viper/releases)
- [Commits](https://github.com/spf13/viper/compare/v1.8.0...v1.8.1)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-28 07:32:48 +02:00
dependabot[bot]
dfdf0d33a6 Bump github.com/golang/mock from 1.5.0 to 1.6.0 (#25)
Bumps [github.com/golang/mock](https://github.com/golang/mock) from 1.5.0 to 1.6.0.
- [Release notes](https://github.com/golang/mock/releases)
- [Changelog](https://github.com/golang/mock/blob/master/.goreleaser.yml)
- [Commits](https://github.com/golang/mock/compare/v1.5.0...v1.6.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-21 09:09:08 +02:00
dependabot[bot]
dd18873552 Bump github.com/onsi/ginkgo from 1.16.3 to 1.16.4 (#22)
Bumps [github.com/onsi/ginkgo](https://github.com/onsi/ginkgo) from 1.16.3 to 1.16.4.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v1.16.3...v1.16.4)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-21 08:59:01 +02:00
dependabot[bot]
4cca9ff588 Bump github.com/spf13/viper from 1.7.1 to 1.8.0 (#26)
Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.7.1 to 1.8.0.
- [Release notes](https://github.com/spf13/viper/releases)
- [Commits](https://github.com/spf13/viper/compare/v1.7.1...v1.8.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-21 08:58:52 +02:00
bakito
61fa17893a bump versions 2021-06-01 22:45:59 +02:00
Marc Brugger
00f353bac4 Dns dhcpas beta feature (#17)
* add dhcp and dns types

* sync dns #12

* add test #12

* implement dhcp #12

* add beta flags
2021-05-28 20:07:32 +02:00
dependabot[bot]
d19cca6fcf Bump github.com/onsi/gomega from 1.11.0 to 1.12.0 (#14)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.11.0 to 1.12.0.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.11.0...v1.12.0)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-10 11:07:57 +02:00
bakito
a73f696ef6 update readme #9 2021-04-18 22:30:25 +02:00
bakito
2e93920931 run on startup #10 2021-04-18 22:20:08 +02:00
Marc Brugger
3edb5222d6 Initial setup (#11)
automatically setup new AdGuardHome instances #9
2021-04-18 22:03:57 +02:00
bakito
d58c8f115e log status 2021-04-18 19:32:03 +02:00
Marc Brugger
f8dd7e6136 Update issue templates 2021-04-18 18:40:43 +02:00
bakito
9fd3694237 add support debug messages 2021-04-18 18:28:35 +02:00
bakito
258ecae016 fix filter synch error 2021-04-18 18:04:53 +02:00
Marc Brugger
04a912fb56 Update issue templates 2021-04-18 11:03:44 +02:00
Marc Brugger
cd75a5f46e Merge pull request #8 from bakito/dependabot/go_modules/github.com/golang/mock-1.5.0
Bump github.com/golang/mock from 1.3.1 to 1.5.0
2021-04-12 12:28:55 +02:00
dependabot[bot]
87c578a07b Bump github.com/golang/mock from 1.3.1 to 1.5.0
Bumps [github.com/golang/mock](https://github.com/golang/mock) from 1.3.1 to 1.5.0.
- [Release notes](https://github.com/golang/mock/releases)
- [Changelog](https://github.com/golang/mock/blob/master/.goreleaser.yml)
- [Commits](https://github.com/golang/mock/compare/1.3.1...v1.5.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-12 10:16:48 +00:00
Marc Brugger
28782fc364 Merge pull request #7 from bakito/dependabot/go_modules/github.com/onsi/ginkgo-1.16.1
Bump github.com/onsi/ginkgo from 1.16.0 to 1.16.1
2021-04-12 10:15:17 +02:00
Marc Brugger
7470a11547 Merge pull request #6 from bakito/dependabot/go_modules/github.com/go-resty/resty/v2-2.6.0
Bump github.com/go-resty/resty/v2 from 2.4.0 to 2.6.0
2021-04-12 10:12:59 +02:00
dependabot[bot]
0d35a77e1b Bump github.com/onsi/ginkgo from 1.16.0 to 1.16.1
Bumps [github.com/onsi/ginkgo](https://github.com/onsi/ginkgo) from 1.16.0 to 1.16.1.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v1.16.0...v1.16.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-12 08:11:52 +00:00
dependabot[bot]
bc8fc796ec Bump github.com/go-resty/resty/v2 from 2.4.0 to 2.6.0
Bumps [github.com/go-resty/resty/v2](https://github.com/go-resty/resty) from 2.4.0 to 2.6.0.
- [Release notes](https://github.com/go-resty/resty/releases)
- [Commits](https://github.com/go-resty/resty/compare/v2.4.0...v2.6.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-12 08:11:21 +00:00
bakito
da3b037009 add sync tests 2021-04-11 18:35:03 +02:00
bakito
4921af09a5 add client tests 2021-04-11 16:13:37 +02:00
bakito
e7a2604268 prepare sync and client tests 2021-04-11 11:56:55 +02:00
bakito
cb624ea52b extend tests 2021-04-11 10:51:24 +02:00
bakito
57612bae1f log enable for filters 2021-04-10 14:48:02 +02:00
bakito
680729580e add merge tests 2021-04-10 13:30:26 +02:00
bakito
97fc7be19a update changed filters #5 2021-04-10 13:15:56 +02:00
bakito
59a55db582 use helper methods for error handling 2021-04-10 11:44:12 +02:00
bakito
d984d66883 abort on http status code != 200 2021-04-10 00:18:29 +02:00
bakito
a78f3f00dc start writing tests 2021-04-06 21:31:26 +02:00
bakito
933a3d8066 add missing command 2021-04-06 20:45:36 +02:00
bakito
64463b6842 add multi replica env support #4 2021-04-05 21:07:28 +02:00
bakito
9450c09e2a add docker build #4 2021-04-05 20:13:13 +02:00
bakito
514b228739 add prerequisites 2021-04-05 13:33:21 +02:00
bakito
06f95de085 ignore last updated timestamp / add testcase #3 2021-04-05 13:20:11 +02:00
bakito
1986e87518 correct log fields 2021-04-03 21:04:50 +02:00
bakito
cf0a381f80 log fields 2021-04-03 20:38:41 +02:00
bakito
5e591e04c3 simplifiy
cleanup code
skip arm for darwin and windows
2021-04-03 20:19:34 +02:00
bakito
fcf25538c0 make log available in api 2021-04-03 18:41:28 +02:00
bakito
aa95031136 sync stats and query log config 2021-04-03 17:52:08 +02:00
bakito
6fb2dd12a8 move log attribute to message 2021-03-31 02:34:26 +02:00
bakito
8db9c98644 synch toggles 2021-03-31 02:28:18 +02:00
Marc Brugger
f8578e85b2 add API server to trigger sync remotely 2021-03-29 08:43:18 +02:00
bakito
5dafdf5472 change output paths 2021-03-28 20:28:58 +02:00
bakito
23d5f30eb8 add systemd script 2021-03-28 20:25:25 +02:00
bakito
44609a93e3 correct logger name 2021-03-28 20:00:22 +02:00
bakito
70e60bb7d0 use external semver 2021-03-28 19:31:11 +02:00
bakito
aacffb1518 document config file 2021-03-28 17:04:41 +02:00
48 changed files with 4083 additions and 390 deletions

27
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,27 @@
---
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.
**Screenshots**
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.

10
.github/ISSUE_TEMPLATE/custom.md vendored Normal file
View File

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

View File

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

View File

@@ -32,7 +32,7 @@ jobs:
uses: actions/checkout@v2
- name: Test
run: go test ./... -coverprofile=coverage.out
run: make test
- name: Send coverage
uses: shogo82148/actions-goveralls@v1

58
.github/workflows/publish.yml vendored Normal file
View File

@@ -0,0 +1,58 @@
name: docker-image
on:
push:
branches:
- main
release:
types:
- published
jobs:
main:
runs-on: ubuntu-latest
steps:
- name: Get current date
id: date
run: echo "::set-output name=date::$(date --utc +%Y-%m-%dT%H:%M:%SZ)"
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to Quay
uses: docker/login-action@v1
with:
registry: quay.io
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Login to ghcr.io
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push ${{github.event.release.tag_name }}
id: docker_build_release
uses: docker/build-push-action@v2
if: ${{ github.event.release.tag_name != '' }}
with:
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 }}
platforms: linux/amd64,linux/arm64,linux/arm/v7
build-args: |
VERSION=${{ github.event.release.tag_name }}
BUILD=${{ steps.date.outputs.date }}
- name: Build and push main
id: docker_build_main
uses: docker/build-push-action@v2
if: ${{ github.event.release.tag_name == '' }}
with:
push: true
tags: quay.io/bakito/adguardhome-sync:main,ghcr.io/bakito/adguardhome-sync:main
platforms: linux/amd64,linux/arm64,linux/arm/v7
build-args: |
VERSION=main
BUILD=${{ steps.date.outputs.date }}
- name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}

3
.gitignore vendored
View File

@@ -1,3 +1,6 @@
.idea
coverage.out
dist
adguardhome-sync
main
.adguardhome-sync.yaml

View File

@@ -1,29 +1,38 @@
# This is an example goreleaser.yaml file with some sane defaults.
# Make sure to check the documentation at http://goreleaser.com
builds:
- env:
- CGO_ENABLED=0
ldflags:
- -s -w -X github.com/bakito/adguardhome-sync/version.Version={{.Version}}
goos:
- linux
- windows
- darwin
goarch:
- 386
- amd64
- arm
- arm64
goarm:
- 5
- 6
- 7
hooks:
post: upx {{ .Path }}
- env:
- CGO_ENABLED=0
ldflags:
- -s -w -X github.com/bakito/adguardhome-sync/version.Version={{.Version}} -X github.com/bakito/adguardhome-sync/version.Build={{.Date}}
goos:
- linux
- windows
- darwin
goarch:
- 386
- amd64
- arm
- arm64
goarm:
- 5
- 6
- 7
ignore:
- goos: darwin
goarch: arm
- goos: darwin
goarch: arm64
- goos: windows
goarch: arm
- goos: windows
goarch: arm64
hooks:
post: upx {{ .Path }}
archives:
- replacements:
386: i386
amd64: x86_64
- replacements:
386: i386
amd64: x86_64
checksum:
name_template: 'checksums.txt'
snapshot:
@@ -32,5 +41,7 @@ changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
- '^docs:'
- '^test:'
release:
prerelease: auto

29
Dockerfile Normal file
View File

@@ -0,0 +1,29 @@
FROM docker.io/library/golang:1.16 as builder
WORKDIR /go/src/app
RUN apt-get update && apt-get install -y upx
ARG VERSION=main
ARG BUILD="N/A"
ENV GOPROXY=https://goproxy.io \
GO111MODULE=on \
CGO_ENABLED=0 \
GOOS=linux
ADD . /go/src/app/
RUN go build -a -installsuffix cgo -ldflags="-w -s -X github.com/bakito/adguardhome-sync/version.Version=${VERSION} -X github.com/bakito/adguardhome-sync/version.Build=${BUILD}" -o adguardhome-sync . \
&& upx -q adguardhome-sync
# application image
FROM scratch
WORKDIR /opt/go
LABEL maintainer="bakito <github@bakito.ch>"
EXPOSE 8080
ENTRYPOINT ["/opt/go/adguardhome-sync"]
CMD ["run", "--config", "/config/adguardhome-sync.yaml"]
COPY --from=builder /go/src/app/adguardhome-sync /opt/go/adguardhome-sync
USER 1001

View File

@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Copyright 2021 bakito
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@@ -16,14 +16,27 @@ tidy:
go mod tidy
# Run tests
test: tidy fmt vet
test: mocks tidy fmt vet
go test ./... -coverprofile=coverage.out
go tool cover -func=coverage.out
release:
@version=$$(go run version/semver/main.go); \
mocks: mockgen
mockgen -destination pkg/mocks/client/mock.go github.com/bakito/adguardhome-sync/pkg/client Client
release: semver
@version=$$(semver); \
git tag -s $$version -m"Release $$version"
goreleaser --rm-dist
test-release:
goreleaser --skip-publish --snapshot --rm-dist
goreleaser --skip-publish --snapshot --rm-dist
semver:
ifeq (, $(shell which semver))
$(shell go get -u github.com/bakito/semver)
endif
mockgen:
ifeq (, $(shell which mockgen))
$(shell go get github.com/golang/mock/mockgen@v1.5)
endif

136
README.md
View File

@@ -1,4 +1,6 @@
[![Go](https://github.com/bakito/adguardhome-sync/actions/workflows/go.yml/badge.svg)](https://github.com/bakito/adguardhome-sync/actions/workflows/go.yml) [![Go Report Card](https://goreportcard.com/badge/github.com/bakito/adguardhome-sync)](https://goreportcard.com/report/github.com/bakito/adguardhome-sync)
[![Go](https://github.com/bakito/adguardhome-sync/actions/workflows/go.yml/badge.svg)](https://github.com/bakito/adguardhome-sync/actions/workflows/go.yml)
[![Go Report Card](https://goreportcard.com/badge/github.com/bakito/adguardhome-sync)](https://goreportcard.com/report/github.com/bakito/adguardhome-sync)
[![Coverage Status](https://coveralls.io/repos/github/bakito/adguardhome-sync/badge.svg?branch=main)](https://coveralls.io/github/bakito/adguardhome-sync?branch=main)
# AdGuardHome sync
@@ -6,10 +8,20 @@ Synchronize [AdGuardHome](https://github.com/AdguardTeam/AdGuardHome) config to
## Current sync features
- General Settings
- Filters
- Rewrites
- Services
- Clients
- DNS Config
- DHCP Config
### Setup of initial instances
New AdGuardHome replica instances can be automatically installed if enabled via the config autoSetup. During automatic
installation, the admin interface will be listening on port 3000 in runtime.
To skip automatic setup
## Install
@@ -17,6 +29,10 @@ Synchronize [AdGuardHome](https://github.com/AdguardTeam/AdGuardHome) config to
go get -u github.com/bakito/adguardhome-sync
```
## Prerequisites
Both the origin instance must be initially setup via the AdguardHome installation wizard.
## Run
```bash
@@ -33,4 +49,120 @@ adguardhome-sync run
# run as daemon
adguardhome-sync run --cron "*/10 * * * *"
```
```
## docker cli
```bash
docker run -d \
--name=adguardhome-sync \
-p 8080:8080 \
-v /path/to/appdata/config/adguardhome-sync.yaml:/config/adguardhome-sync.yaml \
--restart unless-stopped \
quay.io/bakito/adguardhome-sync:latest
```
## docker compose
### config file
```yaml
---
version: "2.1"
services:
adguardhome-sync:
image: quay.io/bakito/adguardhome-sync
container_name: adguardhome-sync
volumes:
- /path/to/appdata/config/adguardhome-sync.yaml:/config/adguardhome-sync.yaml
ports:
- 8080:8080
restart: unless-stopped
```
### env
```yaml
---
version: "2.1"
services:
adguardhome-sync:
image: quay.io/bakito/adguardhome-sync
container_name: adguardhome-sync
command: run
environment:
- ORIGIN_URL=https://192.168.1.2:3000
- ORIGIN_USERNAME=username
- ORIGIN_PASSWORD=password
- REPLICA_URL=http://192.168.1.3
- REPLICA_USERNAME=username
- REPLICA_PASSWORD=password
- REPLICA1_URL=http://192.168.1.4
- REPLICA1_USERNAME=username
- REPLICA1_PASSWORD=password
- REPLICA1_APIPATH=/some/path/control
# - REPLICA1_AUTOSETUP=true # if true, AdGuardHome is automatically initialized.
- CRON=*/10 * * * * # run every 10 minutes
- RUNONSTART=true
ports:
- 8080:8080
restart: unless-stopped
```
### Config file
location: $HOME/.adguardhome-sync.yaml
```yaml
# cron expression to run in daemon mode. (default; "" = runs only once)
cron: "*/10 * * * *"
# runs the synchronisation on startup
runOnStart: true
origin:
# url of the origin instance
url: https://192.168.1.2:3000
# apiPath: define an api path if other than "/control"
# insecureSkipVerify: true # disable tls check
username: username
password: password
# replica instance (optional, if only one)
replica:
# url of the replica instance
url: http://192.168.1.3
username: username
password: password
# replicas instances (optional, if more than one)
replicas:
# url of the replica instance
- url: http://192.168.1.3
username: username
password: password
- url: http://192.168.1.4
username: username
password: password
# autoSetup: true # if true, AdGuardHome is automatically initialized.
# Configure the sync API server, disabled if api port is 0
api:
# Port, default 8080
port: 8080
# if username and password are defined, basic auth is applied to the sync API
username: username
password: password
```
## Log Level
The log level can be set with the environment variable: LOG_LEVEL
The following log levels are supported (default: info)
- debug
- info
- warn
- error

View File

@@ -3,18 +3,24 @@ package cmd
import (
"fmt"
"os"
"regexp"
"strings"
"github.com/bakito/adguardhome-sync/pkg/log"
"github.com/bakito/adguardhome-sync/pkg/types"
"github.com/mitchellh/go-homedir"
"github.com/spf13/cobra"
homedir "github.com/mitchellh/go-homedir"
"github.com/spf13/viper"
)
const (
configCron = "cron"
configCron = "cron"
configRunOnStart = "runOnStart"
configBeta = "beta"
configAPIPort = "api.port"
configAPIUsername = "api.username"
configAPIPassword = "api.password"
configOriginURL = "origin.url"
configOriginAPIPath = "origin.apiPath"
@@ -27,11 +33,19 @@ const (
configReplicaUsername = "replica.username"
configReplicaPassword = "replica.password"
configReplicaInsecureSkipVerify = "replica.insecureSkipVerify"
configReplicaAutoSetup = "replica.autoSetup"
envReplicasUsernameFormat = "REPLICA%s_USERNAME"
envReplicasPasswordFormat = "REPLICA%s_PASSWORD"
envReplicasAPIPathFormat = "REPLICA%s_APIPATH"
envReplicasInsecureSkipVerifyFormat = "REPLICA%s_INSECURESKIPVERIFY"
envReplicasAutoSetup = "REPLICA%s_AUTOSETUP"
)
var (
cfgFile string
logger = log.GetLogger("root")
cfgFile string
logger = log.GetLogger("root")
envReplicasURLPattern = regexp.MustCompile(`^REPLICA(\d+)_URL=(.*)`)
)
// rootCmd represents the base command when called without any subcommands
@@ -79,13 +93,48 @@ func initConfig() {
// Search config in home directory with name ".adguardhome-sync" (without extension).
viper.AddConfigPath(home)
viper.SetConfigName(".adguardhome-sync")
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_", ".", "_"))
}
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_", ".", "_"))
viper.AutomaticEnv() // read in environment variables that match
// If a config file is found, read it in.
if err := viper.ReadInConfig(); err == nil {
logger.Info("Using config file:", viper.ConfigFileUsed())
} else if cfgFile != "" {
fmt.Println(err)
os.Exit(1)
}
}
func getConfig() (*types.Config, error) {
cfg := &types.Config{}
if err := viper.Unmarshal(cfg); err != nil {
return nil, err
}
if len(cfg.Replicas) == 0 {
cfg.Replicas = append(cfg.Replicas, collectEnvReplicas()...)
}
return cfg, nil
}
// Manually collect replicas from env.
func collectEnvReplicas() []types.AdGuardInstance {
var replicas []types.AdGuardInstance
for _, v := range os.Environ() {
if envReplicasURLPattern.MatchString(v) {
sm := envReplicasURLPattern.FindStringSubmatch(v)
re := types.AdGuardInstance{
URL: sm[2],
Username: os.Getenv(fmt.Sprintf(envReplicasUsernameFormat, sm[1])),
Password: os.Getenv(fmt.Sprintf(envReplicasPasswordFormat, sm[1])),
APIPath: os.Getenv(fmt.Sprintf(envReplicasAPIPathFormat, sm[1])),
InsecureSkipVerify: strings.EqualFold(os.Getenv(fmt.Sprintf(envReplicasInsecureSkipVerifyFormat, sm[1])), "true"),
AutoSetup: strings.EqualFold(os.Getenv(fmt.Sprintf(envReplicasAutoSetup, sm[1])), "true"),
}
replicas = append(replicas, re)
}
}
return replicas
}

View File

@@ -3,10 +3,8 @@ package cmd
import (
"github.com/bakito/adguardhome-sync/pkg/log"
"github.com/bakito/adguardhome-sync/pkg/sync"
"github.com/bakito/adguardhome-sync/pkg/types"
"github.com/spf13/viper"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
// runCmd represents the run command
@@ -14,16 +12,15 @@ var doCmd = &cobra.Command{
Use: "run",
Short: "Start a synchronisation from origin to replica",
Long: `Synchronizes the configuration form an origin instance to a replica`,
Run: func(cmd *cobra.Command, args []string) {
logger = log.GetLogger("root")
cfg := &types.Config{}
if err := viper.Unmarshal(cfg); err != nil {
RunE: func(cmd *cobra.Command, args []string) error {
logger = log.GetLogger("run")
cfg, err := getConfig()
if err != nil {
logger.Error(err)
return
return err
}
sync.Sync(cfg)
return sync.Sync(cfg)
},
}
@@ -31,6 +28,17 @@ func init() {
rootCmd.AddCommand(doCmd)
doCmd.PersistentFlags().String("cron", "", "The cron expression to run in daemon mode")
_ = viper.BindPFlag(configCron, doCmd.PersistentFlags().Lookup("cron"))
doCmd.PersistentFlags().Bool("runOnStart", true, "Run the sync job on start.")
_ = viper.BindPFlag(configRunOnStart, doCmd.PersistentFlags().Lookup("runOnStart"))
doCmd.PersistentFlags().Int("api-port", 8080, "Sync API Port, the API endpoint will be started to enable remote triggering; if 0 port API is disabled.")
_ = viper.BindPFlag(configAPIPort, doCmd.PersistentFlags().Lookup("api-port"))
doCmd.PersistentFlags().String("api-username", "", "Sync API username")
_ = viper.BindPFlag(configAPIUsername, doCmd.PersistentFlags().Lookup("api-username"))
doCmd.PersistentFlags().String("api-password", "", "Sync API password")
_ = viper.BindPFlag(configAPIPassword, doCmd.PersistentFlags().Lookup("api-password"))
doCmd.PersistentFlags().String("beta", "", "Enable beta features (comma separated list)")
_ = viper.BindPFlag(configBeta, doCmd.PersistentFlags().Lookup("beta"))
doCmd.PersistentFlags().String("origin-url", "", "Origin instance url")
_ = viper.BindPFlag(configOriginURL, doCmd.PersistentFlags().Lookup("origin-url"))
@@ -51,6 +59,8 @@ func init() {
_ = viper.BindPFlag(configReplicaUsername, doCmd.PersistentFlags().Lookup("replica-username"))
doCmd.PersistentFlags().String("replica-password", "", "Replica instance password")
_ = viper.BindPFlag(configReplicaPassword, doCmd.PersistentFlags().Lookup("replica-password"))
doCmd.PersistentFlags().String("replica-insecure-skip-verify", "", "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"))
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"))
}

14
go.mod
View File

@@ -3,12 +3,14 @@ module github.com/bakito/adguardhome-sync
go 1.16
require (
github.com/coreos/go-semver v0.3.0
github.com/go-resty/resty/v2 v2.5.0
github.com/go-resty/resty/v2 v2.6.0
github.com/golang/mock v1.6.0
github.com/google/uuid v1.3.0
github.com/mitchellh/go-homedir v1.1.0
github.com/onsi/ginkgo v1.16.4
github.com/onsi/gomega v1.15.0
github.com/robfig/cron/v3 v3.0.1
github.com/spf13/cobra v1.1.3
github.com/spf13/viper v1.7.1
go.uber.org/zap v1.16.0
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 // indirect
github.com/spf13/cobra v1.2.1
github.com/spf13/viper v1.8.1
go.uber.org/zap v1.19.0
)

522
go.sum
View File

@@ -5,76 +5,151 @@ cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6A
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-resty/resty/v2 v2.5.0 h1:WFb5bD49/85PO7WgAjZ+/TJQ+Ty1XOcWEfD1zIFCM1c=
github.com/go-resty/resty/v2 v2.5.0/go.mod h1:B88+xCTEwvfD94NOuE6GS1wMlnoKNY8eEiNizfNwOwA=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-resty/resty/v2 v2.6.0 h1:joIR5PNLM2EFqqESUjCMGXrWmXNHEU9CEiK813oKYS4=
github.com/go-resty/resty/v2 v2.6.0/go.mod h1:PwvJS6hvaPkjtjNg9ph+VrSD92bi5Zq73w/BIH7cC3Q=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -96,28 +171,28 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
@@ -127,93 +202,108 @@ github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eI
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.15.0 h1:WjP/FQ/sk43MRmnEcT+MlDw2TFvkrXlprrPST/IudjU=
github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ=
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M=
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY=
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw=
github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44=
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM=
go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
go.uber.org/zap v1.19.0 h1:mZQZefskPPCMIBCSEH0v2/iUqqLrYtaeqwD6FUGUnFE=
go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -222,18 +312,29 @@ golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTk
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -242,23 +343,60 @@ golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -266,17 +404,55 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -286,26 +462,85 @@ golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8=
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@@ -315,26 +550,97 @@ google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

View File

@@ -2,7 +2,9 @@ package client
import (
"crypto/tls"
"errors"
"fmt"
"net/http"
"net/url"
"path"
@@ -13,11 +15,12 @@ import (
)
var (
l = log.GetLogger("client")
l = log.GetLogger("client")
SetupNeededError = errors.New("setup needed")
)
// New create a new client
func New(config types.AdGuardInstance) (Client, error) {
var apiURL string
if config.APIPath == "" {
apiURL = fmt.Sprintf("%s/control", config.URL)
@@ -39,6 +42,9 @@ func New(config types.AdGuardInstance) (Client, error) {
cl = cl.SetBasicAuth(config.Username, config.Password)
}
// no redirect
cl.SetRedirectPolicy(resty.NoRedirectPolicy())
return &client{
host: u.Host,
client: cl,
@@ -46,10 +52,12 @@ func New(config types.AdGuardInstance) (Client, error) {
}, nil
}
// Client AdguardHome API client interface
type Client interface {
Host() string
Status() (*types.Status, error)
ToggleProtection(enable bool) error
RewriteList() (*types.RewriteEntries, error)
AddRewriteEntries(e ...types.RewriteEntry) error
DeleteRewriteEntries(e ...types.RewriteEntry) error
@@ -58,20 +66,41 @@ type Client interface {
ToggleFiltering(enabled bool, interval int) error
AddFilters(whitelist bool, e ...types.Filter) error
DeleteFilters(whitelist bool, e ...types.Filter) error
UpdateFilters(whitelist bool, e ...types.Filter) error
RefreshFilters(whitelist bool) error
SetCustomRules(rules types.UserRules) error
ToggleSaveBrowsing(enable bool) error
SafeBrowsing() (bool, error)
ToggleSafeBrowsing(enable bool) error
Parental() (bool, error)
ToggleParental(enable bool) error
SafeSearch() (bool, error)
ToggleSafeSearch(enable bool) error
Services() (*types.Services, error)
Services() (types.Services, error)
SetServices(services types.Services) error
Clients() (*types.Clients, error)
AddClients(client ...types.Client) error
UpdateClients(client ...types.Client) error
DeleteClients(client ...types.Client) error
QueryLogConfig() (*types.QueryLogConfig, error)
SetQueryLogConfig(enabled bool, interval int, anonymizeClientIP bool) error
StatsConfig() (*types.IntervalConfig, error)
SetStatsConfig(interval int) error
Setup() error
AccessList() (*types.AccessList, error)
SetAccessList(*types.AccessList) error
DNSConfig() (*types.DNSConfig, error)
SetDNSConfig(*types.DNSConfig) error
DHCPServerConfig() (*types.DHCPServerConfig, error)
SetDHCPServerConfig(*types.DHCPServerConfig) error
AddDHCPStaticLeases(leases ...types.Lease) error
DeleteDHCPStaticLeases(leases ...types.Lease) error
}
type client struct {
@@ -83,23 +112,65 @@ type client struct {
func (cl *client) Host() string {
return cl.host
}
func (cl *client) doGet(req *resty.Request, url string) error {
rl := cl.log.With("method", "GET", "path", url)
if cl.client.UserInfo != nil {
rl = rl.With("username", cl.client.UserInfo.Username)
}
rl.Debug("do get")
resp, err := req.Get(url)
if err != nil {
if resp != nil && resp.StatusCode() == http.StatusFound {
loc := resp.Header().Get("Location")
if loc == "/install.html" {
return SetupNeededError
}
}
rl.With("status", resp.StatusCode(), "body", string(resp.Body()), "error", err).Debug("error in do get")
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) doPost(req *resty.Request, url string) error {
rl := cl.log.With("method", "POST", "path", url)
if cl.client.UserInfo != nil {
rl = rl.With("username", cl.client.UserInfo.Username)
}
rl.Debug("do post")
resp, err := req.Post(url)
if err != nil {
rl.With("status", resp.StatusCode(), "body", string(resp.Body()), "error", err).Debug("error in do post")
return err
}
rl.With("status", resp.StatusCode(), "body", string(resp.Body())).Debug("got response")
if resp.StatusCode() != http.StatusOK {
return errors.New(resp.Status())
}
return nil
}
func (cl *client) Status() (*types.Status, error) {
status := &types.Status{}
_, err := cl.client.R().EnableTrace().SetResult(status).Get("status")
err := cl.doGet(cl.client.R().EnableTrace().SetResult(status), "status")
return status, err
}
func (cl *client) RewriteList() (*types.RewriteEntries, error) {
rewrites := &types.RewriteEntries{}
_, err := cl.client.R().EnableTrace().SetResult(&rewrites).Get("/rewrite/list")
err := cl.doGet(cl.client.R().EnableTrace().SetResult(&rewrites), "/rewrite/list")
return rewrites, err
}
func (cl *client) AddRewriteEntries(entries ...types.RewriteEntry) error {
for _, e := range entries {
cl.log.With("domain", e.Domain, "answer", e.Answer).Info("Add rewrite entry")
_, err := cl.client.R().EnableTrace().SetBody(&e).Post("/rewrite/add")
err := cl.doPost(cl.client.R().EnableTrace().SetBody(&e), "/rewrite/add")
if err != nil {
return err
}
@@ -110,7 +181,7 @@ func (cl *client) AddRewriteEntries(entries ...types.RewriteEntry) error {
func (cl *client) DeleteRewriteEntries(entries ...types.RewriteEntry) error {
for _, e := range entries {
cl.log.With("domain", e.Domain, "answer", e.Answer).Info("Delete rewrite entry")
_, err := cl.client.R().EnableTrace().SetBody(&e).Post("/rewrite/delete")
err := cl.doPost(cl.client.R().EnableTrace().SetBody(&e), "/rewrite/delete")
if err != nil {
return err
}
@@ -118,41 +189,58 @@ func (cl *client) DeleteRewriteEntries(entries ...types.RewriteEntry) error {
return nil
}
func (cl *client) ToggleSaveBrowsing(enable bool) error {
return cl.toggle("safebrowsing", enable)
func (cl *client) SafeBrowsing() (bool, error) {
return cl.toggleStatus("safebrowsing")
}
func (cl *client) ToggleSafeBrowsing(enable bool) error {
return cl.toggleBool("safebrowsing", enable)
}
func (cl *client) Parental() (bool, error) {
return cl.toggleStatus("parental")
}
func (cl *client) ToggleParental(enable bool) error {
return cl.toggle("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.toggle("safesearch", enable)
return cl.toggleBool("safesearch", enable)
}
func (cl *client) toggle(mode string, enable bool) error {
cl.log.With("mode", mode, "enable", enable).Info("Toggle")
func (cl *client) toggleStatus(mode string) (bool, error) {
fs := &types.EnableConfig{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(fs), fmt.Sprintf("/%s/status", mode))
return fs.Enabled, err
}
func (cl *client) toggleBool(mode string, enable bool) error {
cl.log.With("enable", enable).Info(fmt.Sprintf("Toggle %s", mode))
var target string
if enable {
target = "enable"
} else {
target = "disable"
}
_, err := cl.client.R().EnableTrace().Post(fmt.Sprintf("/%s/%s", mode, target))
return err
return cl.doPost(cl.client.R().EnableTrace(), fmt.Sprintf("/%s/%s", mode, target))
}
func (cl *client) Filtering() (*types.FilteringStatus, error) {
f := &types.FilteringStatus{}
_, err := cl.client.R().EnableTrace().SetResult(f).Get("/filtering/status")
err := cl.doGet(cl.client.R().EnableTrace().SetResult(f), "/filtering/status")
return f, err
}
func (cl *client) AddFilters(whitelist bool, filters ...types.Filter) error {
for _, f := range filters {
cl.log.With("url", f.URL, "whitelist", whitelist).Info("Add filter")
cl.log.With("url", f.URL, "whitelist", whitelist, "enabled", f.Enabled).Info("Add filter")
ff := &types.Filter{Name: f.Name, URL: f.URL, Whitelist: whitelist}
_, err := cl.client.R().EnableTrace().SetBody(ff).Post("/filtering/add_url")
err := cl.doPost(cl.client.R().EnableTrace().SetBody(ff), "/filtering/add_url")
if err != nil {
return err
}
@@ -162,9 +250,21 @@ func (cl *client) AddFilters(whitelist bool, filters ...types.Filter) error {
func (cl *client) DeleteFilters(whitelist bool, filters ...types.Filter) error {
for _, f := range filters {
cl.log.With("url", f.URL, "whitelist", whitelist).Info("Delete filter")
cl.log.With("url", f.URL, "whitelist", whitelist, "enabled", f.Enabled).Info("Delete filter")
ff := &types.Filter{URL: f.URL, Whitelist: whitelist}
_, err := cl.client.R().EnableTrace().SetBody(ff).Post("/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 {
for _, f := range filters {
cl.log.With("url", f.URL, "whitelist", whitelist, "enabled", f.Enabled).Info("Update filter")
fu := &types.FilterUpdate{Whitelist: whitelist, URL: f.URL, Data: types.Filter{ID: f.ID, Name: f.Name, URL: f.URL, Whitelist: whitelist, Enabled: f.Enabled}}
err := cl.doPost(cl.client.R().EnableTrace().SetBody(fu), "/filtering/set_url")
if err != nil {
return err
}
@@ -174,44 +274,48 @@ func (cl *client) DeleteFilters(whitelist bool, filters ...types.Filter) error {
func (cl *client) RefreshFilters(whitelist bool) error {
cl.log.With("whitelist", whitelist).Info("Refresh filter")
_, err := cl.client.R().EnableTrace().SetBody(&types.RefreshFilter{Whitelist: whitelist}).Post("/filtering/refresh")
return err
return cl.doPost(cl.client.R().EnableTrace().SetBody(&types.RefreshFilter{Whitelist: whitelist}), "/filtering/refresh")
}
func (cl *client) ToggleProtection(enable bool) error {
cl.log.With("enable", enable).Info("Toggle protection")
return cl.doPost(cl.client.R().EnableTrace().SetBody(&types.Protection{ProtectionEnabled: enable}), "/dns_config")
}
func (cl *client) SetCustomRules(rules types.UserRules) error {
cl.log.With("rules", len(rules)).Info("Set user rules")
_, err := cl.client.R().EnableTrace().SetBody(rules.String()).Post("/filtering/set_rules")
return err
return cl.doPost(cl.client.R().EnableTrace().SetBody(rules.String()), "/filtering/set_rules")
}
func (cl *client) ToggleFiltering(enabled bool, interval int) error {
cl.log.With("enabled", enabled, "interval", interval).Info("Toggle filtering")
_, err := cl.client.R().EnableTrace().SetBody(&types.FilteringConfig{Enabled: enabled, Interval: interval}).Post("/filtering/config")
return err
return cl.doPost(cl.client.R().EnableTrace().SetBody(&types.FilteringConfig{
EnableConfig: types.EnableConfig{Enabled: enabled},
IntervalConfig: types.IntervalConfig{Interval: interval},
}), "/filtering/config")
}
func (cl *client) Services() (*types.Services, error) {
svcs := &types.Services{}
_, err := cl.client.R().EnableTrace().SetResult(svcs).Get("/blocked_services/list")
func (cl *client) Services() (types.Services, error) {
svcs := types.Services{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(&svcs), "/blocked_services/list")
return svcs, err
}
func (cl *client) SetServices(services types.Services) error {
cl.log.With("services", len(services)).Info("Set services")
_, err := cl.client.R().EnableTrace().SetBody(&services).Post("/blocked_services/set")
return err
return cl.doPost(cl.client.R().EnableTrace().SetBody(&services), "/blocked_services/set")
}
func (cl *client) Clients() (*types.Clients, error) {
clients := &types.Clients{}
_, err := cl.client.R().EnableTrace().SetResult(clients).Get("/clients")
err := cl.doGet(cl.client.R().EnableTrace().SetResult(clients), "/clients")
return clients, err
}
func (cl *client) AddClients(clients ...types.Client) error {
for _, client := range clients {
cl.log.With("name", client.Name).Info("Add client")
_, err := cl.client.R().EnableTrace().SetBody(&client).Post("/clients/add")
err := cl.doPost(cl.client.R().EnableTrace().SetBody(&client), "/clients/add")
if err != nil {
return err
}
@@ -222,7 +326,7 @@ func (cl *client) AddClients(clients ...types.Client) error {
func (cl *client) UpdateClients(clients ...types.Client) error {
for _, client := range clients {
cl.log.With("name", client.Name).Info("Update client")
_, err := cl.client.R().EnableTrace().SetBody(&types.ClientUpdate{Name: client.Name, Data: client}).Post("/clients/update")
err := cl.doPost(cl.client.R().EnableTrace().SetBody(&types.ClientUpdate{Name: client.Name, Data: client}), "/clients/update")
if err != nil {
return err
}
@@ -233,7 +337,114 @@ func (cl *client) UpdateClients(clients ...types.Client) error {
func (cl *client) DeleteClients(clients ...types.Client) error {
for _, client := range clients {
cl.log.With("name", client.Name).Info("Delete client")
_, err := cl.client.R().EnableTrace().SetBody(&client).Post("/clients/delete")
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) {
qlc := &types.QueryLogConfig{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(qlc), "/querylog_info")
return qlc, err
}
func (cl *client) SetQueryLogConfig(enabled bool, interval int, anonymizeClientIP bool) error {
cl.log.With("enabled", enabled, "interval", interval, "anonymizeClientIP", anonymizeClientIP).Info("Set query log config")
return cl.doPost(cl.client.R().EnableTrace().SetBody(&types.QueryLogConfig{
EnableConfig: types.EnableConfig{Enabled: enabled},
IntervalConfig: types.IntervalConfig{Interval: interval},
AnonymizeClientIP: anonymizeClientIP,
}), "/querylog_config")
}
func (cl *client) StatsConfig() (*types.IntervalConfig, error) {
stats := &types.IntervalConfig{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(stats), "/stats_info")
return stats, err
}
func (cl *client) SetStatsConfig(interval int) error {
cl.log.With("interval", interval).Info("Set stats config")
return cl.doPost(cl.client.R().EnableTrace().SetBody(&types.IntervalConfig{Interval: interval}), "/stats_config")
}
func (cl *client) Setup() error {
cl.log.Info("Setup new AdguardHome instance")
cfg := &types.InstallConfig{
Web: types.InstallPort{
IP: "0.0.0.0",
Port: 3000,
Status: "",
CanAutofix: false,
},
DNS: types.InstallPort{
IP: "0.0.0.0",
Port: 53,
Status: "",
CanAutofix: false,
},
}
if cl.client.UserInfo != nil {
cfg.Username = cl.client.UserInfo.Username
cfg.Password = cl.client.UserInfo.Password
}
req := cl.client.R().EnableTrace().SetBody(cfg)
req.UserInfo = nil
return cl.doPost(req, "/install/configure")
}
func (cl *client) AccessList() (*types.AccessList, error) {
al := &types.AccessList{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(al), "/access/list")
return al, err
}
func (cl *client) SetAccessList(list *types.AccessList) error {
cl.log.Info("Set access list")
return cl.doPost(cl.client.R().EnableTrace().SetBody(list), "/access/set")
}
func (cl *client) DNSConfig() (*types.DNSConfig, error) {
cfg := &types.DNSConfig{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(cfg), "/dns_info")
return cfg, err
}
func (cl *client) SetDNSConfig(config *types.DNSConfig) error {
cl.log.Info("Set dns config list")
return cl.doPost(cl.client.R().EnableTrace().SetBody(config), "/dns_config")
}
func (cl *client) DHCPServerConfig() (*types.DHCPServerConfig, error) {
cfg := &types.DHCPServerConfig{}
err := cl.doGet(cl.client.R().EnableTrace().SetResult(cfg), "/dhcp/status")
return cfg, err
}
func (cl *client) SetDHCPServerConfig(config *types.DHCPServerConfig) error {
cl.log.Info("Set dhcp server config")
return cl.doPost(cl.client.R().EnableTrace().SetBody(config), "/dhcp/set_config")
}
func (cl *client) AddDHCPStaticLeases(leases ...types.Lease) error {
for _, l := range leases {
cl.log.With("mac", l.HWAddr, "ip", l.IP, "hostname", l.Hostname).Info("Add static dhcp lease")
err := cl.doPost(cl.client.R().EnableTrace().SetBody(l), "/dhcp/add_static_lease")
if err != nil {
return err
}
}
return nil
}
func (cl *client) DeleteDHCPStaticLeases(leases ...types.Lease) error {
for _, l := range leases {
cl.log.With("mac", l.HWAddr, "ip", l.IP, "hostname", l.Hostname).Info("Delete static dhcp lease")
err := cl.doPost(cl.client.R().EnableTrace().SetBody(l), "/dhcp/remove_static_lease")
if err != nil {
return err
}

View File

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

349
pkg/client/client_test.go Normal file
View File

@@ -0,0 +1,349 @@
package client_test
import (
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"path/filepath"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/bakito/adguardhome-sync/pkg/client"
"github.com/bakito/adguardhome-sync/pkg/types"
"github.com/google/uuid"
)
var (
username = uuid.NewString()
password = uuid.NewString()
)
var _ = Describe("Client", func() {
var (
cl client.Client
ts *httptest.Server
)
AfterEach(func() {
if ts != nil {
ts.Close()
}
})
Context("Host", func() {
It("should read the current host", func() {
cl, _ := client.New(types.AdGuardInstance{URL: "https://foo.bar:3000"})
host := cl.Host()
Ω(host).Should(Equal("foo.bar:3000"))
})
})
Context("Filtering", func() {
It("should read filtering status", func() {
ts, cl = ClientGet("filtering-status.json", "/filtering/status")
fs, err := cl.Filtering()
Ω(err).ShouldNot(HaveOccurred())
Ω(fs.Enabled).Should(BeTrue())
Ω(fs.Filters).Should(HaveLen(2))
})
It("should enable protection", func() {
ts, cl = ClientPost("/filtering/config", `{"enabled":true,"interval":123}`)
err := cl.ToggleFiltering(true, 123)
Ω(err).ShouldNot(HaveOccurred())
})
It("should disable protection", func() {
ts, cl = ClientPost("/filtering/config", `{"enabled":false,"interval":123}`)
err := cl.ToggleFiltering(false, 123)
Ω(err).ShouldNot(HaveOccurred())
})
It("should call RefreshFilters", func() {
ts, cl = ClientPost("/filtering/refresh", `{"whitelist":true}`)
err := cl.RefreshFilters(true)
Ω(err).ShouldNot(HaveOccurred())
})
It("should add Filters", func() {
ts, cl = ClientPost("/filtering/add_url",
`{"id":0,"enabled":false,"url":"foo","name":"","rules_count":0,"whitelist":true}`,
`{"id":0,"enabled":false,"url":"bar","name":"","rules_count":0,"whitelist":true}`,
)
err := cl.AddFilters(true, types.Filter{URL: "foo"}, types.Filter{URL: "bar"})
Ω(err).ShouldNot(HaveOccurred())
})
It("should update Filters", func() {
ts, cl = ClientPost("/filtering/set_url",
`{"url":"foo","data":{"id":0,"enabled":false,"url":"foo","name":"","rules_count":0,"whitelist":true},"whitelist":true}`,
`{"url":"bar","data":{"id":0,"enabled":false,"url":"bar","name":"","rules_count":0,"whitelist":true},"whitelist":true}`,
)
err := cl.UpdateFilters(true, types.Filter{URL: "foo"}, types.Filter{URL: "bar"})
Ω(err).ShouldNot(HaveOccurred())
})
It("should delete Filters", func() {
ts, cl = ClientPost("/filtering/remove_url",
`{"id":0,"enabled":false,"url":"foo","name":"","rules_count":0,"whitelist":true}`,
`{"id":0,"enabled":false,"url":"bar","name":"","rules_count":0,"whitelist":true}`,
)
err := cl.DeleteFilters(true, types.Filter{URL: "foo"}, types.Filter{URL: "bar"})
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("CustomRules", func() {
It("should set SetCustomRules", func() {
ts, cl = ClientPost("/filtering/set_rules", `foo
bar`)
err := cl.SetCustomRules([]string{"foo", "bar"})
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("Status", func() {
It("should read status", func() {
ts, cl = ClientGet("status.json", "/status")
fs, err := cl.Status()
Ω(err).ShouldNot(HaveOccurred())
Ω(fs.DNSAddresses).Should(HaveLen(1))
Ω(fs.DNSAddresses[0]).Should(Equal("192.168.1.2"))
Ω(fs.Version).Should(Equal("v0.105.2"))
})
It("should return SetupNeededError", func() {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Location", "/install.html")
w.WriteHeader(http.StatusFound)
}))
cl, err := client.New(types.AdGuardInstance{URL: ts.URL})
Ω(err).ShouldNot(HaveOccurred())
_, err = cl.Status()
Ω(err).Should(HaveOccurred())
Ω(err).Should(Equal(client.SetupNeededError))
})
})
Context("Setup", func() {
It("should add setup the instance", func() {
ts, cl = ClientPost("/install/configure", fmt.Sprintf(`{"web":{"ip":"0.0.0.0","port":3000,"status":"","can_autofix":false},"dns":{"ip":"0.0.0.0","port":53,"status":"","can_autofix":false},"username":"%s","password":"%s"}`, username, password))
err := cl.Setup()
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("RewriteList", func() {
It("should read RewriteList", func() {
ts, cl = ClientGet("rewrite-list.json", "/rewrite/list")
rwl, err := cl.RewriteList()
Ω(err).ShouldNot(HaveOccurred())
Ω(*rwl).Should(HaveLen(2))
})
It("should add RewriteList", func() {
ts, cl = ClientPost("/rewrite/add", `{"domain":"foo","answer":"foo"}`, `{"domain":"bar","answer":"bar"}`)
err := cl.AddRewriteEntries(types.RewriteEntry{Answer: "foo", Domain: "foo"}, types.RewriteEntry{Answer: "bar", Domain: "bar"})
Ω(err).ShouldNot(HaveOccurred())
})
It("should delete RewriteList", func() {
ts, cl = ClientPost("/rewrite/delete", `{"domain":"foo","answer":"foo"}`, `{"domain":"bar","answer":"bar"}`)
err := cl.DeleteRewriteEntries(types.RewriteEntry{Answer: "foo", Domain: "foo"}, types.RewriteEntry{Answer: "bar", Domain: "bar"})
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("SafeBrowsing", func() {
It("should read safebrowsing status", func() {
ts, cl = ClientGet("safebrowsing-status.json", "/safebrowsing/status")
sb, err := cl.SafeBrowsing()
Ω(err).ShouldNot(HaveOccurred())
Ω(sb).Should(BeTrue())
})
It("should enable safebrowsing", func() {
ts, cl = ClientPost("/safebrowsing/enable", "")
err := cl.ToggleSafeBrowsing(true)
Ω(err).ShouldNot(HaveOccurred())
})
It("should disable safebrowsing", func() {
ts, cl = ClientPost("/safebrowsing/disable", "")
err := cl.ToggleSafeBrowsing(false)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("SafeSearch", func() {
It("should read safesearch status", func() {
ts, cl = ClientGet("safesearch-status.json", "/safesearch/status")
ss, err := cl.SafeSearch()
Ω(err).ShouldNot(HaveOccurred())
Ω(ss).Should(BeTrue())
})
It("should enable safesearch", func() {
ts, cl = ClientPost("/safesearch/enable", "")
err := cl.ToggleSafeSearch(true)
Ω(err).ShouldNot(HaveOccurred())
})
It("should disable safesearch", func() {
ts, cl = ClientPost("/safesearch/disable", "")
err := cl.ToggleSafeSearch(false)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("Parental", func() {
It("should read parental status", func() {
ts, cl = ClientGet("parental-status.json", "/parental/status")
p, err := cl.Parental()
Ω(err).ShouldNot(HaveOccurred())
Ω(p).Should(BeTrue())
})
It("should enable parental", func() {
ts, cl = ClientPost("/parental/enable", "")
err := cl.ToggleParental(true)
Ω(err).ShouldNot(HaveOccurred())
})
It("should disable parental", func() {
ts, cl = ClientPost("/parental/disable", "")
err := cl.ToggleParental(false)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("Protection", func() {
It("should enable protection", func() {
ts, cl = ClientPost("/dns_config", `{"protection_enabled":true}`)
err := cl.ToggleProtection(true)
Ω(err).ShouldNot(HaveOccurred())
})
It("should disable protection", func() {
ts, cl = ClientPost("/dns_config", `{"protection_enabled":false}`)
err := cl.ToggleProtection(false)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("Services", func() {
It("should read Services", func() {
ts, cl = ClientGet("blockedservices-list.json", "/blocked_services/list")
s, err := cl.Services()
Ω(err).ShouldNot(HaveOccurred())
Ω(s).Should(HaveLen(2))
})
It("should set Services", func() {
ts, cl = ClientPost("/blocked_services/set", `["foo","bar"]`)
err := cl.SetServices([]string{"foo", "bar"})
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("Clients", func() {
It("should read Clients", func() {
ts, cl = ClientGet("clients.json", "/clients")
c, err := cl.Clients()
Ω(err).ShouldNot(HaveOccurred())
Ω(c.Clients).Should(HaveLen(2))
})
It("should add Clients", func() {
ts, cl = ClientPost("/clients/add",
`{"ids":["id"],"use_global_settings":false,"use_global_blocked_services":false,"name":"foo","filtering_enabled":false,"parental_enabled":false,"safesearch_enabled":false,"safebrowsing_enabled":false,"disallowed":false,"disallowed_rule":""}`,
)
err := cl.AddClients(types.Client{Name: "foo", Ids: []string{"id"}})
Ω(err).ShouldNot(HaveOccurred())
})
It("should update Clients", func() {
ts, cl = ClientPost("/clients/update",
`{"name":"foo","data":{"ids":["id"],"use_global_settings":false,"use_global_blocked_services":false,"name":"foo","filtering_enabled":false,"parental_enabled":false,"safesearch_enabled":false,"safebrowsing_enabled":false,"disallowed":false,"disallowed_rule":""}}`,
)
err := cl.UpdateClients(types.Client{Name: "foo", Ids: []string{"id"}})
Ω(err).ShouldNot(HaveOccurred())
})
It("should delete Clients", func() {
ts, cl = ClientPost("/clients/delete",
`{"ids":["id"],"use_global_settings":false,"use_global_blocked_services":false,"name":"foo","filtering_enabled":false,"parental_enabled":false,"safesearch_enabled":false,"safebrowsing_enabled":false,"disallowed":false,"disallowed_rule":""}`,
)
err := cl.DeleteClients(types.Client{Name: "foo", Ids: []string{"id"}})
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("QueryLogConfig", func() {
It("should read QueryLogConfig", func() {
ts, cl = ClientGet("querylog_info.json", "/querylog_info")
qlc, err := cl.QueryLogConfig()
Ω(err).ShouldNot(HaveOccurred())
Ω(qlc.Enabled).Should(BeTrue())
Ω(qlc.Interval).Should(Equal(90))
})
It("should set QueryLogConfig", func() {
ts, cl = ClientPost("/querylog_config", `{"enabled":true,"interval":123,"anonymize_client_ip":true}`)
err := cl.SetQueryLogConfig(true, 123, true)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("StatsConfig", func() {
It("should read StatsConfig", func() {
ts, cl = ClientGet("stats_info.json", "/stats_info")
sc, err := cl.StatsConfig()
Ω(err).ShouldNot(HaveOccurred())
Ω(sc.Interval).Should(Equal(1))
})
It("should set StatsConfig", func() {
ts, cl = ClientPost("/stats_config", `{"interval":123}`)
err := cl.SetStatsConfig(123)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("helper functions", func() {
var (
cl client.Client
)
BeforeEach(func() {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusUnauthorized)
}))
var err error
cl, err = client.New(types.AdGuardInstance{URL: ts.URL})
Ω(err).ShouldNot(HaveOccurred())
})
Context("doGet", func() {
It("should return an error on status code != 200", func() {
_, err := cl.Status()
Ω(err).Should(HaveOccurred())
Ω(err.Error()).Should(Equal("401 Unauthorized"))
})
})
Context("doPost", func() {
It("should return an error on status code != 200", func() {
err := cl.SetStatsConfig(123)
Ω(err).Should(HaveOccurred())
Ω(err.Error()).Should(Equal("401 Unauthorized"))
})
})
})
})
func ClientGet(file string, path string) (*httptest.Server, client.Client) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Ω(r.URL.Path).Should(Equal(types.DefaultAPIPath + path))
b, err := ioutil.ReadFile(filepath.Join("../../testdata", file))
Ω(err).ShouldNot(HaveOccurred())
w.Header().Set("Content-Type", "application/json")
_, err = w.Write(b)
Ω(err).ShouldNot(HaveOccurred())
}))
cl, err := client.New(types.AdGuardInstance{URL: ts.URL})
Ω(err).ShouldNot(HaveOccurred())
return ts, cl
}
func ClientPost(path string, content ...string) (*httptest.Server, client.Client) {
index := 0
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Ω(r.URL.Path).Should(Equal(types.DefaultAPIPath + path))
body, err := ioutil.ReadAll(r.Body)
Ω(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
}

View File

@@ -1,10 +1,21 @@
package log
import (
"os"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
var rootLogger *zap.Logger
const (
logHistorySize = 50
envLogLevel = "LOG_LEVEL"
)
var (
rootLogger *zap.Logger
logs []string
)
// GetLogger returns a named logger
func GetLogger(name string) *zap.SugaredLogger {
@@ -14,15 +25,79 @@ func GetLogger(name string) *zap.SugaredLogger {
func init() {
level := zap.InfoLevel
if lvl, ok := os.LookupEnv(envLogLevel); ok {
if err := level.Set(lvl); err != nil {
panic(err)
}
}
cfg := zap.Config{
Level: zap.NewAtomicLevelAt(level),
Development: false,
Encoding: "console",
EncoderConfig: zap.NewDevelopmentEncoderConfig(),
OutputPaths: []string{"stderr"},
OutputPaths: []string{"stdout"},
ErrorOutputPaths: []string{"stderr"},
}
opt := zap.WrapCore(func(c zapcore.Core) zapcore.Core {
return zapcore.NewTee(c, &logList{
enc: zapcore.NewConsoleEncoder(cfg.EncoderConfig),
LevelEnabler: cfg.Level,
})
})
rootLogger, _ = cfg.Build()
rootLogger.Sugar()
rootLogger, _ = cfg.Build(opt)
}
type logList struct {
zapcore.LevelEnabler
enc zapcore.Encoder
}
func (l *logList) clone() *logList {
return &logList{
LevelEnabler: l.LevelEnabler,
enc: l.enc.Clone(),
}
}
func (l *logList) With(fields []zapcore.Field) zapcore.Core {
clone := l.clone()
addFields(clone.enc, fields)
return clone
}
func (l *logList) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {
if l.Enabled(ent.Level) {
return ce.AddCore(ent, l)
}
return ce
}
func (l *logList) Write(ent zapcore.Entry, fields []zapcore.Field) error {
buf, err := l.enc.EncodeEntry(ent, fields)
if err != nil {
return err
}
logs = append(logs, buf.String())
if len(logs) > logHistorySize {
logs = logs[len(logs)-logHistorySize:]
}
return nil
}
func (l *logList) Sync() error {
return nil
}
// Logs get the current logs
func Logs() []string {
return logs
}
func addFields(enc zapcore.ObjectEncoder, fields []zapcore.Field) {
for i := range fields {
fields[i].AddTo(enc)
}
}

623
pkg/mocks/client/mock.go Normal file
View File

@@ -0,0 +1,623 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/bakito/adguardhome-sync/pkg/client (interfaces: Client)
// Package mock_client is a generated GoMock package.
package mock_client
import (
reflect "reflect"
types "github.com/bakito/adguardhome-sync/pkg/types"
gomock "github.com/golang/mock/gomock"
)
// MockClient is a mock of Client interface.
type MockClient struct {
ctrl *gomock.Controller
recorder *MockClientMockRecorder
}
// MockClientMockRecorder is the mock recorder for MockClient.
type MockClientMockRecorder struct {
mock *MockClient
}
// NewMockClient creates a new mock instance.
func NewMockClient(ctrl *gomock.Controller) *MockClient {
mock := &MockClient{ctrl: ctrl}
mock.recorder = &MockClientMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockClient) EXPECT() *MockClientMockRecorder {
return m.recorder
}
// AccessList mocks base method.
func (m *MockClient) AccessList() (*types.AccessList, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AccessList")
ret0, _ := ret[0].(*types.AccessList)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// AccessList indicates an expected call of AccessList.
func (mr *MockClientMockRecorder) AccessList() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AccessList", reflect.TypeOf((*MockClient)(nil).AccessList))
}
// AddClients mocks base method.
func (m *MockClient) AddClients(arg0 ...types.Client) error {
m.ctrl.T.Helper()
varargs := []interface{}{}
for _, a := range arg0 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "AddClients", varargs...)
ret0, _ := ret[0].(error)
return ret0
}
// AddClients indicates an expected call of AddClients.
func (mr *MockClientMockRecorder) AddClients(arg0 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddClients", reflect.TypeOf((*MockClient)(nil).AddClients), arg0...)
}
// AddDHCPStaticLeases mocks base method.
func (m *MockClient) AddDHCPStaticLeases(arg0 ...types.Lease) error {
m.ctrl.T.Helper()
varargs := []interface{}{}
for _, a := range arg0 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "AddDHCPStaticLeases", varargs...)
ret0, _ := ret[0].(error)
return ret0
}
// AddDHCPStaticLeases indicates an expected call of AddDHCPStaticLeases.
func (mr *MockClientMockRecorder) AddDHCPStaticLeases(arg0 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddDHCPStaticLeases", reflect.TypeOf((*MockClient)(nil).AddDHCPStaticLeases), arg0...)
}
// AddFilters mocks base method.
func (m *MockClient) AddFilters(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, "AddFilters", varargs...)
ret0, _ := ret[0].(error)
return ret0
}
// AddFilters indicates an expected call of AddFilters.
func (mr *MockClientMockRecorder) AddFilters(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0}, arg1...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddFilters", reflect.TypeOf((*MockClient)(nil).AddFilters), varargs...)
}
// AddRewriteEntries mocks base method.
func (m *MockClient) AddRewriteEntries(arg0 ...types.RewriteEntry) error {
m.ctrl.T.Helper()
varargs := []interface{}{}
for _, a := range arg0 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "AddRewriteEntries", varargs...)
ret0, _ := ret[0].(error)
return ret0
}
// AddRewriteEntries indicates an expected call of AddRewriteEntries.
func (mr *MockClientMockRecorder) AddRewriteEntries(arg0 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddRewriteEntries", reflect.TypeOf((*MockClient)(nil).AddRewriteEntries), arg0...)
}
// Clients mocks base method.
func (m *MockClient) Clients() (*types.Clients, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Clients")
ret0, _ := ret[0].(*types.Clients)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Clients indicates an expected call of Clients.
func (mr *MockClientMockRecorder) Clients() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Clients", reflect.TypeOf((*MockClient)(nil).Clients))
}
// DHCPServerConfig mocks base method.
func (m *MockClient) DHCPServerConfig() (*types.DHCPServerConfig, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DHCPServerConfig")
ret0, _ := ret[0].(*types.DHCPServerConfig)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// DHCPServerConfig indicates an expected call of DHCPServerConfig.
func (mr *MockClientMockRecorder) DHCPServerConfig() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DHCPServerConfig", reflect.TypeOf((*MockClient)(nil).DHCPServerConfig))
}
// DNSConfig mocks base method.
func (m *MockClient) DNSConfig() (*types.DNSConfig, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DNSConfig")
ret0, _ := ret[0].(*types.DNSConfig)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// DNSConfig indicates an expected call of DNSConfig.
func (mr *MockClientMockRecorder) DNSConfig() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DNSConfig", reflect.TypeOf((*MockClient)(nil).DNSConfig))
}
// DeleteClients mocks base method.
func (m *MockClient) DeleteClients(arg0 ...types.Client) error {
m.ctrl.T.Helper()
varargs := []interface{}{}
for _, a := range arg0 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "DeleteClients", varargs...)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteClients indicates an expected call of DeleteClients.
func (mr *MockClientMockRecorder) DeleteClients(arg0 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteClients", reflect.TypeOf((*MockClient)(nil).DeleteClients), arg0...)
}
// DeleteDHCPStaticLeases mocks base method.
func (m *MockClient) DeleteDHCPStaticLeases(arg0 ...types.Lease) error {
m.ctrl.T.Helper()
varargs := []interface{}{}
for _, a := range arg0 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "DeleteDHCPStaticLeases", varargs...)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteDHCPStaticLeases indicates an expected call of DeleteDHCPStaticLeases.
func (mr *MockClientMockRecorder) DeleteDHCPStaticLeases(arg0 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteDHCPStaticLeases", reflect.TypeOf((*MockClient)(nil).DeleteDHCPStaticLeases), arg0...)
}
// DeleteFilters mocks base method.
func (m *MockClient) DeleteFilters(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, "DeleteFilters", varargs...)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteFilters indicates an expected call of DeleteFilters.
func (mr *MockClientMockRecorder) DeleteFilters(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0}, arg1...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteFilters", reflect.TypeOf((*MockClient)(nil).DeleteFilters), varargs...)
}
// DeleteRewriteEntries mocks base method.
func (m *MockClient) DeleteRewriteEntries(arg0 ...types.RewriteEntry) error {
m.ctrl.T.Helper()
varargs := []interface{}{}
for _, a := range arg0 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "DeleteRewriteEntries", varargs...)
ret0, _ := ret[0].(error)
return ret0
}
// DeleteRewriteEntries indicates an expected call of DeleteRewriteEntries.
func (mr *MockClientMockRecorder) DeleteRewriteEntries(arg0 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteRewriteEntries", reflect.TypeOf((*MockClient)(nil).DeleteRewriteEntries), arg0...)
}
// Filtering mocks base method.
func (m *MockClient) Filtering() (*types.FilteringStatus, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Filtering")
ret0, _ := ret[0].(*types.FilteringStatus)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Filtering indicates an expected call of Filtering.
func (mr *MockClientMockRecorder) Filtering() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Filtering", reflect.TypeOf((*MockClient)(nil).Filtering))
}
// Host mocks base method.
func (m *MockClient) Host() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Host")
ret0, _ := ret[0].(string)
return ret0
}
// Host indicates an expected call of Host.
func (mr *MockClientMockRecorder) Host() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Host", reflect.TypeOf((*MockClient)(nil).Host))
}
// Parental mocks base method.
func (m *MockClient) Parental() (bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Parental")
ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Parental indicates an expected call of Parental.
func (mr *MockClientMockRecorder) Parental() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Parental", reflect.TypeOf((*MockClient)(nil).Parental))
}
// QueryLogConfig mocks base method.
func (m *MockClient) QueryLogConfig() (*types.QueryLogConfig, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "QueryLogConfig")
ret0, _ := ret[0].(*types.QueryLogConfig)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// QueryLogConfig indicates an expected call of QueryLogConfig.
func (mr *MockClientMockRecorder) QueryLogConfig() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueryLogConfig", reflect.TypeOf((*MockClient)(nil).QueryLogConfig))
}
// RefreshFilters mocks base method.
func (m *MockClient) RefreshFilters(arg0 bool) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "RefreshFilters", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// RefreshFilters indicates an expected call of RefreshFilters.
func (mr *MockClientMockRecorder) RefreshFilters(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RefreshFilters", reflect.TypeOf((*MockClient)(nil).RefreshFilters), arg0)
}
// RewriteList mocks base method.
func (m *MockClient) RewriteList() (*types.RewriteEntries, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "RewriteList")
ret0, _ := ret[0].(*types.RewriteEntries)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// RewriteList indicates an expected call of RewriteList.
func (mr *MockClientMockRecorder) RewriteList() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RewriteList", reflect.TypeOf((*MockClient)(nil).RewriteList))
}
// SafeBrowsing mocks base method.
func (m *MockClient) SafeBrowsing() (bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SafeBrowsing")
ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// SafeBrowsing indicates an expected call of SafeBrowsing.
func (mr *MockClientMockRecorder) SafeBrowsing() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SafeBrowsing", reflect.TypeOf((*MockClient)(nil).SafeBrowsing))
}
// SafeSearch mocks base method.
func (m *MockClient) SafeSearch() (bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SafeSearch")
ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// SafeSearch indicates an expected call of SafeSearch.
func (mr *MockClientMockRecorder) SafeSearch() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SafeSearch", reflect.TypeOf((*MockClient)(nil).SafeSearch))
}
// Services mocks base method.
func (m *MockClient) Services() (types.Services, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Services")
ret0, _ := ret[0].(types.Services)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Services indicates an expected call of Services.
func (mr *MockClientMockRecorder) Services() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Services", reflect.TypeOf((*MockClient)(nil).Services))
}
// SetAccessList mocks base method.
func (m *MockClient) SetAccessList(arg0 *types.AccessList) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetAccessList", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// SetAccessList indicates an expected call of SetAccessList.
func (mr *MockClientMockRecorder) SetAccessList(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetAccessList", reflect.TypeOf((*MockClient)(nil).SetAccessList), arg0)
}
// SetCustomRules mocks base method.
func (m *MockClient) SetCustomRules(arg0 types.UserRules) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetCustomRules", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// SetCustomRules indicates an expected call of SetCustomRules.
func (mr *MockClientMockRecorder) SetCustomRules(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetCustomRules", reflect.TypeOf((*MockClient)(nil).SetCustomRules), arg0)
}
// SetDHCPServerConfig mocks base method.
func (m *MockClient) SetDHCPServerConfig(arg0 *types.DHCPServerConfig) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetDHCPServerConfig", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// SetDHCPServerConfig indicates an expected call of SetDHCPServerConfig.
func (mr *MockClientMockRecorder) SetDHCPServerConfig(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDHCPServerConfig", reflect.TypeOf((*MockClient)(nil).SetDHCPServerConfig), arg0)
}
// SetDNSConfig mocks base method.
func (m *MockClient) SetDNSConfig(arg0 *types.DNSConfig) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetDNSConfig", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// SetDNSConfig indicates an expected call of SetDNSConfig.
func (mr *MockClientMockRecorder) SetDNSConfig(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDNSConfig", reflect.TypeOf((*MockClient)(nil).SetDNSConfig), arg0)
}
// SetQueryLogConfig mocks base method.
func (m *MockClient) SetQueryLogConfig(arg0 bool, arg1 int, arg2 bool) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetQueryLogConfig", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
return ret0
}
// SetQueryLogConfig indicates an expected call of SetQueryLogConfig.
func (mr *MockClientMockRecorder) SetQueryLogConfig(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetQueryLogConfig", reflect.TypeOf((*MockClient)(nil).SetQueryLogConfig), arg0, arg1, arg2)
}
// SetServices mocks base method.
func (m *MockClient) SetServices(arg0 types.Services) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetServices", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// SetServices indicates an expected call of SetServices.
func (mr *MockClientMockRecorder) SetServices(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetServices", reflect.TypeOf((*MockClient)(nil).SetServices), arg0)
}
// SetStatsConfig mocks base method.
func (m *MockClient) SetStatsConfig(arg0 int) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetStatsConfig", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// SetStatsConfig indicates an expected call of SetStatsConfig.
func (mr *MockClientMockRecorder) SetStatsConfig(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetStatsConfig", reflect.TypeOf((*MockClient)(nil).SetStatsConfig), arg0)
}
// Setup mocks base method.
func (m *MockClient) Setup() error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Setup")
ret0, _ := ret[0].(error)
return ret0
}
// Setup indicates an expected call of Setup.
func (mr *MockClientMockRecorder) Setup() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Setup", reflect.TypeOf((*MockClient)(nil).Setup))
}
// StatsConfig mocks base method.
func (m *MockClient) StatsConfig() (*types.IntervalConfig, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "StatsConfig")
ret0, _ := ret[0].(*types.IntervalConfig)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// StatsConfig indicates an expected call of StatsConfig.
func (mr *MockClientMockRecorder) StatsConfig() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StatsConfig", reflect.TypeOf((*MockClient)(nil).StatsConfig))
}
// Status mocks base method.
func (m *MockClient) Status() (*types.Status, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Status")
ret0, _ := ret[0].(*types.Status)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Status indicates an expected call of Status.
func (mr *MockClientMockRecorder) Status() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Status", reflect.TypeOf((*MockClient)(nil).Status))
}
// ToggleFiltering mocks base method.
func (m *MockClient) ToggleFiltering(arg0 bool, arg1 int) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ToggleFiltering", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// ToggleFiltering indicates an expected call of ToggleFiltering.
func (mr *MockClientMockRecorder) ToggleFiltering(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleFiltering", reflect.TypeOf((*MockClient)(nil).ToggleFiltering), arg0, arg1)
}
// ToggleParental mocks base method.
func (m *MockClient) ToggleParental(arg0 bool) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ToggleParental", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// ToggleParental indicates an expected call of ToggleParental.
func (mr *MockClientMockRecorder) ToggleParental(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleParental", reflect.TypeOf((*MockClient)(nil).ToggleParental), arg0)
}
// ToggleProtection mocks base method.
func (m *MockClient) ToggleProtection(arg0 bool) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ToggleProtection", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// ToggleProtection indicates an expected call of ToggleProtection.
func (mr *MockClientMockRecorder) ToggleProtection(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleProtection", reflect.TypeOf((*MockClient)(nil).ToggleProtection), arg0)
}
// ToggleSafeBrowsing mocks base method.
func (m *MockClient) ToggleSafeBrowsing(arg0 bool) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ToggleSafeBrowsing", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// ToggleSafeBrowsing indicates an expected call of ToggleSafeBrowsing.
func (mr *MockClientMockRecorder) ToggleSafeBrowsing(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleSafeBrowsing", reflect.TypeOf((*MockClient)(nil).ToggleSafeBrowsing), arg0)
}
// ToggleSafeSearch mocks base method.
func (m *MockClient) ToggleSafeSearch(arg0 bool) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ToggleSafeSearch", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// ToggleSafeSearch indicates an expected call of ToggleSafeSearch.
func (mr *MockClientMockRecorder) ToggleSafeSearch(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToggleSafeSearch", reflect.TypeOf((*MockClient)(nil).ToggleSafeSearch), arg0)
}
// UpdateClients mocks base method.
func (m *MockClient) UpdateClients(arg0 ...types.Client) error {
m.ctrl.T.Helper()
varargs := []interface{}{}
for _, a := range arg0 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "UpdateClients", varargs...)
ret0, _ := ret[0].(error)
return ret0
}
// UpdateClients indicates an expected call of UpdateClients.
func (mr *MockClientMockRecorder) UpdateClients(arg0 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateClients", reflect.TypeOf((*MockClient)(nil).UpdateClients), arg0...)
}
// UpdateFilters mocks base method.
func (m *MockClient) UpdateFilters(arg0 bool, arg1 ...types.Filter) error {
m.ctrl.T.Helper()
varargs := []interface{}{arg0}
for _, a := range arg1 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "UpdateFilters", varargs...)
ret0, _ := ret[0].(error)
return ret0
}
// UpdateFilters indicates an expected call of UpdateFilters.
func (mr *MockClientMockRecorder) UpdateFilters(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0}, arg1...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateFilters", reflect.TypeOf((*MockClient)(nil).UpdateFilters), varargs...)
}

BIN
pkg/sync/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

141
pkg/sync/http.go Normal file
View File

@@ -0,0 +1,141 @@
package sync
import (
_ "embed"
"context"
"fmt"
"net"
"net/http"
"os"
"os/signal"
"strings"
"syscall"
"time"
"github.com/bakito/adguardhome-sync/pkg/log"
)
var (
//go:embed index.html
index []byte
//go:embed favicon.ico
favicon []byte
)
func (w *worker) handleSync(rw http.ResponseWriter, req *http.Request) {
switch req.Method {
case http.MethodPost:
l.With("remote-addr", req.RemoteAddr).Info("Starting sync from API")
w.sync()
default:
http.Error(rw, "only POST allowed", http.StatusBadRequest)
}
}
func (w *worker) handleRoot(rw http.ResponseWriter, _ *http.Request) {
rw.Header().Set("Content-Type", "text/html")
_, _ = rw.Write(index)
}
func (w *worker) handleFavicon(rw http.ResponseWriter, _ *http.Request) {
rw.Header().Set("Content-Type", "image/x-icon")
_, _ = rw.Write(favicon)
}
func (w *worker) handleLogs(rw http.ResponseWriter, _ *http.Request) {
_, _ = rw.Write([]byte(strings.Join(log.Logs(), "")))
}
func (w *worker) basicAuth(h http.HandlerFunc) http.HandlerFunc {
return func(rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
username, password, authOK := r.BasicAuth()
if !authOK {
http.Error(rw, "Not authorized", 401)
return
}
if username != w.cfg.API.Username || password != w.cfg.API.Password {
http.Error(rw, "Not authorized", 401)
return
}
h.ServeHTTP(rw, r)
}
}
func use(h http.HandlerFunc, middleware ...func(http.HandlerFunc) http.HandlerFunc) http.HandlerFunc {
for _, m := range middleware {
h = m(h)
}
return h
}
func (w *worker) listenAndServe() {
l.With("port", w.cfg.API.Port).Info("Starting API server")
ctx, cancel := context.WithCancel(context.Background())
mux := http.NewServeMux()
httpServer := &http.Server{
Addr: fmt.Sprintf(":%d", w.cfg.API.Port),
Handler: mux,
BaseContext: func(_ net.Listener) context.Context { return ctx },
}
var mw []func(http.HandlerFunc) http.HandlerFunc
if w.cfg.API.Username != "" && w.cfg.API.Password != "" {
mw = append(mw, w.basicAuth)
}
mux.HandleFunc("/api/v1/sync", use(w.handleSync, mw...))
mux.HandleFunc("/api/v1/logs", use(w.handleLogs, mw...))
mux.HandleFunc("/favicon.ico", use(w.handleFavicon, mw...))
mux.HandleFunc("/", use(w.handleRoot, mw...))
go func() {
if err := httpServer.ListenAndServe(); err != http.ErrServerClosed {
l.With("error", err).Fatalf("HTTP server ListenAndServe")
}
}()
signalChan := make(chan os.Signal, 1)
signal.Notify(
signalChan,
syscall.SIGHUP, // kill -SIGHUP XXXX
syscall.SIGINT, // kill -SIGINT XXXX or Ctrl+c
syscall.SIGQUIT, // kill -SIGQUIT XXXX
)
<-signalChan
l.Info("os.Interrupt - shutting down...")
go func() {
<-signalChan
l.Fatal("os.Kill - terminating...")
}()
gracefullCtx, cancelShutdown := context.WithTimeout(context.Background(), 5*time.Second)
defer cancelShutdown()
if w.cron != nil {
l.Info("Stopping cron")
w.cron.Stop()
}
if err := httpServer.Shutdown(gracefullCtx); err != nil {
l.With("error", err).Error("Shutdown error")
defer os.Exit(1)
} else {
l.Info("API server stopped")
}
// manually cancel context if not using httpServer.RegisterOnShutdown(cancel)
cancel()
defer os.Exit(0)
}

45
pkg/sync/index.html Normal file
View File

@@ -0,0 +1,45 @@
<html>
<head>
<title>AdGuardHome sync</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-KyZXEAg3QhqLMpG8r+8fhAXLRk2vvoC2f3B09zVXn8CA5QIVfZOJ3BCsw2P0p/We" crossorigin="anonymous">
<script type="text/javascript" src="https://code.jquery.com/jquery-3.6.0.min.js">
</script>
<script type="text/javascript">
$(document).ready(function () {
$("#showLogs").click(function () {
$.get("/api/v1/logs", {}, function (data) {
$('#logs').html(data);
}
);
});
$("#sync").click(function () {
$.post("/api/v1/sync", {}, function (data) {
});
});
$("#showLogs").click()
});
</script>
<link rel="shortcut icon" href="/favicon.ico">
</head>
<body>
<div class="container-fluid px-4">
<div class="row">
<h1 class="display-4">AdGuardHome sync</h1>
</div>
<div class="row">
<div class="col">
<div class="btn-group" role="group">
<input class="btn btn-success" type="button" id="sync" value="Synchronize"/>
<input class="btn btn-secondary" type="button" id="showLogs" value="Update Logs"/>
</div>
</div>
</div>
<div class="row mt-3">
<div class="col-12">
<pre class="p-3 border"><code id="logs"></code></pre>
</div>
</div>
</div>
</body>
</html>

View File

@@ -1,9 +1,13 @@
package sync
import (
"errors"
"fmt"
"github.com/bakito/adguardhome-sync/pkg/client"
"github.com/bakito/adguardhome-sync/pkg/log"
"github.com/bakito/adguardhome-sync/pkg/types"
"github.com/bakito/adguardhome-sync/version"
"github.com/robfig/cron/v3"
"go.uber.org/zap"
)
@@ -13,41 +17,98 @@ var (
)
// Sync config from origin to replica
func Sync(cfg *types.Config) {
func Sync(cfg *types.Config) error {
if cfg.Origin.URL == "" {
return fmt.Errorf("origin URL is required")
}
if len(cfg.UniqueReplicas()) == 0 {
return fmt.Errorf("no replicas configured")
}
l.With("version", version.Version, "build", version.Build).Info("AdGuardHome sync")
cfg.Origin.AutoSetup = false
w := &worker{
cfg: cfg,
createClient: func(ai types.AdGuardInstance) (client.Client, error) {
return client.New(ai)
},
}
if cfg.Cron != "" {
c := cron.New()
w.cron = cron.New()
cl := l.With("cron", cfg.Cron)
_, err := c.AddFunc(cfg.Cron, func() {
sync(cfg)
_, err := w.cron.AddFunc(cfg.Cron, func() {
w.sync()
})
if err != nil {
cl.With("error", err).Error("Error creating cron job")
return
cl.With("error", err).Error("Error during cron job setup")
return err
}
cl.Info("Setup cronjob")
if cfg.API.Port != 0 {
w.cron.Start()
} else {
w.cron.Run()
}
cl.Info("Starting cronjob")
c.Run()
} else {
sync(cfg)
}
if cfg.RunOnStart {
l.Info("Running sync on startup")
w.sync()
}
if cfg.API.Port != 0 {
w.listenAndServe()
}
return nil
}
func sync(cfg *types.Config) {
oc, err := client.New(cfg.Origin)
type worker struct {
cfg *types.Config
running bool
cron *cron.Cron
createClient func(instance types.AdGuardInstance) (client.Client, error)
}
func (w *worker) sync() {
if w.running {
l.Info("Sync already running")
return
}
w.running = true
defer func() { w.running = false }()
oc, err := w.createClient(w.cfg.Origin)
if err != nil {
l.With("error", err, "url", cfg.Origin.URL).Error("Error creating origin client")
l.With("error", err, "url", w.cfg.Origin.URL).Error("Error creating origin client")
return
}
sl := l.With("from", oc.Host())
o := &origin{}
o.status, err = oc.Status()
if err != nil {
sl.With("error", err).Error("Error getting origin status")
return
}
o.parental, err = oc.Parental()
if err != nil {
sl.With("error", err).Error("Error getting parental status")
return
}
o.safeSearch, err = oc.SafeSearch()
if err != nil {
sl.With("error", err).Error("Error getting safe search status")
return
}
o.safeBrowsing, err = oc.SafeBrowsing()
if err != nil {
sl.With("error", err).Error("Error getting safe browsing status")
return
}
o.rewrites, err = oc.RewriteList()
if err != nil {
sl.With("error", err).Error("Error getting origin rewrites")
@@ -70,106 +131,148 @@ func sync(cfg *types.Config) {
sl.With("error", err).Error("Error getting origin clients")
return
}
o.queryLogConfig, err = oc.QueryLogConfig()
if err != nil {
sl.With("error", err).Error("Error getting query log config")
return
}
o.statsConfig, err = oc.StatsConfig()
if err != nil {
sl.With("error", err).Error("Error getting stats config")
return
}
replicas := cfg.UniqueReplicas()
o.accessList, err = oc.AccessList()
if err != nil {
sl.With("error", err).Error("Error getting access list")
return
}
o.dnsConfig, err = oc.DNSConfig()
if err != nil {
sl.With("error", err).Error("Error getting dns config")
return
}
o.dhcpServerConfig, err = oc.DHCPServerConfig()
if err != nil {
sl.With("error", err).Error("Error getting dhcp server config")
return
}
replicas := w.cfg.UniqueReplicas()
for _, replica := range replicas {
syncTo(sl, o, replica)
w.syncTo(sl, o, replica)
}
}
func syncTo(l *zap.SugaredLogger, o *origin, replica types.AdGuardInstance) {
func (w *worker) syncTo(l *zap.SugaredLogger, o *origin, replica types.AdGuardInstance) {
rc, err := client.New(replica)
rc, err := w.createClient(replica)
if err != nil {
l.With("error", err, "url", replica.URL).Error("Error creating replica client")
return
}
rl := l.With("to", rc.Host())
rl.Info("Start sync")
rs, err := rc.Status()
rs, err := w.statusWithSetup(rl, replica, rc)
if err != nil {
l.With("error", err).Error("Error getting replica status")
rl.With("error", err).Error("Error getting replica status")
return
}
if o.status.Version != rs.Version {
l.With("originVersion", o.status.Version, "replicaVersion", rs.Version).Warn("Versions do not match")
rl.With("originVersion", o.status.Version, "replicaVersion", rs.Version).Warn("Versions do not match")
}
err = syncRewrites(o.rewrites, rc)
err = w.syncGeneralSettings(o, rs, rc)
if err != nil {
l.With("error", err).Error("Error syncing rewrites")
return
}
err = syncFilters(o.filters, rc)
if err != nil {
l.With("error", err).Error("Error syncing filters")
rl.With("error", err).Error("Error syncing general settings")
return
}
err = syncServices(o.services, rc)
err = w.syncConfigs(o, rc)
if err != nil {
l.With("error", err).Error("Error syncing services")
rl.With("error", err).Error("Error syncing configs")
return
}
if err = syncClients(o.clients, rc); err != nil {
l.With("error", err).Error("Error syncing clients")
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")
}
func syncServices(os *types.Services, replica client.Client) error {
func (w *worker) statusWithSetup(rl *zap.SugaredLogger, replica types.AdGuardInstance, rc client.Client) (*types.Status, error) {
rs, err := rc.Status()
if err != nil {
if replica.AutoSetup && errors.Is(err, client.SetupNeededError) {
if serr := rc.Setup(); serr != nil {
rl.With("error", serr).Error("Error setup AdGuardHome")
return nil, err
}
return rc.Status()
}
return nil, err
}
return rs, err
}
func (w *worker) syncServices(os types.Services, replica client.Client) error {
rs, err := replica.Services()
if err != nil {
return err
}
if !os.Equals(rs) {
if err := replica.SetServices(*os); err != nil {
if err := replica.SetServices(os); err != nil {
return err
}
}
return nil
}
func syncFilters(of *types.FilteringStatus, replica client.Client) error {
func (w *worker) syncFilters(of *types.FilteringStatus, replica client.Client) error {
rf, err := replica.Filtering()
if err != nil {
return err
}
fa, fd := rf.Filters.Merge(of.Filters)
if err = replica.AddFilters(false, fa...); err != nil {
if err = w.syncFilterType(of.Filters, rf.Filters, false, replica); err != nil {
return err
}
if len(fa) > 0 {
if err = replica.RefreshFilters(false); err != nil {
return err
}
}
if err = replica.DeleteFilters(false, fd...); err != nil {
return err
}
fa, fd = rf.WhitelistFilters.Merge(of.WhitelistFilters)
if err = replica.AddFilters(true, fa...); err != nil {
return err
}
if len(fa) > 0 {
if err = replica.RefreshFilters(true); err != nil {
return err
}
}
if err = replica.DeleteFilters(true, fd...); err != nil {
if err = w.syncFilterType(of.WhitelistFilters, rf.WhitelistFilters, true, replica); err != nil {
return err
}
@@ -185,14 +288,36 @@ func syncFilters(of *types.FilteringStatus, replica client.Client) error {
return nil
}
func syncRewrites(or *types.RewriteEntries, replica client.Client) error {
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 {
replicaRewrites, err := replica.RewriteList()
if err != nil {
return err
}
a, r := replicaRewrites.Merge(or)
a, r, d := replicaRewrites.Merge(or)
if err = replica.AddRewriteEntries(a...); err != nil {
return err
@@ -200,10 +325,15 @@ func syncRewrites(or *types.RewriteEntries, replica client.Client) error {
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 syncClients(oc *types.Clients, replica client.Client) error {
func (w *worker) syncClients(oc *types.Clients, replica client.Client) error {
rc, err := replica.Clients()
if err != nil {
return err
@@ -223,10 +353,117 @@ func syncClients(oc *types.Clients, replica client.Client) error {
return nil
}
type origin struct {
status *types.Status
rewrites *types.RewriteEntries
services *types.Services
filters *types.FilteringStatus
clients *types.Clients
func (w *worker) syncGeneralSettings(o *origin, rs *types.Status, replica client.Client) error {
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 {
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
}
}
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 {
al, err := rc.AccessList()
if err != nil {
return err
}
if !al.Equals(oal) {
if err = rc.SetAccessList(oal); err != nil {
return err
}
}
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 err != nil {
return err
}
if !sc.Equals(osc) {
if err = rc.SetDHCPServerConfig(osc); err != nil {
return err
}
}
a, r := sc.StaticLeases.Merge(osc.StaticLeases)
if err = rc.AddDHCPStaticLeases(a...); err != nil {
return err
}
if err = rc.DeleteDHCPStaticLeases(r...); err != nil {
return err
}
return nil
}
type origin struct {
status *types.Status
rewrites *types.RewriteEntries
services types.Services
filters *types.FilteringStatus
clients *types.Clients
queryLogConfig *types.QueryLogConfig
statsConfig *types.IntervalConfig
accessList *types.AccessList
dnsConfig *types.DNSConfig
dhcpServerConfig *types.DHCPServerConfig
parental bool
safeSearch bool
safeBrowsing bool
}

View File

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

466
pkg/sync/sync_test.go Normal file
View File

@@ -0,0 +1,466 @@
package sync
import (
"errors"
"github.com/bakito/adguardhome-sync/pkg/client"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
mc "github.com/bakito/adguardhome-sync/pkg/mocks/client"
"github.com/bakito/adguardhome-sync/pkg/types"
gm "github.com/golang/mock/gomock"
"github.com/google/uuid"
)
var _ = Describe("Sync", func() {
var (
mockCtrl *gm.Controller
cl *mc.MockClient
w *worker
te error
)
BeforeEach(func() {
mockCtrl = gm.NewController(GinkgoT())
cl = mc.NewMockClient(mockCtrl)
w = &worker{
createClient: func(instance types.AdGuardInstance) (client.Client, error) {
return cl, nil
},
}
te = errors.New(uuid.NewString())
})
AfterEach(func() {
defer mockCtrl.Finish()
})
Context("worker", func() {
Context("syncRewrites", func() {
var (
domain string
answer string
reO types.RewriteEntries
reR types.RewriteEntries
)
BeforeEach(func() {
domain = uuid.NewString()
answer = uuid.NewString()
reO = []types.RewriteEntry{{Domain: domain, Answer: answer}}
reR = []types.RewriteEntry{{Domain: domain, Answer: answer}}
})
It("should have no changes (empty slices)", func() {
cl.EXPECT().RewriteList().Return(&reR, nil)
cl.EXPECT().AddRewriteEntries()
cl.EXPECT().DeleteRewriteEntries()
err := w.syncRewrites(l, &reO, cl)
Ω(err).ShouldNot(HaveOccurred())
})
It("should add one rewrite entry", func() {
reR = []types.RewriteEntry{}
cl.EXPECT().RewriteList().Return(&reR, nil)
cl.EXPECT().AddRewriteEntries(reO[0])
cl.EXPECT().DeleteRewriteEntries()
err := w.syncRewrites(l, &reO, cl)
Ω(err).ShouldNot(HaveOccurred())
})
It("should remove one rewrite entry", func() {
reO = []types.RewriteEntry{}
cl.EXPECT().RewriteList().Return(&reR, nil)
cl.EXPECT().AddRewriteEntries()
cl.EXPECT().DeleteRewriteEntries(reR[0])
err := w.syncRewrites(l, &reO, cl)
Ω(err).ShouldNot(HaveOccurred())
})
It("should remove one rewrite entry", func() {
reO = []types.RewriteEntry{}
cl.EXPECT().RewriteList().Return(&reR, nil)
cl.EXPECT().AddRewriteEntries()
cl.EXPECT().DeleteRewriteEntries(reR[0])
err := w.syncRewrites(l, &reO, cl)
Ω(err).ShouldNot(HaveOccurred())
})
It("should return error when error on RewriteList()", func() {
cl.EXPECT().RewriteList().Return(nil, te)
err := w.syncRewrites(l, &reO, cl)
Ω(err).Should(HaveOccurred())
})
It("should return error when error on AddRewriteEntries()", func() {
cl.EXPECT().RewriteList().Return(&reR, nil)
cl.EXPECT().AddRewriteEntries().Return(te)
err := w.syncRewrites(l, &reO, cl)
Ω(err).Should(HaveOccurred())
})
It("should return error when error on DeleteRewriteEntries()", func() {
cl.EXPECT().RewriteList().Return(&reR, nil)
cl.EXPECT().AddRewriteEntries()
cl.EXPECT().DeleteRewriteEntries().Return(te)
err := w.syncRewrites(l, &reO, cl)
Ω(err).Should(HaveOccurred())
})
})
Context("syncClients", func() {
var (
clO *types.Clients
clR *types.Clients
name string
)
BeforeEach(func() {
name = uuid.NewString()
clO = &types.Clients{Clients: []types.Client{{Name: name}}}
clR = &types.Clients{Clients: []types.Client{{Name: name}}}
})
It("should have no changes (empty slices)", func() {
cl.EXPECT().Clients().Return(clR, nil)
cl.EXPECT().AddClients()
cl.EXPECT().UpdateClients()
cl.EXPECT().DeleteClients()
err := w.syncClients(clO, cl)
Ω(err).ShouldNot(HaveOccurred())
})
It("should add one client", func() {
clR.Clients = []types.Client{}
cl.EXPECT().Clients().Return(clR, nil)
cl.EXPECT().AddClients(clO.Clients[0])
cl.EXPECT().UpdateClients()
cl.EXPECT().DeleteClients()
err := w.syncClients(clO, cl)
Ω(err).ShouldNot(HaveOccurred())
})
It("should update one client", func() {
clR.Clients[0].Disallowed = true
cl.EXPECT().Clients().Return(clR, nil)
cl.EXPECT().AddClients()
cl.EXPECT().UpdateClients(clO.Clients[0])
cl.EXPECT().DeleteClients()
err := w.syncClients(clO, cl)
Ω(err).ShouldNot(HaveOccurred())
})
It("should delete one client", func() {
clO.Clients = []types.Client{}
cl.EXPECT().Clients().Return(clR, nil)
cl.EXPECT().AddClients()
cl.EXPECT().UpdateClients()
cl.EXPECT().DeleteClients(clR.Clients[0])
err := w.syncClients(clO, cl)
Ω(err).ShouldNot(HaveOccurred())
})
It("should return error when error on Clients()", func() {
cl.EXPECT().Clients().Return(nil, te)
err := w.syncClients(clO, cl)
Ω(err).Should(HaveOccurred())
})
It("should return error when error on AddClients()", func() {
cl.EXPECT().Clients().Return(clR, nil)
cl.EXPECT().AddClients().Return(te)
err := w.syncClients(clO, cl)
Ω(err).Should(HaveOccurred())
})
It("should return error when error on UpdateClients()", func() {
cl.EXPECT().Clients().Return(clR, nil)
cl.EXPECT().AddClients()
cl.EXPECT().UpdateClients().Return(te)
err := w.syncClients(clO, cl)
Ω(err).Should(HaveOccurred())
})
It("should return error when error on DeleteClients()", func() {
cl.EXPECT().Clients().Return(clR, nil)
cl.EXPECT().AddClients()
cl.EXPECT().UpdateClients()
cl.EXPECT().DeleteClients().Return(te)
err := w.syncClients(clO, cl)
Ω(err).Should(HaveOccurred())
})
})
Context("syncGeneralSettings", func() {
var (
o *origin
rs *types.Status
)
BeforeEach(func() {
o = &origin{
status: &types.Status{},
}
rs = &types.Status{}
})
It("should have no changes", func() {
cl.EXPECT().Parental()
cl.EXPECT().SafeSearch()
cl.EXPECT().SafeBrowsing()
err := w.syncGeneralSettings(o, rs, cl)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have protection enabled changes", func() {
o.status.ProtectionEnabled = true
cl.EXPECT().ToggleProtection(true)
cl.EXPECT().Parental()
cl.EXPECT().SafeSearch()
cl.EXPECT().SafeBrowsing()
err := w.syncGeneralSettings(o, rs, cl)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have parental enabled changes", func() {
o.parental = true
cl.EXPECT().Parental()
cl.EXPECT().ToggleParental(true)
cl.EXPECT().SafeSearch()
cl.EXPECT().SafeBrowsing()
err := w.syncGeneralSettings(o, rs, cl)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have safeSearch enabled changes", func() {
o.safeSearch = true
cl.EXPECT().Parental()
cl.EXPECT().SafeSearch()
cl.EXPECT().ToggleSafeSearch(true)
cl.EXPECT().SafeBrowsing()
err := w.syncGeneralSettings(o, rs, cl)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have safeBrowsing enabled changes", func() {
o.safeBrowsing = true
cl.EXPECT().Parental()
cl.EXPECT().SafeSearch()
cl.EXPECT().SafeBrowsing()
cl.EXPECT().ToggleSafeBrowsing(true)
err := w.syncGeneralSettings(o, rs, cl)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("syncConfigs", func() {
var (
o *origin
qlc *types.QueryLogConfig
sc *types.IntervalConfig
)
BeforeEach(func() {
o = &origin{
queryLogConfig: &types.QueryLogConfig{},
statsConfig: &types.IntervalConfig{},
}
qlc = &types.QueryLogConfig{}
sc = &types.IntervalConfig{}
})
It("should have no changes", func() {
cl.EXPECT().QueryLogConfig().Return(qlc, nil)
cl.EXPECT().StatsConfig().Return(sc, nil)
err := w.syncConfigs(o, cl)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have QueryLogConfig changes", func() {
o.queryLogConfig.Interval = 123
cl.EXPECT().QueryLogConfig().Return(qlc, nil)
cl.EXPECT().SetQueryLogConfig(false, 123, false)
cl.EXPECT().StatsConfig().Return(sc, nil)
err := w.syncConfigs(o, cl)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have StatsConfig changes", func() {
o.statsConfig.Interval = 123
cl.EXPECT().QueryLogConfig().Return(qlc, nil)
cl.EXPECT().StatsConfig().Return(sc, nil)
cl.EXPECT().SetStatsConfig(123)
err := w.syncConfigs(o, cl)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("statusWithSetup", func() {
var (
status *types.Status
inst types.AdGuardInstance
)
BeforeEach(func() {
status = &types.Status{}
inst = types.AdGuardInstance{
AutoSetup: true,
}
})
It("should get the replica status", func() {
cl.EXPECT().Status().Return(status, nil)
st, err := w.statusWithSetup(l, inst, cl)
Ω(err).ShouldNot(HaveOccurred())
Ω(st).Should(Equal(status))
})
It("should runs setup before getting replica status", func() {
cl.EXPECT().Status().Return(nil, client.SetupNeededError)
cl.EXPECT().Setup()
cl.EXPECT().Status().Return(status, nil)
st, err := w.statusWithSetup(l, inst, cl)
Ω(err).ShouldNot(HaveOccurred())
Ω(st).Should(Equal(status))
})
It("should fail on setup", func() {
cl.EXPECT().Status().Return(nil, client.SetupNeededError)
cl.EXPECT().Setup().Return(te)
st, err := w.statusWithSetup(l, inst, cl)
Ω(err).Should(HaveOccurred())
Ω(st).Should(BeNil())
})
})
Context("syncServices", func() {
var (
os types.Services
rs types.Services
)
BeforeEach(func() {
os = []string{"foo"}
rs = []string{"foo"}
})
It("should have no changes", func() {
cl.EXPECT().Services().Return(rs, nil)
err := w.syncServices(os, cl)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have services changes", func() {
os = []string{"bar"}
cl.EXPECT().Services().Return(rs, nil)
cl.EXPECT().SetServices(os)
err := w.syncServices(os, cl)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("syncFilters", func() {
var (
of *types.FilteringStatus
rf *types.FilteringStatus
)
BeforeEach(func() {
of = &types.FilteringStatus{}
rf = &types.FilteringStatus{}
})
It("should have no changes", func() {
cl.EXPECT().Filtering().Return(rf, nil)
cl.EXPECT().AddFilters(false)
cl.EXPECT().UpdateFilters(false)
cl.EXPECT().DeleteFilters(false)
cl.EXPECT().AddFilters(true)
cl.EXPECT().UpdateFilters(true)
cl.EXPECT().DeleteFilters(true)
err := w.syncFilters(of, cl)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have changes user roles", func() {
of.UserRules = []string{"foo"}
cl.EXPECT().Filtering().Return(rf, nil)
cl.EXPECT().AddFilters(false)
cl.EXPECT().UpdateFilters(false)
cl.EXPECT().DeleteFilters(false)
cl.EXPECT().AddFilters(true)
cl.EXPECT().UpdateFilters(true)
cl.EXPECT().DeleteFilters(true)
cl.EXPECT().SetCustomRules(of.UserRules)
err := w.syncFilters(of, cl)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have changed filtering config", func() {
of.Enabled = true
of.Interval = 123
cl.EXPECT().Filtering().Return(rf, nil)
cl.EXPECT().AddFilters(false)
cl.EXPECT().UpdateFilters(false)
cl.EXPECT().DeleteFilters(false)
cl.EXPECT().AddFilters(true)
cl.EXPECT().UpdateFilters(true)
cl.EXPECT().DeleteFilters(true)
cl.EXPECT().ToggleFiltering(of.Enabled, of.Interval)
err := w.syncFilters(of, cl)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("syncDNS", func() {
var (
oal *types.AccessList
ral *types.AccessList
odc *types.DNSConfig
rdc *types.DNSConfig
)
BeforeEach(func() {
oal = &types.AccessList{}
ral = &types.AccessList{}
odc = &types.DNSConfig{}
rdc = &types.DNSConfig{}
})
It("should have no changes", func() {
cl.EXPECT().AccessList().Return(ral, nil)
cl.EXPECT().DNSConfig().Return(rdc, nil)
err := w.syncDNS(oal, odc, cl)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have access list changes", func() {
ral.BlockedHosts = []string{"foo"}
cl.EXPECT().AccessList().Return(ral, nil)
cl.EXPECT().DNSConfig().Return(rdc, nil)
cl.EXPECT().SetAccessList(oal)
err := w.syncDNS(oal, odc, cl)
Ω(err).ShouldNot(HaveOccurred())
})
It("should have dns config changes", func() {
rdc.Bootstraps = []string{"foo"}
cl.EXPECT().AccessList().Return(ral, nil)
cl.EXPECT().DNSConfig().Return(rdc, nil)
cl.EXPECT().SetDNSConfig(odc)
err := w.syncDNS(oal, odc, cl)
Ω(err).ShouldNot(HaveOccurred())
})
})
Context("sync", func() {
It("should have no changes", func() {
w.cfg = &types.Config{
Origin: types.AdGuardInstance{},
Replica: types.AdGuardInstance{URL: "foo"},
Beta: "",
}
// origin
cl.EXPECT().Host()
cl.EXPECT().Status().Return(&types.Status{}, nil)
cl.EXPECT().Parental()
cl.EXPECT().SafeSearch()
cl.EXPECT().SafeBrowsing()
cl.EXPECT().RewriteList().Return(&types.RewriteEntries{}, nil)
cl.EXPECT().Services()
cl.EXPECT().Filtering().Return(&types.FilteringStatus{}, nil)
cl.EXPECT().Clients().Return(&types.Clients{}, nil)
cl.EXPECT().QueryLogConfig().Return(&types.QueryLogConfig{}, nil)
cl.EXPECT().StatsConfig().Return(&types.IntervalConfig{}, nil)
cl.EXPECT().AccessList().Return(&types.AccessList{}, nil)
cl.EXPECT().DNSConfig().Return(&types.DNSConfig{}, nil)
cl.EXPECT().DHCPServerConfig().Return(&types.DHCPServerConfig{}, nil)
// replica
cl.EXPECT().Host()
cl.EXPECT().Status().Return(&types.Status{}, nil)
cl.EXPECT().Parental()
cl.EXPECT().SafeSearch()
cl.EXPECT().SafeBrowsing()
cl.EXPECT().QueryLogConfig().Return(&types.QueryLogConfig{}, nil)
cl.EXPECT().StatsConfig().Return(&types.IntervalConfig{}, nil)
cl.EXPECT().RewriteList().Return(&types.RewriteEntries{}, nil)
cl.EXPECT().AddRewriteEntries()
cl.EXPECT().DeleteRewriteEntries()
cl.EXPECT().Filtering().Return(&types.FilteringStatus{}, nil)
cl.EXPECT().AddFilters(false)
cl.EXPECT().UpdateFilters(false)
cl.EXPECT().DeleteFilters(false)
cl.EXPECT().AddFilters(true)
cl.EXPECT().UpdateFilters(true)
cl.EXPECT().DeleteFilters(true)
cl.EXPECT().Services()
cl.EXPECT().Clients().Return(&types.Clients{}, nil)
cl.EXPECT().AddClients()
cl.EXPECT().UpdateClients()
cl.EXPECT().DeleteClients()
cl.EXPECT().AccessList().Return(&types.AccessList{}, nil)
cl.EXPECT().DNSConfig().Return(&types.DNSConfig{}, nil)
cl.EXPECT().DHCPServerConfig().Return(&types.DHCPServerConfig{}, nil)
cl.EXPECT().AddDHCPStaticLeases().Return(nil)
cl.EXPECT().DeleteDHCPStaticLeases().Return(nil)
w.sync()
})
})
})
})

76
pkg/types/dhcp.go Normal file
View File

@@ -0,0 +1,76 @@
package types
import (
"encoding/json"
"net"
"time"
)
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)
}
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"`
}
type V6ServerConfJSON struct {
RangeStart net.IP `json:"range_start"`
RangeEnd net.IP `json:"range_end"`
LeaseDuration uint32 `json:"lease_duration"`
}
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"`
}

78
pkg/types/dns.go Normal file
View File

@@ -0,0 +1,78 @@
package types
import (
"encoding/json"
"net"
"sort"
)
// https://ha.bakito.net:3000/control/dns_config
// {"bootstrap_dns":["1.1.1.1:53"],"upstream_mode":"parallel","upstream_dns":["https://dns10.quad9.net/dns-query"]}
// {"bootstrap_dns":["1.1.1.1:53"],"upstream_mode":"","upstream_dns":["https://dns10.quad9.net/dns-query"]}
// {"bootstrap_dns":["1.1.1.1:53"],"upstream_mode":"fastest_addr","upstream_dns":["https://dns10.quad9.net/dns-query"]}
// {"ratelimit":20,"blocking_mode":"default","blocking_ipv4":"0.0.0.0","blocking_ipv6":"::","edns_cs_enabled":true,"disable_ipv6":false,"dnssec_enabled":false}
// {"cache_size":4194304,"cache_ttl_max":0,"cache_ttl_min":0}
// https://ha.bakito.net:3000/control/access/set
// {"allowed_clients":["2.2.2.2"],"disallowed_clients":["1.1.1.1"],"blocked_hosts":["version.bind","id.server","hostname.bind"]}
// https://ha.bakito.net:3000/control/access/list
// {"allowed_clients":[],"disallowed_clients":[],"blocked_hosts":["version.bind","id.server","hostname.bind"]}
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)
}
type AccessList struct {
AllowedClients []string `json:"allowed_clients"`
DisallowedClients []string `json:"disallowed_clients"`
BlockedHosts []string `json:"blocked_hosts"`
}
// Equals access list equal check
func (al *AccessList) Equals(o *AccessList) bool {
return equals(al.AllowedClients, o.AllowedClients) &&
equals(al.DisallowedClients, o.DisallowedClients) &&
equals(al.BlockedHosts, o.BlockedHosts)
}
// Sort sort access list
func (al *AccessList) Sort() {
sort.Strings(al.AllowedClients)
sort.Strings(al.DisallowedClients)
sort.Strings(al.BlockedHosts)
}

View File

@@ -5,71 +5,135 @@ import (
"fmt"
"sort"
"strings"
"time"
"sync"
)
const (
DefaultAPIPath = "/control"
)
var (
doOnce sync.Once
)
// Config application configuration struct
type Config struct {
Origin AdGuardInstance `json:"origin" yaml:"origin"`
Replica *AdGuardInstance `json:"replica,omitempty" yaml:"replica,omitempty"`
Replicas []AdGuardInstance `json:"replicas,omitempty" yaml:"replicas,omitempty"`
Cron string `json:"cron,omitempty" yaml:"cron,omitempty"`
Origin AdGuardInstance `json:"origin" yaml:"origin"`
Replica AdGuardInstance `json:"replica,omitempty" yaml:"replica,omitempty"`
Replicas []AdGuardInstance `json:"replicas,omitempty" yaml:"replicas,omitempty"`
Cron string `json:"cron,omitempty" yaml:"cron,omitempty"`
RunOnStart bool `json:"runOnStart,omitempty" yaml:"runOnStart,omitempty"`
API API `json:"api,omitempty" yaml:"api,omitempty"`
Beta string `json:"beta,omitempty" yaml:"beta,omitempty"`
enabledBeta map[string]bool `json:"-" yaml:"-"`
}
// API configuration
type API struct {
Port int `json:"port,omitempty" yaml:"port,omitempty"`
Username string `json:"username,omitempty" yaml:"username,omitempty"`
Password string `json:"password,omitempty" yaml:"password,omitempty"`
}
// UniqueReplicas get unique replication instances
func (cfg *Config) UniqueReplicas() []AdGuardInstance {
dedup := make(map[string]AdGuardInstance)
if cfg.Replica != nil {
dedup[cfg.Replica.Key()] = *cfg.Replica
if cfg.Replica.URL != "" {
dedup[cfg.Replica.Key()] = cfg.Replica
}
for _, replica := range cfg.Replicas {
dedup[replica.Key()] = replica
if replica.URL != "" {
dedup[replica.Key()] = replica
}
}
var r []AdGuardInstance
for _, replica := range dedup {
if replica.APIPath == "" {
replica.APIPath = DefaultAPIPath
}
r = append(r, replica)
}
return r
}
func (cfg *Config) WithBeta(name string) bool {
doOnce.Do(func() {
cfg.enabledBeta = make(map[string]bool)
features := strings.Split(cfg.Beta, ",")
for _, f := range features {
cfg.enabledBeta[strings.ToLower(strings.TrimSpace(f))] = true
}
})
return cfg.enabledBeta[name]
}
// AdGuardInstance AdguardHome config instance
type AdGuardInstance struct {
URL string `json:"url" yaml:"url"`
APIPath string `json:"apiPath,omitempty" yaml:"apiPath,omitempty"`
Username string `json:"username,omitempty" yaml:"username,omitempty"`
Password string `json:"password,omitempty" yaml:"password,omitempty"`
InsecureSkipVerify bool `json:"insecureSkipVerify" yaml:"insecureSkipVerify"`
AutoSetup bool `json:"autoSetup" yaml:"autoSetup"`
}
// Key AdGuardInstance key
func (i *AdGuardInstance) Key() string {
return fmt.Sprintf("%s%s", i.URL, i.APIPath)
return fmt.Sprintf("%s#%s", i.URL, i.APIPath)
}
// Protection API struct
type Protection struct {
ProtectionEnabled bool `json:"protection_enabled"`
}
// Status API struct
type Status struct {
DNSAddresses []string `json:"dns_addresses"`
DNSPort int `json:"dns_port"`
HTTPPort int `json:"http_port"`
ProtectionEnabled bool `json:"protection_enabled"`
DhcpAvailable bool `json:"dhcp_available"`
Running bool `json:"running"`
Version string `json:"version"`
Language string `json:"language"`
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
func (rwe *RewriteEntries) Merge(other *RewriteEntries) (RewriteEntries, RewriteEntries) {
// 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 {
current[rr.Key()] = rr
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 {
adds = append(adds, rr)
if _, ok := processed[rr.Key()]; !ok {
adds = append(adds, rr)
processed[rr.Key()] = true
} else {
// skip duplicate
duplicates = append(duplicates, rr)
}
}
}
@@ -77,63 +141,39 @@ func (rwe *RewriteEntries) Merge(other *RewriteEntries) (RewriteEntries, Rewrite
removes = append(removes, rr)
}
return adds, removes
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
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"`
LastUpdated time.Time `json:"last_updated"`
Whitelist bool `json:"whitelist"` // needed for add
}
type FilteringStatus struct {
FilteringConfig
Filters Filters `json:"filters"`
WhitelistFilters Filters `json:"whitelist_filters"`
UserRules UserRules `json:"user_rules"`
}
type UserRules []string
func (ur UserRules) String() string {
return strings.Join(ur, "\n")
}
type FilteringConfig struct {
Enabled bool `json:"enabled"`
Interval int `json:"interval"`
}
type RefreshFilter struct {
Whitelist bool `json:"whitelist"`
}
func (fs *Filters) Merge(other Filters) (Filters, Filters) {
// 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 *fs {
for _, f := range f {
current[f.URL] = f
}
for _, rr := range other {
if _, ok := current[rr.URL]; ok {
if c, ok := current[rr.URL]; ok {
if !c.Equals(&rr) {
updates = append(updates, rr)
}
delete(current, rr.URL)
} else {
adds = append(adds, rr)
@@ -144,21 +184,96 @@ func (fs *Filters) Merge(other Filters) (Filters, Filters) {
removes = append(removes, rr)
}
return adds, removes
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 int `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)
}
func (s *Services) Equals(o *Services) bool {
// Equals Services equal check
func (s Services) Equals(o Services) bool {
s.Sort()
o.Sort()
return equals(*s, *o)
return equals(s, o)
}
// Clients API struct
type Clients struct {
Clients []Client `json:"clients"`
AutoClients []struct {
@@ -171,11 +286,12 @@ type Clients struct {
SupportedTags []string `json:"supported_tags"`
}
// Client API struct
type Client struct {
Ids []string `json:"ids"`
Tags []string `json:"tags"`
BlockedServices []string `json:"blocked_services"`
Upstreams []string `json:"upstreams"`
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"`
@@ -188,6 +304,7 @@ type Client struct {
DisallowedRule string `json:"disallowed_rule"`
}
// Sort sort clients
func (cl *Client) Sort() {
sort.Strings(cl.Ids)
sort.Strings(cl.Tags)
@@ -195,7 +312,8 @@ func (cl *Client) Sort() {
sort.Strings(cl.Upstreams)
}
func (cl *Client) Equal(o *Client) bool {
// Equals Clients equal check
func (cl *Client) Equals(o *Client) bool {
cl.Sort()
o.Sort()
@@ -204,6 +322,7 @@ func (cl *Client) Equal(o *Client) bool {
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 {
@@ -221,7 +340,7 @@ func (clients *Clients) Merge(other *Clients) ([]Client, []Client, []Client) {
for _, cl := range expected {
if oc, ok := current[cl.Name]; ok {
if !cl.Equal(&oc) {
if !cl.Equals(&oc) {
updates = append(updates, cl)
}
delete(current, cl.Name)
@@ -237,6 +356,7 @@ func (clients *Clients) Merge(other *Clients) ([]Client, []Client, []Client) {
return adds, updates, removes
}
// ClientUpdate API struct
type ClientUpdate struct {
Name string `json:"name"`
Data Client `json:"data"`
@@ -253,3 +373,19 @@ func equals(a []string, b []string) bool {
}
return true
}
// InstallConfig AdguardHome install config
type InstallConfig struct {
Web InstallPort `json:"web"`
DNS InstallPort `json:"dns"`
Username string `json:"username"`
Password string `json:"password"`
}
// InstallPort AdguardHome install config port
type InstallPort struct {
IP string `json:"ip"`
Port int `json:"port"`
Status string `json:"status"`
CanAutofix bool `json:"can_autofix"`
}

View File

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

345
pkg/types/types_test.go Normal file
View File

@@ -0,0 +1,345 @@
package types_test
import (
"encoding/json"
"io/ioutil"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/bakito/adguardhome-sync/pkg/types"
"github.com/google/uuid"
)
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 := 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() {
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 *types.Clients
replicaClients types.Clients
name string
)
BeforeEach(func() {
originClients = &types.Clients{}
replicaClients = types.Clients{}
name = uuid.NewString()
})
It("should add a missing client", func() {
originClients.Clients = append(originClients.Clients, types.Client{Name: name})
a, u, d := replicaClients.Merge(originClients)
Ω(a).Should(HaveLen(1))
Ω(u).Should(BeEmpty())
Ω(d).Should(BeEmpty())
Ω(a[0].Name).Should(Equal(name))
})
It("should remove additional client", func() {
replicaClients.Clients = append(replicaClients.Clients, types.Client{Name: name})
a, u, d := replicaClients.Merge(originClients)
Ω(a).Should(BeEmpty())
Ω(u).Should(BeEmpty())
Ω(d).Should(HaveLen(1))
Ω(d[0].Name).Should(Equal(name))
})
It("should update existing client when name differs", func() {
disallowed := true
originClients.Clients = append(originClients.Clients, types.Client{Name: name, Disallowed: disallowed})
replicaClients.Clients = append(replicaClients.Clients, types.Client{Name: name, Disallowed: !disallowed})
a, u, d := replicaClients.Merge(originClients)
Ω(a).Should(BeEmpty())
Ω(u).Should(HaveLen(1))
Ω(d).Should(BeEmpty())
Ω(u[0].Disallowed).Should(Equal(disallowed))
})
})
})
Context("Services", func() {
Context("Equals", func() {
It("should be equal", func() {
s1 := types.Services([]string{"a", "b"})
s2 := types.Services([]string{"b", "a"})
Ω(s1.Equals(s2)).Should(BeTrue())
})
It("should not be equal different values", func() {
s1 := types.Services([]string{"a", "b"})
s2 := types.Services([]string{"B", "a"})
Ω(s1.Equals(s2)).ShouldNot(BeTrue())
})
It("should not be equal different length", func() {
s1 := types.Services([]string{"a", "b"})
s2 := types.Services([]string{"b", "a", "c"})
Ω(s1.Equals(s2)).ShouldNot(BeTrue())
})
})
})
Context("DNSConfig", func() {
Context("Equals", func() {
It("should be equal", func() {
dc1 := &types.DNSConfig{Upstreams: []string{"a"}}
dc2 := &types.DNSConfig{Upstreams: []string{"a"}}
Ω(dc1.Equals(dc2)).Should(BeTrue())
})
It("should not be equal", func() {
dc1 := &types.DNSConfig{Upstreams: []string{"a"}}
dc2 := &types.DNSConfig{Upstreams: []string{"b"}}
Ω(dc1.Equals(dc2)).ShouldNot(BeTrue())
})
})
})
})

View File

@@ -0,0 +1,28 @@
[Unit]
Description=AdGuard Home Sync service
ConditionFileIsExecutable=/opt/AdGuardHomeSync/adguardhome-sync
Requires=network.target
After=network-online.target syslog.target
[Service]
StartLimitInterval=5
StartLimitBurst=10
ExecStart=/opt/AdGuardHomeSync/adguardhome-sync "run" "--config" "/opt/AdGuardHomeSync/adguardhome-sync.yaml"
WorkingDirectory=/opt/AdGuardHome
Restart=on-success
SuccessExitStatus=1 2 8 SIGKILL
RestartSec=120
EnvironmentFile=-/etc/sysconfig/GoServiceExampleLogging
StandardOutput=file:/var/log/AdGuardHomeSync.out
StandardError=file:/var/log/AdGuardHomeSync.err
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target

11
systemd/README.md Normal file
View File

@@ -0,0 +1,11 @@
# Install
```bash
mkdir -p /opt/AdGuardHomeSync/
sudo cp adguardhome-sync /opt/AdGuardHomeSync/adguardhome-sync
sudo cp adguardhome-sync.yaml /opt/AdGuardHomeSync/adguardhome-sync.yaml
sudo cp AdGuardHomeSync.service /etc/systemd/system/AdGuardHomeSync.service
sudo systemctl enable AdGuardHomeSync
```

4
testdata/blockedservices-list.json vendored Normal file
View File

@@ -0,0 +1,4 @@
[
"9gag",
"dailymotion"
]

81
testdata/clients.json vendored Normal file
View File

@@ -0,0 +1,81 @@
{
"clients": [
{
"ids": [
"192.168.1.3"
],
"tags": [
"device_pc"
],
"name": "PC",
"use_global_settings": true,
"filtering_enabled": false,
"parental_enabled": false,
"safesearch_enabled": false,
"safebrowsing_enabled": false,
"use_global_blocked_services": true,
"blocked_services": null,
"upstreams": null,
"whois_info": null,
"disallowed": false,
"disallowed_rule": ""
},
{
"ids": [
"192.168.1.2"
],
"tags": [
"device_phone"
],
"name": "Phone LAN",
"use_global_settings": true,
"filtering_enabled": false,
"parental_enabled": false,
"safesearch_enabled": false,
"safebrowsing_enabled": false,
"use_global_blocked_services": false,
"blocked_services": [
"facebook",
"ok",
"vk",
"mail_ru",
"qq"
],
"upstreams": [],
"whois_info": null,
"disallowed": false,
"disallowed_rule": ""
}
],
"auto_clients": [
{
"ip": "127.0.0.1",
"name": "localhost",
"source": "etc/hosts",
"whois_info": {}
}
],
"supported_tags": [
"device_audio",
"device_camera",
"device_gameconsole",
"device_laptop",
"device_nas",
"device_other",
"device_pc",
"device_phone",
"device_printer",
"device_securityalarm",
"device_tablet",
"device_tv",
"os_android",
"os_ios",
"os_linux",
"os_macos",
"os_other",
"os_windows",
"user_admin",
"user_child",
"user_regular"
]
}

17
testdata/dhcp-status.json vendored Normal file
View File

@@ -0,0 +1,17 @@
{
"enabled": false,
"interface_name": "docker0",
"v4": {
"gateway_ip": "172.17.0.1",
"subnet_mask": "255.255.255.0",
"range_start": "172.17.0.100",
"range_end": "172.17.0.200",
"lease_duration": 888888
},
"v6": {
"range_start": "",
"lease_duration": 0
},
"leases": [],
"static_leases": []
}

22
testdata/dns-info.json vendored Normal file
View File

@@ -0,0 +1,22 @@
{
"upstream_dns": [
"https://dns10.quad9.net/dns-query"
],
"upstream_dns_file": "",
"bootstrap_dns": [
"1.1.1.1:53"
],
"protection_enabled": true,
"ratelimit": 20,
"blocking_mode": "default",
"blocking_ipv4": "",
"blocking_ipv6": "",
"edns_cs_enabled": false,
"dnssec_enabled": false,
"disable_ipv6": false,
"upstream_mode": "",
"cache_size": 4194304,
"cache_ttl_min": 0,
"cache_ttl_max": 0
}

27
testdata/filtering-status.json vendored Normal file
View File

@@ -0,0 +1,27 @@
{
"enabled": true,
"interval": 24,
"filters": [
{
"id": 1616956420,
"enabled": true,
"url": "https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt",
"name": "AdGuard DNS filter",
"rules_count": 37330,
"last_updated": ""
},
{
"id": 1616956421,
"enabled": true,
"url": "https://adaway.org/hosts.txt",
"name": "AdAway Default Blocklist",
"rules_count": 8717,
"last_updated": "2021-04-04T20:35:03+01:00"
}
],
"whitelist_filters": null,
"user_rules": [
"||metrics2.data.hicloud.com^$important",
""
]
}

3
testdata/parental-status.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"enabled": true
}

5
testdata/querylog_info.json vendored Normal file
View File

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

10
testdata/rewrite-list.json vendored Normal file
View File

@@ -0,0 +1,10 @@
[
{
"domain": "foo.com",
"answer": "192.168.1.10"
},
{
"domain": "bar.com",
"answer": "192.168.1.12"
}
]

3
testdata/safebrowsing-status.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"enabled": true
}

3
testdata/safesearch-status.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"enabled": true
}

3
testdata/stats_info.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"interval": 1
}

12
testdata/status.json vendored Normal file
View File

@@ -0,0 +1,12 @@
{
"dns_addresses": [
"192.168.1.2"
],
"dns_port": 53,
"http_port": 45158,
"protection_enabled": true,
"dhcp_available": true,
"running": true,
"version": "v0.105.2",
"language": "en"
}

20
testdata/tls-status.json vendored Normal file
View File

@@ -0,0 +1,20 @@
{
"enabled": false,
"port_https": 443,
"port_dns_over_tls": 853,
"port_dns_over_quic": 784,
"port_dnscrypt": 0,
"dnscrypt_config_file": "",
"allow_unencrypted_doh": false,
"certificate_chain": "",
"private_key": "",
"certificate_path": "",
"private_key_path": "",
"valid_cert": false,
"valid_chain": false,
"not_before": "0001-01-01T00:00:00Z",
"not_after": "0001-01-01T00:00:00Z",
"dns_names": null,
"valid_key": false,
"valid_pair": false
}

View File

@@ -1,49 +0,0 @@
package main
import (
"bufio"
"fmt"
"os"
"os/exec"
"strings"
"github.com/coreos/go-semver/semver"
)
func main() {
out, err := exec.Command("git", "branch", "--show-current").Output()
if err != nil {
panic(err)
}
branch := strings.TrimSpace(string(out))
if branch != "main" {
panic(fmt.Errorf(`error: must be in "master" branch, current branch: %q`, branch))
}
out, err = exec.Command("git", "describe").Output()
if err != nil {
panic(err)
}
version := strings.TrimPrefix(strings.TrimSpace(string(out)), "v")
v := semver.New(version)
v.BumpPatch()
reader := bufio.NewReader(os.Stdin)
if _, err = fmt.Fprintf(os.Stderr, "Enter Release Version: [v%v] ", v); err != nil {
panic(err)
}
text, err := reader.ReadString('\n')
if err != nil {
panic(err)
}
if strings.HasPrefix(text, "v") {
text = text[1:]
v = semver.New(strings.TrimSpace(text))
}
if _, err = fmt.Fprintf(os.Stderr, "Using Version: v%v\n", v); err != nil {
panic(err)
}
fmt.Printf("v%v", v)
}

View File

@@ -3,4 +3,6 @@ package version
var (
// Version the module version
Version = "devel"
// Build the build time
Build = "N/A"
)