mirror of
https://github.com/lovelaze/nebula-sync.git
synced 2025-11-05 18:29:19 +01:00
0.1.0 Release
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
name: release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*.*.*"
|
||||
jobs:
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract tag
|
||||
id: extract_tag
|
||||
run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: |
|
||||
ghcr.io/lovelaze/nebula-sync:latest
|
||||
ghcr.io/lovelaze/nebula-sync:${{ env.TAG }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
provenance: false
|
||||
|
||||
release:
|
||||
needs: docker
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
@@ -0,0 +1,25 @@
|
||||
name: Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
jobs:
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
|
||||
- name: Build
|
||||
run: go build -v ./...
|
||||
|
||||
- name: Test
|
||||
run: go test -cover -v ./...
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
# intellij
|
||||
.idea
|
||||
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
nebula-sync
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
.env
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
go.work.sum
|
||||
@@ -0,0 +1,10 @@
|
||||
with-expecter: True
|
||||
dir: internal/mocks/{{.PackageName}}
|
||||
mockname: "{{.InterfaceName}}"
|
||||
outpkg: "{{.PackageName}}"
|
||||
filename: "{{.InterfaceName}}.go"
|
||||
all: True
|
||||
packages:
|
||||
github.com/lovelaze/nebula-sync:
|
||||
config:
|
||||
recursive: True
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
FROM golang:1.22-alpine AS golang
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apk add -U tzdata upx && \
|
||||
apk --update add ca-certificates
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN go mod download
|
||||
RUN go mod verify
|
||||
|
||||
ENV GO111MODULE=on
|
||||
ENV CGO_ENABLED=0
|
||||
ENV GOOS=linux
|
||||
ENV GOFLAGS="-a -ldflags=-w -ldflags=-s -trimpath -o=nebula-sync"
|
||||
|
||||
RUN go build . && \
|
||||
upx -q nebula-sync
|
||||
|
||||
FROM scratch
|
||||
|
||||
COPY --from=golang /usr/share/zoneinfo/ /usr/share/zoneinfo/
|
||||
COPY --from=golang /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||
COPY --from=golang /app/nebula-sync /usr/local/bin/
|
||||
|
||||
USER 1001
|
||||
|
||||
ENTRYPOINT ["nebula-sync"]
|
||||
CMD ["run"]
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Fredrik Berntsson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1 +1,92 @@
|
||||
# nebula-sync
|
||||
# nebula-sync
|
||||
|
||||
[](https://github.com/lovelaze/nebula-sync/actions/workflows/test.yml)
|
||||
|
||||
Synchronize Pi-hole 6+ configuration to replicas.
|
||||
|
||||
This project is not a part of the [official Pi-hole project](https://github.com/pi-hole), but uses the api provided by Pi-hole instances to perform the synchronization actions.
|
||||
|
||||
## Features
|
||||
- **Full sync**: Use Pi-hole Teleporter for full synchronization.
|
||||
- **Manual sync**: Selective feature synchronization.
|
||||
- **Cron schedule**: Run on chron schedule.
|
||||
|
||||
## Installation
|
||||
|
||||
### Docker Compose (recommended)
|
||||
|
||||
```yaml
|
||||
---
|
||||
services:
|
||||
nebula-sync:
|
||||
image: ghcr.io/lovelaze/nebula-sync:latest
|
||||
container_name: nebula-sync
|
||||
environment:
|
||||
- PRIMARY=http://ph1.example.com|password
|
||||
- REPLICAS=http://ph2.example.com|password,http://ph3.example.com|password
|
||||
- FULL_SYNC=true
|
||||
- CRON=0 * * * *
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
### Docker CLI
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name nebula-sync \
|
||||
-e PRIMARY=http://ph1.example.com|password \
|
||||
-e REPLICAS=http://ph2.example.com|password,http://ph3.example.com|password \
|
||||
-e FULL_SYNC=true \
|
||||
-e CRON=0 * * * * \
|
||||
--restart unless-stopped \
|
||||
ghcr.io/lovelaze/nebula-sync:latest
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
The following environment variables can be specified:
|
||||
|
||||
### Required Environment Variables
|
||||
|
||||
| Name | Default | Example | Description |
|
||||
|-----------|---------|--------------------------------------------------|----------------------------------------------------------|
|
||||
| `PRIMARY` | n/a | `http://ph1.example.com\|password` | Specifies the primary Pi-hole configuration |
|
||||
| `REPLICAS`| n/a | `http://ph2.example.com\|password,http://ph3.example.com\|password` | Specifies the list of replica Pi-hole configurations |
|
||||
| `FULL_SYNC` | n/a | `true` | Specifies whether to perform a full synchronization |
|
||||
|
||||
> **Note:** When `FULL_SYNC=true`, the system will perform a full Teleporter import/export from the primary Pi-hole to the replicas. This will synchronize all settings and configurations.
|
||||
|
||||
### Optional Environment Variables
|
||||
|
||||
| Name | Default | Example | Description |
|
||||
|----------|---------|---------------|------------------------------------------------|
|
||||
| `CRON` | n/a | `0 * * * *` | Specifies the cron schedule for synchronization |
|
||||
|
||||
> **Note:** The following optional settings apply only if `FULL_SYNC=false`. They allow for granular control of synchronization if a full sync is not wanted.
|
||||
|
||||
| Name | Default | Description |
|
||||
|-----------------------------------|---------|----------------------------------------|
|
||||
| `SYNC_CONFIG_DNS` | false | Synchronize DNS settings |
|
||||
| `SYNC_CONFIG_DHCP` | false | Synchronize DHCP settings |
|
||||
| `SYNC_CONFIG_NTP` | false | Synchronize NTP settings |
|
||||
| `SYNC_CONFIG_RESOLVER` | false | Synchronize resolver settings |
|
||||
| `SYNC_CONFIG_DATABASE` | false | Synchronize database settings |
|
||||
| `SYNC_CONFIG_MISC` | false | Synchronize miscellaneous settings |
|
||||
| `SYNC_CONFIG_DEBUG` | false | Synchronize debug settings |
|
||||
| `SYNC_GRAVITY_DHCP_LEASES` | false | Synchronize DHCP leases |
|
||||
| `SYNC_GRAVITY_GROUP` | false | Synchronize groups |
|
||||
| `SYNC_GRAVITY_AD_LIST` | false | Synchronize ad lists |
|
||||
| `SYNC_GRAVITY_AD_LIST_BY_GROUP` | false | Synchronize ad lists by group |
|
||||
| `SYNC_GRAVITY_DOMAIN_LIST` | false | Synchronize domain lists |
|
||||
| `SYNC_GRAVITY_DOMAIN_LIST_BY_GROUP`| false | Synchronize domain lists by group |
|
||||
| `SYNC_GRAVITY_CLIENT` | false | Synchronize clients |
|
||||
| `SYNC_GRAVITY_CLIENT_BY_GROUP` | false | Synchronize clients by group |
|
||||
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This project is an unofficial, community-maintained project and is not affiliated with the [official Pi-hole project](https://github.com/pi-hole). It aims to add sync/replication features not available in the core Pi-hole product but operates independently of Pi-hole LLC. Although tested across various environments, using any software from the Internet involves inherent risks. See the [license](https://github.com/lovelaze/nebula-sync/blob/main/LICENSE) for more details.
|
||||
|
||||
Pi-hole and the Pi-hole logo are [registered trademarks](https://pi-hole.net/trademark-rules-and-brand-guidelines) of Pi-hole LLC.
|
||||
|
||||
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/lovelaze/nebula-sync/internal/log"
|
||||
"github.com/lovelaze/nebula-sync/version"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "nebula-sync",
|
||||
Version: version.Version,
|
||||
}
|
||||
|
||||
func Execute() {
|
||||
rootCmd.Execute()
|
||||
}
|
||||
|
||||
func init() {
|
||||
cobra.OnInitialize(log.Init)
|
||||
rootCmd.CompletionOptions.HiddenDefaultCmd = true
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/lovelaze/nebula-sync/internal/config"
|
||||
"github.com/lovelaze/nebula-sync/internal/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var runCmd = &cobra.Command{
|
||||
Use: "run",
|
||||
Short: "Run sync",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
conf := config.Config{}
|
||||
conf.Load()
|
||||
|
||||
service := service.NewService(conf)
|
||||
service.Run()
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(runCmd)
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
module github.com/lovelaze/nebula-sync
|
||||
|
||||
go 1.22
|
||||
|
||||
require (
|
||||
github.com/kelseyhightower/envconfig v1.4.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/rs/zerolog v1.33.0
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/testcontainers/testcontainers-go v0.32.0
|
||||
)
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/Microsoft/hcsshim v0.11.5 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
|
||||
github.com/containerd/containerd v1.7.18 // indirect
|
||||
github.com/containerd/errdefs v0.1.0 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/cpuguy83/dockercfg v0.3.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/docker v27.0.3+incompatible // indirect
|
||||
github.com/docker/go-connections v0.5.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/klauspost/compress v1.17.4 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/patternmatcher v0.6.0 // indirect
|
||||
github.com/moby/sys/sequential v0.5.0 // indirect
|
||||
github.com/moby/sys/user v0.1.0 // indirect
|
||||
github.com/moby/term v0.5.0 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/shirou/gopsutil/v3 v3.23.12 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
|
||||
go.opentelemetry.io/otel v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.24.0 // indirect
|
||||
golang.org/x/crypto v0.22.0 // indirect
|
||||
golang.org/x/sys v0.23.0 // indirect
|
||||
golang.org/x/time v0.1.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect
|
||||
google.golang.org/grpc v1.59.0 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
@@ -0,0 +1,223 @@
|
||||
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/Microsoft/hcsshim v0.11.5 h1:haEcLNpj9Ka1gd3B3tAEs9CpE0c+1IhoL59w/exYU38=
|
||||
github.com/Microsoft/hcsshim v0.11.5/go.mod h1:MV8xMfmECjl5HdO7U/3/hFVnkmSBjAjmA09d4bExKcU=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao=
|
||||
github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4=
|
||||
github.com/containerd/errdefs v0.1.0 h1:m0wCRBiu1WJT/Fr+iOoQHMQS/eP5myQ8lCv4Dz5ZURM=
|
||||
github.com/containerd/errdefs v0.1.0/go.mod h1:YgWiiHtLmSeBrvpw+UfPijzbLaB77mEG1WwJTDETIV0=
|
||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E=
|
||||
github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
||||
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/docker/docker v27.0.3+incompatible h1:aBGI9TeQ4MPlhquTQKq9XbK79rKFVwXNUAYz9aXyEBE=
|
||||
github.com/docker/docker v27.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
|
||||
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
||||
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
|
||||
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
||||
github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
|
||||
github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
|
||||
github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg=
|
||||
github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU=
|
||||
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
|
||||
github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
|
||||
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4=
|
||||
github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM=
|
||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/testcontainers/testcontainers-go v0.32.0 h1:ug1aK08L3gCHdhknlTTwWjPHPS+/alvLJU/DRxTD/ME=
|
||||
github.com/testcontainers/testcontainers-go v0.32.0/go.mod h1:CRHrzHLQhlXUsa5gXjTOfqIEJcrK5+xMDmBr/WMI88E=
|
||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
||||
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
|
||||
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
|
||||
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
|
||||
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
|
||||
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
|
||||
go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o=
|
||||
go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A=
|
||||
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
|
||||
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
|
||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
|
||||
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
|
||||
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA=
|
||||
golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto v0.0.0-20231012201019-e917dd12ba7a h1:fwgW9j3vHirt4ObdHoYNwuO24BEZjSzbh+zPaNWoiY8=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb h1:lK0oleSc7IQsUxO3U5TjL9DWlsxpEBemh+zpB7IqhWI=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b h1:ZlWIi1wSK56/8hn4QcBp/j9M7Gt3U/3hZw3mC7vDICo=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:swOH3j0KzcDDgGUWr+SNpyTen5YrXjS3eyPzFYKc6lc=
|
||||
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
|
||||
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
|
||||
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
||||
@@ -0,0 +1,70 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/kelseyhightower/envconfig"
|
||||
"github.com/lovelaze/nebula-sync/internal/pihole/model"
|
||||
"log"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Primary model.PiHole `required:"true" envconfig:"PRIMARY"`
|
||||
Replicas []model.PiHole `required:"true" envconfig:"REPLICAS"`
|
||||
FullSync bool `required:"true" envconfig:"FULL_SYNC"`
|
||||
Cron *string `envconfig:"CRON"`
|
||||
SyncSettings *SyncSettings `ignored:"true"`
|
||||
}
|
||||
|
||||
type ManualGravity struct {
|
||||
DHCPLeases bool `default:"false" envconfig:"SYNC_GRAVITY_DHCP_LEASES"`
|
||||
Group bool `default:"false" envconfig:"SYNC_GRAVITY_GROUP"`
|
||||
Adlist bool `default:"false" envconfig:"SYNC_GRAVITY_AD_LIST"`
|
||||
AdlistByGroup bool `default:"false" envconfig:"SYNC_GRAVITY_AD_LIST_BY_GROUP"`
|
||||
Domainlist bool `default:"false" envconfig:"SYNC_GRAVITY_DOMAIN_LIST"`
|
||||
DomainlistByGroup bool `default:"false" envconfig:"SYNC_GRAVITY_DOMAIN_LIST_BY_GROUP"`
|
||||
Client bool `default:"false" envconfig:"SYNC_GRAVITY_CLIENT"`
|
||||
ClientByGroup bool `default:"false" envconfig:"SYNC_GRAVITY_CLIENT_BY_GROUP"`
|
||||
}
|
||||
|
||||
type ManualConfig struct {
|
||||
DNS bool `default:"false" envconfig:"SYNC_CONFIG_DNS"`
|
||||
DHCP bool `default:"false" envconfig:"SYNC_CONFIG_DHCP"`
|
||||
NTP bool `default:"false" envconfig:"SYNC_CONFIG_NTP"`
|
||||
Resolver bool `default:"false" envconfig:"SYNC_CONFIG_RESOLVER"`
|
||||
Database bool `default:"false" envconfig:"SYNC_CONFIG_DATABASE"`
|
||||
Webserver bool `default:"false" ignored:"true"` // ignore for now
|
||||
Files bool `default:"false" ignored:"true"` // ignore for now
|
||||
Misc bool `default:"false" envconfig:"SYNC_CONFIG_MISC"`
|
||||
Debug bool `default:"false" envconfig:"SYNC_CONFIG_DEBUG"`
|
||||
}
|
||||
|
||||
type SyncSettings struct {
|
||||
Gravity *ManualGravity `ignored:"true"`
|
||||
Config *ManualConfig `ignored:"true"`
|
||||
}
|
||||
|
||||
func (c *Config) Load() {
|
||||
if err := envconfig.Process("", c); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if !c.FullSync {
|
||||
c.loadSyncSettings()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Config) loadSyncSettings() {
|
||||
manualGravity := ManualGravity{}
|
||||
if err := envconfig.Process("", &manualGravity); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
manualConfig := ManualConfig{}
|
||||
if err := envconfig.Process("", &manualConfig); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
c.SyncSettings = &SyncSettings{
|
||||
Gravity: &manualGravity,
|
||||
Config: &manualConfig,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestConfig_Load(t *testing.T) {
|
||||
conf := Config{}
|
||||
|
||||
t.Setenv("PRIMARY", "http://localhost:1337|asdf")
|
||||
t.Setenv("REPLICAS", "http://localhost:1338|qwerty")
|
||||
t.Setenv("FULL_SYNC", "true")
|
||||
t.Setenv("CRON", "* * * * *")
|
||||
|
||||
conf.Load()
|
||||
|
||||
assert.Equal(t, "http://localhost:1337", conf.Primary.Url.String())
|
||||
assert.Equal(t, "asdf", conf.Primary.Password)
|
||||
assert.Len(t, conf.Replicas, 1)
|
||||
assert.Equal(t, "http://localhost:1338", conf.Replicas[0].Url.String())
|
||||
assert.Equal(t, "qwerty", conf.Replicas[0].Password)
|
||||
assert.Equal(t, true, conf.FullSync)
|
||||
assert.Equal(t, "* * * * *", *conf.Cron)
|
||||
assert.Nil(t, conf.SyncSettings)
|
||||
}
|
||||
|
||||
func TestConfig_loadSyncSettings(t *testing.T) {
|
||||
conf := Config{}
|
||||
assert.Nil(t, conf.SyncSettings)
|
||||
|
||||
t.Setenv("SYNC_CONFIG_DNS", "true")
|
||||
t.Setenv("SYNC_CONFIG_DHCP", "true")
|
||||
t.Setenv("SYNC_CONFIG_NTP", "true")
|
||||
t.Setenv("SYNC_CONFIG_RESOLVER", "true")
|
||||
t.Setenv("SYNC_CONFIG_DATABASE", "true")
|
||||
t.Setenv("SYNC_CONFIG_MISC", "true")
|
||||
t.Setenv("SYNC_CONFIG_DEBUG", "true")
|
||||
|
||||
t.Setenv("SYNC_GRAVITY_DHCP_LEASES", "true")
|
||||
t.Setenv("SYNC_GRAVITY_GROUP", "true")
|
||||
t.Setenv("SYNC_GRAVITY_AD_LIST", "true")
|
||||
t.Setenv("SYNC_GRAVITY_AD_LIST_BY_GROUP", "true")
|
||||
t.Setenv("SYNC_GRAVITY_DOMAIN_LIST", "true")
|
||||
t.Setenv("SYNC_GRAVITY_DOMAIN_LIST_BY_GROUP", "true")
|
||||
t.Setenv("SYNC_GRAVITY_CLIENT", "true")
|
||||
t.Setenv("SYNC_GRAVITY_CLIENT_BY_GROUP", "true")
|
||||
|
||||
conf.loadSyncSettings()
|
||||
|
||||
assert.NotNil(t, conf.SyncSettings.Config)
|
||||
assert.NotNil(t, conf.SyncSettings.Gravity)
|
||||
|
||||
assert.True(t, conf.SyncSettings.Config.DNS)
|
||||
assert.True(t, conf.SyncSettings.Config.DHCP)
|
||||
assert.True(t, conf.SyncSettings.Config.NTP)
|
||||
assert.True(t, conf.SyncSettings.Config.Resolver)
|
||||
assert.True(t, conf.SyncSettings.Config.Database)
|
||||
assert.True(t, conf.SyncSettings.Config.Misc)
|
||||
assert.True(t, conf.SyncSettings.Config.Debug)
|
||||
|
||||
assert.True(t, conf.SyncSettings.Gravity.DHCPLeases)
|
||||
assert.True(t, conf.SyncSettings.Gravity.Group)
|
||||
assert.True(t, conf.SyncSettings.Gravity.Adlist)
|
||||
assert.True(t, conf.SyncSettings.Gravity.AdlistByGroup)
|
||||
assert.True(t, conf.SyncSettings.Gravity.Domainlist)
|
||||
assert.True(t, conf.SyncSettings.Gravity.DomainlistByGroup)
|
||||
assert.True(t, conf.SyncSettings.Gravity.Client)
|
||||
assert.True(t, conf.SyncSettings.Gravity.ClientByGroup)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Init() {
|
||||
logger := log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339})
|
||||
log.Logger = logger
|
||||
zerolog.SetGlobalLevel(zerolog.InfoLevel)
|
||||
|
||||
if debugEnv := os.Getenv("NS_DEBUG"); debugEnv != "" {
|
||||
debug, err := strconv.ParseBool(debugEnv)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msgf("failed to parse boolean env NS_DEBUG")
|
||||
}
|
||||
|
||||
if debug {
|
||||
zerolog.SetGlobalLevel(zerolog.DebugLevel)
|
||||
logger = logger.With().Caller().Logger()
|
||||
}
|
||||
}
|
||||
|
||||
log.Logger = logger
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestInit_info(t *testing.T) {
|
||||
|
||||
Init()
|
||||
assert.Equal(t, zerolog.InfoLevel, zerolog.GlobalLevel())
|
||||
}
|
||||
|
||||
func TestInit_debug(t *testing.T) {
|
||||
|
||||
t.Setenv("NS_DEBUG", "true")
|
||||
Init()
|
||||
assert.Equal(t, zerolog.DebugLevel, zerolog.GlobalLevel())
|
||||
}
|
||||
@@ -0,0 +1,480 @@
|
||||
// Code generated by mockery v2.44.1. DO NOT EDIT.
|
||||
|
||||
package pihole
|
||||
|
||||
import (
|
||||
model "github.com/lovelaze/nebula-sync/internal/pihole/model"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// Client is an autogenerated mock type for the Client type
|
||||
type Client struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type Client_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *Client) EXPECT() *Client_Expecter {
|
||||
return &Client_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// ApiPath provides a mock function with given fields: target
|
||||
func (_m *Client) ApiPath(target string) string {
|
||||
ret := _m.Called(target)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ApiPath")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
if rf, ok := ret.Get(0).(func(string) string); ok {
|
||||
r0 = rf(target)
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Client_ApiPath_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ApiPath'
|
||||
type Client_ApiPath_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ApiPath is a helper method to define mock.On call
|
||||
// - target string
|
||||
func (_e *Client_Expecter) ApiPath(target interface{}) *Client_ApiPath_Call {
|
||||
return &Client_ApiPath_Call{Call: _e.mock.On("ApiPath", target)}
|
||||
}
|
||||
|
||||
func (_c *Client_ApiPath_Call) Run(run func(target string)) *Client_ApiPath_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Client_ApiPath_Call) Return(_a0 string) *Client_ApiPath_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Client_ApiPath_Call) RunAndReturn(run func(string) string) *Client_ApiPath_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Authenticate provides a mock function with given fields:
|
||||
func (_m *Client) Authenticate() error {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Authenticate")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func() error); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Client_Authenticate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Authenticate'
|
||||
type Client_Authenticate_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Authenticate is a helper method to define mock.On call
|
||||
func (_e *Client_Expecter) Authenticate() *Client_Authenticate_Call {
|
||||
return &Client_Authenticate_Call{Call: _e.mock.On("Authenticate")}
|
||||
}
|
||||
|
||||
func (_c *Client_Authenticate_Call) Run(run func()) *Client_Authenticate_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Client_Authenticate_Call) Return(_a0 error) *Client_Authenticate_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Client_Authenticate_Call) RunAndReturn(run func() error) *Client_Authenticate_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// DeleteSession provides a mock function with given fields:
|
||||
func (_m *Client) DeleteSession() error {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DeleteSession")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func() error); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Client_DeleteSession_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteSession'
|
||||
type Client_DeleteSession_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// DeleteSession is a helper method to define mock.On call
|
||||
func (_e *Client_Expecter) DeleteSession() *Client_DeleteSession_Call {
|
||||
return &Client_DeleteSession_Call{Call: _e.mock.On("DeleteSession")}
|
||||
}
|
||||
|
||||
func (_c *Client_DeleteSession_Call) Run(run func()) *Client_DeleteSession_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Client_DeleteSession_Call) Return(_a0 error) *Client_DeleteSession_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Client_DeleteSession_Call) RunAndReturn(run func() error) *Client_DeleteSession_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetConfig provides a mock function with given fields:
|
||||
func (_m *Client) GetConfig() (*model.ConfigResponse, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetConfig")
|
||||
}
|
||||
|
||||
var r0 *model.ConfigResponse
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func() (*model.ConfigResponse, error)); ok {
|
||||
return rf()
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func() *model.ConfigResponse); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.ConfigResponse)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func() error); ok {
|
||||
r1 = rf()
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Client_GetConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetConfig'
|
||||
type Client_GetConfig_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetConfig is a helper method to define mock.On call
|
||||
func (_e *Client_Expecter) GetConfig() *Client_GetConfig_Call {
|
||||
return &Client_GetConfig_Call{Call: _e.mock.On("GetConfig")}
|
||||
}
|
||||
|
||||
func (_c *Client_GetConfig_Call) Run(run func()) *Client_GetConfig_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Client_GetConfig_Call) Return(configResponse *model.ConfigResponse, err error) *Client_GetConfig_Call {
|
||||
_c.Call.Return(configResponse, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Client_GetConfig_Call) RunAndReturn(run func() (*model.ConfigResponse, error)) *Client_GetConfig_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetTeleporter provides a mock function with given fields:
|
||||
func (_m *Client) GetTeleporter() ([]byte, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetTeleporter")
|
||||
}
|
||||
|
||||
var r0 []byte
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func() ([]byte, error)); ok {
|
||||
return rf()
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func() []byte); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]byte)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func() error); ok {
|
||||
r1 = rf()
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Client_GetTeleporter_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetTeleporter'
|
||||
type Client_GetTeleporter_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetTeleporter is a helper method to define mock.On call
|
||||
func (_e *Client_Expecter) GetTeleporter() *Client_GetTeleporter_Call {
|
||||
return &Client_GetTeleporter_Call{Call: _e.mock.On("GetTeleporter")}
|
||||
}
|
||||
|
||||
func (_c *Client_GetTeleporter_Call) Run(run func()) *Client_GetTeleporter_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Client_GetTeleporter_Call) Return(_a0 []byte, _a1 error) *Client_GetTeleporter_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Client_GetTeleporter_Call) RunAndReturn(run func() ([]byte, error)) *Client_GetTeleporter_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetVersion provides a mock function with given fields:
|
||||
func (_m *Client) GetVersion() (*model.VersionResponse, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetVersion")
|
||||
}
|
||||
|
||||
var r0 *model.VersionResponse
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func() (*model.VersionResponse, error)); ok {
|
||||
return rf()
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func() *model.VersionResponse); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.VersionResponse)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func() error); ok {
|
||||
r1 = rf()
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Client_GetVersion_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetVersion'
|
||||
type Client_GetVersion_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetVersion is a helper method to define mock.On call
|
||||
func (_e *Client_Expecter) GetVersion() *Client_GetVersion_Call {
|
||||
return &Client_GetVersion_Call{Call: _e.mock.On("GetVersion")}
|
||||
}
|
||||
|
||||
func (_c *Client_GetVersion_Call) Run(run func()) *Client_GetVersion_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Client_GetVersion_Call) Return(_a0 *model.VersionResponse, _a1 error) *Client_GetVersion_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Client_GetVersion_Call) RunAndReturn(run func() (*model.VersionResponse, error)) *Client_GetVersion_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// PatchConfig provides a mock function with given fields: patchRequest
|
||||
func (_m *Client) PatchConfig(patchRequest *model.PatchConfigRequest) error {
|
||||
ret := _m.Called(patchRequest)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for PatchConfig")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(*model.PatchConfigRequest) error); ok {
|
||||
r0 = rf(patchRequest)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Client_PatchConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PatchConfig'
|
||||
type Client_PatchConfig_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// PatchConfig is a helper method to define mock.On call
|
||||
// - patchRequest *model.PatchConfigRequest
|
||||
func (_e *Client_Expecter) PatchConfig(patchRequest interface{}) *Client_PatchConfig_Call {
|
||||
return &Client_PatchConfig_Call{Call: _e.mock.On("PatchConfig", patchRequest)}
|
||||
}
|
||||
|
||||
func (_c *Client_PatchConfig_Call) Run(run func(patchRequest *model.PatchConfigRequest)) *Client_PatchConfig_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(*model.PatchConfigRequest))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Client_PatchConfig_Call) Return(_a0 error) *Client_PatchConfig_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Client_PatchConfig_Call) RunAndReturn(run func(*model.PatchConfigRequest) error) *Client_PatchConfig_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// PostTeleporter provides a mock function with given fields: payload, teleporterRequest
|
||||
func (_m *Client) PostTeleporter(payload []byte, teleporterRequest *model.PostTeleporterRequest) error {
|
||||
ret := _m.Called(payload, teleporterRequest)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for PostTeleporter")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func([]byte, *model.PostTeleporterRequest) error); ok {
|
||||
r0 = rf(payload, teleporterRequest)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Client_PostTeleporter_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PostTeleporter'
|
||||
type Client_PostTeleporter_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// PostTeleporter is a helper method to define mock.On call
|
||||
// - payload []byte
|
||||
// - teleporterRequest *model.PostTeleporterRequest
|
||||
func (_e *Client_Expecter) PostTeleporter(payload interface{}, teleporterRequest interface{}) *Client_PostTeleporter_Call {
|
||||
return &Client_PostTeleporter_Call{Call: _e.mock.On("PostTeleporter", payload, teleporterRequest)}
|
||||
}
|
||||
|
||||
func (_c *Client_PostTeleporter_Call) Run(run func(payload []byte, teleporterRequest *model.PostTeleporterRequest)) *Client_PostTeleporter_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].([]byte), args[1].(*model.PostTeleporterRequest))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Client_PostTeleporter_Call) Return(_a0 error) *Client_PostTeleporter_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Client_PostTeleporter_Call) RunAndReturn(run func([]byte, *model.PostTeleporterRequest) error) *Client_PostTeleporter_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// String provides a mock function with given fields:
|
||||
func (_m *Client) String() string {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for String")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
if rf, ok := ret.Get(0).(func() string); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Client_String_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'String'
|
||||
type Client_String_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// String is a helper method to define mock.On call
|
||||
func (_e *Client_Expecter) String() *Client_String_Call {
|
||||
return &Client_String_Call{Call: _e.mock.On("String")}
|
||||
}
|
||||
|
||||
func (_c *Client_String_Call) Run(run func()) *Client_String_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Client_String_Call) Return(_a0 string) *Client_String_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Client_String_Call) RunAndReturn(run func() string) *Client_String_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewClient creates a new instance of Client. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewClient(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *Client {
|
||||
mock := &Client{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
// Code generated by mockery v2.44.1. DO NOT EDIT.
|
||||
|
||||
package sync
|
||||
|
||||
import (
|
||||
config "github.com/lovelaze/nebula-sync/internal/config"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// Target is an autogenerated mock type for the Target type
|
||||
type Target struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type Target_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *Target) EXPECT() *Target_Expecter {
|
||||
return &Target_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// FullSync provides a mock function with given fields:
|
||||
func (_m *Target) FullSync() error {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for FullSync")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func() error); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Target_FullSync_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FullSync'
|
||||
type Target_FullSync_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// FullSync is a helper method to define mock.On call
|
||||
func (_e *Target_Expecter) FullSync() *Target_FullSync_Call {
|
||||
return &Target_FullSync_Call{Call: _e.mock.On("FullSync")}
|
||||
}
|
||||
|
||||
func (_c *Target_FullSync_Call) Run(run func()) *Target_FullSync_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Target_FullSync_Call) Return(_a0 error) *Target_FullSync_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Target_FullSync_Call) RunAndReturn(run func() error) *Target_FullSync_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ManualSync provides a mock function with given fields: syncSettings
|
||||
func (_m *Target) ManualSync(syncSettings *config.SyncSettings) error {
|
||||
ret := _m.Called(syncSettings)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ManualSync")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(*config.SyncSettings) error); ok {
|
||||
r0 = rf(syncSettings)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Target_ManualSync_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ManualSync'
|
||||
type Target_ManualSync_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ManualSync is a helper method to define mock.On call
|
||||
// - syncSettings *config.SyncSettings
|
||||
func (_e *Target_Expecter) ManualSync(syncSettings interface{}) *Target_ManualSync_Call {
|
||||
return &Target_ManualSync_Call{Call: _e.mock.On("ManualSync", syncSettings)}
|
||||
}
|
||||
|
||||
func (_c *Target_ManualSync_Call) Run(run func(syncSettings *config.SyncSettings)) *Target_ManualSync_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(*config.SyncSettings))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Target_ManualSync_Call) Return(_a0 error) *Target_ManualSync_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Target_ManualSync_Call) RunAndReturn(run func(*config.SyncSettings) error) *Target_ManualSync_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewTarget creates a new instance of Target. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewTarget(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *Target {
|
||||
mock := &Target{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
package pihole
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/lovelaze/nebula-sync/internal/pihole/model"
|
||||
"github.com/lovelaze/nebula-sync/version"
|
||||
"github.com/rs/zerolog/log"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
userAgent = fmt.Sprintf("nebula-sync/%s", version.Version)
|
||||
httpClient = &http.Client{Timeout: 5 * time.Second}
|
||||
)
|
||||
|
||||
func NewClient(piHole model.PiHole) Client {
|
||||
return &client{PiHole: piHole}
|
||||
}
|
||||
|
||||
type Client interface {
|
||||
Authenticate() error
|
||||
DeleteSession() error
|
||||
GetVersion() (*model.VersionResponse, error)
|
||||
GetTeleporter() ([]byte, error)
|
||||
PostTeleporter(payload []byte, teleporterRequest *model.PostTeleporterRequest) error
|
||||
GetConfig() (configResponse *model.ConfigResponse, err error)
|
||||
PatchConfig(patchRequest *model.PatchConfigRequest) error
|
||||
String() string
|
||||
ApiPath(target string) string
|
||||
}
|
||||
|
||||
type client struct {
|
||||
PiHole model.PiHole
|
||||
auth auth
|
||||
}
|
||||
|
||||
type auth struct {
|
||||
sid string
|
||||
csrf string
|
||||
validity int
|
||||
valid bool
|
||||
}
|
||||
|
||||
func (a *auth) verify() error {
|
||||
if !a.valid {
|
||||
return errors.New("invalid sid found")
|
||||
}
|
||||
|
||||
if a.sid == "" {
|
||||
return errors.New("no sid found")
|
||||
}
|
||||
|
||||
if a.validity <= 0 {
|
||||
return errors.New("expired sid found")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (client *client) Authenticate() error {
|
||||
log.Debug().Msgf("Authenticate, client %s", client.String())
|
||||
authResponse := model.AuthResponse{}
|
||||
|
||||
reqBytes, err := json.Marshal(model.AuthRequest{Password: client.PiHole.Password})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", client.ApiPath("/auth"), bytes.NewReader(reqBytes))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
|
||||
response, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := successfulHttpStatus(response.StatusCode); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(body, &authResponse); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client.auth = auth{
|
||||
sid: authResponse.Session.Sid,
|
||||
csrf: authResponse.Session.Csrf,
|
||||
validity: authResponse.Session.Validity,
|
||||
valid: authResponse.Session.Valid,
|
||||
}
|
||||
|
||||
return client.auth.verify()
|
||||
}
|
||||
|
||||
func (client *client) DeleteSession() error {
|
||||
log.Debug().Msgf("Delete session, client %s", client.String())
|
||||
if err := client.auth.verify(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("DELETE", client.ApiPath("auth"), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("sid", client.auth.sid)
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
|
||||
response, err := httpClient.Do(req)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := successfulHttpStatus(response.StatusCode); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (client *client) GetVersion() (*model.VersionResponse, error) {
|
||||
log.Debug().Msgf("Get version, client %s", client.String())
|
||||
versionResponse := model.VersionResponse{}
|
||||
if err := client.auth.verify(); err != nil {
|
||||
return &versionResponse, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", client.ApiPath("info/version"), nil)
|
||||
if err != nil {
|
||||
return &versionResponse, err
|
||||
}
|
||||
req.Header.Set("sid", client.auth.sid)
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
|
||||
response, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return &versionResponse, err
|
||||
}
|
||||
|
||||
if err := successfulHttpStatus(response.StatusCode); err != nil {
|
||||
return &versionResponse, err
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return &versionResponse, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(body, &versionResponse)
|
||||
|
||||
return &versionResponse, err
|
||||
}
|
||||
|
||||
func (client *client) GetTeleporter() ([]byte, error) {
|
||||
log.Debug().Msgf("Get teleporter, client %s", client.String())
|
||||
if err := client.auth.verify(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := http.NewRequest("GET", client.ApiPath("teleporter"), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("sid", client.auth.sid)
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
|
||||
response, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := successfulHttpStatus(response.StatusCode); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(response.Body)
|
||||
return body, err
|
||||
}
|
||||
|
||||
func (client *client) PostTeleporter(payload []byte, teleporterRequest *model.PostTeleporterRequest) error {
|
||||
log.Debug().Msgf("Post teleporter, client %s, request %v", client.String(), teleporterRequest)
|
||||
if err := client.auth.verify(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var requestBody bytes.Buffer
|
||||
writer := multipart.NewWriter(&requestBody)
|
||||
|
||||
fileWriter, _ := writer.CreateFormFile("file", "config.zip")
|
||||
if _, err := io.Copy(fileWriter, bytes.NewReader(payload)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if teleporterRequest != nil {
|
||||
jsonData, err := json.Marshal(teleporterRequest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = writer.WriteField("import", string(jsonData)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := writer.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", client.ApiPath("teleporter"), &requestBody)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("sid", client.auth.sid)
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
|
||||
response, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := successfulHttpStatus(response.StatusCode); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (client *client) GetConfig() (configResponse *model.ConfigResponse, err error) {
|
||||
log.Debug().Msgf("Get config, client %s", client.String())
|
||||
if err := client.auth.verify(); err != nil {
|
||||
return configResponse, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", client.ApiPath("config"), nil)
|
||||
if err != nil {
|
||||
return configResponse, err
|
||||
}
|
||||
req.Header.Set("sid", client.auth.sid)
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
|
||||
response, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return configResponse, err
|
||||
}
|
||||
|
||||
if err := successfulHttpStatus(response.StatusCode); err != nil {
|
||||
return configResponse, err
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return configResponse, err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(body, &configResponse); err != nil {
|
||||
return configResponse, err
|
||||
}
|
||||
|
||||
return configResponse, err
|
||||
}
|
||||
|
||||
func (client *client) PatchConfig(patchRequest *model.PatchConfigRequest) error {
|
||||
log.Debug().Msgf("Patch config, client %s", client.String())
|
||||
if err := client.auth.verify(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reqBytes, err := json.Marshal(patchRequest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("PATCH", client.ApiPath("config"), bytes.NewReader(reqBytes))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("sid", client.auth.sid)
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
|
||||
response, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := successfulHttpStatus(response.StatusCode); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (client *client) String() string {
|
||||
return client.PiHole.Url.String()
|
||||
}
|
||||
|
||||
func (client *client) ApiPath(target string) string {
|
||||
return client.PiHole.Url.JoinPath("api", target).String()
|
||||
}
|
||||
|
||||
func successfulHttpStatus(statusCode int) error {
|
||||
if statusCode >= 200 && statusCode <= 299 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("unexpected status code: %d", statusCode)
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package pihole
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/lovelaze/nebula-sync/internal/pihole/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/testcontainers/testcontainers-go"
|
||||
"github.com/testcontainers/testcontainers-go/wait"
|
||||
"log"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
dockerImage string = "pihole/pihole:development-v6"
|
||||
apiPassword string = "test"
|
||||
)
|
||||
|
||||
var (
|
||||
container = startContainer()
|
||||
)
|
||||
|
||||
type ClientTestSuite struct {
|
||||
suite.Suite
|
||||
client Client
|
||||
}
|
||||
|
||||
func (suite *ClientTestSuite) SetupTest() {
|
||||
client := createClient(container)
|
||||
err := client.Authenticate()
|
||||
require.NoError(suite.T(), err)
|
||||
suite.client = client
|
||||
}
|
||||
|
||||
func TestClientIntegration(t *testing.T) {
|
||||
suite.Run(t, new(ClientTestSuite))
|
||||
}
|
||||
|
||||
func (suite *ClientTestSuite) TestClient_Authenticate() {
|
||||
err := suite.client.Authenticate()
|
||||
|
||||
assert.NoError(suite.T(), err)
|
||||
}
|
||||
|
||||
func (suite *ClientTestSuite) TestClient_DeleteSession() {
|
||||
err := suite.client.DeleteSession()
|
||||
|
||||
assert.NoError(suite.T(), err)
|
||||
}
|
||||
|
||||
func (suite *ClientTestSuite) TestClient_GetVersion() {
|
||||
version, err := suite.client.GetVersion()
|
||||
|
||||
assert.NoError(suite.T(), err)
|
||||
assert.NotNil(suite.T(), version)
|
||||
}
|
||||
|
||||
func (suite *ClientTestSuite) TestClient_GetTeleporter() {
|
||||
payload, err := suite.client.GetTeleporter()
|
||||
|
||||
assert.NoError(suite.T(), err)
|
||||
assert.NotNil(suite.T(), payload)
|
||||
}
|
||||
|
||||
func (suite *ClientTestSuite) TestClient_PostTeleporter() {
|
||||
payload, _ := suite.client.GetTeleporter()
|
||||
err := suite.client.PostTeleporter(payload, &model.PostTeleporterRequest{
|
||||
Config: true,
|
||||
DHCPLeases: true,
|
||||
Gravity: model.PostGravityRequest{
|
||||
Group: true,
|
||||
Adlist: true,
|
||||
AdlistByGroup: true,
|
||||
Domainlist: true,
|
||||
DomainlistByGroup: true,
|
||||
Client: true,
|
||||
ClientByGroup: true,
|
||||
},
|
||||
})
|
||||
|
||||
assert.NoError(suite.T(), err)
|
||||
}
|
||||
|
||||
func (suite *ClientTestSuite) TestClient_GetConfig() {
|
||||
conf, err := suite.client.GetConfig()
|
||||
|
||||
assert.NoError(suite.T(), err)
|
||||
assert.NotNil(suite.T(), conf)
|
||||
}
|
||||
|
||||
func (suite *ClientTestSuite) TestClient_PatchConfig() {
|
||||
request := model.PatchConfigRequest{
|
||||
Config: model.PatchConfig{
|
||||
DNS: nil,
|
||||
DHCP: nil,
|
||||
NTP: nil,
|
||||
Resolver: nil,
|
||||
Database: nil,
|
||||
Misc: nil,
|
||||
Debug: nil,
|
||||
}}
|
||||
err := suite.client.PatchConfig(&request)
|
||||
|
||||
assert.NoError(suite.T(), err)
|
||||
}
|
||||
|
||||
func TestClient_String(t *testing.T) {
|
||||
piHole := model.NewPiHole("http://asdfasdf.com:1234", apiPassword)
|
||||
s := NewClient(piHole).String()
|
||||
|
||||
assert.Equal(t, "http://asdfasdf.com:1234", s)
|
||||
}
|
||||
|
||||
func TestClient_ApiPath(t *testing.T) {
|
||||
piHole := model.NewPiHole("http://asdfasdf.com:1234", apiPassword)
|
||||
c := NewClient(piHole)
|
||||
|
||||
url := c.String()
|
||||
path := c.ApiPath("testing")
|
||||
expectedPath := fmt.Sprintf("%s/api/testing", url)
|
||||
|
||||
assert.Equal(t, expectedPath, path)
|
||||
}
|
||||
|
||||
func Test_auth_verify(t *testing.T) {
|
||||
a := auth{
|
||||
sid: "",
|
||||
csrf: "",
|
||||
validity: 0,
|
||||
valid: false,
|
||||
}
|
||||
assert.Error(t, a.verify())
|
||||
|
||||
a.valid = true
|
||||
assert.Error(t, a.verify())
|
||||
|
||||
a.sid = "sid123"
|
||||
assert.Error(t, a.verify())
|
||||
|
||||
a.validity = 1
|
||||
assert.NoError(t, a.verify())
|
||||
|
||||
}
|
||||
|
||||
func startContainer() testcontainers.Container {
|
||||
containerRequest := testcontainers.ContainerRequest{
|
||||
Image: dockerImage,
|
||||
ExposedPorts: []string{"80/tcp", "53/tcp", "53/udp"},
|
||||
WaitingFor: wait.ForListeningPort("80").WithStartupTimeout(30 * time.Second),
|
||||
Env: map[string]string{
|
||||
"FTLCONF_dns_upstreams": "8.8.8.8",
|
||||
"FTLCONF_webserver_api_password": apiPassword,
|
||||
},
|
||||
}
|
||||
|
||||
container, err := testcontainers.GenericContainer(context.Background(), testcontainers.GenericContainerRequest{
|
||||
ContainerRequest: containerRequest,
|
||||
Started: true,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("starting pihole test container: %v", err)
|
||||
}
|
||||
return container
|
||||
}
|
||||
|
||||
func createClient(container testcontainers.Container) Client {
|
||||
apiPort, err := container.MappedPort(context.Background(), "80/tcp")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
host := fmt.Sprintf("http://localhost:%s", apiPort.Port())
|
||||
|
||||
return NewClient(model.NewPiHole(host, "test"))
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/rs/zerolog/log"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type PiHole struct {
|
||||
Url *url.URL
|
||||
Password string
|
||||
}
|
||||
|
||||
func NewPiHole(host, password string) PiHole {
|
||||
u, err := url.Parse(host)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Error parsing host %s", host)
|
||||
}
|
||||
|
||||
return PiHole{
|
||||
Url: u,
|
||||
Password: password,
|
||||
}
|
||||
}
|
||||
|
||||
func (piHole *PiHole) Decode(value string) error {
|
||||
split := strings.Split(value, "|")
|
||||
if len(split) != 2 {
|
||||
return fmt.Errorf("invalid pihole format")
|
||||
}
|
||||
|
||||
res, err := url.Parse(split[0])
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse url: %s", err)
|
||||
}
|
||||
|
||||
*piHole = PiHole{
|
||||
Url: res,
|
||||
Password: split[1],
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPiHole_Decode(t *testing.T) {
|
||||
ph := PiHole{}
|
||||
|
||||
err := ph.Decode("http://localhost:1337|asdfasdf")
|
||||
assert.NoError(t, err)
|
||||
|
||||
expectedUrl, err := url.Parse("http://localhost:1337")
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, expectedUrl, ph.Url)
|
||||
assert.Equal(t, "asdfasdf", ph.Password)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package model
|
||||
|
||||
type AuthRequest struct {
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type PostGravityRequest struct {
|
||||
Group bool `json:"group"`
|
||||
Adlist bool `json:"adlist"`
|
||||
AdlistByGroup bool `json:"adlist_by_group"`
|
||||
Domainlist bool `json:"domainlist"`
|
||||
DomainlistByGroup bool `json:"domainlist_by_group"`
|
||||
Client bool `json:"client"`
|
||||
ClientByGroup bool `json:"client_by_group"`
|
||||
}
|
||||
|
||||
type PostTeleporterRequest struct {
|
||||
Config bool `json:"config"`
|
||||
DHCPLeases bool `json:"dhcp_leases"`
|
||||
Gravity PostGravityRequest `json:"gravity"`
|
||||
}
|
||||
|
||||
type PatchConfig struct {
|
||||
DNS map[string]interface{} `json:"dns"`
|
||||
DHCP map[string]interface{} `json:"dhcp"`
|
||||
NTP map[string]interface{} `json:"ntp"`
|
||||
Resolver map[string]interface{} `json:"resolver"`
|
||||
Database map[string]interface{} `json:"database"`
|
||||
Misc map[string]interface{} `json:"misc"`
|
||||
Debug map[string]interface{} `json:"debug"`
|
||||
}
|
||||
|
||||
type PatchConfigRequest struct {
|
||||
Config PatchConfig `json:"config"`
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package model
|
||||
|
||||
type AuthResponse struct {
|
||||
Session struct {
|
||||
Valid bool `json:"valid"`
|
||||
Totp bool `json:"totp"`
|
||||
Sid string `json:"sid"`
|
||||
Csrf string `json:"csrf"`
|
||||
Validity int `json:"validity"`
|
||||
Message string `json:"message"`
|
||||
} `json:"session"`
|
||||
}
|
||||
|
||||
type VersionResponse struct {
|
||||
Version struct {
|
||||
Core struct {
|
||||
Local struct {
|
||||
Branch string `json:"branch"`
|
||||
Version string `json:"version"`
|
||||
Hash string `json:"hash"`
|
||||
} `json:"local"`
|
||||
Remote struct {
|
||||
Version string `json:"version"`
|
||||
Hash string `json:"hash"`
|
||||
} `json:"remote"`
|
||||
} `json:"core"`
|
||||
Web struct {
|
||||
Local struct {
|
||||
Branch string `json:"branch"`
|
||||
Version string `json:"version"`
|
||||
Hash string `json:"hash"`
|
||||
} `json:"local"`
|
||||
Remote struct {
|
||||
Version string `json:"version"`
|
||||
Hash string `json:"hash"`
|
||||
} `json:"remote"`
|
||||
} `json:"web"`
|
||||
Ftl struct {
|
||||
Local struct {
|
||||
Branch string `json:"branch"`
|
||||
Version string `json:"version"`
|
||||
Hash string `json:"hash"`
|
||||
Date string `json:"date"`
|
||||
} `json:"local"`
|
||||
Remote struct {
|
||||
Version string `json:"version"`
|
||||
Hash string `json:"hash"`
|
||||
} `json:"remote"`
|
||||
} `json:"ftl"`
|
||||
Docker struct {
|
||||
Local string `json:"local"`
|
||||
Remote string `json:"remote"`
|
||||
} `json:"docker"`
|
||||
} `json:"version"`
|
||||
Took float64 `json:"took"`
|
||||
}
|
||||
|
||||
type ConfigResponse struct {
|
||||
Config map[string]interface{} `json:"config"`
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"github.com/lovelaze/nebula-sync/internal/config"
|
||||
"github.com/lovelaze/nebula-sync/internal/pihole"
|
||||
"github.com/lovelaze/nebula-sync/internal/sync"
|
||||
"github.com/lovelaze/nebula-sync/version"
|
||||
"github.com/robfig/cron/v3"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
target sync.Target
|
||||
conf config.Config
|
||||
}
|
||||
|
||||
func NewService(conf config.Config) *Service {
|
||||
primary := pihole.NewClient(conf.Primary)
|
||||
var rs []pihole.Client
|
||||
for _, replica := range conf.Replicas {
|
||||
rs = append(rs, pihole.NewClient(replica))
|
||||
}
|
||||
|
||||
return &Service{
|
||||
target: sync.NewTarget(primary, rs),
|
||||
conf: conf,
|
||||
}
|
||||
}
|
||||
|
||||
func (service *Service) Run() {
|
||||
log.Info().Msgf("Starting nebula-sync v%s", version.Version)
|
||||
log.Debug().Msgf("Settings cron=%v, fullsync=%v, syncsettings=%v", service.conf.Cron, service.conf.FullSync, service.conf.SyncSettings)
|
||||
|
||||
if service.conf.Cron == nil {
|
||||
service.doSync(service.target)
|
||||
} else {
|
||||
service.startCron(func() {
|
||||
service.doSync(service.target)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (service *Service) doSync(t sync.Target) {
|
||||
var err error
|
||||
if service.conf.FullSync {
|
||||
err = t.FullSync()
|
||||
} else {
|
||||
err = t.ManualSync(service.conf.SyncSettings)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Sync failed")
|
||||
return
|
||||
}
|
||||
|
||||
log.Info().Msg("Sync complete")
|
||||
}
|
||||
|
||||
func (service *Service) startCron(cmd func()) {
|
||||
cron := cron.New()
|
||||
|
||||
if _, err := cron.AddFunc(*service.conf.Cron, cmd); err != nil {
|
||||
log.Fatal().Err(err).Msgf("Failed to start cron: %s", *service.conf.Cron)
|
||||
}
|
||||
|
||||
cron.Run()
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"github.com/lovelaze/nebula-sync/internal/config"
|
||||
syncmock "github.com/lovelaze/nebula-sync/internal/mocks/sync"
|
||||
"github.com/lovelaze/nebula-sync/internal/pihole/model"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRun_full(t *testing.T) {
|
||||
conf := config.Config{
|
||||
Primary: model.PiHole{},
|
||||
Replicas: []model.PiHole{},
|
||||
FullSync: true,
|
||||
Cron: nil,
|
||||
SyncSettings: nil,
|
||||
}
|
||||
|
||||
target := syncmock.NewTarget(t)
|
||||
target.On("FullSync").Return(nil)
|
||||
|
||||
service := Service{
|
||||
target: target,
|
||||
conf: conf,
|
||||
}
|
||||
|
||||
service.Run()
|
||||
}
|
||||
|
||||
func TestRun_manual(t *testing.T) {
|
||||
conf := config.Config{
|
||||
Primary: model.PiHole{},
|
||||
Replicas: []model.PiHole{},
|
||||
FullSync: false,
|
||||
Cron: nil,
|
||||
SyncSettings: nil,
|
||||
}
|
||||
|
||||
target := syncmock.NewTarget(t)
|
||||
target.On("ManualSync", (*config.SyncSettings)(nil)).Return(nil)
|
||||
|
||||
service := Service{
|
||||
target: target,
|
||||
conf: conf,
|
||||
}
|
||||
|
||||
service.Run()
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
package sync
|
||||
|
||||
import (
|
||||
"github.com/lovelaze/nebula-sync/internal/config"
|
||||
"github.com/lovelaze/nebula-sync/internal/pihole"
|
||||
"github.com/lovelaze/nebula-sync/internal/pihole/model"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type Target interface {
|
||||
FullSync() error
|
||||
ManualSync(syncSettings *config.SyncSettings) error
|
||||
}
|
||||
|
||||
type target struct {
|
||||
Primary pihole.Client
|
||||
Replicas []pihole.Client
|
||||
}
|
||||
|
||||
func NewTarget(primary pihole.Client, replicas []pihole.Client) Target {
|
||||
return &target{
|
||||
Primary: primary,
|
||||
Replicas: replicas,
|
||||
}
|
||||
}
|
||||
|
||||
func (target *target) FullSync() error {
|
||||
log.Info().Int("replicas", len(target.Replicas)).Msg("Running full sync")
|
||||
if err := target.authenticate(); err != nil {
|
||||
return errors.Wrap(err, "authentication failed")
|
||||
}
|
||||
|
||||
if err := target.syncTeleporters(nil); err != nil {
|
||||
return errors.Wrap(err, "sync Teleporters failed")
|
||||
}
|
||||
|
||||
if err := target.deleteSessions(); err != nil {
|
||||
return errors.Wrap(err, "delete sessions failed")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (target *target) ManualSync(syncSettings *config.SyncSettings) error {
|
||||
log.Info().Int("replicas", len(target.Replicas)).Msg("Running manual sync")
|
||||
|
||||
if err := target.authenticate(); err != nil {
|
||||
return errors.Wrap(err, "authentication failed")
|
||||
}
|
||||
|
||||
if err := target.syncTeleporters(syncSettings.Gravity); err != nil {
|
||||
return errors.Wrap(err, "sync Teleporters failed")
|
||||
}
|
||||
|
||||
if err := target.syncConfigs(syncSettings.Config); err != nil {
|
||||
return errors.Wrap(err, "sync configs failed")
|
||||
}
|
||||
|
||||
if err := target.deleteSessions(); err != nil {
|
||||
return errors.Wrap(err, "delete sessions failed")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (target *target) authenticate() (err error) {
|
||||
log.Info().Msg("Authenticating clients...")
|
||||
if err := target.Primary.Authenticate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, replica := range target.Replicas {
|
||||
if err := replica.Authenticate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (target *target) deleteSessions() (err error) {
|
||||
log.Info().Msg("Invalidating sessions...")
|
||||
if err := target.Primary.DeleteSession(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, replica := range target.Replicas {
|
||||
if err := replica.DeleteSession(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (target *target) syncTeleporters(manualGravity *config.ManualGravity) error {
|
||||
log.Info().Msg("Syncing Teleporters...")
|
||||
conf, err := target.Primary.GetTeleporter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var teleporterRequest *model.PostTeleporterRequest = nil
|
||||
if manualGravity != nil {
|
||||
teleporterRequest = createPostTeleporterRequest(manualGravity)
|
||||
}
|
||||
|
||||
for _, replica := range target.Replicas {
|
||||
if err := replica.PostTeleporter(conf, teleporterRequest); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (target *target) syncConfigs(manualConfig *config.ManualConfig) error {
|
||||
configResponse, err := target.Primary.GetConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
configRequest := createPatchConfigRequest(manualConfig, configResponse)
|
||||
|
||||
for _, replica := range target.Replicas {
|
||||
if err := replica.PatchConfig(configRequest); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func createPatchConfigRequest(config *config.ManualConfig, configResponse *model.ConfigResponse) *model.PatchConfigRequest {
|
||||
patchConfig := model.PatchConfig{}
|
||||
|
||||
if config.DNS {
|
||||
patchConfig.DNS = configResponse.Config["dns"].(map[string]interface{})
|
||||
}
|
||||
if config.DHCP {
|
||||
patchConfig.DHCP = configResponse.Config["dhcp"].(map[string]interface{})
|
||||
}
|
||||
if config.NTP {
|
||||
patchConfig.NTP = configResponse.Config["ntp"].(map[string]interface{})
|
||||
}
|
||||
if config.Resolver {
|
||||
patchConfig.Resolver = configResponse.Config["resolver"].(map[string]interface{})
|
||||
}
|
||||
if config.Database {
|
||||
patchConfig.Database = configResponse.Config["database"].(map[string]interface{})
|
||||
}
|
||||
if config.Misc {
|
||||
patchConfig.Misc = configResponse.Config["misc"].(map[string]interface{})
|
||||
}
|
||||
if config.Debug {
|
||||
patchConfig.Debug = configResponse.Config["debug"].(map[string]interface{})
|
||||
}
|
||||
|
||||
return &model.PatchConfigRequest{Config: patchConfig}
|
||||
}
|
||||
|
||||
func createPostTeleporterRequest(gravity *config.ManualGravity) *model.PostTeleporterRequest {
|
||||
return &model.PostTeleporterRequest{
|
||||
Config: false,
|
||||
DHCPLeases: gravity.DHCPLeases,
|
||||
Gravity: model.PostGravityRequest{
|
||||
Group: gravity.Group,
|
||||
Adlist: gravity.Adlist,
|
||||
AdlistByGroup: gravity.AdlistByGroup,
|
||||
Domainlist: gravity.Domainlist,
|
||||
DomainlistByGroup: gravity.DomainlistByGroup,
|
||||
Client: gravity.Client,
|
||||
ClientByGroup: gravity.ClientByGroup,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
package sync
|
||||
|
||||
import (
|
||||
"github.com/lovelaze/nebula-sync/internal/config"
|
||||
piholemock "github.com/lovelaze/nebula-sync/internal/mocks/pihole"
|
||||
"github.com/lovelaze/nebula-sync/internal/pihole"
|
||||
"github.com/lovelaze/nebula-sync/internal/pihole/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTarget_FullSync(t *testing.T) {
|
||||
primary := piholemock.NewClient(t)
|
||||
replica := piholemock.NewClient(t)
|
||||
|
||||
target := NewTarget(primary, []pihole.Client{replica})
|
||||
|
||||
primary.
|
||||
EXPECT().
|
||||
Authenticate().
|
||||
Times(1).
|
||||
Return(nil)
|
||||
replica.
|
||||
EXPECT().
|
||||
Authenticate().
|
||||
Times(1).
|
||||
Return(nil)
|
||||
|
||||
primary.
|
||||
EXPECT().
|
||||
GetTeleporter().
|
||||
Times(1).
|
||||
Return([]byte{}, nil)
|
||||
replica.
|
||||
EXPECT().
|
||||
PostTeleporter(mock.Anything, mock.Anything).
|
||||
Times(1).
|
||||
Return(nil)
|
||||
|
||||
primary.
|
||||
EXPECT().
|
||||
DeleteSession().
|
||||
Times(1).
|
||||
Return(nil)
|
||||
replica.
|
||||
EXPECT().
|
||||
DeleteSession().
|
||||
Times(1).
|
||||
Return(nil)
|
||||
|
||||
target.FullSync()
|
||||
}
|
||||
|
||||
func TestTarget_ManualSync(t *testing.T) {
|
||||
primary := piholemock.NewClient(t)
|
||||
replica := piholemock.NewClient(t)
|
||||
|
||||
target := NewTarget(primary, []pihole.Client{replica})
|
||||
|
||||
settings := config.SyncSettings{
|
||||
Gravity: &config.ManualGravity{
|
||||
DHCPLeases: false,
|
||||
Group: false,
|
||||
Adlist: false,
|
||||
AdlistByGroup: false,
|
||||
Domainlist: false,
|
||||
DomainlistByGroup: false,
|
||||
Client: false,
|
||||
ClientByGroup: false,
|
||||
},
|
||||
Config: &config.ManualConfig{
|
||||
DNS: false,
|
||||
DHCP: false,
|
||||
NTP: false,
|
||||
Resolver: false,
|
||||
Database: false,
|
||||
Webserver: false,
|
||||
Files: false,
|
||||
Misc: false,
|
||||
Debug: false,
|
||||
},
|
||||
}
|
||||
|
||||
primary.
|
||||
EXPECT().
|
||||
Authenticate().
|
||||
Times(1).
|
||||
Return(nil)
|
||||
replica.
|
||||
EXPECT().
|
||||
Authenticate().
|
||||
Times(1).
|
||||
Return(nil)
|
||||
|
||||
primary.
|
||||
EXPECT().
|
||||
GetTeleporter().
|
||||
Times(1).
|
||||
Return([]byte{}, nil)
|
||||
replica.
|
||||
EXPECT().
|
||||
PostTeleporter(mock.Anything, mock.Anything).
|
||||
Times(1).
|
||||
Return(nil)
|
||||
|
||||
primary.
|
||||
EXPECT().
|
||||
GetConfig().
|
||||
Times(1).
|
||||
Return(&model.ConfigResponse{Config: make(map[string]interface{})}, nil)
|
||||
replica.
|
||||
EXPECT().
|
||||
PatchConfig(mock.Anything).
|
||||
Times(1).
|
||||
Return(nil)
|
||||
|
||||
primary.
|
||||
EXPECT().
|
||||
DeleteSession().
|
||||
Times(1).
|
||||
Return(nil)
|
||||
replica.
|
||||
EXPECT().
|
||||
DeleteSession().
|
||||
Times(1).
|
||||
Return(nil)
|
||||
|
||||
target.ManualSync(&settings)
|
||||
}
|
||||
|
||||
func Test_target_authenticate(t *testing.T) {
|
||||
primary := piholemock.NewClient(t)
|
||||
replica := piholemock.NewClient(t)
|
||||
|
||||
target := target{
|
||||
Primary: primary,
|
||||
Replicas: []pihole.Client{replica},
|
||||
}
|
||||
|
||||
primary.
|
||||
EXPECT().
|
||||
Authenticate().
|
||||
Times(1).
|
||||
Return(nil)
|
||||
replica.
|
||||
EXPECT().
|
||||
Authenticate().
|
||||
Times(1).
|
||||
Return(nil)
|
||||
|
||||
err := target.authenticate()
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func Test_target_deleteSessions(t *testing.T) {
|
||||
primary := piholemock.NewClient(t)
|
||||
replica := piholemock.NewClient(t)
|
||||
|
||||
target := target{
|
||||
Primary: primary,
|
||||
Replicas: []pihole.Client{replica},
|
||||
}
|
||||
|
||||
primary.
|
||||
EXPECT().
|
||||
DeleteSession().
|
||||
Times(1).
|
||||
Return(nil)
|
||||
replica.
|
||||
EXPECT().
|
||||
DeleteSession().
|
||||
Times(1).
|
||||
Return(nil)
|
||||
|
||||
err := target.deleteSessions()
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func Test_target_syncTeleporters(t *testing.T) {
|
||||
primary := piholemock.NewClient(t)
|
||||
replica := piholemock.NewClient(t)
|
||||
|
||||
target := target{
|
||||
Primary: primary,
|
||||
Replicas: []pihole.Client{replica},
|
||||
}
|
||||
|
||||
manualGravity := config.ManualGravity{
|
||||
DHCPLeases: false,
|
||||
Group: false,
|
||||
Adlist: false,
|
||||
AdlistByGroup: false,
|
||||
Domainlist: false,
|
||||
DomainlistByGroup: false,
|
||||
Client: false,
|
||||
ClientByGroup: false,
|
||||
}
|
||||
|
||||
primary.
|
||||
EXPECT().
|
||||
GetTeleporter().
|
||||
Times(1).
|
||||
Return([]byte{}, nil)
|
||||
replica.
|
||||
EXPECT().
|
||||
PostTeleporter([]byte{}, createPostTeleporterRequest(&manualGravity)).
|
||||
Times(1).
|
||||
Return(nil)
|
||||
|
||||
err := target.syncTeleporters(&manualGravity)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func Test_target_syncConfigs(t *testing.T) {
|
||||
primary := piholemock.NewClient(t)
|
||||
replica := piholemock.NewClient(t)
|
||||
|
||||
target := target{
|
||||
Primary: primary,
|
||||
Replicas: []pihole.Client{replica},
|
||||
}
|
||||
|
||||
configResponse := model.ConfigResponse{Config: make(map[string]interface{})}
|
||||
|
||||
manualConfig := config.ManualConfig{
|
||||
DNS: false,
|
||||
DHCP: false,
|
||||
NTP: false,
|
||||
Resolver: false,
|
||||
Database: false,
|
||||
Webserver: false,
|
||||
Files: false,
|
||||
Misc: false,
|
||||
Debug: false,
|
||||
}
|
||||
|
||||
primary.
|
||||
EXPECT().
|
||||
GetConfig().
|
||||
Times(1).
|
||||
Return(&configResponse, nil)
|
||||
replica.
|
||||
EXPECT().
|
||||
PatchConfig(createPatchConfigRequest(&manualConfig, &configResponse)).
|
||||
Times(1).
|
||||
Return(nil)
|
||||
|
||||
err := target.syncConfigs(&manualConfig)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package main
|
||||
|
||||
import "github.com/lovelaze/nebula-sync/cmd"
|
||||
|
||||
func main() {
|
||||
cmd.Execute()
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package version
|
||||
|
||||
const Version = "0.1.0"
|
||||
Reference in New Issue
Block a user