Compare commits

...

24 Commits

Author SHA1 Message Date
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
34 changed files with 2251 additions and 155 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: ''
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 uses: actions/checkout@v2
- name: Test - name: Test
run: go test ./... -coverprofile=coverage.out run: make test
- name: Send coverage - name: Send coverage
uses: shogo82148/actions-goveralls@v1 uses: shogo82148/actions-goveralls@v1

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

@@ -0,0 +1,44 @@
name: quay
on:
push:
branches: main
release:
types:
- published
jobs:
main:
runs-on: ubuntu-latest
steps:
- 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: 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 }}
platforms: linux/amd64,linux/arm64,linux/arm/v7
build-args: VERSION=${{ github.event.release.tag_name }}
- name: Build and push main
id: docker_build_main
uses: docker/build-push-action@v2
if: ${{ github.event.release.tag_name == '' }}
with:
push: true
tags: quay.io/bakito/adguardhome-sync:main
platforms: linux/amd64,linux/arm64,linux/arm/v7
build-args: VERSION=main
- name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}

27
Dockerfile Normal file
View File

@@ -0,0 +1,27 @@
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
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}" -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

@@ -16,10 +16,13 @@ tidy:
go mod tidy go mod tidy
# Run tests # Run tests
test: tidy fmt vet test: mocks tidy fmt vet
go test ./... -coverprofile=coverage.out go test ./... -coverprofile=coverage.out
go tool cover -func=coverage.out go tool cover -func=coverage.out
mocks: mockgen
mockgen -destination pkg/mocks/client/mock.go github.com/bakito/adguardhome-sync/pkg/client Client
release: semver release: semver
@version=$$(semver); \ @version=$$(semver); \
git tag -s $$version -m"Release $$version" git tag -s $$version -m"Release $$version"
@@ -31,4 +34,9 @@ test-release:
semver: semver:
ifeq (, $(shell which semver)) ifeq (, $(shell which semver))
$(shell go get -u github.com/bakito/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 endif

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 # AdGuardHome sync
@@ -18,6 +20,10 @@ Synchronize [AdGuardHome](https://github.com/AdguardTeam/AdGuardHome) config to
go get -u github.com/bakito/adguardhome-sync go get -u github.com/bakito/adguardhome-sync
``` ```
## Prerequisites
Both the origin and replica mist be initially setup via the Adguard Home installation wizard.
## Run ## Run
```bash ```bash
@@ -36,6 +42,62 @@ adguardhome-sync run
adguardhome-sync run --cron "*/10 * * * *" 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
- CRON=*/10 * * * * # run every 10 minutes
ports:
- 8080:8080
restart: unless-stopped
```
### Config file ### Config file
location: $HOME/.adguardhome-sync.yaml location: $HOME/.adguardhome-sync.yaml
@@ -78,3 +140,14 @@ api:
password: password 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,13 +3,13 @@ package cmd
import ( import (
"fmt" "fmt"
"os" "os"
"regexp"
"strings" "strings"
"github.com/bakito/adguardhome-sync/pkg/log" "github.com/bakito/adguardhome-sync/pkg/log"
"github.com/bakito/adguardhome-sync/pkg/types"
"github.com/mitchellh/go-homedir"
"github.com/spf13/cobra" "github.com/spf13/cobra"
homedir "github.com/mitchellh/go-homedir"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
@@ -31,11 +31,17 @@ const (
configReplicaUsername = "replica.username" configReplicaUsername = "replica.username"
configReplicaPassword = "replica.password" configReplicaPassword = "replica.password"
configReplicaInsecureSkipVerify = "replica.insecureSkipVerify" configReplicaInsecureSkipVerify = "replica.insecureSkipVerify"
envReplicasUsernameFormat = "REPLICA%s_USERNAME"
envReplicasPasswordFormat = "REPLICA%s_PASSWORD"
envReplicasAPIPathFormat = "REPLICA%s_APIPATH"
envReplicasInsecureSkipVerifyFormat = "REPLICA%s_INSECURESKIPVERIFY"
) )
var ( var (
cfgFile string cfgFile string
logger = log.GetLogger("root") logger = log.GetLogger("root")
envReplicasURLPattern = regexp.MustCompile(`^REPLICA(\d+)_URL=(.*)`)
) )
// rootCmd represents the base command when called without any subcommands // rootCmd represents the base command when called without any subcommands
@@ -83,13 +89,47 @@ func initConfig() {
// Search config in home directory with name ".adguardhome-sync" (without extension). // Search config in home directory with name ".adguardhome-sync" (without extension).
viper.AddConfigPath(home) viper.AddConfigPath(home)
viper.SetConfigName(".adguardhome-sync") viper.SetConfigName(".adguardhome-sync")
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_", ".", "_"))
} }
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_", ".", "_"))
viper.AutomaticEnv() // read in environment variables that match viper.AutomaticEnv() // read in environment variables that match
// If a config file is found, read it in. // If a config file is found, read it in.
if err := viper.ReadInConfig(); err == nil { if err := viper.ReadInConfig(); err == nil {
logger.Info("Using config file:", viper.ConfigFileUsed()) 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"),
}
replicas = append(replicas, re)
}
}
return replicas
}

View File

@@ -3,10 +3,8 @@ package cmd
import ( import (
"github.com/bakito/adguardhome-sync/pkg/log" "github.com/bakito/adguardhome-sync/pkg/log"
"github.com/bakito/adguardhome-sync/pkg/sync" "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/cobra"
"github.com/spf13/viper"
) )
// runCmd represents the run command // runCmd represents the run command
@@ -14,15 +12,15 @@ var doCmd = &cobra.Command{
Use: "run", Use: "run",
Short: "Start a synchronisation from origin to replica", Short: "Start a synchronisation from origin to replica",
Long: `Synchronizes the configuration form an origin instance to a replica`, Long: `Synchronizes the configuration form an origin instance to a replica`,
Run: func(cmd *cobra.Command, args []string) { RunE: func(cmd *cobra.Command, args []string) error {
logger = log.GetLogger("run") logger = log.GetLogger("run")
cfg := &types.Config{} cfg, err := getConfig()
if err := viper.Unmarshal(cfg); err != nil { if err != nil {
logger.Error(err) logger.Error(err)
return return err
} }
sync.Sync(cfg) return sync.Sync(cfg)
}, },
} }

7
go.mod
View File

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

81
go.sum
View File

@@ -38,16 +38,18 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 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/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 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.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/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-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-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.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 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.6.0 h1:joIR5PNLM2EFqqESUjCMGXrWmXNHEU9CEiK813oKYS4=
github.com/go-resty/resty/v2 v2.5.0/go.mod h1:B88+xCTEwvfD94NOuE6GS1wMlnoKNY8eEiNizfNwOwA= github.com/go-resty/resty/v2 v2.6.0/go.mod h1:PwvJS6hvaPkjtjNg9ph+VrSD92bi5Zq73w/BIH7cC3Q=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 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/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@@ -55,17 +57,31 @@ github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4er
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 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.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.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.5.0 h1:jlYHihg//f7RRwuPfptm04yp4s7O6Kw8EZiVYIGcH0g=
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 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.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.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
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.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 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/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.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.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/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 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-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
github.com/google/uuid v1.2.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.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 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 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
@@ -95,6 +111,7 @@ 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/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/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= 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/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 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/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
@@ -131,7 +148,18 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/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/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
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/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
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.1 h1:foqVmeWDD6yYpK+Yz3fHyNIxFYNxswxqNFjSKe+vI54=
github.com/onsi/ginkgo v1.16.1/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
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.11.0 h1:+CqWgvj0OZycCaqclBD1pxKHAU+tOkHmQIWvDHq2aug=
github.com/onsi/gomega v1.11.0/go.mod h1:azGKhqFUon9Vuj0YmTfLSmx0FUwqXYSTl5re8lQLTUg=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 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 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
@@ -183,12 +211,14 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.1.1/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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= 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/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/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= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
@@ -208,6 +238,8 @@ golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 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-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-20190605123033-f99c8df09eb5/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-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-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-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -227,8 +259,11 @@ golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 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.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.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/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-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-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-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-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-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -241,9 +276,11 @@ 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-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-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-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-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 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-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-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -252,9 +289,11 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/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-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-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/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-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-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-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-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-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-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -265,8 +304,15 @@ 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-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-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-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/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-20200323222414-85ca7c5b95cd/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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44 h1:Bli41pIlzTzf3KEY06n+xnzK/BESIg2ze4Pgfh/aI8c=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/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.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -293,9 +339,15 @@ golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/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-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-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-20191112195655-aa38f8e97acc/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-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e h1:4nW4NLDYnU28ojHaHO8OVxFHk/aQ33U01a9cjED+pzE=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
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.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.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.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
@@ -317,18 +369,29 @@ google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvx
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 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.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
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.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 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/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
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.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 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/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/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.4/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 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@@ -2,7 +2,9 @@ package client
import ( import (
"crypto/tls" "crypto/tls"
"errors"
"fmt" "fmt"
"net/http"
"net/url" "net/url"
"path" "path"
@@ -18,7 +20,6 @@ var (
// New create a new client // New create a new client
func New(config types.AdGuardInstance) (Client, error) { func New(config types.AdGuardInstance) (Client, error) {
var apiURL string var apiURL string
if config.APIPath == "" { if config.APIPath == "" {
apiURL = fmt.Sprintf("%s/control", config.URL) apiURL = fmt.Sprintf("%s/control", config.URL)
@@ -61,6 +62,7 @@ type Client interface {
ToggleFiltering(enabled bool, interval int) error ToggleFiltering(enabled bool, interval int) error
AddFilters(whitelist bool, e ...types.Filter) error AddFilters(whitelist bool, e ...types.Filter) error
DeleteFilters(whitelist bool, e ...types.Filter) error DeleteFilters(whitelist bool, e ...types.Filter) error
UpdateFilters(whitelist bool, e ...types.Filter) error
RefreshFilters(whitelist bool) error RefreshFilters(whitelist bool) error
SetCustomRules(rules types.UserRules) error SetCustomRules(rules types.UserRules) error
@@ -71,7 +73,7 @@ type Client interface {
SafeSearch() (bool, error) SafeSearch() (bool, error)
ToggleSafeSearch(enable bool) error ToggleSafeSearch(enable bool) error
Services() (*types.Services, error) Services() (types.Services, error)
SetServices(services types.Services) error SetServices(services types.Services) error
Clients() (*types.Clients, error) Clients() (*types.Clients, error)
@@ -94,23 +96,57 @@ type client struct {
func (cl *client) Host() string { func (cl *client) Host() string {
return cl.host return cl.host
} }
func (cl *client) doGet(req *resty.Request, url string) error {
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 {
return err
}
rl.With("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 {
return err
}
rl.With("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) { func (cl *client) Status() (*types.Status, error) {
status := &types.Status{} 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 return status, err
} }
func (cl *client) RewriteList() (*types.RewriteEntries, error) { func (cl *client) RewriteList() (*types.RewriteEntries, error) {
rewrites := &types.RewriteEntries{} 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 return rewrites, err
} }
func (cl *client) AddRewriteEntries(entries ...types.RewriteEntry) error { func (cl *client) AddRewriteEntries(entries ...types.RewriteEntry) error {
for _, e := range entries { for _, e := range entries {
cl.log.With("domain", e.Domain, "answer", e.Answer).Info("Add rewrite entry") 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 { if err != nil {
return err return err
} }
@@ -121,7 +157,7 @@ func (cl *client) AddRewriteEntries(entries ...types.RewriteEntry) error {
func (cl *client) DeleteRewriteEntries(entries ...types.RewriteEntry) error { func (cl *client) DeleteRewriteEntries(entries ...types.RewriteEntry) error {
for _, e := range entries { for _, e := range entries {
cl.log.With("domain", e.Domain, "answer", e.Answer).Info("Delete rewrite entry") 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 { if err != nil {
return err return err
} }
@@ -155,7 +191,7 @@ func (cl *client) ToggleSafeSearch(enable bool) error {
func (cl *client) toggleStatus(mode string) (bool, error) { func (cl *client) toggleStatus(mode string) (bool, error) {
fs := &types.EnableConfig{} fs := &types.EnableConfig{}
_, err := cl.client.R().EnableTrace().SetResult(fs).Get(fmt.Sprintf("/%s/status", mode)) err := cl.doGet(cl.client.R().EnableTrace().SetResult(fs), fmt.Sprintf("/%s/status", mode))
return fs.Enabled, err return fs.Enabled, err
} }
@@ -167,21 +203,20 @@ func (cl *client) toggleBool(mode string, enable bool) error {
} else { } else {
target = "disable" target = "disable"
} }
_, err := cl.client.R().EnableTrace().Post(fmt.Sprintf("/%s/%s", mode, target)) return cl.doPost(cl.client.R().EnableTrace(), fmt.Sprintf("/%s/%s", mode, target))
return err
} }
func (cl *client) Filtering() (*types.FilteringStatus, error) { func (cl *client) Filtering() (*types.FilteringStatus, error) {
f := &types.FilteringStatus{} 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 return f, err
} }
func (cl *client) AddFilters(whitelist bool, filters ...types.Filter) error { func (cl *client) AddFilters(whitelist bool, filters ...types.Filter) error {
for _, f := range filters { 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} 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 { if err != nil {
return err return err
} }
@@ -191,9 +226,21 @@ func (cl *client) AddFilters(whitelist bool, filters ...types.Filter) error {
func (cl *client) DeleteFilters(whitelist bool, filters ...types.Filter) error { func (cl *client) DeleteFilters(whitelist bool, filters ...types.Filter) error {
for _, f := range filters { 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} 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 { if err != nil {
return err return err
} }
@@ -203,53 +250,48 @@ func (cl *client) DeleteFilters(whitelist bool, filters ...types.Filter) error {
func (cl *client) RefreshFilters(whitelist bool) error { func (cl *client) RefreshFilters(whitelist bool) error {
cl.log.With("whitelist", whitelist).Info("Refresh filter") cl.log.With("whitelist", whitelist).Info("Refresh filter")
_, err := cl.client.R().EnableTrace().SetBody(&types.RefreshFilter{Whitelist: whitelist}).Post("/filtering/refresh") return cl.doPost(cl.client.R().EnableTrace().SetBody(&types.RefreshFilter{Whitelist: whitelist}), "/filtering/refresh")
return err
} }
func (cl *client) ToggleProtection(enable bool) error { func (cl *client) ToggleProtection(enable bool) error {
cl.log.With("enable", enable).Info("Toggle protection") cl.log.With("enable", enable).Info("Toggle protection")
_, err := cl.client.R().EnableTrace().SetBody(&types.Protection{ProtectionEnabled: enable}).Post("/dns_config") return cl.doPost(cl.client.R().EnableTrace().SetBody(&types.Protection{ProtectionEnabled: enable}), "/dns_config")
return err
} }
func (cl *client) SetCustomRules(rules types.UserRules) error { func (cl *client) SetCustomRules(rules types.UserRules) error {
cl.log.With("rules", len(rules)).Info("Set user rules") cl.log.With("rules", len(rules)).Info("Set user rules")
_, err := cl.client.R().EnableTrace().SetBody(rules.String()).Post("/filtering/set_rules") return cl.doPost(cl.client.R().EnableTrace().SetBody(rules.String()), "/filtering/set_rules")
return err
} }
func (cl *client) ToggleFiltering(enabled bool, interval int) error { func (cl *client) ToggleFiltering(enabled bool, interval int) error {
cl.log.With("enabled", enabled, "interval", interval).Info("Toggle filtering") cl.log.With("enabled", enabled, "interval", interval).Info("Toggle filtering")
_, err := cl.client.R().EnableTrace().SetBody(&types.FilteringConfig{ return cl.doPost(cl.client.R().EnableTrace().SetBody(&types.FilteringConfig{
EnableConfig: types.EnableConfig{Enabled: enabled}, EnableConfig: types.EnableConfig{Enabled: enabled},
IntervalConfig: types.IntervalConfig{Interval: interval}, IntervalConfig: types.IntervalConfig{Interval: interval},
}).Post("/filtering/config") }), "/filtering/config")
return err
} }
func (cl *client) Services() (*types.Services, error) { func (cl *client) Services() (types.Services, error) {
svcs := &types.Services{} svcs := types.Services{}
_, err := cl.client.R().EnableTrace().SetResult(svcs).Get("/blocked_services/list") err := cl.doGet(cl.client.R().EnableTrace().SetResult(&svcs), "/blocked_services/list")
return svcs, err return svcs, err
} }
func (cl *client) SetServices(services types.Services) error { func (cl *client) SetServices(services types.Services) error {
cl.log.With("services", len(services)).Info("Set services") cl.log.With("services", len(services)).Info("Set services")
_, err := cl.client.R().EnableTrace().SetBody(&services).Post("/blocked_services/set") return cl.doPost(cl.client.R().EnableTrace().SetBody(&services), "/blocked_services/set")
return err
} }
func (cl *client) Clients() (*types.Clients, error) { func (cl *client) Clients() (*types.Clients, error) {
clients := &types.Clients{} 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 return clients, err
} }
func (cl *client) AddClients(clients ...types.Client) error { func (cl *client) AddClients(clients ...types.Client) error {
for _, client := range clients { for _, client := range clients {
cl.log.With("name", client.Name).Info("Add client") 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 { if err != nil {
return err return err
} }
@@ -260,7 +302,7 @@ func (cl *client) AddClients(clients ...types.Client) error {
func (cl *client) UpdateClients(clients ...types.Client) error { func (cl *client) UpdateClients(clients ...types.Client) error {
for _, client := range clients { for _, client := range clients {
cl.log.With("name", client.Name).Info("Update client") 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 { if err != nil {
return err return err
} }
@@ -271,7 +313,7 @@ func (cl *client) UpdateClients(clients ...types.Client) error {
func (cl *client) DeleteClients(clients ...types.Client) error { func (cl *client) DeleteClients(clients ...types.Client) error {
for _, client := range clients { for _, client := range clients {
cl.log.With("name", client.Name).Info("Delete client") 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 { if err != nil {
return err return err
} }
@@ -281,28 +323,26 @@ func (cl *client) DeleteClients(clients ...types.Client) error {
func (cl *client) QueryLogConfig() (*types.QueryLogConfig, error) { func (cl *client) QueryLogConfig() (*types.QueryLogConfig, error) {
qlc := &types.QueryLogConfig{} qlc := &types.QueryLogConfig{}
_, err := cl.client.R().EnableTrace().SetResult(qlc).Get("/querylog_info") err := cl.doGet(cl.client.R().EnableTrace().SetResult(qlc), "/querylog_info")
return qlc, err return qlc, err
} }
func (cl *client) SetQueryLogConfig(enabled bool, interval int, anonymizeClientIP bool) error { 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") cl.log.With("enabled", enabled, "interval", interval, "anonymizeClientIP", anonymizeClientIP).Info("Set query log config")
_, err := cl.client.R().EnableTrace().SetBody(&types.QueryLogConfig{ return cl.doPost(cl.client.R().EnableTrace().SetBody(&types.QueryLogConfig{
EnableConfig: types.EnableConfig{Enabled: enabled}, EnableConfig: types.EnableConfig{Enabled: enabled},
IntervalConfig: types.IntervalConfig{Interval: interval}, IntervalConfig: types.IntervalConfig{Interval: interval},
AnonymizeClientIP: anonymizeClientIP, AnonymizeClientIP: anonymizeClientIP,
}).Post("/querylog_config") }), "/querylog_config")
return err
} }
func (cl *client) StatsConfig() (*types.IntervalConfig, error) { func (cl *client) StatsConfig() (*types.IntervalConfig, error) {
stats := &types.IntervalConfig{} stats := &types.IntervalConfig{}
_, err := cl.client.R().EnableTrace().SetResult(stats).Get("/stats_info") err := cl.doGet(cl.client.R().EnableTrace().SetResult(stats), "/stats_info")
return stats, err return stats, err
} }
func (cl *client) SetStatsConfig(interval int) error { func (cl *client) SetStatsConfig(interval int) error {
cl.log.With("interval", interval).Info("Set stats config") cl.log.With("interval", interval).Info("Set stats config")
_, err := cl.client.R().EnableTrace().SetBody(&types.IntervalConfig{Interval: interval}).Post("/stats_config") return cl.doPost(cl.client.R().EnableTrace().SetBody(&types.IntervalConfig{Interval: interval}), "/stats_config")
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")
}

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

@@ -0,0 +1,323 @@
package client_test
import (
"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"
)
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"))
})
})
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})
Ω(err).ShouldNot(HaveOccurred())
return ts, cl
}

View File

@@ -1,11 +1,16 @@
package log package log
import ( import (
"os"
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/zapcore" "go.uber.org/zap/zapcore"
) )
const logHistorySize = 50 const (
logHistorySize = 50
envLogLevel = "LOG_LEVEL"
)
var ( var (
rootLogger *zap.Logger rootLogger *zap.Logger
@@ -20,6 +25,12 @@ func GetLogger(name string) *zap.SugaredLogger {
func init() { func init() {
level := zap.InfoLevel level := zap.InfoLevel
if lvl, ok := os.LookupEnv(envLogLevel); ok {
if err := level.Set(lvl); err != nil {
panic(err)
}
}
cfg := zap.Config{ cfg := zap.Config{
Level: zap.NewAtomicLevelAt(level), Level: zap.NewAtomicLevelAt(level),
Development: false, Development: false,

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

@@ -0,0 +1,486 @@
// 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
}
// 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...)
}
// 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))
}
// 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...)
}
// 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))
}
// 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)
}
// 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)
}
// 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...)
}

View File

@@ -12,6 +12,7 @@ import (
"time" "time"
"github.com/bakito/adguardhome-sync/pkg/log" "github.com/bakito/adguardhome-sync/pkg/log"
"github.com/bakito/adguardhome-sync/version"
) )
func (w *worker) handleSync(rw http.ResponseWriter, req *http.Request) { func (w *worker) handleSync(rw http.ResponseWriter, req *http.Request) {
@@ -61,7 +62,7 @@ func use(h http.HandlerFunc, middleware ...func(http.HandlerFunc) http.HandlerFu
} }
func (w *worker) listenAndServe() { func (w *worker) listenAndServe() {
l.With("port", w.cfg.API.Port).Info("Starting API server") l.With("version", version.Version, "port", w.cfg.API.Port).Info("Starting API server")
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
mux := http.NewServeMux() mux := http.NewServeMux()

View File

@@ -1,9 +1,12 @@
package sync package sync
import ( import (
"fmt"
"github.com/bakito/adguardhome-sync/pkg/client" "github.com/bakito/adguardhome-sync/pkg/client"
"github.com/bakito/adguardhome-sync/pkg/log" "github.com/bakito/adguardhome-sync/pkg/log"
"github.com/bakito/adguardhome-sync/pkg/types" "github.com/bakito/adguardhome-sync/pkg/types"
"github.com/bakito/adguardhome-sync/version"
"github.com/robfig/cron/v3" "github.com/robfig/cron/v3"
"go.uber.org/zap" "go.uber.org/zap"
) )
@@ -13,19 +16,31 @@ var (
) )
// Sync config from origin to replica // 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")
}
w := &worker{ w := &worker{
cfg: cfg, cfg: cfg,
createClient: func(ai types.AdGuardInstance) (client.Client, error) {
return client.New(ai)
},
} }
if cfg.Cron != "" { if cfg.Cron != "" {
w.cron = cron.New() w.cron = cron.New()
cl := l.With("cron", cfg.Cron) cl := l.With("version", version.Version, "cron", cfg.Cron)
_, err := w.cron.AddFunc(cfg.Cron, func() { _, err := w.cron.AddFunc(cfg.Cron, func() {
w.sync() w.sync()
}) })
if err != nil { if err != nil {
cl.With("error", err).Error("Error during cron job setup") cl.With("error", err).Error("Error during cron job setup")
return return err
} }
cl.Info("Setup cronjob") cl.Info("Setup cronjob")
if cfg.API.Port != 0 { if cfg.API.Port != 0 {
@@ -39,12 +54,14 @@ func Sync(cfg *types.Config) {
if cfg.API.Port != 0 { if cfg.API.Port != 0 {
w.listenAndServe() w.listenAndServe()
} }
return nil
} }
type worker struct { type worker struct {
cfg *types.Config cfg *types.Config
running bool running bool
cron *cron.Cron cron *cron.Cron
createClient func(instance types.AdGuardInstance) (client.Client, error)
} }
func (w *worker) sync() { func (w *worker) sync() {
@@ -55,7 +72,7 @@ func (w *worker) sync() {
w.running = true w.running = true
defer func() { w.running = false }() defer func() { w.running = false }()
oc, err := client.New(w.cfg.Origin) oc, err := w.createClient(w.cfg.Origin)
if err != nil { if err != nil {
l.With("error", err, "url", w.cfg.Origin.URL).Error("Error creating origin client") l.With("error", err, "url", w.cfg.Origin.URL).Error("Error creating origin client")
return return
@@ -64,7 +81,6 @@ func (w *worker) sync() {
sl := l.With("from", oc.Host()) sl := l.With("from", oc.Host())
o := &origin{} o := &origin{}
o.status, err = oc.Status() o.status, err = oc.Status()
if err != nil { if err != nil {
sl.With("error", err).Error("Error getting origin status") sl.With("error", err).Error("Error getting origin status")
@@ -128,9 +144,10 @@ func (w *worker) sync() {
func (w *worker) 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 { if err != nil {
l.With("error", err, "url", replica.URL).Error("Error creating replica client") l.With("error", err, "url", replica.URL).Error("Error creating replica client")
return
} }
rl := l.With("to", rc.Host()) rl := l.With("to", rc.Host())
@@ -152,7 +169,7 @@ func (w *worker) syncTo(l *zap.SugaredLogger, o *origin, replica types.AdGuardIn
return return
} }
err = w.syncConfigs(o, rs, rc) err = w.syncConfigs(o, rc)
if err != nil { if err != nil {
l.With("error", err).Error("Error syncing configs") l.With("error", err).Error("Error syncing configs")
return return
@@ -183,14 +200,14 @@ func (w *worker) syncTo(l *zap.SugaredLogger, o *origin, replica types.AdGuardIn
rl.Info("Sync done") rl.Info("Sync done")
} }
func (w *worker) syncServices(os *types.Services, replica client.Client) error { func (w *worker) syncServices(os types.Services, replica client.Client) error {
rs, err := replica.Services() rs, err := replica.Services()
if err != nil { if err != nil {
return err return err
} }
if !os.Equals(rs) { if !os.Equals(rs) {
if err := replica.SetServices(*os); err != nil { if err := replica.SetServices(os); err != nil {
return err return err
} }
} }
@@ -203,34 +220,10 @@ func (w *worker) syncFilters(of *types.FilteringStatus, replica client.Client) e
return err return err
} }
fa, fd := rf.Filters.Merge(of.Filters) if err = w.syncFilterType(of.Filters, rf.Filters, false, replica); err != nil {
if err = replica.AddFilters(false, fa...); err != nil {
return err return err
} }
if err = w.syncFilterType(of.WhitelistFilters, rf.WhitelistFilters, true, replica); err != nil {
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 {
return err return err
} }
@@ -246,6 +239,28 @@ func (w *worker) syncFilters(of *types.FilteringStatus, replica client.Client) e
return nil return nil
} }
func (w *worker) syncFilterType(of types.Filters, rFilters types.Filters, whitelist bool, replica client.Client) error {
fa, fu, fd := rFilters.Merge(of)
if err := replica.AddFilters(whitelist, fa...); err != nil {
return err
}
if err := replica.UpdateFilters(whitelist, fu...); err != nil {
return err
}
if len(fa) > 0 || len(fu) > 0 {
if err := replica.RefreshFilters(whitelist); err != nil {
return err
}
}
if err := replica.DeleteFilters(whitelist, fd...); err != nil {
return err
}
return nil
}
func (w *worker) syncRewrites(or *types.RewriteEntries, replica client.Client) error { func (w *worker) syncRewrites(or *types.RewriteEntries, replica client.Client) error {
replicaRewrites, err := replica.RewriteList() replicaRewrites, err := replica.RewriteList()
@@ -314,7 +329,7 @@ func (w *worker) syncGeneralSettings(o *origin, rs *types.Status, replica client
return nil return nil
} }
func (w *worker) syncConfigs(o *origin, rs *types.Status, replica client.Client) error { func (w *worker) syncConfigs(o *origin, replica client.Client) error {
qlc, err := replica.QueryLogConfig() qlc, err := replica.QueryLogConfig()
if err != nil { if err != nil {
return err return err
@@ -341,7 +356,7 @@ func (w *worker) syncConfigs(o *origin, rs *types.Status, replica client.Client)
type origin struct { type origin struct {
status *types.Status status *types.Status
rewrites *types.RewriteEntries rewrites *types.RewriteEntries
services *types.Services services types.Services
filters *types.FilteringStatus filters *types.FilteringStatus
clients *types.Clients clients *types.Clients
queryLogConfig *types.QueryLogConfig queryLogConfig *types.QueryLogConfig

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")
}

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

@@ -0,0 +1,386 @@
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(&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(&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(&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(&reO, cl)
Ω(err).ShouldNot(HaveOccurred())
})
It("should return error when error on RewriteList()", func() {
cl.EXPECT().RewriteList().Return(nil, te)
err := w.syncRewrites(&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(&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(&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("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("sync", func() {
It("should have no changes", func() {
w.cfg = &types.Config{
Origin: types.AdGuardInstance{},
Replica: types.AdGuardInstance{URL: "foo"},
}
// 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)
// 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()
w.sync()
})
})
})
})

View File

@@ -5,13 +5,16 @@ import (
"fmt" "fmt"
"sort" "sort"
"strings" "strings"
"time" )
const (
DefaultAPIPath = "/control"
) )
// Config application configuration struct // Config application configuration struct
type Config struct { type Config struct {
Origin AdGuardInstance `json:"origin" yaml:"origin"` Origin AdGuardInstance `json:"origin" yaml:"origin"`
Replica *AdGuardInstance `json:"replica,omitempty" yaml:"replica,omitempty"` Replica AdGuardInstance `json:"replica,omitempty" yaml:"replica,omitempty"`
Replicas []AdGuardInstance `json:"replicas,omitempty" yaml:"replicas,omitempty"` Replicas []AdGuardInstance `json:"replicas,omitempty" yaml:"replicas,omitempty"`
Cron string `json:"cron,omitempty" yaml:"cron,omitempty"` Cron string `json:"cron,omitempty" yaml:"cron,omitempty"`
API API `json:"api,omitempty" yaml:"api,omitempty"` API API `json:"api,omitempty" yaml:"api,omitempty"`
@@ -27,15 +30,20 @@ type API struct {
// UniqueReplicas get unique replication instances // UniqueReplicas get unique replication instances
func (cfg *Config) UniqueReplicas() []AdGuardInstance { func (cfg *Config) UniqueReplicas() []AdGuardInstance {
dedup := make(map[string]AdGuardInstance) dedup := make(map[string]AdGuardInstance)
if cfg.Replica != nil { if cfg.Replica.URL != "" {
dedup[cfg.Replica.Key()] = *cfg.Replica dedup[cfg.Replica.Key()] = cfg.Replica
} }
for _, replica := range cfg.Replicas { for _, replica := range cfg.Replicas {
dedup[replica.Key()] = replica if replica.URL != "" {
dedup[replica.Key()] = replica
}
} }
var r []AdGuardInstance var r []AdGuardInstance
for _, replica := range dedup { for _, replica := range dedup {
if replica.APIPath == "" {
replica.APIPath = DefaultAPIPath
}
r = append(r, replica) r = append(r, replica)
} }
return r return r
@@ -52,7 +60,7 @@ type AdGuardInstance struct {
// Key AdGuardInstance key // Key AdGuardInstance key
func (i *AdGuardInstance) Key() string { 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 // Protection API struct
@@ -114,15 +122,55 @@ func (re *RewriteEntry) Key() string {
// Filters list of Filter // Filters list of Filter
type Filters []Filter type Filters []Filter
// Merge merge Filters
func (f Filters) Merge(other Filters) (Filters, Filters, Filters) {
current := make(map[string]Filter)
var adds Filters
var updates Filters
var removes Filters
for _, f := range f {
current[f.URL] = f
}
for _, rr := range other {
if c, ok := current[rr.URL]; ok {
if !c.Equals(&rr) {
updates = append(updates, rr)
}
delete(current, rr.URL)
} else {
adds = append(adds, rr)
}
}
for _, rr := range current {
removes = append(removes, rr)
}
return adds, updates, removes
}
// Filter API struct // Filter API struct
type Filter struct { type Filter struct {
ID int `json:"id"` ID int `json:"id"`
Enabled bool `json:"enabled"` Enabled bool `json:"enabled"`
URL string `json:"url"` // needed for add URL string `json:"url"` // needed for add
Name string `json:"name"` // needed for add Name string `json:"name"` // needed for add
RulesCount int `json:"rules_count"` RulesCount int `json:"rules_count"`
LastUpdated time.Time `json:"last_updated"` Whitelist bool `json:"whitelist"` // needed for add
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 // FilteringStatus API struct
@@ -174,31 +222,6 @@ type RefreshFilter struct {
Whitelist bool `json:"whitelist"` Whitelist bool `json:"whitelist"`
} }
// Merge merge RefreshFilters
func (fs *Filters) Merge(other Filters) (Filters, Filters) {
current := make(map[string]Filter)
var adds Filters
var removes Filters
for _, f := range *fs {
current[f.URL] = f
}
for _, rr := range other {
if _, ok := current[rr.URL]; ok {
delete(current, rr.URL)
} else {
adds = append(adds, rr)
}
}
for _, rr := range current {
removes = append(removes, rr)
}
return adds, removes
}
// Services API struct // Services API struct
type Services []string type Services []string
@@ -208,10 +231,10 @@ func (s Services) Sort() {
} }
// Equals Services equal check // Equals Services equal check
func (s *Services) Equals(o *Services) bool { func (s Services) Equals(o Services) bool {
s.Sort() s.Sort()
o.Sort() o.Sort()
return equals(*s, *o) return equals(s, o)
} }
// Clients API struct // Clients API struct
@@ -229,10 +252,10 @@ type Clients struct {
// Client API struct // Client API struct
type Client struct { type Client struct {
Ids []string `json:"ids"` Ids []string `json:"ids,omitempty"`
Tags []string `json:"tags"` Tags []string `json:"tags,omitempty"`
BlockedServices []string `json:"blocked_services"` BlockedServices []string `json:"blocked_services,omitempty"`
Upstreams []string `json:"upstreams"` Upstreams []string `json:"upstreams,omitempty"`
UseGlobalSettings bool `json:"use_global_settings"` UseGlobalSettings bool `json:"use_global_settings"`
UseGlobalBlockedServices bool `json:"use_global_blocked_services"` UseGlobalBlockedServices bool `json:"use_global_blocked_services"`

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")
}

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

@@ -0,0 +1,308 @@
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, d := replicaRE.Merge(&originRE)
Ω(a).Should(HaveLen(1))
Ω(d).Should(BeEmpty())
Ω(a[0].Domain).Should(Equal(domain))
})
It("should remove additional ewrite entry", func() {
replicaRE = append(replicaRE, types.RewriteEntry{Domain: domain})
a, d := replicaRE.Merge(&originRE)
Ω(a).Should(BeEmpty())
Ω(d).Should(HaveLen(1))
Ω(d[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, d := replicaRE.Merge(&originRE)
Ω(a).Should(BeEmpty())
Ω(d).Should(BeEmpty())
})
})
})
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())
})
})
})
})

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"
]
}

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