From a34b9f7324c783a24b51a18a48cde62924121423 Mon Sep 17 00:00:00 2001 From: sekulicd Date: Wed, 20 Sep 2023 16:39:04 +0200 Subject: [PATCH 01/39] traefik routing, tls, dns feature --- .DS_Store | Bin 0 -> 6148 bytes Makefile | 164 ++++ README.md | 63 ++ auth/Dockerfile | 44 + auth/README.md | 6 + auth/cmd/authd/main.go | 31 + auth/go.mod | 7 + auth/go.sum | 15 + controller/Dockerfile | 44 + controller/README.md | 6 + controller/cmd/controllerd/main.go | 422 +++++++++ controller/go.mod | 29 + controller/go.sum | 81 ++ dns/Dockerfile | 46 + dns/README.md | 25 + dns/cmd/dnsd/main.go | 52 ++ dns/docker-compose.yml | 31 + dns/docs/docs.go | 343 ++++++++ dns/docs/swagger.json | 317 +++++++ dns/docs/swagger.yaml | 214 +++++ dns/go.mod | 83 ++ dns/go.sum | 808 ++++++++++++++++++ dns/internal/config/config.go | 65 ++ dns/internal/core/application/dns_service.go | 121 +++ dns/internal/core/application/types.go | 32 + dns/internal/core/domain/dns.go | 9 + dns/internal/core/domain/dns_repository.go | 10 + dns/internal/core/domain/errors.go | 8 + .../core/domain/repository_service.go | 5 + dns/internal/core/port/controllerd_wrapper.go | 8 + .../core/port/controllerd_wrapper_mock.go | 56 ++ dns/internal/core/port/ip_service.go | 8 + dns/internal/core/port/ip_service_mock.go | 76 ++ .../http-clients/controllerd_wrapper.go | 89 ++ .../infrastructure/http-clients/ip_service.go | 61 ++ .../infrastructure/storage/pg/db_service.go | 149 ++++ .../storage/pg/dns_repository_impl.go | 144 ++++ .../pg/migration/20230726095612_init.down.sql | 1 + .../pg/migration/20230726095612_init.up.sql | 7 + .../infrastructure/storage/pg/sqlc.yaml | 8 + .../storage/pg/sqlc/queries/db.go | 32 + .../storage/pg/sqlc/queries/models.go | 17 + .../storage/pg/sqlc/queries/query.sql.go | 102 +++ .../infrastructure/storage/pg/sqlc/query.sql | 16 + .../interface/http/handler/dns_handler.go | 220 +++++ dns/internal/interface/http/handler/types.go | 36 + dns/internal/interface/http/server.go | 133 +++ dns/internal/interface/http/server_opts.go | 54 ++ dns/script/create_testdb | 5 + dns/test/http/router_test.go | 107 +++ dns/test/pg/db_util.go | 80 ++ dns/test/pg/dns_repository_test.go | 49 ++ dns/test/pg/run_suite_test.go | 11 + dns/test/pg/test_setup.go | 54 ++ docker-compose.yml | 81 ++ e2e/main_test.go | 34 + script/docker-compose-box.yml | 40 + script/run_all.sh | 26 + script/stop_all.sh | 9 + traefik/letsencrypt/acme.json | 1 + 60 files changed, 4795 insertions(+) create mode 100644 .DS_Store create mode 100644 Makefile create mode 100644 README.md create mode 100644 auth/Dockerfile create mode 100644 auth/README.md create mode 100644 auth/cmd/authd/main.go create mode 100644 auth/go.mod create mode 100644 auth/go.sum create mode 100644 controller/Dockerfile create mode 100644 controller/README.md create mode 100644 controller/cmd/controllerd/main.go create mode 100644 controller/go.mod create mode 100644 controller/go.sum create mode 100644 dns/Dockerfile create mode 100644 dns/README.md create mode 100644 dns/cmd/dnsd/main.go create mode 100644 dns/docker-compose.yml create mode 100644 dns/docs/docs.go create mode 100644 dns/docs/swagger.json create mode 100644 dns/docs/swagger.yaml create mode 100644 dns/go.mod create mode 100644 dns/go.sum create mode 100644 dns/internal/config/config.go create mode 100644 dns/internal/core/application/dns_service.go create mode 100644 dns/internal/core/application/types.go create mode 100644 dns/internal/core/domain/dns.go create mode 100644 dns/internal/core/domain/dns_repository.go create mode 100644 dns/internal/core/domain/errors.go create mode 100644 dns/internal/core/domain/repository_service.go create mode 100644 dns/internal/core/port/controllerd_wrapper.go create mode 100644 dns/internal/core/port/controllerd_wrapper_mock.go create mode 100644 dns/internal/core/port/ip_service.go create mode 100644 dns/internal/core/port/ip_service_mock.go create mode 100644 dns/internal/infrastructure/http-clients/controllerd_wrapper.go create mode 100644 dns/internal/infrastructure/http-clients/ip_service.go create mode 100644 dns/internal/infrastructure/storage/pg/db_service.go create mode 100644 dns/internal/infrastructure/storage/pg/dns_repository_impl.go create mode 100644 dns/internal/infrastructure/storage/pg/migration/20230726095612_init.down.sql create mode 100644 dns/internal/infrastructure/storage/pg/migration/20230726095612_init.up.sql create mode 100644 dns/internal/infrastructure/storage/pg/sqlc.yaml create mode 100644 dns/internal/infrastructure/storage/pg/sqlc/queries/db.go create mode 100644 dns/internal/infrastructure/storage/pg/sqlc/queries/models.go create mode 100644 dns/internal/infrastructure/storage/pg/sqlc/queries/query.sql.go create mode 100644 dns/internal/infrastructure/storage/pg/sqlc/query.sql create mode 100644 dns/internal/interface/http/handler/dns_handler.go create mode 100644 dns/internal/interface/http/handler/types.go create mode 100644 dns/internal/interface/http/server.go create mode 100644 dns/internal/interface/http/server_opts.go create mode 100755 dns/script/create_testdb create mode 100644 dns/test/http/router_test.go create mode 100644 dns/test/pg/db_util.go create mode 100644 dns/test/pg/dns_repository_test.go create mode 100644 dns/test/pg/run_suite_test.go create mode 100644 dns/test/pg/test_setup.go create mode 100644 docker-compose.yml create mode 100644 e2e/main_test.go create mode 100644 script/docker-compose-box.yml create mode 100644 script/run_all.sh create mode 100644 script/stop_all.sh create mode 100644 traefik/letsencrypt/acme.json diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..4e5dce3be7b87e2a6b70077d7638e3b041ffbe02 GIT binary patch literal 6148 zcmeHKOG-mQ5UkdK0XJD@IalxoLx?BH1tMsIC=r5Y{Z^jKqow+T7+$gw+( zJYFqczXo87kHgc<-f6Zxa=zfE17d-wOEmq0t?C;gA@g4u%*3h!dv6 zxQ literal 0 HcmV?d00001 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..59ad5cd --- /dev/null +++ b/Makefile @@ -0,0 +1,164 @@ +PONY: build-dns run-dns pg droppg createdb dropdb createtestdb droptestdb recreatedb recreatetestdb pgcreatetestdb psql mig_file mig_up_test mig_up mig_down_test mig_down mig_down_yes vet_db sqlc doc dev up down + +##### DNS Daemon ##### + +## build-dns prem-gateway dns service +build-dns: + @echo "Building prem-gateway dns service..." + @export GO111MODULE=on; \ + env go build -tags netgo -ldflags="-s -w" -o bin/dnsd ./dns/cmd/dnsd/main.go + +## run-dns runs prem-gateway dns service +run-dns: + @echo "Running prem-gateway dns service..." + ./bin/dnsd + +##### DNS Daemon ##### + + +#### Postgres database #### + +## pg: starts postgres db inside docker container +pg: + docker run --name dnsd-db-pg -p 5432:5432 -e POSTGRES_USER=root -e POSTGRES_PASSWORD=secret -d postgres + +## droppg: stop and remove postgres container +droppg: + docker stop dnsd-db-pg + docker rm dnsd-db-pg + +## createdb: create db inside docker container +createdb: + docker exec dnsd-db-pg createdb --username=root --owner=root dnsd-db + +## dropdb: drops db inside docker container +dropdb: + docker exec dnsd-db-pg dropdb dnsd-db + +## createtestdb: create test db inside docker container +createtestdb: + docker exec dnsd-db-pg createdb --username=root --owner=root dnsd-db-test + +## droptestdb: drops test db inside docker container +droptestdb: + docker exec dnsd-db-pg dropdb dnsd-db-test + +## recreatedb: drop and create main and test db +recreatedb: dropdb createdb droptestdb createtestdb + +## recreatetestdb: drop and create test db +recreatetestdb: droptestdb createtestdb + +## pgcreatetestdb: starts docker container and creates test db, used in CI +pgcreatetestdb: + chmod u+x ./script/create_testdb + ./script/create_testdb + +## psql: connects to postgres terminal running inside docker container +psql: + docker exec -it dnsd-db-pg psql -U root -d dnsd-db + + +## mig_file: creates pg migration file(eg. make FILE=init mig_file) +mig_file: + @migrate create -ext sql -dir ./dns/internal/infrastructure/storage/pg/migration/ $(FILE) + +## mig_up_test: creates test db schema +mig_up_test: + @echo "creating db schema..." + @migrate -database "postgres://root:secret@localhost:5432/dnsd-db-test?sslmode=disable" -path ./dns/internal/infrastructure/storage/pg/migration/ up + +## mig_up: creates db schema +mig_up: + @echo "creating db schema..." + @migrate -database "postgres://root:secret@localhost:5432/dnsd-db?sslmode=disable" -path ./dns/internal/infrastructure/storage/pg/migration/ up + +## mig_down_test: apply down migration on test db +mig_down_test: + @echo "migration down on test db..." + @migrate -database "postgres://root:secret@localhost:5432/dnsd-db-test?sslmode=disable" -path ./dns/internal/infrastructure/storage/pg/migration/ down + +## mig_down: apply down migration +mig_down: + @echo "migration down..." + @migrate -database "postgres://root:secret@localhost:5432/dnsd-db?sslmode=disable" -path ./dns/internal/infrastructure/storage/pg/migration/ down + +## mig_down_yes: apply down migration without prompt +mig_down_yes: + @echo "migration down..." + @"yes" | migrate -database "postgres://root:secret@localhost:5432/dnsd-db?sslmode=disable" -path ./dns/internal/infrastructure/storage/pg/migration/ down + +## vet_db: check if mig_up and mig_down are ok +vet_db: recreatedb mig_up mig_down_yes + @echo "vet db migration scripts..." + +## sqlc: gen sql +sqlc: + @echo "gen sql..." + cd ./dns/internal/infrastructure/storage/pg; sqlc generate + +#### Postgres database #### + + +#### Swagger doc #### + +## doc: generate swagger doc +doc: + @echo "generating swagger doc..." + swag init -g ./dns/cmd/dnsd/main.go -o ./dns/docs + +#### Swagger doc #### + +## dev-dns: run dnsd and postgres +dev-dns: + export POSTGRES_USER=root; \ + export POSTGRES_PASSWORD=secret; \ + export POSTGRES_DB=dnsd-db; \ + cd ./dns; \ + DOCKER_BUILDKIT=0 docker-compose up -d --build + +## up: run prem-gateway +up: + export POSTGRES_USER=root; \ + export POSTGRES_PASSWORD=secret; \ + export POSTGRES_DB=dnsd-db; \ + DOCKER_BUILDKIT=0 docker-compose up -d --build + +## down: stop prem-gateway +down: + docker-compose down -v + +## runall: run prem-gateway and prem-box +runall: + @chmod +x ./script/run_all.sh + @export PREMD_IMAGE=$(PREMD_IMAGE); \ + export PREMAPP_IMAGE=$(PREMAPP_IMAGE); \ + ./script/run_all.sh + +## stopall: stop prem-gateway and prem-box +stopall: + @chmod +x ./script/stop_all.sh + @export PREMD_IMAGE=$(PREMD_IMAGE); \ + export PREMAPP_IMAGE=$(PREMAPP_IMAGE); \ + ./script/stop_all.sh + +#### Go lint #### + +## vetdnsd: run go vet on dnsd +vetdnsd: + @echo "go vet dnsd..." + @cd dns && go vet ./... + +#### Go lint #### + +#### Go mock #### + +## mockdnsd: generater mocks +mockdnsd: + cd ./dns/internal/core/port/; \ + mockery --name=ControllerdWrapper --structname=MockControllerdWrapper \ + --output=./ --outpkg=port --filename=controllerd_wrapper_mock.go --inpackage; \ + mockery --name=IpService --structname=MockIpService \ + --output=./ --outpkg=port --filename=ip_service_mock.go --inpackage; + +#### Go mock #### \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..44a3694 --- /dev/null +++ b/README.md @@ -0,0 +1,63 @@ +# prem-gateway + +## Project Description + +`prem-gateway` acts as an API gateway for directing and managing a multitude of operations.
+It is responsible for routing requests from the frontend `prem-app` to either the `prem-daemon` for Docker image management or directly to Docker images providing `prem-services`. + +## Features + +- [x] API Gateway +- [ ] Authentication/Authorization +- [x] Domain Management +- [x] TLS +- [ ] Rate Limiting +- [ ] Logging +- [ ] Metrics + +## Services + +- [dnsd](./dns/README.md) +- [authd](./auth/README.md) +- [controllerd](./controller/README.md) + +## Usage +Create network: +```bash +docker network create prem-gateway +``` + +Change permission: +```bash +chmod 600 ./traefik/letsencrypt/acme.json +``` + +Start prem-gateway: +```bash +make up +``` +#### Default Let's Encrypt CA server is the staging. For production, start prem-gateway with bellow command'. +```bash +make up LETSENCRYPT_PROD=true +``` + +Stop prem-gateway: +```bash +make down +``` + +#### In order to restart services outside prem-gateway and to assign them with subdomain/tls certificate, use bellow command. +```bash +make up LETSENCRYPT_PROD=true SERVICES=premd,premapp +``` + +#### Run prem-gateway with prem-app and prem-daemon: +```bash +make runall PREMD_IMAGE={IMG} PREMAPP_IMAGE={IMG} +``` + +#### Stop prem-gateway, prem-app and prem-daemon: +```bash +make stopall PREMD_IMAGE={IMG} PREMAPP_IMAGE={IMG} +``` + diff --git a/auth/Dockerfile b/auth/Dockerfile new file mode 100644 index 0000000..bd5d39b --- /dev/null +++ b/auth/Dockerfile @@ -0,0 +1,44 @@ +# first image used to build the sources +FROM golang:1.20-buster AS builder + +ARG VERSION +ARG COMMIT +ARG DATE +ARG TARGETOS +ARG TARGETARCH + +WORKDIR /app + +COPY . . + +RUN go mod download + +RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -ldflags="-X 'main.Version=${COMMIT}' -X 'main.Commit=${COMMIT}' -X 'main.Date=${COMMIT}'" -o bin/authd cmd/authd/main.go +RUN go build -ldflags="-X 'main.version=${VERSION}' -X 'main.commit=${COMMIT}' -X 'main.date=${DATE}'" -o bin/authd cmd/authd/* + +# Second image, running the oceand executable +FROM debian:buster-slim + +# $USER name, and data $DIR to be used in the `final` image +ARG USER=authd +ARG DIR=/home/authd + +RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates + +COPY --from=builder /app/bin/* /usr/local/bin/ + +# NOTE: Default GID == UID == 1000 +RUN adduser --disabled-password \ + --home "$DIR/" \ + --gecos "" \ + "$USER" +USER $USER + +# Prevents `VOLUME $DIR/.authd/` being created as owned by `root` +RUN mkdir -p "$DIR/.authd/" + +# Expose volume containing all `authd` data +VOLUME $DIR/.authd/ + +ENTRYPOINT [ "authd" ] + diff --git a/auth/README.md b/auth/README.md new file mode 100644 index 0000000..3d8726b --- /dev/null +++ b/auth/README.md @@ -0,0 +1,6 @@ +# Auth Daemon + +## Description +Auth Daemon is a microservice which provides api key authentication. +Calls coming to prem-gateway are routed by traefik forward-auth middleware to auth daemon. +Auth daemon checks if the api key is valid and if it is, it forwards the request to the appropriate service. \ No newline at end of file diff --git a/auth/cmd/authd/main.go b/auth/cmd/authd/main.go new file mode 100644 index 0000000..ed570b5 --- /dev/null +++ b/auth/cmd/authd/main.go @@ -0,0 +1,31 @@ +package main + +import ( + "fmt" + log "github.com/sirupsen/logrus" + "net/http" +) + +const apiKey = "dummy-api-key" + +func main() { + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + log.Info("Authorization header: %s\n", r.Header.Get("Authorization")) + if r.Header.Get("Authorization") == apiKey { + w.WriteHeader(http.StatusOK) + if _, err := fmt.Fprint(w, "Authenticated"); err != nil { + return + } + } else { + w.WriteHeader(http.StatusUnauthorized) + if _, err := fmt.Fprint(w, "Unauthorized"); err != nil { + return + } + } + }) + + log.Info("Starting auth daemon on port 8080") + if err := http.ListenAndServe(":8080", nil); err != nil { + fmt.Printf("Auth daemon failed to start: %v", err) + } +} diff --git a/auth/go.mod b/auth/go.mod new file mode 100644 index 0000000..8dddc0a --- /dev/null +++ b/auth/go.mod @@ -0,0 +1,7 @@ +module prem-gateway/auth + +go 1.20 + +require github.com/sirupsen/logrus v1.9.3 + +require golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect diff --git a/auth/go.sum b/auth/go.sum new file mode 100644 index 0000000..21f9bfb --- /dev/null +++ b/auth/go.sum @@ -0,0 +1,15 @@ +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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/controller/Dockerfile b/controller/Dockerfile new file mode 100644 index 0000000..9c7c9de --- /dev/null +++ b/controller/Dockerfile @@ -0,0 +1,44 @@ +# first image used to build the sources +FROM golang:1.20-buster AS builder + +ARG VERSION +ARG COMMIT +ARG DATE +ARG TARGETOS +ARG TARGETARCH + +WORKDIR /app + +COPY . . + +RUN go mod download + +RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -ldflags="-X 'main.Version=${COMMIT}' -X 'main.Commit=${COMMIT}' -X 'main.Date=${COMMIT}'" -o bin/controllerd cmd/controllerd/main.go +RUN go build -ldflags="-X 'main.version=${VERSION}' -X 'main.commit=${COMMIT}' -X 'main.date=${DATE}'" -o bin/controllerd cmd/controllerd/* + +# Second image, running the oceand executable +FROM debian:buster-slim + +# $USER name, and data $DIR to be used in the `final` image +ARG USER=controllerd +ARG DIR=/home/controllerd + +RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates + +COPY --from=builder /app/bin/* /usr/local/bin/ + +# NOTE: Default GID == UID == 1000 +RUN adduser --disabled-password \ + --home "$DIR/" \ + --gecos "" \ + "$USER" +USER $USER + +# Prevents `VOLUME $DIR/.controllerd/` being created as owned by `root` +RUN mkdir -p "$DIR/.controllerd/" + +# Expose volume containing all `controllerd` data +VOLUME $DIR/.controllerd/ + +ENTRYPOINT [ "controllerd" ] + diff --git a/controller/README.md b/controller/README.md new file mode 100644 index 0000000..74bb1e9 --- /dev/null +++ b/controller/README.md @@ -0,0 +1,6 @@ +# Controllerd + +## Description +Controller Daemon is a microservice which is responsible for restarting traefik, dnsd and other Docker containers when domain is set by user.
+On initial startup, domain is not set by user, traefik and other services starts without tls and real subdomains reachable from outside.
+When user sets domain, controller daemon restarts traefik and other services with tls and real subdomains become reachable from outside.
\ No newline at end of file diff --git a/controller/cmd/controllerd/main.go b/controller/cmd/controllerd/main.go new file mode 100644 index 0000000..58cc89a --- /dev/null +++ b/controller/cmd/controllerd/main.go @@ -0,0 +1,422 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/network" + "github.com/docker/docker/api/types/strslice" + "github.com/docker/docker/client" + log "github.com/sirupsen/logrus" + "io" + "net/http" + "os" + "strconv" + "strings" + "time" +) + +const ( + letsEncryptStaging = "https://acme-staging-v02.api.letsencrypt.org/directory" + letsEncryptProd = "https://acme-v02.api.letsencrypt.org/directory" + + premappService = "premapp" + premdService = "premd" +) + +var ( + letEncryptProd bool +) + +type DnsInfo struct { + Domain string `json:"domain"` + SubDomain string `json:"sub_domain"` + NodeName string `json:"node_name"` + Email string `json:"email"` +} + +func main() { + serviceNames := os.Getenv("SERVICES") + services := make([]string, 0) + if serviceNames != "" { + services = append(services, strings.Split(serviceNames, ",")...) + } + services = append(services, "dnsd") + + letsEncrypt := os.Getenv("LETSENCRYPT_PROD") + if letsEncrypt != "" { + letEncryptProd = true + } + + http.HandleFunc("/domain-provisioned", func(w http.ResponseWriter, r *http.Request) { + go func() { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + email := r.URL.Query().Get("email") + domain := r.URL.Query().Get("domain") + + premServices := getPremServicesForRestart(services) + if len(premServices) > 0 { + if err := restartServicesWithTls(domain, nil, premServices); err != nil { + log.Error("Error restarting containers from domainProvisioned : ", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + } + + if err := restartServicesWithTls(domain, services, nil); err != nil { + log.Error("Error restarting containers from domainProvisioned : ", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + //TODO maybe add health check to all restarted services since services + //needs to be restarted before traefik can pick up the new labels + time.Sleep(time.Second * 3) + + if err := restartTraefikWithTls(email); err != nil { + log.Error("Error restarting traefik from domain-provisioned : ", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + }() + + if _, err := io.WriteString(w, "OK"); err != nil { + return + } + }) + + // TODO expose domainUpdated/Deleted endpoints to restart services without TLS + //think how to fetch AI running services + + go func() { + log.Info("Starting checking if dns exists") + for { + if _, err := http.Get("http://traefik:8080/ping"); err != nil { + log.Error("Error pinging Traefik: ", err) + time.Sleep(time.Second * 5) + continue + } + + if _, err := http.Get("http://dnsd:8080/dns/check"); err != nil { + log.Error("Error checking DNSd: ", err) + time.Sleep(time.Second * 5) + continue + } + + resp, err := http.Get("http://dnsd:8080/dns/existing") + if err != nil { + log.Info("Error getting existing DNS: ", err) + time.Sleep(time.Second * 5) + continue + } + + if resp.StatusCode != 200 { + log.Error("Got non-200 status code: ", resp.StatusCode) + resp.Body.Close() + time.Sleep(time.Second * 5) + continue + } + + body, err := io.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + log.Error("Error reading body: ", err) + time.Sleep(time.Second * 5) + continue + } + + var dnsInfo DnsInfo + if err := json.Unmarshal(body, &dnsInfo); err != nil { + log.Error("Error unmarshaling DNS info: ", err) + time.Sleep(time.Second * 5) + continue + } + + if dnsInfo.Domain == "" || dnsInfo.Email == "" { + log.Info("Domain or email is empty, skipping restart") + break + } + + if err := restartServicesWithTls(dnsInfo.Domain, services, nil); err != nil { + log.Error("Error restarting containers: ", err) + time.Sleep(time.Second * 5) + continue + } + + //TODO maybe add health check to all restarted services since services + //needs to be restarted before traefik can pick up the new labels + time.Sleep(time.Second * 3) + + if err := restartTraefikWithTls(dnsInfo.Email); err != nil { + log.Error("Error restarting traefik: ", err) + time.Sleep(time.Second * 5) + continue + } + + log.Info("Containers restarted") + break + } + log.Info("Finished checking if dns exists") + }() + + log.Info("Starting controller daemon on port 8080") + if err := http.ListenAndServe(":8080", nil); err != nil { + log.Errorf("Controller daemon failed to start: %v", err) + } +} + +func getPremServicesForRestart(srvcs []string) map[string]int { + svcs := make(map[string]int) + if contains(srvcs, premdService) { + resp, err := http.Get("http://premd:8000/v1/services/") + if err != nil { + return nil + } + + if resp == nil { + return nil + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil + } + + var premServices []PremService + if err := json.NewDecoder(resp.Body).Decode(&premServices); err != nil { + return nil + } + + for _, v := range premServices { + if v.Running { + svcs[v.Id] = v.RunningPort + } + } + } + + return svcs +} + +func contains(slice []string, str string) bool { + for _, a := range slice { + if a == str { + return true + } + } + return false +} + +func restartContainer( + ctx context.Context, + cli *client.Client, + containerName string, + labels map[string]string, + cmds strslice.StrSlice, +) error { + containerJson, err := cli.ContainerInspect(ctx, containerName) + if err != nil { + return err + } + newConfig := containerJson.Config + //TODO check duplicate labels and cmds + if len(labels) > 0 { + newLabels := make(map[string]string) + for k, v := range newConfig.Labels { + if !strings.Contains(k, "traefik") { + newLabels[k] = v + } + } + for k, v := range labels { + newLabels[k] = v + } + newConfig.Labels = newLabels + } + if len(cmds) > 0 { + newConfig.Cmd = append(newConfig.Cmd, cmds...) + } + + noWaitTimeout := 0 + if err := cli.ContainerStop( + ctx, containerName, container.StopOptions{Timeout: &noWaitTimeout}, + ); err != nil { + return err + } + if err := cli.ContainerRemove( + ctx, containerName, types.ContainerRemoveOptions{}, + ); err != nil { + //TODO this is workaround for restarting prem services, container removal + //fails because prem-services are started with --rm flag in prem-daemon + //and we can't remove them, so we just ignore the error + //TODO maybe we should check if the container is prem-service and if it is + //then we should just restart it without removing it + //sleeping for 5 seconds to wait until container is removed + log.Warning("Error removing container: ", err) + time.Sleep(time.Second * 5) + //return err + } + + if _, err := cli.ContainerCreate( + ctx, + newConfig, + containerJson.HostConfig, + &network.NetworkingConfig{ + EndpointsConfig: containerJson.NetworkSettings.Networks, + }, + nil, + containerName, + ); err != nil { + log.Error("Error creating container: ", err) + return err + } + + if err := cli.ContainerStart( + ctx, containerName, types.ContainerStartOptions{}, + ); err != nil { + log.Error("Error starting container: ", err) + return err + } + + return nil +} + +func restartServicesWithTls(domain string, services []string, premServices map[string]int) error { + ctx := context.Background() + cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + if err != nil { + return fmt.Errorf("failed to create docker client: %v", err) + } + + for _, v := range services { + switch v { + case premappService: + labels := map[string]string{ + "traefik.enable": "true", + "traefik.http.routers.premapp-http.rule": fmt.Sprintf("PathPrefix(`/`) && Host(`%s`)", domain), + "traefik.http.routers.premapp-http.entrypoints": "web", + "traefik.http.routers.premapp-https.rule": fmt.Sprintf("PathPrefix(`/`) && Host(`%s`)", domain), + "traefik.http.routers.premapp-https.entrypoints": "websecure", + fmt.Sprintf("traefik.http.routers.%s-%s.tls.certresolver", v, "https"): "myresolver", + "traefik.http.middlewares.http-to-https.redirectscheme.scheme": "https", + "traefik.http.routers.premapp-http.middlewares": "http-to-https", + "traefik.http.services.premapp.loadbalancer.server.port": "8080", + } + + if err := restartContainer(ctx, cli, v, labels, nil); err != nil { + return fmt.Errorf("failed to restart container %s: %v", v, err) + } + case premdService: + labels := map[string]string{ + "traefik.enable": "true", + fmt.Sprintf("traefik.http.routers.%s.rule", v): fmt.Sprintf("Host(`%s.%s`)", v, domain), + fmt.Sprintf("traefik.http.routers.%s.entrypoints", v): "websecure", + fmt.Sprintf("traefik.http.routers.%s.tls.certresolver", v): "myresolver", + } + + if err := restartContainer(ctx, cli, v, labels, nil); err != nil { + return fmt.Errorf("failed to restart container %s: %v", v, err) + } + } + + log.Infof("Restarted container %s\n", v) + } + + for k, v := range premServices { + labels := map[string]string{ + "traefik.enable": "true", + fmt.Sprintf("traefik.http.routers.%s-http.rule", k): fmt.Sprintf("Host(`%s.%s`)", k, domain), + fmt.Sprintf("traefik.http.routers.%s-http.entrypoints", k): "web", + fmt.Sprintf("traefik.http.routers.%s-https.rule", k): fmt.Sprintf("Host(`%s.%s`)", k, domain), + fmt.Sprintf("traefik.http.routers.%s-https.entrypoints", k): "websecure", + fmt.Sprintf("traefik.http.routers.%s-%s.tls.certresolver", k, "https"): "myresolver", + "traefik.http.middlewares.http-to-https.redirectscheme.scheme": "https", + fmt.Sprintf("traefik.http.routers.%s-http.middlewares", k): "http-to-https", + fmt.Sprintf("traefik.http.services.%s.loadbalancer.server.port", k): strconv.Itoa(v), + } + + if err := restartContainer(ctx, cli, k, labels, nil); err != nil { + return fmt.Errorf("failed to restart container %s: %v", k, err) + } + + log.Infof("Restarted container %s\n", k) + } + + return nil +} + +func restartTraefikWithTls(email string) error { + ctx := context.Background() + cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + if err != nil { + return fmt.Errorf("failed to create docker client: %v", err) + } + + traefikLetsEncryptUrl := letsEncryptProd + if !letEncryptProd { + traefikLetsEncryptUrl = letsEncryptStaging + } + + cmds := strslice.StrSlice{ + "--providers.docker=true", + "--providers.docker.exposedbydefault=false", + "--accesslog=true", + "--ping", + "--entrypoints.web.address=:80", + "--certificatesresolvers.myresolver.acme.email=" + email, + "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json", + "--certificatesresolvers.myresolver.acme.tlschallenge=true", + "--certificatesresolvers.myresolver.acme.caserver=" + traefikLetsEncryptUrl, + "--entrypoints.websecure.address=:443", + } + + if err := restartContainer(ctx, cli, "traefik", nil, cmds); err != nil { + return fmt.Errorf("failed to restart container traefik: %v", err) + } + + log.Info("Restarted container traefik") + + return nil +} + +type PremService struct { + Id string `json:"id"` + Name string `json:"name"` + Beta bool `json:"beta"` + Description string `json:"description"` + Documentation string `json:"documentation"` + Icon string `json:"icon"` + ModelInfo struct { + MemoryRequirements int `json:"memoryRequirements"` + TokensPerSecond int `json:"tokensPerSecond"` + } `json:"modelInfo"` + Interfaces []string `json:"interfaces"` + DockerImages struct { + Gpu struct { + Size int64 `json:"size"` + Image string `json:"image"` + } `json:"gpu"` + } `json:"dockerImages"` + DefaultPort int `json:"defaultPort"` + DefaultExternalPort int `json:"defaultExternalPort"` + RunningPort int `json:"runningPort"` + Banner string `json:"banner"` + Running bool `json:"running"` + Downloaded bool `json:"downloaded"` + EnoughMemory bool `json:"enoughMemory"` + EnoughSystemMemory bool `json:"enoughSystemMemory"` + EnoughStorage bool `json:"enoughStorage"` + Command string `json:"command"` + DockerImage string `json:"dockerImage"` + DockerImageSize int `json:"dockerImageSize"` + Supported bool `json:"supported"` + InvokeMethod struct { + Header string `json:"header"` + SendTo string `json:"baseUrl"` + } `json:"invokeMethod"` +} diff --git a/controller/go.mod b/controller/go.mod new file mode 100644 index 0000000..a206fea --- /dev/null +++ b/controller/go.mod @@ -0,0 +1,29 @@ +module prem-gateway/controllerd + +go 1.20 + +require ( + github.com/davecgh/go-spew v1.1.1 + github.com/docker/docker v24.0.5+incompatible + github.com/sirupsen/logrus v1.9.0 +) + +require ( + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/docker/distribution v2.8.2+incompatible // indirect + github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/gogo/protobuf v1.3.2 // 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.0.2 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/stretchr/testify v1.8.4 // indirect + golang.org/x/mod v0.8.0 // indirect + golang.org/x/net v0.6.0 // indirect + golang.org/x/sys v0.5.0 // indirect + golang.org/x/time v0.3.0 // indirect + golang.org/x/tools v0.6.0 // indirect + gotest.tools/v3 v3.5.0 // indirect +) diff --git a/controller/go.sum b/controller/go.sum new file mode 100644 index 0000000..3d37a25 --- /dev/null +++ b/controller/go.sum @@ -0,0 +1,81 @@ +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +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/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= +github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v24.0.5+incompatible h1:WmgcE4fxyI6EEXxBRxsHnZXrO1pQ3smi0k/jho4HLeY= +github.com/docker/docker v24.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +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/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +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/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.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= +github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +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/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +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/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +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.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +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/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +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/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.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/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +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= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +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= +gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= +gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/dns/Dockerfile b/dns/Dockerfile new file mode 100644 index 0000000..4ea6c9c --- /dev/null +++ b/dns/Dockerfile @@ -0,0 +1,46 @@ +# first image used to build the sources +FROM golang:1.20-buster AS builder + +ARG VERSION +ARG COMMIT +ARG DATE +ARG TARGETOS +ARG TARGETARCH + +WORKDIR /app + +COPY . . + +RUN go mod download + +RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -ldflags="-X 'main.Version=${COMMIT}' -X 'main.Commit=${COMMIT}' -X 'main.Date=${COMMIT}'" -o bin/dnsd cmd/dnsd/main.go +RUN go build -ldflags="-X 'main.version=${VERSION}' -X 'main.commit=${COMMIT}' -X 'main.date=${DATE}'" -o bin/dnsd cmd/dnsd/* + +# Second image, running the oceand executable +FROM debian:buster-slim + +# $USER name, and data $DIR to be used in the `final` image +ARG USER=dnsd +ARG DIR=/home/dnsd + +RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates + +COPY --from=builder /app/bin/* /usr/local/bin/ +COPY --from=builder /app/internal/infrastructure/storage/pg/migration/* / +ENV PREM_GATEWAY_DNS_DB_MIGRATION_PATH=file:// + +# NOTE: Default GID == UID == 1000 +RUN adduser --disabled-password \ + --home "$DIR/" \ + --gecos "" \ + "$USER" +USER $USER + +# Prevents `VOLUME $DIR/.dnsd/` being created as owned by `root` +RUN mkdir -p "$DIR/.dnsd/" + +# Expose volume containing all `dnsd` data +VOLUME $DIR/.dnsd/ + +ENTRYPOINT [ "dnsd" ] + diff --git a/dns/README.md b/dns/README.md new file mode 100644 index 0000000..de88ba8 --- /dev/null +++ b/dns/README.md @@ -0,0 +1,25 @@ +# DNS Daemon + +## Description + +DNS Daemon is a microservice developed in Go, providing DNS management functionality via a REST API.
+It allows users to create, update, delete and retrieve DNS information.
+Additional utility functions include checking the status of a DNS record and retrieving the gateway IP address.
+ +## Features + +- Manage DNS records, including creating, updating and deleting DNS information. +- Retrieve specific DNS record information. +- Check the status of a DNS record. +- Get the Gateway IP address. +- Swagger documentation for a clear understanding of API endpoints. + +## Run standalone (from root directory) + +```bash +make dev-dns +``` + +## API Documentation + +API documentation is available via Swagger at the /docs endpoint, e.g., http://localhost:8080/docs/index.html \ No newline at end of file diff --git a/dns/cmd/dnsd/main.go b/dns/cmd/dnsd/main.go new file mode 100644 index 0000000..8f5dced --- /dev/null +++ b/dns/cmd/dnsd/main.go @@ -0,0 +1,52 @@ +package main + +import ( + "context" + log "github.com/sirupsen/logrus" + "os" + "os/signal" + _ "prem-gateway/dns/docs" + "prem-gateway/dns/internal/config" + pgdb "prem-gateway/dns/internal/infrastructure/storage/pg" + dnsdhttp "prem-gateway/dns/internal/interface/http" + "syscall" +) + +// @title Dns Daemon API +// @description DNS Daemon is designed to manage Domain Name System (DNS) records.
It exposes a RESTful API that allows for the creation, modification, retrieval, and deletion of DNS information, as well as checking the status of a DNS entry.
The DNS information includes attributes such as domain, subdomain, A records, and node names. +func main() { + if err := config.LoadConfig(); err != nil { + log.Fatalf("failed to load config: %s", err) + } + + svc, err := pgdb.NewDBService(pgdb.DbConfig{ + DbUser: config.GetString(config.DbUserKey), + DbPassword: config.GetString(config.DbPassKey), + DbHost: config.GetString(config.DbHostKey), + DbPort: config.GetInt(config.DbPortKey), + DbName: config.GetString(config.DbNameKey), + MigrationSourceURL: config.GetString(config.DbMigrationPathKey), + }) + if err != nil { + log.Fatalf("failed to create pgdb service: %s", err) + } + + premgd, err := dnsdhttp.NewServer( + config.GetServerAddress(), + svc, + config.GetString(config.ControllerDaemonUrlKey), + ) + if err != nil { + log.Errorf("failed to create prem-gateway dns daemon: %s", err) + } + + ctx, stop := signal.NotifyContext(context.Background(), + os.Interrupt, + syscall.SIGTERM, + syscall.SIGQUIT) + + errC := premgd.Start(ctx, stop) + if err := <-errC; err != nil { + log.Panicf("prem-gateway dns daemon noticed error while running: %s", err) + } +} diff --git a/dns/docker-compose.yml b/dns/docker-compose.yml new file mode 100644 index 0000000..81cdede --- /dev/null +++ b/dns/docker-compose.yml @@ -0,0 +1,31 @@ +version: "3.7" +services: + dnsd: + container_name: dnsd + build: + context: . + dockerfile: Dockerfile + depends_on: + - dnsd-db-pg + restart: unless-stopped + ports: + - "8080:8080" + environment: + PREM_GATEWAY_DNS_DB_HOST: dnsd-db-pg + + dnsd-db-pg: + container_name: dnsd-db-pg + image: postgres:14.7 + restart: unless-stopped + ports: + - "5432:5432" + environment: + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${POSTGRES_DB} + volumes: + - ./pg-data:/var/lib/postgresql/data + +networks: + default: + name: prem-gateway-dns \ No newline at end of file diff --git a/dns/docs/docs.go b/dns/docs/docs.go new file mode 100644 index 0000000..b4242b0 --- /dev/null +++ b/dns/docs/docs.go @@ -0,0 +1,343 @@ +// Code generated by swaggo/swag. DO NOT EDIT. + +package docs + +import "github.com/swaggo/swag" + +const docTemplate = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{escape .Description}}", + "title": "{{.Title}}", + "contact": {}, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/dns": { + "post": { + "description": "This endpoint creates a new DNS record based on the provided information", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "dns" + ], + "summary": "Creates a new DNS record", + "parameters": [ + { + "description": "dns information", + "name": "DnsInfo", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/httphandler.DnsInfo" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/httphandler.SuccessResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/httphandler.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/httphandler.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/httphandler.ErrorResponse" + } + } + } + } + }, + "/dns/check": { + "get": { + "description": "This endpoint checks if the service is up and running", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "dns" + ], + "summary": "Check if the service is up and running", + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/dns/existing": { + "get": { + "description": "This endpoint retrieves the existing DNS record", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "dns" + ], + "summary": "Retrieves the existing DNS record", + "responses": { + "200": { + "description": "Returns the existing DNS record", + "schema": { + "$ref": "#/definitions/httphandler.DnsInfo" + } + }, + "500": { + "description": "Returns error message for server error", + "schema": { + "$ref": "#/definitions/httphandler.ErrorResponse" + } + } + } + } + }, + "/dns/ip": { + "get": { + "description": "This endpoint retrieves the IP address of the Gateway", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "dns" + ], + "summary": "Retrieves the IP address of the Gateway", + "responses": { + "200": { + "description": "Returns IP address of the Gateway", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Returns error message for server error", + "schema": { + "$ref": "#/definitions/httphandler.ErrorResponse" + } + } + } + } + }, + "/dns/status/{domain}": { + "get": { + "description": "This endpoint checks the status of a DNS record based on the provided domain name", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "dns" + ], + "summary": "Check status of a DNS record", + "parameters": [ + { + "type": "string", + "description": "Domain Name", + "name": "domain", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Returns true if the DNS record is valid, false otherwise", + "schema": { + "type": "boolean" + } + }, + "400": { + "description": "Returns error message for invalid input", + "schema": { + "$ref": "#/definitions/httphandler.ErrorResponse" + } + }, + "404": { + "description": "Returns error message for record not found", + "schema": { + "$ref": "#/definitions/httphandler.ErrorResponse" + } + }, + "500": { + "description": "Returns error message for server error", + "schema": { + "$ref": "#/definitions/httphandler.ErrorResponse" + } + } + } + } + }, + "/dns/{domain}": { + "get": { + "description": "This endpoint retrieves a DNS record based on the provided domain name", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "dns" + ], + "summary": "Retrieves a DNS record", + "parameters": [ + { + "type": "string", + "description": "Domain Name", + "name": "domain", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Returns the DNS record", + "schema": { + "$ref": "#/definitions/httphandler.DnsInfo" + } + }, + "400": { + "description": "Returns error message for invalid input", + "schema": { + "$ref": "#/definitions/httphandler.ErrorResponse" + } + }, + "404": { + "description": "Returns error message for record not found", + "schema": { + "$ref": "#/definitions/httphandler.ErrorResponse" + } + }, + "500": { + "description": "Returns error message for server error", + "schema": { + "$ref": "#/definitions/httphandler.ErrorResponse" + } + } + } + }, + "delete": { + "description": "This endpoint deletes a DNS record based on the provided domain name", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "dns" + ], + "summary": "Deletes a DNS record", + "parameters": [ + { + "type": "string", + "description": "Domain Name", + "name": "domain", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Returns status of operation", + "schema": { + "$ref": "#/definitions/httphandler.SuccessResponse" + } + }, + "400": { + "description": "Returns error message for invalid input", + "schema": { + "$ref": "#/definitions/httphandler.ErrorResponse" + } + }, + "500": { + "description": "Returns error message for server error", + "schema": { + "$ref": "#/definitions/httphandler.ErrorResponse" + } + } + } + } + } + }, + "definitions": { + "httphandler.DnsInfo": { + "type": "object", + "properties": { + "domain": { + "type": "string" + }, + "email": { + "type": "string" + }, + "ip": { + "type": "string" + }, + "node_name": { + "type": "string" + } + } + }, + "httphandler.ErrorResponse": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + }, + "httphandler.SuccessResponse": { + "type": "object", + "properties": { + "status": { + "type": "string" + } + } + } + } +}` + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = &swag.Spec{ + Version: "", + Host: "", + BasePath: "", + Schemes: []string{}, + Title: "Dns Daemon API", + Description: "DNS Daemon is designed to manage Domain Name System (DNS) records.
It exposes a RESTful API that allows for the creation, modification, retrieval, and deletion of DNS information, as well as checking the status of a DNS entry.
The DNS information includes attributes such as domain, subdomain, A records, and node names.", + InfoInstanceName: "swagger", + SwaggerTemplate: docTemplate, +} + +func init() { + swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) +} diff --git a/dns/docs/swagger.json b/dns/docs/swagger.json new file mode 100644 index 0000000..304d714 --- /dev/null +++ b/dns/docs/swagger.json @@ -0,0 +1,317 @@ +{ + "swagger": "2.0", + "info": { + "description": "DNS Daemon is designed to manage Domain Name System (DNS) records. \u003cbr /\u003eIt exposes a RESTful API that allows for the creation, modification, retrieval, and deletion of DNS information, as well as checking the status of a DNS entry. \u003cbr /\u003e The DNS information includes attributes such as domain, subdomain, A records, and node names.", + "title": "Dns Daemon API", + "contact": {} + }, + "paths": { + "/dns": { + "post": { + "description": "This endpoint creates a new DNS record based on the provided information", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "dns" + ], + "summary": "Creates a new DNS record", + "parameters": [ + { + "description": "dns information", + "name": "DnsInfo", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/httphandler.DnsInfo" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/httphandler.SuccessResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/httphandler.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/httphandler.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/httphandler.ErrorResponse" + } + } + } + } + }, + "/dns/check": { + "get": { + "description": "This endpoint checks if the service is up and running", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "dns" + ], + "summary": "Check if the service is up and running", + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/dns/existing": { + "get": { + "description": "This endpoint retrieves the existing DNS record", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "dns" + ], + "summary": "Retrieves the existing DNS record", + "responses": { + "200": { + "description": "Returns the existing DNS record", + "schema": { + "$ref": "#/definitions/httphandler.DnsInfo" + } + }, + "500": { + "description": "Returns error message for server error", + "schema": { + "$ref": "#/definitions/httphandler.ErrorResponse" + } + } + } + } + }, + "/dns/ip": { + "get": { + "description": "This endpoint retrieves the IP address of the Gateway", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "dns" + ], + "summary": "Retrieves the IP address of the Gateway", + "responses": { + "200": { + "description": "Returns IP address of the Gateway", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Returns error message for server error", + "schema": { + "$ref": "#/definitions/httphandler.ErrorResponse" + } + } + } + } + }, + "/dns/status/{domain}": { + "get": { + "description": "This endpoint checks the status of a DNS record based on the provided domain name", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "dns" + ], + "summary": "Check status of a DNS record", + "parameters": [ + { + "type": "string", + "description": "Domain Name", + "name": "domain", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Returns true if the DNS record is valid, false otherwise", + "schema": { + "type": "boolean" + } + }, + "400": { + "description": "Returns error message for invalid input", + "schema": { + "$ref": "#/definitions/httphandler.ErrorResponse" + } + }, + "404": { + "description": "Returns error message for record not found", + "schema": { + "$ref": "#/definitions/httphandler.ErrorResponse" + } + }, + "500": { + "description": "Returns error message for server error", + "schema": { + "$ref": "#/definitions/httphandler.ErrorResponse" + } + } + } + } + }, + "/dns/{domain}": { + "get": { + "description": "This endpoint retrieves a DNS record based on the provided domain name", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "dns" + ], + "summary": "Retrieves a DNS record", + "parameters": [ + { + "type": "string", + "description": "Domain Name", + "name": "domain", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Returns the DNS record", + "schema": { + "$ref": "#/definitions/httphandler.DnsInfo" + } + }, + "400": { + "description": "Returns error message for invalid input", + "schema": { + "$ref": "#/definitions/httphandler.ErrorResponse" + } + }, + "404": { + "description": "Returns error message for record not found", + "schema": { + "$ref": "#/definitions/httphandler.ErrorResponse" + } + }, + "500": { + "description": "Returns error message for server error", + "schema": { + "$ref": "#/definitions/httphandler.ErrorResponse" + } + } + } + }, + "delete": { + "description": "This endpoint deletes a DNS record based on the provided domain name", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "dns" + ], + "summary": "Deletes a DNS record", + "parameters": [ + { + "type": "string", + "description": "Domain Name", + "name": "domain", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Returns status of operation", + "schema": { + "$ref": "#/definitions/httphandler.SuccessResponse" + } + }, + "400": { + "description": "Returns error message for invalid input", + "schema": { + "$ref": "#/definitions/httphandler.ErrorResponse" + } + }, + "500": { + "description": "Returns error message for server error", + "schema": { + "$ref": "#/definitions/httphandler.ErrorResponse" + } + } + } + } + } + }, + "definitions": { + "httphandler.DnsInfo": { + "type": "object", + "properties": { + "domain": { + "type": "string" + }, + "email": { + "type": "string" + }, + "ip": { + "type": "string" + }, + "node_name": { + "type": "string" + } + } + }, + "httphandler.ErrorResponse": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + }, + "httphandler.SuccessResponse": { + "type": "object", + "properties": { + "status": { + "type": "string" + } + } + } + } +} \ No newline at end of file diff --git a/dns/docs/swagger.yaml b/dns/docs/swagger.yaml new file mode 100644 index 0000000..0bab851 --- /dev/null +++ b/dns/docs/swagger.yaml @@ -0,0 +1,214 @@ +definitions: + httphandler.DnsInfo: + properties: + domain: + type: string + email: + type: string + ip: + type: string + node_name: + type: string + type: object + httphandler.ErrorResponse: + properties: + error: + type: string + type: object + httphandler.SuccessResponse: + properties: + status: + type: string + type: object +info: + contact: {} + description: DNS Daemon is designed to manage Domain Name System (DNS) records. +
It exposes a RESTful API that allows for the creation, modification, retrieval, + and deletion of DNS information, as well as checking the status of a DNS entry. +
The DNS information includes attributes such as domain, subdomain, A records, + and node names. + title: Dns Daemon API +paths: + /dns: + post: + consumes: + - application/json + description: This endpoint creates a new DNS record based on the provided information + parameters: + - description: dns information + in: body + name: DnsInfo + required: true + schema: + $ref: '#/definitions/httphandler.DnsInfo' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/httphandler.SuccessResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/httphandler.ErrorResponse' + "404": + description: Not Found + schema: + $ref: '#/definitions/httphandler.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/httphandler.ErrorResponse' + summary: Creates a new DNS record + tags: + - dns + /dns/{domain}: + delete: + consumes: + - application/json + description: This endpoint deletes a DNS record based on the provided domain + name + parameters: + - description: Domain Name + in: path + name: domain + required: true + type: string + produces: + - application/json + responses: + "200": + description: Returns status of operation + schema: + $ref: '#/definitions/httphandler.SuccessResponse' + "400": + description: Returns error message for invalid input + schema: + $ref: '#/definitions/httphandler.ErrorResponse' + "500": + description: Returns error message for server error + schema: + $ref: '#/definitions/httphandler.ErrorResponse' + summary: Deletes a DNS record + tags: + - dns + get: + consumes: + - application/json + description: This endpoint retrieves a DNS record based on the provided domain + name + parameters: + - description: Domain Name + in: path + name: domain + required: true + type: string + produces: + - application/json + responses: + "200": + description: Returns the DNS record + schema: + $ref: '#/definitions/httphandler.DnsInfo' + "400": + description: Returns error message for invalid input + schema: + $ref: '#/definitions/httphandler.ErrorResponse' + "404": + description: Returns error message for record not found + schema: + $ref: '#/definitions/httphandler.ErrorResponse' + "500": + description: Returns error message for server error + schema: + $ref: '#/definitions/httphandler.ErrorResponse' + summary: Retrieves a DNS record + tags: + - dns + /dns/check: + get: + consumes: + - application/json + description: This endpoint checks if the service is up and running + produces: + - application/json + responses: + "200": + description: OK + summary: Check if the service is up and running + tags: + - dns + /dns/existing: + get: + consumes: + - application/json + description: This endpoint retrieves the existing DNS record + produces: + - application/json + responses: + "200": + description: Returns the existing DNS record + schema: + $ref: '#/definitions/httphandler.DnsInfo' + "500": + description: Returns error message for server error + schema: + $ref: '#/definitions/httphandler.ErrorResponse' + summary: Retrieves the existing DNS record + tags: + - dns + /dns/ip: + get: + consumes: + - application/json + description: This endpoint retrieves the IP address of the Gateway + produces: + - application/json + responses: + "200": + description: Returns IP address of the Gateway + schema: + type: string + "500": + description: Returns error message for server error + schema: + $ref: '#/definitions/httphandler.ErrorResponse' + summary: Retrieves the IP address of the Gateway + tags: + - dns + /dns/status/{domain}: + get: + consumes: + - application/json + description: This endpoint checks the status of a DNS record based on the provided + domain name + parameters: + - description: Domain Name + in: path + name: domain + required: true + type: string + produces: + - application/json + responses: + "200": + description: Returns true if the DNS record is valid, false otherwise + schema: + type: boolean + "400": + description: Returns error message for invalid input + schema: + $ref: '#/definitions/httphandler.ErrorResponse' + "404": + description: Returns error message for record not found + schema: + $ref: '#/definitions/httphandler.ErrorResponse' + "500": + description: Returns error message for server error + schema: + $ref: '#/definitions/httphandler.ErrorResponse' + summary: Check status of a DNS record + tags: + - dns +swagger: "2.0" diff --git a/dns/go.mod b/dns/go.mod new file mode 100644 index 0000000..bd3ab00 --- /dev/null +++ b/dns/go.mod @@ -0,0 +1,83 @@ +module prem-gateway/dns + +go 1.20 + +require ( + github.com/btcsuite/btcd/btcutil v1.1.3 + github.com/gin-gonic/gin v1.9.1 + github.com/golang-migrate/migrate/v4 v4.16.2 + github.com/jackc/pgconn v1.14.1 + github.com/jackc/pgx/v4 v4.18.1 + github.com/sirupsen/logrus v1.9.3 + github.com/spf13/viper v1.16.0 + github.com/stretchr/testify v1.8.4 + github.com/swaggo/files v1.0.1 + github.com/swaggo/gin-swagger v1.6.0 + github.com/swaggo/swag v1.16.1 +) + +require ( + github.com/KyleBanks/depth v1.2.1 // indirect + github.com/PuerkitoBio/purell v1.1.1 // indirect + github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect + github.com/btcsuite/btcd v0.23.0 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.1.3 // indirect + github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect + github.com/bytedance/sonic v1.9.1 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.19.6 // indirect + github.com/go-openapi/spec v0.20.4 // indirect + github.com/go-openapi/swag v0.19.15 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.14.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/jackc/chunkreader/v2 v2.0.1 // indirect + github.com/jackc/pgio v1.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgproto3/v2 v2.3.2 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgtype v1.14.0 // indirect + github.com/jackc/puddle v1.3.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/leodido/go-urn v1.2.4 // indirect + github.com/lib/pq v1.10.2 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mailru/easyjson v0.7.6 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/spf13/afero v1.9.5 // indirect + github.com/spf13/cast v1.5.1 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/objx v0.5.0 // indirect + github.com/subosito/gotenv v1.4.2 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.11 // indirect + go.uber.org/atomic v1.9.0 // indirect + golang.org/x/arch v0.3.0 // indirect + golang.org/x/crypto v0.9.0 // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/sys v0.8.0 // indirect + golang.org/x/text v0.9.0 // indirect + golang.org/x/tools v0.9.1 // indirect + google.golang.org/protobuf v1.30.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/dns/go.sum b/dns/go.sum new file mode 100644 index 0000000..fa8992b --- /dev/null +++ b/dns/go.sum @@ -0,0 +1,808 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= +github.com/btcsuite/btcd v0.23.0 h1:V2/ZgjfDFIygAX3ZapeigkVBoVUtOJKSwrhZdlpSvaA= +github.com/btcsuite/btcd v0.23.0/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY= +github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= +github.com/btcsuite/btcd/btcec/v2 v2.1.3 h1:xM/n3yIhHAhHy04z4i43C8p4ehixJZMsnrVJkgl+MTE= +github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= +github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= +github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= +github.com/btcsuite/btcd/btcutil v1.1.3 h1:xfbtw8lwpp0G6NwSHb+UE67ryTFHJAiNuipusjXSohQ= +github.com/btcsuite/btcd/btcutil v1.1.3/go.mod h1:UR7dsSJzJUfMmFiiLlIrMq1lS9jh9EdCV7FStZSnpi0= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= +github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +github.com/dhui/dktest v0.3.16 h1:i6gq2YQEtcrjKbeJpBkWjE8MmLZPYllcjOFbTZuPDnw= +github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= +github.com/docker/docker v20.10.24+incompatible h1:Ugvxm7a8+Gz6vqQYQQ2W7GYq5EUPaAiuPgIfVyI3dYE= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= +github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= +github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= +github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= +github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M= +github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= +github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= +github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/golang-migrate/migrate/v4 v4.16.2 h1:8coYbMKUyInrFk1lfGfRovTLAW7PhWp8qQDT2iKfuoA= +github.com/golang-migrate/migrate/v4 v4.16.2/go.mod h1:pfcJX4nPHaVdc5nmdCikFBWtm+UBpiZjRNNsyBbp0/o= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= +github.com/jackc/pgconn v1.14.1 h1:smbxIaZA08n6YuxEX1sDyjV/qkbtUtkH20qLkR9MUR4= +github.com/jackc/pgconn v1.14.1/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0= +github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= +github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= +github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= +github.com/jackc/pgx/v4 v4.18.1 h1:YP7G1KABtKpB5IHrO9vYwSrCOhs7p3uqhvhhQBptya0= +github.com/jackc/pgx/v4 v4.18.1/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0= +github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= +github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= +github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= +github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= +github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= +github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= +github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/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.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= +github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= +github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= +github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M= +github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo= +github.com/swaggo/swag v1.16.1 h1:fTNRhKstPKxcnoKsytm4sahr8FaYzUcT7i1/3nd/fBg= +github.com/swaggo/swag v1.16.1/go.mod h1:9/LMvHycG3NFHfR6LwvikHv5iFvmPADQ359cKikGxto= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= +golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/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-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= +golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/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= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/dns/internal/config/config.go b/dns/internal/config/config.go new file mode 100644 index 0000000..6b51077 --- /dev/null +++ b/dns/internal/config/config.go @@ -0,0 +1,65 @@ +package config + +import ( + "github.com/btcsuite/btcd/btcutil" + log "github.com/sirupsen/logrus" + "github.com/spf13/viper" +) + +const ( + // PortKey is port on which server is running + PortKey = "PORT_PORT" + // LogLevelKey is log level used by dnsd + LogLevelKey = "LOG_LEVEL" + // DatadirKey is the local data directory to store the internal state of daemon + DatadirKey = "DATADIR" + // DbUserKey is the user name to connect to the database + DbUserKey = "DB_USER" + // DbPassKey is the password to connect to the database + DbPassKey = "DB_PASS" + // DbHostKey is the host address of the database + DbHostKey = "DB_HOST" + // DbPortKey is the port of the database + DbPortKey = "DB_PORT" + // DbNameKey is the name of the database + DbNameKey = "DB_NAME" + // DbMigrationPathKey is the path to the database migration files + DbMigrationPathKey = "DB_MIGRATION_PATH" + ControllerDaemonUrlKey = "CONTROLLER_DAEMON_URL" +) + +var ( + vip *viper.Viper +) + +func LoadConfig() error { + vip = viper.New() + vip.SetEnvPrefix("PREM_GATEWAY_DNS") + vip.AutomaticEnv() + defaultDataDir := btcutil.AppDataDir("dnsd", false) + + vip.SetDefault(PortKey, 8080) + vip.SetDefault(LogLevelKey, int(log.DebugLevel)) + vip.SetDefault(DatadirKey, defaultDataDir) + vip.SetDefault(DbUserKey, "root") + vip.SetDefault(DbPassKey, "secret") + vip.SetDefault(DbHostKey, "127.0.0.1") + vip.SetDefault(DbPortKey, 5432) + vip.SetDefault(DbNameKey, "dnsd-db") + vip.SetDefault(DbMigrationPathKey, "file://dns/internal/infrastructure/storage/pg/migration") + vip.SetDefault(ControllerDaemonUrlKey, "http://controllerd:8080") + + return nil +} + +func GetString(key string) string { + return vip.GetString(key) +} + +func GetInt(key string) int { + return vip.GetInt(key) +} + +func GetServerAddress() string { + return ":" + GetString(PortKey) +} diff --git a/dns/internal/core/application/dns_service.go b/dns/internal/core/application/dns_service.go new file mode 100644 index 0000000..98379bf --- /dev/null +++ b/dns/internal/core/application/dns_service.go @@ -0,0 +1,121 @@ +package application + +import ( + "context" + "errors" + "prem-gateway/dns/internal/core/domain" + "prem-gateway/dns/internal/core/port" + "strings" +) + +type DnsService interface { + CreateDomain(ctx context.Context, dnsInfo DnsInfo) error + DeleteDomain(ctx context.Context, domainName string) error + GetDomain(ctx context.Context, domainName string) (DnsInfo, error) + GetGatewayIp(ctx context.Context) (string, error) + CheckDnsRecordStatus(ctx context.Context, domainName string) (bool, error) + GetExistingDomain(ctx context.Context) (*DnsInfo, error) +} + +type dnsService struct { + repositorySvc domain.RepositoryService + ipSvc port.IpService + controllerdWrapper port.ControllerdWrapper +} + +func NewDnsService( + repositorySvc domain.RepositoryService, + ipSvc port.IpService, + controllerdWrapper port.ControllerdWrapper, +) (DnsService, error) { + return &dnsService{ + repositorySvc: repositorySvc, + ipSvc: ipSvc, + controllerdWrapper: controllerdWrapper, + }, nil +} + +func (d *dnsService) CreateDomain(ctx context.Context, dnsInfo DnsInfo) error { + //assumption is that there should be only one domain + dnsDomain, _ := d.repositorySvc.DnsRepository().Get(ctx, dnsInfo.Domain) + if dnsDomain != nil { + return domain.ErrAlreadyExists + } + + valid, err := d.ipSvc.VerifyDnsRecord(ctx, dnsInfo.Ip, dnsInfo.Domain) + if err != nil { + return err + } + + if !valid { + return errors.New("dns record not found, check if A record is set correctly") + } + + if err := d.repositorySvc.DnsRepository().Create( + ctx, FromAppDnsInfoToDomainDnsInfo(dnsInfo), + ); err != nil { + return err + } + + //on initial docker-compose up(main one in proj root) services are + //started without tls and real subdomains, this will invoke contoller daemon + //to restart treafik and services with tls/subdomains set + if err := d.controllerdWrapper.DomainProvisioned( + ctx, dnsInfo.Email, dnsInfo.Domain, + ); err != nil { + return err + } + + return nil +} + +func (d *dnsService) DeleteDomain(ctx context.Context, domainName string) error { + // TODO invoke controllerd to restart services + return d.repositorySvc.DnsRepository().Delete(ctx, domainName) +} + +func (d *dnsService) GetDomain(ctx context.Context, domainName string) (DnsInfo, error) { + dnsInfo, err := d.repositorySvc.DnsRepository().Get(ctx, domainName) + if err != nil { + return DnsInfo{}, err + } + + return FromDomainDnsInfoToAppDnsInfo(*dnsInfo), nil +} + +func (d *dnsService) GetGatewayIp(ctx context.Context) (string, error) { + ip, err := d.ipSvc.GetHostIp(ctx) + if err != nil { + return "", err + } + + return strings.Replace(ip, "\n", "", -1), nil +} + +func (d *dnsService) CheckDnsRecordStatus( + ctx context.Context, domainName string, +) (bool, error) { + dnsInfo, err := d.repositorySvc.DnsRepository().Get(ctx, domainName) + if err != nil { + return false, err + } + + return d.ipSvc.VerifyDnsRecord(ctx, dnsInfo.Ip, dnsInfo.Domain) +} + +func (d *dnsService) GetExistingDomain( + ctx context.Context, +) (*DnsInfo, error) { + dnsInfo, err := d.repositorySvc.DnsRepository().GetExistingDomain(ctx) + if err != nil { + if err == domain.ErrEntityNotFound { + return nil, nil + } + + return nil, err + } + + dns := FromDomainDnsInfoToAppDnsInfo(*dnsInfo) + + return &dns, nil +} diff --git a/dns/internal/core/application/types.go b/dns/internal/core/application/types.go new file mode 100644 index 0000000..92be118 --- /dev/null +++ b/dns/internal/core/application/types.go @@ -0,0 +1,32 @@ +package application + +import ( + "fmt" + "prem-gateway/dns/internal/core/domain" +) + +type DnsInfo struct { + Domain string + Ip string + NodeName string + Email string +} + +func FromAppDnsInfoToDomainDnsInfo(dnsInfo DnsInfo) domain.DnsInfo { + return domain.DnsInfo{ + Domain: dnsInfo.Domain, + SubDomain: fmt.Sprintf("*.%s", dnsInfo.Domain), + Ip: dnsInfo.Ip, + NodeName: dnsInfo.NodeName, + Email: dnsInfo.Email, + } +} + +func FromDomainDnsInfoToAppDnsInfo(dnsInfo domain.DnsInfo) DnsInfo { + return DnsInfo{ + Domain: dnsInfo.Domain, + Ip: dnsInfo.Ip, + NodeName: dnsInfo.NodeName, + Email: dnsInfo.Email, + } +} diff --git a/dns/internal/core/domain/dns.go b/dns/internal/core/domain/dns.go new file mode 100644 index 0000000..3b8f36e --- /dev/null +++ b/dns/internal/core/domain/dns.go @@ -0,0 +1,9 @@ +package domain + +type DnsInfo struct { + Domain string + SubDomain string + Ip string + NodeName string + Email string +} diff --git a/dns/internal/core/domain/dns_repository.go b/dns/internal/core/domain/dns_repository.go new file mode 100644 index 0000000..b03dfd5 --- /dev/null +++ b/dns/internal/core/domain/dns_repository.go @@ -0,0 +1,10 @@ +package domain + +import "context" + +type DnsRepository interface { + Create(ctx context.Context, dnsInfo DnsInfo) error + Delete(ctx context.Context, domainName string) error + Get(ctx context.Context, domainName string) (*DnsInfo, error) + GetExistingDomain(ctx context.Context) (*DnsInfo, error) +} diff --git a/dns/internal/core/domain/errors.go b/dns/internal/core/domain/errors.go new file mode 100644 index 0000000..1987d8e --- /dev/null +++ b/dns/internal/core/domain/errors.go @@ -0,0 +1,8 @@ +package domain + +import "errors" + +var ( + ErrEntityNotFound = errors.New("entity not found") + ErrAlreadyExists = errors.New("entity already exists") +) diff --git a/dns/internal/core/domain/repository_service.go b/dns/internal/core/domain/repository_service.go new file mode 100644 index 0000000..bd88d8b --- /dev/null +++ b/dns/internal/core/domain/repository_service.go @@ -0,0 +1,5 @@ +package domain + +type RepositoryService interface { + DnsRepository() DnsRepository +} diff --git a/dns/internal/core/port/controllerd_wrapper.go b/dns/internal/core/port/controllerd_wrapper.go new file mode 100644 index 0000000..36d2fbe --- /dev/null +++ b/dns/internal/core/port/controllerd_wrapper.go @@ -0,0 +1,8 @@ +package port + +import "context" + +type ControllerdWrapper interface { + DomainProvisioned(ctx context.Context, email, domainName string) error + DomainDeleted(ctx context.Context, domainName string) error +} diff --git a/dns/internal/core/port/controllerd_wrapper_mock.go b/dns/internal/core/port/controllerd_wrapper_mock.go new file mode 100644 index 0000000..a47870b --- /dev/null +++ b/dns/internal/core/port/controllerd_wrapper_mock.go @@ -0,0 +1,56 @@ +// Code generated by mockery v2.33.1. DO NOT EDIT. + +package port + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" +) + +// MockControllerdWrapper is an autogenerated mock type for the ControllerdWrapper type +type MockControllerdWrapper struct { + mock.Mock +} + +// DomainDeleted provides a mock function with given fields: ctx, domainName +func (_m *MockControllerdWrapper) DomainDeleted(ctx context.Context, domainName string) error { + ret := _m.Called(ctx, domainName) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, domainName) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// DomainProvisioned provides a mock function with given fields: ctx, email, domainName +func (_m *MockControllerdWrapper) DomainProvisioned(ctx context.Context, email string, domainName string) error { + ret := _m.Called(ctx, email, domainName) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { + r0 = rf(ctx, email, domainName) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// NewMockControllerdWrapper creates a new instance of MockControllerdWrapper. 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 NewMockControllerdWrapper(t interface { + mock.TestingT + Cleanup(func()) +}) *MockControllerdWrapper { + mock := &MockControllerdWrapper{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/dns/internal/core/port/ip_service.go b/dns/internal/core/port/ip_service.go new file mode 100644 index 0000000..6df14cb --- /dev/null +++ b/dns/internal/core/port/ip_service.go @@ -0,0 +1,8 @@ +package port + +import "context" + +type IpService interface { + VerifyDnsRecord(ctx context.Context, ip, domainName string) (bool, error) + GetHostIp(ctx context.Context) (string, error) +} diff --git a/dns/internal/core/port/ip_service_mock.go b/dns/internal/core/port/ip_service_mock.go new file mode 100644 index 0000000..36e98eb --- /dev/null +++ b/dns/internal/core/port/ip_service_mock.go @@ -0,0 +1,76 @@ +// Code generated by mockery v2.33.1. DO NOT EDIT. + +package port + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" +) + +// MockIpService is an autogenerated mock type for the IpService type +type MockIpService struct { + mock.Mock +} + +// GetHostIp provides a mock function with given fields: ctx +func (_m *MockIpService) GetHostIp(ctx context.Context) (string, error) { + ret := _m.Called(ctx) + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (string, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) string); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// VerifyDnsRecord provides a mock function with given fields: ctx, ip, domainName +func (_m *MockIpService) VerifyDnsRecord(ctx context.Context, ip string, domainName string) (bool, error) { + ret := _m.Called(ctx, ip, domainName) + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) (bool, error)); ok { + return rf(ctx, ip, domainName) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string) bool); ok { + r0 = rf(ctx, ip, domainName) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = rf(ctx, ip, domainName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewMockIpService creates a new instance of MockIpService. 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 NewMockIpService(t interface { + mock.TestingT + Cleanup(func()) +}) *MockIpService { + mock := &MockIpService{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/dns/internal/infrastructure/http-clients/controllerd_wrapper.go b/dns/internal/infrastructure/http-clients/controllerd_wrapper.go new file mode 100644 index 0000000..9d5537a --- /dev/null +++ b/dns/internal/infrastructure/http-clients/controllerd_wrapper.go @@ -0,0 +1,89 @@ +package httpclients + +import ( + "context" + "fmt" + "io" + "net/http" + "prem-gateway/dns/internal/core/port" + "time" +) + +type controllerdWrapper struct { + controllerDaemonUrl string +} + +func NewControllerdWrapper(controllerDaemonUrl string) port.ControllerdWrapper { + return &controllerdWrapper{ + controllerDaemonUrl: controllerDaemonUrl, + } +} + +func (c *controllerdWrapper) DomainProvisioned( + ctx context.Context, email, domainName string, +) error { + url := fmt.Sprintf( + "%s/domain-provisioned?domain=%s&email=%s", + c.controllerDaemonUrl, + domainName, + email, + ) + return c.sendReq(ctx, url, http.MethodPost) +} + +func (c *controllerdWrapper) DomainDeleted( + ctx context.Context, domainName string, +) error { + //TODO uncomment once implemented + + //url := fmt.Sprintf( + // "%s/domain-deleted?domain=%s", + // c.controllerDaemonUrl, + // domainName, + //) + //return c.sendReq(ctx, url, http.MethodPost) + + return nil +} + +func (c *controllerdWrapper) sendReq(ctx context.Context, url string, method string) error { + req, err := http.NewRequestWithContext( + ctx, + method, + url, + nil, + ) + if err != nil { + return err + } + + client := &http.Client{ + Timeout: time.Second * 5, + } + resp, err := client.Do(req) + if err != nil { + return err + } + defer func() { + if err := resp.Body.Close(); err != nil { + fmt.Println(err) + } + }() + + if resp.StatusCode != http.StatusOK { + body, readErr := io.ReadAll(resp.Body) + if readErr != nil { + return fmt.Errorf("controllerd returned status code: %v, and error reading body: %v", resp.StatusCode, readErr) + } + + defer func() { + if closeErr := resp.Body.Close(); closeErr != nil { + err = fmt.Errorf("error closing response body: %v, previous error: %v", closeErr, err) + } + }() + + return fmt.Errorf("controllerd returned status code: %v, response: %s", resp.StatusCode, body) + } + + return nil +} diff --git a/dns/internal/infrastructure/http-clients/ip_service.go b/dns/internal/infrastructure/http-clients/ip_service.go new file mode 100644 index 0000000..db061d3 --- /dev/null +++ b/dns/internal/infrastructure/http-clients/ip_service.go @@ -0,0 +1,61 @@ +package httpclients + +import ( + "context" + "errors" + "fmt" + "io" + "net" + "net/http" + "prem-gateway/dns/internal/core/port" + "time" +) + +type ipService struct { +} + +func NewIpService() port.IpService { + return &ipService{} +} + +func (i *ipService) VerifyDnsRecord( + ctx context.Context, expectedIP, domainName string, +) (bool, error) { + ips, err := net.LookupIP(domainName) + if err != nil { + return false, fmt.Errorf("DNS record not found for domain: %v", domainName) + } else { + for _, ip := range ips { + if ip.String() == expectedIP { + return true, nil + } + } + return false, errors.New("DNS record found, but IP does not match") + } +} + +func (i *ipService) GetHostIp(ctx context.Context) (string, error) { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + client := &http.Client{} + + req, err := http.NewRequestWithContext(ctx, "GET", "https://ifconfig.io", nil) + if err != nil { + return "", err + } + + resp, err := client.Do(req) + if err != nil { + return "", err + } + + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", err + } + + return string(body), nil +} diff --git a/dns/internal/infrastructure/storage/pg/db_service.go b/dns/internal/infrastructure/storage/pg/db_service.go new file mode 100644 index 0000000..2e9c672 --- /dev/null +++ b/dns/internal/infrastructure/storage/pg/db_service.go @@ -0,0 +1,149 @@ +package pgdb + +import ( + "context" + "errors" + "fmt" + "github.com/golang-migrate/migrate/v4" + "github.com/golang-migrate/migrate/v4/database/postgres" + "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v4/pgxpool" + log "github.com/sirupsen/logrus" + "prem-gateway/dns/internal/core/domain" + "prem-gateway/dns/internal/infrastructure/storage/pg/sqlc/queries" + + _ "github.com/golang-migrate/migrate/v4/source/file" +) + +const ( + postgresDriver = "pgx" + insecureDataSourceTemplate = "postgresql://%s:%s@%s:%d/%s?sslmode=disable" + + uniqueViolation = "23505" + pgxNoRows = "no rows in result set" +) + +type Service struct { + pgxPool *pgxpool.Pool + querier *queries.Queries + + dnsRepository domain.DnsRepository +} + +func NewDBService(dbConfig DbConfig) (*Service, error) { + dataSource := insecureDataSourceStr(dbConfig) + + pgxPool, err := connect(dataSource) + if err != nil { + return nil, err + } + + if err = migrateDb(dataSource, dbConfig.MigrationSourceURL); err != nil { + return nil, err + } + + rm := &Service{ + pgxPool: pgxPool, + querier: queries.New(pgxPool), + } + + dnsRepository := NewDnsRepositoryImpl(rm.querier) + rm.dnsRepository = dnsRepository + + return rm, nil +} + +func (s *Service) DnsRepository() domain.DnsRepository { + return s.dnsRepository +} + +func (s *Service) Close() { + s.pgxPool.Close() +} + +func (s *Service) execTx( + ctx context.Context, + txBody func(*queries.Queries) error, +) error { + conn, err := s.pgxPool.Acquire(ctx) + if err != nil { + return err + } + defer conn.Release() + + tx, err := conn.Begin(ctx) + if err != nil { + return err + } + + // Rollback is safe to call even if the tx is already closed, so if + // the tx commits successfully, this is a no-op. + defer func() { + err := tx.Rollback(ctx) + switch { + // If the tx was already closed (it was successfully executed) + // we do not need to log that error. + case errors.Is(err, pgx.ErrTxClosed): + return + + // If this is an unexpected error, log it. + case err != nil: + log.Errorf("unable to rollback db tx: %v", err) + } + }() + + if err := txBody(s.querier.WithTx(tx)); err != nil { + return err + } + + // Commit transaction. + return tx.Commit(ctx) +} + +type DbConfig struct { + DbUser string + DbPassword string + DbHost string + DbPort int + DbName string + MigrationSourceURL string +} + +func connect(dataSource string) (*pgxpool.Pool, error) { + return pgxpool.Connect(context.Background(), dataSource) +} + +func migrateDb(dataSource, migrationSourceUrl string) error { + pg := postgres.Postgres{} + + d, err := pg.Open(dataSource) + if err != nil { + return err + } + + m, err := migrate.NewWithDatabaseInstance( + migrationSourceUrl, + postgresDriver, + d, + ) + if err != nil { + return err + } + + if err := m.Up(); err != nil && err != migrate.ErrNoChange { + return err + } + + return nil +} + +func insecureDataSourceStr(dbConfig DbConfig) string { + return fmt.Sprintf( + insecureDataSourceTemplate, + dbConfig.DbUser, + dbConfig.DbPassword, + dbConfig.DbHost, + dbConfig.DbPort, + dbConfig.DbName, + ) +} diff --git a/dns/internal/infrastructure/storage/pg/dns_repository_impl.go b/dns/internal/infrastructure/storage/pg/dns_repository_impl.go new file mode 100644 index 0000000..516b691 --- /dev/null +++ b/dns/internal/infrastructure/storage/pg/dns_repository_impl.go @@ -0,0 +1,144 @@ +package pgdb + +import ( + "context" + "database/sql" + "github.com/jackc/pgconn" + "prem-gateway/dns/internal/core/domain" + "prem-gateway/dns/internal/infrastructure/storage/pg/sqlc/queries" +) + +type dnsRepositoryImpl struct { + querier *queries.Queries +} + +func NewDnsRepositoryImpl(querier *queries.Queries) domain.DnsRepository { + return &dnsRepositoryImpl{ + querier: querier, + } +} + +func (d *dnsRepositoryImpl) Create( + ctx context.Context, dnsInfo domain.DnsInfo, +) error { + var subDomain, ip, nodeName, email sql.NullString + if dnsInfo.SubDomain != "" { + subDomain = sql.NullString{ + String: dnsInfo.SubDomain, + Valid: true, + } + } + if dnsInfo.Ip != "" { + ip = sql.NullString{ + String: dnsInfo.Ip, + Valid: true, + } + } + if dnsInfo.NodeName != "" { + nodeName = sql.NullString{ + String: dnsInfo.NodeName, + Valid: true, + } + } + if dnsInfo.Email != "" { + email = sql.NullString{ + String: dnsInfo.Email, + Valid: true, + } + } + + if err := d.querier.InsertDnsInfo(ctx, queries.InsertDnsInfoParams{ + Domain: dnsInfo.Domain, + SubDomain: subDomain, + Ip: ip, + NodeName: nodeName, + Email: email, + }); err != nil { + if pqErr := err.(*pgconn.PgError); pqErr != nil { + if pqErr.Code == uniqueViolation { + return nil + } else { + return err + } + } + } + + return nil +} + +func (d *dnsRepositoryImpl) Delete( + ctx context.Context, domain string, +) error { + return d.querier.DeleteDnsInfo(ctx, domain) +} + +func (d *dnsRepositoryImpl) Get( + ctx context.Context, + domainName string, +) (*domain.DnsInfo, error) { + dnsInfo, err := d.querier.GetDnsInfo(ctx, domainName) + if err != nil { + if err != nil { + if err.Error() == pgxNoRows { + return nil, domain.ErrEntityNotFound + } + + return nil, err + } + } + + var subDomain, ip, nodeName, email string + if dnsInfo.SubDomain.Valid { + subDomain = dnsInfo.SubDomain.String + } + if dnsInfo.Ip.Valid { + ip = dnsInfo.Ip.String + } + if dnsInfo.NodeName.Valid { + nodeName = dnsInfo.NodeName.String + } + if dnsInfo.Email.Valid { + email = dnsInfo.Email.String + } + + return &domain.DnsInfo{ + Domain: dnsInfo.Domain, + SubDomain: subDomain, + Ip: ip, + NodeName: nodeName, + Email: email, + }, nil +} + +func (d *dnsRepositoryImpl) GetExistingDomain(ctx context.Context) (*domain.DnsInfo, error) { + dns, err := d.querier.GetExistDnsInfo(ctx) + if err != nil { + if err.Error() == pgxNoRows { + return nil, domain.ErrEntityNotFound + } + + return nil, err + } + + var subDomain, ip, nodeName, email string + if dns.SubDomain.Valid { + subDomain = dns.SubDomain.String + } + if dns.Ip.Valid { + ip = dns.Ip.String + } + if dns.NodeName.Valid { + nodeName = dns.NodeName.String + } + if dns.Email.Valid { + email = dns.Email.String + } + + return &domain.DnsInfo{ + Domain: dns.Domain, + SubDomain: subDomain, + Ip: ip, + NodeName: nodeName, + Email: email, + }, nil +} diff --git a/dns/internal/infrastructure/storage/pg/migration/20230726095612_init.down.sql b/dns/internal/infrastructure/storage/pg/migration/20230726095612_init.down.sql new file mode 100644 index 0000000..910a4d1 --- /dev/null +++ b/dns/internal/infrastructure/storage/pg/migration/20230726095612_init.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS dns_info; \ No newline at end of file diff --git a/dns/internal/infrastructure/storage/pg/migration/20230726095612_init.up.sql b/dns/internal/infrastructure/storage/pg/migration/20230726095612_init.up.sql new file mode 100644 index 0000000..e3d7a03 --- /dev/null +++ b/dns/internal/infrastructure/storage/pg/migration/20230726095612_init.up.sql @@ -0,0 +1,7 @@ +CREATE TABLE dns_info ( + domain VARCHAR(255) PRIMARY KEY, + sub_domain VARCHAR(255), + ip VARCHAR(255), + node_name VARCHAR(255), + email VARCHAR(255) +); \ No newline at end of file diff --git a/dns/internal/infrastructure/storage/pg/sqlc.yaml b/dns/internal/infrastructure/storage/pg/sqlc.yaml new file mode 100644 index 0000000..e667b10 --- /dev/null +++ b/dns/internal/infrastructure/storage/pg/sqlc.yaml @@ -0,0 +1,8 @@ +version: 1 +packages: + - path: "sqlc/queries" + name: "queries" + engine: "postgresql" + schema: "migration" + queries: "sqlc/query.sql" + sql_package: "pgx/v4" \ No newline at end of file diff --git a/dns/internal/infrastructure/storage/pg/sqlc/queries/db.go b/dns/internal/infrastructure/storage/pg/sqlc/queries/db.go new file mode 100644 index 0000000..d5fc243 --- /dev/null +++ b/dns/internal/infrastructure/storage/pg/sqlc/queries/db.go @@ -0,0 +1,32 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.16.0 + +package queries + +import ( + "context" + + "github.com/jackc/pgconn" + "github.com/jackc/pgx/v4" +) + +type DBTX interface { + Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) + Query(context.Context, string, ...interface{}) (pgx.Rows, error) + QueryRow(context.Context, string, ...interface{}) pgx.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx pgx.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/dns/internal/infrastructure/storage/pg/sqlc/queries/models.go b/dns/internal/infrastructure/storage/pg/sqlc/queries/models.go new file mode 100644 index 0000000..a60a1e1 --- /dev/null +++ b/dns/internal/infrastructure/storage/pg/sqlc/queries/models.go @@ -0,0 +1,17 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.16.0 + +package queries + +import ( + "database/sql" +) + +type DnsInfo struct { + Domain string + SubDomain sql.NullString + Ip sql.NullString + NodeName sql.NullString + Email sql.NullString +} diff --git a/dns/internal/infrastructure/storage/pg/sqlc/queries/query.sql.go b/dns/internal/infrastructure/storage/pg/sqlc/queries/query.sql.go new file mode 100644 index 0000000..fb440a7 --- /dev/null +++ b/dns/internal/infrastructure/storage/pg/sqlc/queries/query.sql.go @@ -0,0 +1,102 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.16.0 +// source: query.sql + +package queries + +import ( + "context" + "database/sql" +) + +const deleteDnsInfo = `-- name: DeleteDnsInfo :exec +DELETE FROM dns_info WHERE domain = $1 +` + +func (q *Queries) DeleteDnsInfo(ctx context.Context, domain string) error { + _, err := q.db.Exec(ctx, deleteDnsInfo, domain) + return err +} + +const getDnsInfo = `-- name: GetDnsInfo :one +SELECT domain, sub_domain, ip, node_name, email FROM dns_info WHERE domain = $1 +` + +func (q *Queries) GetDnsInfo(ctx context.Context, domain string) (DnsInfo, error) { + row := q.db.QueryRow(ctx, getDnsInfo, domain) + var i DnsInfo + err := row.Scan( + &i.Domain, + &i.SubDomain, + &i.Ip, + &i.NodeName, + &i.Email, + ) + return i, err +} + +const getExistDnsInfo = `-- name: GetExistDnsInfo :one +SELECT domain, sub_domain, ip, node_name, email FROM dns_info +` + +func (q *Queries) GetExistDnsInfo(ctx context.Context) (DnsInfo, error) { + row := q.db.QueryRow(ctx, getExistDnsInfo) + var i DnsInfo + err := row.Scan( + &i.Domain, + &i.SubDomain, + &i.Ip, + &i.NodeName, + &i.Email, + ) + return i, err +} + +const insertDnsInfo = `-- name: InsertDnsInfo :exec + +INSERT INTO dns_info(domain, sub_domain, ip, node_name, email) VALUES ($1, $2, $3, $4, $5) +` + +type InsertDnsInfoParams struct { + Domain string + SubDomain sql.NullString + Ip sql.NullString + NodeName sql.NullString + Email sql.NullString +} + +// DNS_INFO +func (q *Queries) InsertDnsInfo(ctx context.Context, arg InsertDnsInfoParams) error { + _, err := q.db.Exec(ctx, insertDnsInfo, + arg.Domain, + arg.SubDomain, + arg.Ip, + arg.NodeName, + arg.Email, + ) + return err +} + +const updateDnsInfo = `-- name: UpdateDnsInfo :exec +UPDATE dns_info SET sub_domain = $1, ip = $2, node_name = $3, email = $4 WHERE domain = $5 +` + +type UpdateDnsInfoParams struct { + SubDomain sql.NullString + Ip sql.NullString + NodeName sql.NullString + Email sql.NullString + Domain string +} + +func (q *Queries) UpdateDnsInfo(ctx context.Context, arg UpdateDnsInfoParams) error { + _, err := q.db.Exec(ctx, updateDnsInfo, + arg.SubDomain, + arg.Ip, + arg.NodeName, + arg.Email, + arg.Domain, + ) + return err +} diff --git a/dns/internal/infrastructure/storage/pg/sqlc/query.sql b/dns/internal/infrastructure/storage/pg/sqlc/query.sql new file mode 100644 index 0000000..cb4f4b2 --- /dev/null +++ b/dns/internal/infrastructure/storage/pg/sqlc/query.sql @@ -0,0 +1,16 @@ +/* DNS_INFO */ + +-- name: InsertDnsInfo :exec +INSERT INTO dns_info(domain, sub_domain, ip, node_name, email) VALUES ($1, $2, $3, $4, $5); + +-- name: UpdateDnsInfo :exec +UPDATE dns_info SET sub_domain = $1, ip = $2, node_name = $3, email = $4 WHERE domain = $5; + +-- name: DeleteDnsInfo :exec +DELETE FROM dns_info WHERE domain = $1; + +-- name: GetDnsInfo :one +SELECT * FROM dns_info WHERE domain = $1; + +-- name: GetExistDnsInfo :one +SELECT * FROM dns_info; \ No newline at end of file diff --git a/dns/internal/interface/http/handler/dns_handler.go b/dns/internal/interface/http/handler/dns_handler.go new file mode 100644 index 0000000..b745c0d --- /dev/null +++ b/dns/internal/interface/http/handler/dns_handler.go @@ -0,0 +1,220 @@ +package httphandler + +import ( + "github.com/gin-gonic/gin" + "net/http" + "prem-gateway/dns/internal/core/application" + "prem-gateway/dns/internal/core/domain" +) + +type DNSHandler interface { + CreateDnsInfo(c *gin.Context) + DeleteDnsInfo(c *gin.Context) + GetDnsInfo(c *gin.Context) + CheckDnsStatus(c *gin.Context) + GetGatewayIp(c *gin.Context) + GetExistingDns(c *gin.Context) + Check(c *gin.Context) +} + +type dnsHandler struct { + dnsSvc application.DnsService +} + +func NewDNSHandler(dnsSvc application.DnsService) (DNSHandler, error) { + return &dnsHandler{ + dnsSvc: dnsSvc, + }, nil +} + +// CreateDnsInfo godoc +// @Summary Creates a new DNS record +// @Description This endpoint creates a new DNS record based on the provided information +// @Tags dns +// @Accept json +// @Produce json +// @Param DnsInfo body DnsInfo true "dns information" +// +// @Success 200 {object} SuccessResponse +// @Failure 400 {object} ErrorResponse +// @Failure 404 {object} ErrorResponse +// @Failure 500 {object} ErrorResponse +// +// @Router /dns [post] +func (d *dnsHandler) CreateDnsInfo(c *gin.Context) { + var info DnsInfo + if err := c.ShouldBindJSON(&info); err != nil { + c.JSON(http.StatusBadRequest, ErrorResponse{Error: err.Error()}) + return + } + + if err := d.dnsSvc.CreateDomain( + c.Request.Context(), + FromHandlerDnsInfoToAppDnsInfo(info), + ); err != nil { + c.JSON(http.StatusInternalServerError, ErrorResponse{Error: err.Error()}) + return + } + + c.JSON(http.StatusCreated, SuccessResponse{Status: "success"}) +} + +// DeleteDnsInfo godoc +// @Summary Deletes a DNS record +// @Description This endpoint deletes a DNS record based on the provided domain name +// @Tags dns +// @Accept json +// @Produce json +// @Param domain path string true "Domain Name" +// +// @Success 200 {object} SuccessResponse "Returns status of operation" +// @Failure 400 {object} ErrorResponse "Returns error message for invalid input" +// @Failure 500 {object} ErrorResponse "Returns error message for server error" +// +// @Router /dns/{domain} [delete] +func (d *dnsHandler) DeleteDnsInfo(c *gin.Context) { + domainName := c.Param("domain") + if domainName == "" { + c.JSON(http.StatusBadRequest, ErrorResponse{Error: "domain is empty"}) + return + } + + if err := d.dnsSvc.DeleteDomain( + c.Request.Context(), + domainName, + ); err != nil { + c.JSON(http.StatusInternalServerError, ErrorResponse{Error: err.Error()}) + return + } + + c.JSON(http.StatusOK, SuccessResponse{Status: "success"}) +} + +// GetDnsInfo godoc +// @Summary Retrieves a DNS record +// @Description This endpoint retrieves a DNS record based on the provided domain name +// @Tags dns +// @Accept json +// @Produce json +// @Param domain path string true "Domain Name" +// +// @Success 200 {object} DnsInfo "Returns the DNS record" +// @Failure 400 {object} ErrorResponse "Returns error message for invalid input" +// @Failure 404 {object} ErrorResponse "Returns error message for record not found" +// @Failure 500 {object} ErrorResponse "Returns error message for server error" +// +// @Router /dns/{domain} [get] +func (d *dnsHandler) GetDnsInfo(c *gin.Context) { + domainName := c.Param("domain") + if domainName == "" { + c.JSON(http.StatusBadRequest, ErrorResponse{Error: "domain is empty"}) + return + } + + dnsInfo, err := d.dnsSvc.GetDomain(c.Request.Context(), domainName) + if err != nil { + if err == domain.ErrEntityNotFound { + c.JSON(http.StatusNotFound, ErrorResponse{Error: err.Error()}) + return + } + + c.JSON(http.StatusInternalServerError, ErrorResponse{Error: err.Error()}) + return + } + + c.JSON(http.StatusOK, FromAppDnsInfoToHandlerDnsInfo(dnsInfo)) +} + +// CheckDnsStatus godoc +// @Summary Check status of a DNS record +// @Description This endpoint checks the status of a DNS record based on the provided domain name +// @Tags dns +// @Accept json +// @Produce json +// @Param domain path string true "Domain Name" +// +// @Success 200 {object} bool "Returns true if the DNS record is valid, false otherwise" +// @Failure 400 {object} ErrorResponse "Returns error message for invalid input" +// @Failure 404 {object} ErrorResponse "Returns error message for record not found" +// @Failure 500 {object} ErrorResponse "Returns error message for server error" +// +// @Router /dns/status/{domain} [get] +func (d *dnsHandler) CheckDnsStatus(c *gin.Context) { + domainName := c.Param("domain") + if domainName == "" { + c.JSON(http.StatusBadRequest, ErrorResponse{Error: "domain is empty"}) + return + } + + valid, err := d.dnsSvc.CheckDnsRecordStatus(c.Request.Context(), domainName) + if err != nil { + c.JSON(http.StatusInternalServerError, ErrorResponse{Error: err.Error()}) + return + } + + if !valid { + c.JSON(http.StatusNotFound, ErrorResponse{Error: "dns record not found"}) + return + } + + c.JSON(http.StatusOK, valid) +} + +// GetGatewayIp godoc +// @Summary Retrieves the IP address of the Gateway +// @Description This endpoint retrieves the IP address of the Gateway +// @Tags dns +// @Accept json +// @Produce json +// +// @Success 200 {object} string "Returns IP address of the Gateway" +// @Failure 500 {object} ErrorResponse "Returns error message for server error" +// +// @Router /dns/ip [get] +func (d *dnsHandler) GetGatewayIp(c *gin.Context) { + ipAddr, err := d.dnsSvc.GetGatewayIp(c.Request.Context()) + if err != nil { + c.JSON(http.StatusInternalServerError, ErrorResponse{Error: err.Error()}) + return + } + + c.JSON(http.StatusOK, ipAddr) +} + +// GetExistingDns godoc +// @Summary Retrieves the existing DNS record +// @Description This endpoint retrieves the existing DNS record +// @Tags dns +// @Accept json +// @Produce json +// +// @Success 200 {object} DnsInfo "Returns the existing DNS record" +// @Failure 500 {object} ErrorResponse "Returns error message for server error" +// +// @Router /dns/existing [get] +func (d *dnsHandler) GetExistingDns(c *gin.Context) { + dnsInfo, err := d.dnsSvc.GetExistingDomain(c.Request.Context()) + if err != nil { + c.JSON(http.StatusInternalServerError, ErrorResponse{Error: err.Error()}) + return + } + + if dnsInfo != nil { + c.JSON(http.StatusOK, FromAppDnsInfoToHandlerDnsInfo(*dnsInfo)) + return + } + + c.JSON(http.StatusOK, nil) +} + +// Check godoc +// @Summary Check if the service is up and running +// @Description This endpoint checks if the service is up and running +// @Tags dns +// @Accept json +// @Produce json +// @Success 200 +// @Router /dns/check [get] +func (d *dnsHandler) Check(c *gin.Context) { + c.JSON(http.StatusOK, nil) +} diff --git a/dns/internal/interface/http/handler/types.go b/dns/internal/interface/http/handler/types.go new file mode 100644 index 0000000..7f1b95b --- /dev/null +++ b/dns/internal/interface/http/handler/types.go @@ -0,0 +1,36 @@ +package httphandler + +import "prem-gateway/dns/internal/core/application" + +type DnsInfo struct { + Domain string `json:"domain"` + Ip string `json:"ip"` + NodeName string `json:"node_name"` + Email string `json:"email"` +} + +func FromHandlerDnsInfoToAppDnsInfo(hdi DnsInfo) application.DnsInfo { + return application.DnsInfo{ + Domain: hdi.Domain, + Ip: hdi.Ip, + NodeName: hdi.NodeName, + Email: hdi.Email, + } +} + +func FromAppDnsInfoToHandlerDnsInfo(adi application.DnsInfo) DnsInfo { + return DnsInfo{ + Domain: adi.Domain, + Ip: adi.Ip, + NodeName: adi.NodeName, + Email: adi.Email, + } +} + +type SuccessResponse struct { + Status string `json:"status"` +} + +type ErrorResponse struct { + Error string `json:"error"` +} diff --git a/dns/internal/interface/http/server.go b/dns/internal/interface/http/server.go new file mode 100644 index 0000000..8b598f3 --- /dev/null +++ b/dns/internal/interface/http/server.go @@ -0,0 +1,133 @@ +package httpdnsd + +import ( + "context" + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" + swaggerFiles "github.com/swaggo/files" + ginSwagger "github.com/swaggo/gin-swagger" + "net/http" + "prem-gateway/dns/internal/core/application" + "prem-gateway/dns/internal/core/domain" + httphandler "prem-gateway/dns/internal/interface/http/handler" + "time" +) + +const ( + shutdownTimeout = time.Second * 5 +) + +type Server interface { + Start(ctx context.Context, stop context.CancelFunc) <-chan error + Stop() error + Router() http.Handler +} + +type server struct { + serverAddress string + opts serverOptions + dnsHandler httphandler.DNSHandler + dnsSvc application.DnsService +} + +func NewServer( + serverAddress string, + repositorySvc domain.RepositoryService, + controllerDaemonUrl string, + opts ...ServerOption, +) (Server, error) { + options := defaultServerOptions(controllerDaemonUrl) + for _, o := range opts { + if err := o.apply(&options); err != nil { + return nil, err + } + } + + dnsSvc, err := application.NewDnsService( + repositorySvc, options.ipSvc, options.controllerdWrapper, + ) + if err != nil { + return nil, err + } + + dnsHandler, err := httphandler.NewDNSHandler(dnsSvc) + if err != nil { + return nil, err + } + + return &server{ + serverAddress: serverAddress, + opts: options, + dnsHandler: dnsHandler, + dnsSvc: dnsSvc, + }, nil +} + +func (s *server) Start(ctx context.Context, stop context.CancelFunc) <-chan error { + errCh := make(chan error) + + httpServer := &http.Server{ + Addr: s.serverAddress, + Handler: s.Router(), + } + + go func() { + <-ctx.Done() + + log.Info("shutdown signal received") + + ctxTimeout, cancel := context.WithTimeout(context.Background(), shutdownTimeout) + + defer func() { + stop() + cancel() + close(errCh) + }() + + httpServer.SetKeepAlivesEnabled(false) + if err := httpServer.Shutdown(ctxTimeout); err != nil { + errCh <- err + } + + log.Info("prem-gateway dns daemon graceful shutdown completed") + }() + + go func() { + log.Infof("prem-gateway dns daemon listening and serving at: %v", s.serverAddress) + + if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { + errCh <- err + } + }() + + return errCh +} + +func (s *server) Stop() error { + return nil +} + +func (s *server) Router() http.Handler { + ginEngine := gin.Default() + ginEngine.Use(func(c *gin.Context) { + c.Writer.Header().Set("Access-Control-Allow-Origin", "http://localhost:1420") // Replace with your frontend origin + c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH") + c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") + if c.Request.Method == "OPTIONS" { + c.AbortWithStatus(http.StatusOK) + return + } + c.Next() + }) + + ginEngine.POST("/dns", s.dnsHandler.CreateDnsInfo) + ginEngine.DELETE("/dns/:domain", s.dnsHandler.DeleteDnsInfo) + ginEngine.GET("/dns/:domain", s.dnsHandler.GetDnsInfo) + ginEngine.GET("/dns/status/:domain", s.dnsHandler.CheckDnsStatus) + ginEngine.GET("/dns/ip", s.dnsHandler.GetGatewayIp) + ginEngine.GET("/dns/check", s.dnsHandler.Check) + ginEngine.GET("/dns/existing", s.dnsHandler.GetExistingDns) + ginEngine.GET("/docs/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) + + return ginEngine +} diff --git a/dns/internal/interface/http/server_opts.go b/dns/internal/interface/http/server_opts.go new file mode 100644 index 0000000..f600af8 --- /dev/null +++ b/dns/internal/interface/http/server_opts.go @@ -0,0 +1,54 @@ +package httpdnsd + +import ( + "prem-gateway/dns/internal/core/port" + httpclients "prem-gateway/dns/internal/infrastructure/http-clients" +) + +type ServerOption interface { + apply(*serverOptions) error +} + +type serverOptions struct { + ipSvc port.IpService + controllerdWrapper port.ControllerdWrapper +} + +func defaultServerOptions(controllerDaemonUrl string) serverOptions { + ipSvc := httpclients.NewIpService() + controllerdWrapper := httpclients.NewControllerdWrapper(controllerDaemonUrl) + return serverOptions{ + ipSvc: ipSvc, + controllerdWrapper: controllerdWrapper, + } +} + +type funcServerOption struct { + f func(*serverOptions) error +} + +func (fdo *funcServerOption) apply(do *serverOptions) error { + return fdo.f(do) +} + +func newFuncServerOption(f func(*serverOptions) error) *funcServerOption { + return &funcServerOption{ + f: f, + } +} + +func WithIpService(ipSvc port.IpService) ServerOption { + return newFuncServerOption(func(o *serverOptions) error { + o.ipSvc = ipSvc + return nil + }) +} + +func WithControllerdWrapper( + controllerdWrapper port.ControllerdWrapper, +) ServerOption { + return newFuncServerOption(func(o *serverOptions) error { + o.controllerdWrapper = controllerdWrapper + return nil + }) +} diff --git a/dns/script/create_testdb b/dns/script/create_testdb new file mode 100755 index 0000000..ed0f7c9 --- /dev/null +++ b/dns/script/create_testdb @@ -0,0 +1,5 @@ +#!/bin/bash + +docker run --name dnsd-db-pg -p 5432:5432 -e POSTGRES_USER=root -e POSTGRES_PASSWORD=secret -d postgres +sleep 3 +docker exec dnsd-db-pg createdb --username=root --owner=root dnsd-db-test diff --git a/dns/test/http/router_test.go b/dns/test/http/router_test.go new file mode 100644 index 0000000..24faa5a --- /dev/null +++ b/dns/test/http/router_test.go @@ -0,0 +1,107 @@ +package http + +import ( + "bytes" + "encoding/json" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "net/http" + "net/http/httptest" + "prem-gateway/dns/internal/core/port" + pgdb "prem-gateway/dns/internal/infrastructure/storage/pg" + dnsdhttp "prem-gateway/dns/internal/interface/http" + httphandler "prem-gateway/dns/internal/interface/http/handler" + "testing" +) + +func TestRouter(t *testing.T) { + svc, err := pgdb.NewDBService(pgdb.DbConfig{ + DbUser: "root", + DbPassword: "secret", + DbHost: "127.0.0.1", + DbPort: 5432, + DbName: "dnsd-db-test", + MigrationSourceURL: "file://../.." + + "/internal/infrastructure/storage/pg/migration", + }) + require.NoError(t, err) + + serverAddress := ":8080" + ipSvcMock := new(port.MockIpService) + ipSvcMock. + On("VerifyDnsRecord", mock.Anything, "100.27.28.72", "dusansekulic.me"). + Return(true, nil) + ipSvcOpt := dnsdhttp.WithIpService(ipSvcMock) + controllerdWrapperMock := new(port.MockControllerdWrapper) + controllerdWrapperMock. + On("DomainProvisioned", mock.Anything, "dusansekulic.me", "dusan.sekulic.mne@gmail.com"). + Return(nil) + + controllerdWrapperOpt := dnsdhttp.WithControllerdWrapper(controllerdWrapperMock) + opts := []dnsdhttp.ServerOption{ + ipSvcOpt, + controllerdWrapperOpt, + } + + dnsd, err := dnsdhttp.NewServer( + serverAddress, svc, "", opts..., + ) + require.NoError(t, err) + ginRouter := dnsd.Router() + + //CREATE DNS INFO + w := httptest.NewRecorder() + dnsInfo := httphandler.DnsInfo{ + Domain: "dusansekulic.me", + Ip: "100.27.28.72", + NodeName: "noder", + Email: "dusan.sekulic.mne@gmail.com", + } + dnsInfoBytes, err := json.Marshal(dnsInfo) + require.NoError(t, err) + req, _ := http.NewRequest( + http.MethodPost, "/dns", bytes.NewReader(dnsInfoBytes), + ) + ginRouter.ServeHTTP(w, req) + require.Equal(t, http.StatusCreated, w.Code) + + //GET DNS INFO + w = httptest.NewRecorder() + req, _ = http.NewRequest( + http.MethodGet, "/dns/dusansekulic.me", nil, + ) + ginRouter.ServeHTTP(w, req) + require.Equal(t, http.StatusOK, w.Code) + var dnsInfos httphandler.DnsInfo + err = json.Unmarshal(w.Body.Bytes(), &dnsInfos) + require.NoError(t, err) + require.Equal(t, dnsInfo.Domain, dnsInfos.Domain) + require.Equal(t, dnsInfo.Ip, dnsInfos.Ip) + require.Equal(t, dnsInfo.NodeName, dnsInfos.NodeName) + require.Equal(t, dnsInfo.Email, dnsInfos.Email) + + //CHECK DNS STATUS + w = httptest.NewRecorder() + req, _ = http.NewRequest( + http.MethodGet, "/dns/status/dusansekulic.me", nil, + ) + ginRouter.ServeHTTP(w, req) + require.Equal(t, http.StatusOK, w.Code) + t.Log(w.Body.String()) + + //DELETE DNS INFO + w = httptest.NewRecorder() + req, _ = http.NewRequest( + http.MethodDelete, "/dns/dusansekulic.me", nil, + ) + ginRouter.ServeHTTP(w, req) + require.Equal(t, http.StatusOK, w.Code) + + //GET DNS INFO + w = httptest.NewRecorder() + req, _ = http.NewRequest( + http.MethodGet, "/dns/dusansekulic.me", nil, + ) + ginRouter.ServeHTTP(w, req) + require.Equal(t, http.StatusNotFound, w.Code) +} diff --git a/dns/test/pg/db_util.go b/dns/test/pg/db_util.go new file mode 100644 index 0000000..d59f72a --- /dev/null +++ b/dns/test/pg/db_util.go @@ -0,0 +1,80 @@ +package pgtest + +import ( + "context" + "database/sql" + "fmt" + + _ "github.com/jackc/pgx/v4/stdlib" +) + +var ( + DB *sql.DB + dbUser = "root" + dbPass = "secret" + dbHost = "127.0.0.1" + dbPort = "5432" + dbName = "dnsd-db-test" +) + +func SetupDB() error { + db, err := createDBConnection() + if err != nil { + return err + } + + DB = db + return nil +} + +func ShutdownDB() error { + if err := TruncateDB(); err != nil { + return err + } + + return DB.Close() +} + +func TruncateDB() error { + truncateQuery := ` + SELECT truncate_tables('%s') + ` + formattedQuery := fmt.Sprintf(truncateQuery, dbUser) + _, err := DB.ExecContext(context.Background(), formattedQuery) + if err != nil { + return err + } + + return nil +} + +func createDBConnection() (*sql.DB, error) { + formattedURL := fmt.Sprintf("postgres://%s:%s@%s:%s/%s", dbUser, dbPass, dbHost, dbPort, dbName) + db, err := sql.Open("pgx", formattedURL) + if err != nil { + return nil, err + } + + DB = db + + truncateFunctionQuery := ` + CREATE OR REPLACE FUNCTION truncate_tables(username IN VARCHAR) RETURNS void AS $$ + DECLARE + statements CURSOR FOR + SELECT tablename FROM pg_tables + WHERE tableowner = username AND schemaname = 'public' AND tablename NOT LIKE '%migrations'; + BEGIN + FOR stmt IN statements LOOP + EXECUTE 'TRUNCATE TABLE ' || quote_ident(stmt.tablename) || ' CASCADE;'; + END LOOP; + END; + $$ LANGUAGE plpgsql; +` + + _, err = db.ExecContext(context.Background(), truncateFunctionQuery) + if err != nil { + return nil, err + } + + return db, nil +} diff --git a/dns/test/pg/dns_repository_test.go b/dns/test/pg/dns_repository_test.go new file mode 100644 index 0000000..983e7b2 --- /dev/null +++ b/dns/test/pg/dns_repository_test.go @@ -0,0 +1,49 @@ +package pgtest + +import "prem-gateway/dns/internal/core/domain" + +func (p *PgDbTestSuite) TestDnsRepository() { + dnsInfo, err := dbSvc.DnsRepository().Get(ctx, "dummy") + p.EqualError(err, domain.ErrEntityNotFound.Error()) + p.Nil(dnsInfo) + + dnsInfo = &domain.DnsInfo{ + Domain: "example.com", + SubDomain: "*example.com", + Ip: "10.10.10.10", + NodeName: "node1", + Email: "test@gmail.com", + } + + err = dbSvc.DnsRepository().Create(ctx, *dnsInfo) + p.NoError(err) + + dnsInfo, err = dbSvc.DnsRepository().Get(ctx, "example.com") + p.NoError(err) + p.Equal("example.com", dnsInfo.Domain) + p.Equal("*example.com", dnsInfo.SubDomain) + p.Equal("10.10.10.10", dnsInfo.Ip) + p.Equal("node1", dnsInfo.NodeName) + p.Equal("test@gmail.com", dnsInfo.Email) + + dns, err := dbSvc.DnsRepository().GetExistingDomain(ctx) + p.NoError(err) + p.Equal("example.com", dns.Domain) + p.Equal("*example.com", dns.SubDomain) + p.Equal("10.10.10.10", dnsInfo.Ip) + p.Equal("node1", dnsInfo.NodeName) + p.Equal("test@gmail.com", dnsInfo.Email) + + err = dbSvc.DnsRepository().Create(ctx, *dnsInfo) + p.NoError(err) + + err = dbSvc.DnsRepository().Delete(ctx, "dummy") + p.NoError(err) + + err = dbSvc.DnsRepository().Delete(ctx, "example.com") + p.NoError(err) + + dnsInfo, err = dbSvc.DnsRepository().Get(ctx, "example.com") + p.EqualError(err, domain.ErrEntityNotFound.Error()) + p.Nil(dnsInfo) +} diff --git a/dns/test/pg/run_suite_test.go b/dns/test/pg/run_suite_test.go new file mode 100644 index 0000000..60ccd65 --- /dev/null +++ b/dns/test/pg/run_suite_test.go @@ -0,0 +1,11 @@ +package pgtest + +import ( + "testing" + + "github.com/stretchr/testify/suite" +) + +func TestPgTestSuite(t *testing.T) { + suite.Run(t, new(PgDbTestSuite)) +} diff --git a/dns/test/pg/test_setup.go b/dns/test/pg/test_setup.go new file mode 100644 index 0000000..2a93433 --- /dev/null +++ b/dns/test/pg/test_setup.go @@ -0,0 +1,54 @@ +package pgtest + +import ( + "context" + "github.com/stretchr/testify/suite" + pgdb "prem-gateway/dns/internal/infrastructure/storage/pg" +) + +var ( + dbSvc *pgdb.Service + ctx = context.Background() +) + +type PgDbTestSuite struct { + suite.Suite +} + +func (p *PgDbTestSuite) SetupSuite() { + svc, err := pgdb.NewDBService(pgdb.DbConfig{ + DbUser: "root", + DbPassword: "secret", + DbHost: "127.0.0.1", + DbPort: 5432, + DbName: "dnsd-db-test", + MigrationSourceURL: "file://../.." + + "/internal/infrastructure/storage/pg/migration", + }) + if err != nil { + p.FailNow(err.Error()) + } + + dbSvc = svc + + if err := SetupDB(); err != nil { + p.FailNow(err.Error()) + } +} + +func (p *PgDbTestSuite) TearDownSuite() { + if err := TruncateDB(); err != nil { + p.FailNow(err.Error()) + } + + dbSvc.Close() +} + +func (p *PgDbTestSuite) BeforeTest(suiteName, testName string) { + if err := TruncateDB(); err != nil { + p.FailNow(err.Error()) + } +} + +func (p *PgDbTestSuite) AfterTest(suiteName, testName string) { +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..ad2d457 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,81 @@ +services: + + traefik: + container_name: traefik + image: traefik:v2.4 + networks: + - prem-gateway + command: + - "--providers.docker=true" + - "--providers.docker.exposedbydefault=false" + - "--accesslog=true" + - "--ping" + - "--entrypoints.web.address=:80" + ports: + - "80:80" + - "8080:8080" + - "443:443" + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ./traefik/letsencrypt:/letsencrypt + depends_on: + - dnsd + restart: always + + dnsd: + container_name: dnsd + build: ./dns + networks: + - prem-gateway + labels: + - "traefik.enable=true" + - "traefik.http.routers.dnsd.rule=HeadersRegexp(`X-Host-Override`,`dnsd`) && PathPrefix(`/`)" + depends_on: + - dnsd-db-pg + - authd + environment: + PREM_GATEWAY_DNS_DB_HOST: dnsd-db-pg + ports: + - "8082:8080" + restart: always + + dnsd-db-pg: + container_name: dnsd-db-pg + image: postgres:14.7 + networks: + - prem-gateway + ports: + - "5432:5432" + environment: + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${POSTGRES_DB} + volumes: + - ./pg-data:/var/lib/postgresql/data + restart: always + authd: + container_name: authd + build: ./auth + networks: + - prem-gateway + ports: + - "8081:8080" + restart: always + + controllerd: + container_name: controllerd + build: ./controller + networks: + - prem-gateway + ports: + - "8083:8080" + volumes: + - /var/run/docker.sock:/var/run/docker.sock + user: root + environment: + LETSENCRYPT_PROD: ${LETSENCRYPT_PROD} + SERVICES: ${SERVICES} + +networks: + prem-gateway: + external: true \ No newline at end of file diff --git a/e2e/main_test.go b/e2e/main_test.go new file mode 100644 index 0000000..c5eeef1 --- /dev/null +++ b/e2e/main_test.go @@ -0,0 +1,34 @@ +package e2e + +import "testing" + +func TestE2e(t *testing.T) { + // start prem-gateway + // make make up LETSENCRYPT_PROD=true + + // check if dnsd is reachable + //GET http://100.27.28.72/dns + //Headers: + //Host: dnsd.docker.localhost + //Authorization: dummy-api-key + + // create domain + //POST http://100.27.28.72/dns + //Headers: + //Host: dnsd.docker.localhost + //Authorization: dummy-api-key + //Body: + //{ + // "domain":"dusansekulic.me", + // "sub_domain":"*dusansekulic.me", + // "a_record":"100.27.28.72", + // "node_name":"node1", + // "email":"dusan.sekulic.mne@gmail.com" + //} + + // prem-gateway should restart which will cause traefik to setup tls and subdomains + // chekck that dnsd is reachable on real subdomain and that connection is secure + //GET https://dusansekulic.me/dns + //Headers: + //Authorization: dummy-api-key +} diff --git a/script/docker-compose-box.yml b/script/docker-compose-box.yml new file mode 100644 index 0000000..b575532 --- /dev/null +++ b/script/docker-compose-box.yml @@ -0,0 +1,40 @@ +services: + premd: + container_name: premd + image: ${PREMD_IMAGE} + restart: on-failure + networks: + - prem-gateway + volumes: + - /var/run/docker.sock:/var/run/docker.sock + environment: + - PREM_REGISTRY_URL=https://raw.githubusercontent.com/premAI-io/prem-registry/main/manifests.json + - SENTRY_DSN=https://75592545ad6b472e9ad7c8ff51740b73@o1068608.ingest.sentry.io/4505244431941632 + - PROXY_ENABLED=True + labels: + - "traefik.enable=true" + - "traefik.http.routers.premd.rule=HeadersRegexp(`X-Host-Override`,`premd`) && PathPrefix(`/`)" + ports: + - "8084:8000" + + premapp: + container_name: premapp + image: ${PREMAPP_IMAGE} + restart: on-failure + networks: + - prem-gateway + environment: + - VITE_DESTINATION=browser + - VITE_IS_PACKAGED=true + - VITE_PROXY_ENABLED=true + labels: + - "traefik.enable=true" + - "traefik.http.routers.premapp-http.rule=PathPrefix(`/`)" + - "traefik.http.routers.premapp-http.entrypoints=web" + - "traefik.http.services.premapp.loadbalancer.server.port=8080" + ports: + - "8085:8080" + +networks: + prem-gateway: + external: true \ No newline at end of file diff --git a/script/run_all.sh b/script/run_all.sh new file mode 100644 index 0000000..2d6d513 --- /dev/null +++ b/script/run_all.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# Run the 'make up' command with environment variables +export LETSENCRYPT_PROD=true +export SERVICES=premd,premapp +make up + +# Loop to check for 'OK' from curl command +while true; do + response=$(curl -s http://localhost:8080/ping) + if [ "$response" == "OK" ]; then + echo "Received OK. Proceeding to next step." + break + else + echo "Waiting for OK response..." + sleep 2 + fi +done + +# Navigate back to the ./script directory to run 'docker-compose' +cd ./script || { echo "Directory ./script does not exist. Exiting."; exit 1; } + +# Run the 'docker-compose' command with environment variables +export PREMD_IMAGE +export PREMAPP_IMAGE +docker-compose -f docker-compose-box.yml up -d --build diff --git a/script/stop_all.sh b/script/stop_all.sh new file mode 100644 index 0000000..9e8c3da --- /dev/null +++ b/script/stop_all.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +make down + +cd ./script + +export PREMD_IMAGE +export PREMAPP_IMAGE +docker-compose -f docker-compose-box.yml down -v \ No newline at end of file diff --git a/traefik/letsencrypt/acme.json b/traefik/letsencrypt/acme.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/traefik/letsencrypt/acme.json @@ -0,0 +1 @@ +{} \ No newline at end of file From fd7e41a182f151016fbe9f3c8a65d53b19df6933 Mon Sep 17 00:00:00 2001 From: sekulicd Date: Thu, 19 Oct 2023 11:55:58 +0200 Subject: [PATCH 02/39] move dns make cmds --- Makefile | 142 +-------------------------------------------------- dns/Makefile | 130 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 132 insertions(+), 140 deletions(-) create mode 100644 dns/Makefile diff --git a/Makefile b/Makefile index 5d3be38..95c7736 100644 --- a/Makefile +++ b/Makefile @@ -1,121 +1,4 @@ -PONY: build-dns run-dns pg droppg createdb dropdb createtestdb droptestdb recreatedb recreatetestdb pgcreatetestdb psql mig_file mig_up_test mig_up mig_down_test mig_down mig_down_yes vet_db sqlc doc dev up down - -##### DNS Daemon ##### - -## build-dns prem-gateway dns service -build-dns: - @echo "Building prem-gateway dns service..." - @export GO111MODULE=on; \ - env go build -tags netgo -ldflags="-s -w" -o bin/dnsd ./dns/cmd/dnsd/main.go - -## run-dns runs prem-gateway dns service -run-dns: - @echo "Running prem-gateway dns service..." - ./bin/dnsd - -##### DNS Daemon ##### - - -#### Postgres database #### - -## pg: starts postgres db inside docker container -pg: - docker run --name dnsd-db-pg -p 5432:5432 -e POSTGRES_USER=root -e POSTGRES_PASSWORD=secret -d postgres - -## droppg: stop and remove postgres container -droppg: - docker stop dnsd-db-pg - docker rm dnsd-db-pg - -## createdb: create db inside docker container -createdb: - docker exec dnsd-db-pg createdb --username=root --owner=root dnsd-db - -## dropdb: drops db inside docker container -dropdb: - docker exec dnsd-db-pg dropdb dnsd-db - -## createtestdb: create test db inside docker container -createtestdb: - docker exec dnsd-db-pg createdb --username=root --owner=root dnsd-db-test - -## droptestdb: drops test db inside docker container -droptestdb: - docker exec dnsd-db-pg dropdb dnsd-db-test - -## recreatedb: drop and create main and test db -recreatedb: dropdb createdb droptestdb createtestdb - -## recreatetestdb: drop and create test db -recreatetestdb: droptestdb createtestdb - -## pgcreatetestdb: starts docker container and creates test db, used in CI -pgcreatetestdb: - chmod u+x ./script/create_testdb - ./script/create_testdb - -## psql: connects to postgres terminal running inside docker container -psql: - docker exec -it dnsd-db-pg psql -U root -d dnsd-db - - -## mig_file: creates pg migration file(eg. make FILE=init mig_file) -mig_file: - @migrate create -ext sql -dir ./dns/internal/infrastructure/storage/pg/migration/ $(FILE) - -## mig_up_test: creates test db schema -mig_up_test: - @echo "creating db schema..." - @migrate -database "postgres://root:secret@localhost:5432/dnsd-db-test?sslmode=disable" -path ./dns/internal/infrastructure/storage/pg/migration/ up - -## mig_up: creates db schema -mig_up: - @echo "creating db schema..." - @migrate -database "postgres://root:secret@localhost:5432/dnsd-db?sslmode=disable" -path ./dns/internal/infrastructure/storage/pg/migration/ up - -## mig_down_test: apply down migration on test db -mig_down_test: - @echo "migration down on test db..." - @migrate -database "postgres://root:secret@localhost:5432/dnsd-db-test?sslmode=disable" -path ./dns/internal/infrastructure/storage/pg/migration/ down - -## mig_down: apply down migration -mig_down: - @echo "migration down..." - @migrate -database "postgres://root:secret@localhost:5432/dnsd-db?sslmode=disable" -path ./dns/internal/infrastructure/storage/pg/migration/ down - -## mig_down_yes: apply down migration without prompt -mig_down_yes: - @echo "migration down..." - @"yes" | migrate -database "postgres://root:secret@localhost:5432/dnsd-db?sslmode=disable" -path ./dns/internal/infrastructure/storage/pg/migration/ down - -## vet_db: check if mig_up and mig_down are ok -vet_db: recreatedb mig_up mig_down_yes - @echo "vet db migration scripts..." - -## sqlc: gen sql -sqlc: - @echo "gen sql..." - cd ./dns/internal/infrastructure/storage/pg; sqlc generate - -#### Postgres database #### - - -#### Swagger doc #### - -## doc: generate swagger doc -doc: - @echo "generating swagger doc..." - swag init -g ./dns/cmd/dnsd/main.go -o ./dns/docs - -#### Swagger doc #### - -## dev-dns: run dnsd and postgres -dev-dns: - export POSTGRES_USER=root; \ - export POSTGRES_PASSWORD=secret; \ - export POSTGRES_DB=dnsd-db; \ - cd ./dns; \ - DOCKER_BUILDKIT=0 docker-compose up -d --build +PONY: up down runall stopall ## up: run prem-gateway up: @@ -139,25 +22,4 @@ runall: ## stopall: stop prem-gateway and prem-box stopall: chmod +x ./script/stop_all.sh - ./script/stop_all.sh - -#### Go lint #### - -## vetdnsd: run go vet on dnsd -vetdnsd: - @echo "go vet dnsd..." - @cd dns && go vet ./... - -#### Go lint #### - -#### Go mock #### - -## mockdnsd: generater mocks -mockdnsd: - cd ./dns/internal/core/port/; \ - mockery --name=ControllerdWrapper --structname=MockControllerdWrapper \ - --output=./ --outpkg=port --filename=controllerd_wrapper_mock.go --inpackage; \ - mockery --name=IpService --structname=MockIpService \ - --output=./ --outpkg=port --filename=ip_service_mock.go --inpackage; - -#### Go mock #### \ No newline at end of file + ./script/stop_all.sh \ No newline at end of file diff --git a/dns/Makefile b/dns/Makefile new file mode 100644 index 0000000..58cc287 --- /dev/null +++ b/dns/Makefile @@ -0,0 +1,130 @@ +PONY: build run pg droppg createdb dropdb createtestdb droptestdb recreatedb recreatetestdb pgcreatetestdb psql mig_file mig_up_test mig_up mig_down_test mig_down mig_down_yes vet_db sqlc doc up vet mock + +## build dnsd +build: + @echo "Building dnsd..." + @export GO111MODULE=on; \ + env go build -tags netgo -ldflags="-s -w" -o bin/dnsd ./cmd/dnsd/main.go + +## run dnsd +run: + @echo "Running dns service..." + ./bin/dnsd + +#### Postgres database #### + +## pg: starts postgres db inside docker container +pg: + docker run --name dnsd-db-pg -p 5432:5432 -e POSTGRES_USER=root -e POSTGRES_PASSWORD=secret -d postgres + +## droppg: stop and remove postgres container +droppg: + docker stop dnsd-db-pg + docker rm dnsd-db-pg + +## createdb: create db inside docker container +createdb: + docker exec dnsd-db-pg createdb --username=root --owner=root dnsd-db + +## dropdb: drops db inside docker container +dropdb: + docker exec dnsd-db-pg dropdb dnsd-db + +## createtestdb: create test db inside docker container +createtestdb: + docker exec dnsd-db-pg createdb --username=root --owner=root dnsd-db-test + +## droptestdb: drops test db inside docker container +droptestdb: + docker exec dnsd-db-pg dropdb dnsd-db-test + +## recreatedb: drop and create main and test db +recreatedb: dropdb createdb droptestdb createtestdb + +## recreatetestdb: drop and create test db +recreatetestdb: droptestdb createtestdb + +## pgcreatetestdb: starts docker container and creates test db, used in CI +pgcreatetestdb: + chmod u+x ./script/create_testdb + ./script/create_testdb + +## psql: connects to postgres terminal running inside docker container +psql: + docker exec -it dnsd-db-pg psql -U root -d dnsd-db + + +## mig_file: creates pg migration file(eg. make FILE=init mig_file) +mig_file: + @migrate create -ext sql -dir ./internal/infrastructure/storage/pg/migration/ $(FILE) + +## mig_up_test: creates test db schema +mig_up_test: + @echo "creating db schema..." + @migrate -database "postgres://root:secret@localhost:5432/dnsd-db-test?sslmode=disable" -path ./internal/infrastructure/storage/pg/migration/ up + +## mig_up: creates db schema +mig_up: + @echo "creating db schema..." + @migrate -database "postgres://root:secret@localhost:5432/dnsd-db?sslmode=disable" -path ./internal/infrastructure/storage/pg/migration/ up + +## mig_down_test: apply down migration on test db +mig_down_test: + @echo "migration down on test db..." + @migrate -database "postgres://root:secret@localhost:5432/dnsd-db-test?sslmode=disable" -path ./internal/infrastructure/storage/pg/migration/ down + +## mig_down: apply down migration +mig_down: + @echo "migration down..." + @migrate -database "postgres://root:secret@localhost:5432/dnsd-db?sslmode=disable" -path ./internal/infrastructure/storage/pg/migration/ down + +## mig_down_yes: apply down migration without prompt +mig_down_yes: + @echo "migration down..." + @"yes" | migrate -database "postgres://root:secret@localhost:5432/dnsd-db?sslmode=disable" -path ./internal/infrastructure/storage/pg/migration/ down + +## vet_db: check if mig_up and mig_down are ok +vet_db: recreatedb mig_up mig_down_yes + @echo "vet db migration scripts..." + +## sqlc: gen sql +sqlc: + @echo "gen sql..." + cd ./internal/infrastructure/storage/pg; sqlc generate + +#### Postgres database #### + + +#### Swagger doc #### + +## doc: generate swagger doc +doc: + @echo "generating swagger doc..." + swag init -g ./cmd/dnsd/main.go -o ./docs + +#### Swagger doc #### + +## up: run dnsd and postgres +up: + export POSTGRES_USER=root; \ + export POSTGRES_PASSWORD=secret; \ + export POSTGRES_DB=dnsd-db; \ + DOCKER_BUILDKIT=0 docker-compose up -d --build + +#### Go lint #### + +## vet: run go vet on dnsd +vet: + @echo "go vet dnsd..." + go vet ./... + +#### Go lint #### + +#### Go mock #### + +## mock: generater mocks +mock: + mockery --name=ControllerdWrapper --structname=MockControllerdWrapper \ + --output=./ --outpkg=port --filename=controllerd_wrapper_mock.go --inpackage; \ + mockery --name=IpService --structname=MockIpService \ + --output=./ --outpkg=port --filename=ip_service_mock.go --inpackage; \ No newline at end of file From 0991e629f209dfb820f7d5146d3a97dc2ef6b808 Mon Sep 17 00:00:00 2001 From: sekulicd Date: Mon, 23 Oct 2023 13:16:25 +0200 Subject: [PATCH 03/39] api key service --- auth/Makefile | 129 ++++++++++ auth/go.mod | 14 +- auth/go.sum | 90 +++++++ .../core/application/api_key_service.go | 225 ++++++++++++++++++ .../core/application/api_key_service_test.go | 152 ++++++++++++ auth/internal/core/application/errors.go | 7 + auth/internal/core/application/types.go | 8 + auth/internal/core/domain/api_key.go | 77 ++++++ .../core/domain/api_key_repository.go | 16 ++ .../core/domain/api_key_repository_mock.go | 134 +++++++++++ dns/Makefile | 1 + 11 files changed, 851 insertions(+), 2 deletions(-) create mode 100644 auth/Makefile create mode 100644 auth/internal/core/application/api_key_service.go create mode 100644 auth/internal/core/application/api_key_service_test.go create mode 100644 auth/internal/core/application/errors.go create mode 100644 auth/internal/core/application/types.go create mode 100644 auth/internal/core/domain/api_key.go create mode 100644 auth/internal/core/domain/api_key_repository.go create mode 100644 auth/internal/core/domain/api_key_repository_mock.go diff --git a/auth/Makefile b/auth/Makefile new file mode 100644 index 0000000..8b21b85 --- /dev/null +++ b/auth/Makefile @@ -0,0 +1,129 @@ +PONY: build run pg droppg createdb dropdb createtestdb droptestdb recreatedb recreatetestdb pgcreatetestdb psql mig_file mig_up_test mig_up mig_down_test mig_down mig_down_yes vet_db sqlc doc up vet + +## build authd +build: + @echo "Building authd..." + @export GO111MODULE=on; \ + env go build -tags netgo -ldflags="-s -w" -o bin/authd ./cmd/authd/main.go + +## run authd +run: + @echo "Running dns service..." + ./bin/authd + +#### Postgres database #### + +## pg: starts postgres db inside docker container +pg: + docker run --name authd-db-pg -p 5432:5432 -e POSTGRES_USER=root -e POSTGRES_PASSWORD=secret -d postgres + +## droppg: stop and remove postgres container +droppg: + docker stop authd-db-pg + docker rm authd-db-pg + +## createdb: create db inside docker container +createdb: + docker exec authd-db-pg createdb --username=root --owner=root authd-db + +## dropdb: drops db inside docker container +dropdb: + docker exec authd-db-pg dropdb authd-db + +## createtestdb: create test db inside docker container +createtestdb: + docker exec authd-db-pg createdb --username=root --owner=root authd-db-test + +## droptestdb: drops test db inside docker container +droptestdb: + docker exec authd-db-pg dropdb authd-db-test + +## recreatedb: drop and create main and test db +recreatedb: dropdb createdb droptestdb createtestdb + +## recreatetestdb: drop and create test db +recreatetestdb: droptestdb createtestdb + +## pgcreatetestdb: starts docker container and creates test db, used in CI +pgcreatetestdb: + chmod u+x ./script/create_testdb + ./script/create_testdb + +## psql: connects to postgres terminal running inside docker container +psql: + docker exec -it authd-db-pg psql -U root -d authd-db + + +## mig_file: creates pg migration file(eg. make FILE=init mig_file) +mig_file: + @migrate create -ext sql -dir ./internal/infrastructure/storage/pg/migration/ $(FILE) + +## mig_up_test: creates test db schema +mig_up_test: + @echo "creating db schema..." + @migrate -database "postgres://root:secret@localhost:5432/authd-db-test?sslmode=disable" -path ./internal/infrastructure/storage/pg/migration/ up + +## mig_up: creates db schema +mig_up: + @echo "creating db schema..." + @migrate -database "postgres://root:secret@localhost:5432/authd-db?sslmode=disable" -path ./internal/infrastructure/storage/pg/migration/ up + +## mig_down_test: apply down migration on test db +mig_down_test: + @echo "migration down on test db..." + @migrate -database "postgres://root:secret@localhost:5432/authd-db-test?sslmode=disable" -path ./internal/infrastructure/storage/pg/migration/ down + +## mig_down: apply down migration +mig_down: + @echo "migration down..." + @migrate -database "postgres://root:secret@localhost:5432/authd-db?sslmode=disable" -path ./internal/infrastructure/storage/pg/migration/ down + +## mig_down_yes: apply down migration without prompt +mig_down_yes: + @echo "migration down..." + @"yes" | migrate -database "postgres://root:secret@localhost:5432/authd-db?sslmode=disable" -path ./internal/infrastructure/storage/pg/migration/ down + +## vet_db: check if mig_up and mig_down are ok +vet_db: recreatedb mig_up mig_down_yes + @echo "vet db migration scripts..." + +## sqlc: gen sql +sqlc: + @echo "gen sql..." + cd ./internal/infrastructure/storage/pg; sqlc generate + +#### Postgres database #### + + +#### Swagger doc #### + +## doc: generate swagger doc +doc: + @echo "generating swagger doc..." + swag init -g ./cmd/authd/main.go -o ./docs + +#### Swagger doc #### + +## up: run authd and postgres +up: + export POSTGRES_USER=root; \ + export POSTGRES_PASSWORD=secret; \ + export POSTGRES_DB=authd-db; \ + DOCKER_BUILDKIT=0 docker-compose up -d --build + +#### Go lint #### + +## vet: run go vet on authd +vet: + @echo "go vet authd..." + go vet ./... + +#### Go lint #### + +#### Go mock #### + +## mock: generater mocks +mock: + cd ./internal/core/domain/; \ + mockery --name=ApiKeyRepository --structname=MockApiKeyRepository \ + --output=./ --outpkg=port --filename=api_key_repository_mock.go --inpackage; \ No newline at end of file diff --git a/auth/go.mod b/auth/go.mod index 8dddc0a..03b1ee6 100644 --- a/auth/go.mod +++ b/auth/go.mod @@ -2,6 +2,16 @@ module prem-gateway/auth go 1.20 -require github.com/sirupsen/logrus v1.9.3 +require ( + github.com/btcsuite/btcd/btcutil v1.1.3 + github.com/sirupsen/logrus v1.9.3 + github.com/stretchr/testify v1.7.0 +) -require golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/objx v0.1.0 // indirect + golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect + gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect +) diff --git a/auth/go.sum b/auth/go.sum index 21f9bfb..8c00312 100644 --- a/auth/go.sum +++ b/auth/go.sum @@ -1,15 +1,105 @@ +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= +github.com/btcsuite/btcd v0.23.0/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY= +github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= +github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= +github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= +github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= +github.com/btcsuite/btcd/btcutil v1.1.3 h1:xfbtw8lwpp0G6NwSHb+UE67ryTFHJAiNuipusjXSohQ= +github.com/btcsuite/btcd/btcutil v1.1.3/go.mod h1:UR7dsSJzJUfMmFiiLlIrMq1lS9jh9EdCV7FStZSnpi0= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +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/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/auth/internal/core/application/api_key_service.go b/auth/internal/core/application/api_key_service.go new file mode 100644 index 0000000..c6aace7 --- /dev/null +++ b/auth/internal/core/application/api_key_service.go @@ -0,0 +1,225 @@ +package application + +import ( + "context" + log "github.com/sirupsen/logrus" + "prem-gateway/auth/internal/core/domain" + "sync" + "time" +) + +// ApiKeyService defines the interface for managing API keys. +type ApiKeyService interface { + // CreateApiKey creates a new API key. + CreateApiKey(ctx context.Context, key CreateApiKeyReq) (string, error) + // AllowRequest checks if a given API key is allowed to access a specified path. + AllowRequest(apiKey string, path string) bool + // GetServiceApiKey fetches the API key for a specific service. + GetServiceApiKey(ctx context.Context, service string) (string, error) +} + +// NewApiKeyService constructs a new instance of the ApiKeyService. +func NewApiKeyService( + ctx context.Context, + apiKeyRepository domain.ApiKeyRepository, +) (ApiKeyService, error) { + keysDb := make(map[string]apiKeyInfo) + keys, err := apiKeyRepository.GetAllApiKeys(ctx) // Fetch all existing API keys from the repository. + if err != nil { + return nil, err + } + + for _, key := range keys { + keysDb[key.ID] = newApiKeyInfo(key) // Populate the in-memory cache with the fetched keys. + } + + return &apiKeyService{ + apiKeyRepository: apiKeyRepository, + keysDb: keysDb, + }, nil +} + +// apiKeyService is the concrete implementation of the ApiKeyService interface. +type apiKeyService struct { + apiKeyRepository domain.ApiKeyRepository // Repository for interacting with the API key datastore. + + keysMtx sync.RWMutex // Mutex for safe concurrent access to the `keysDb` map. + keysDb map[string]apiKeyInfo // In-memory cache of API keys for fast lookup. + + rootKeyMtx sync.RWMutex // Mutex for safe concurrent access to the `rootApiKey` field. + rootApiKey string // The root API key with unrestricted access. +} + +// CreateApiKey creates a new API key and saves it in the datastore. +func (a *apiKeyService) CreateApiKey( + ctx context.Context, key CreateApiKeyReq, +) (string, error) { + if key.IsRootKey { + if a.rootKeyExists() { + return "", ErrRootKeyExists // Ensure only one root key exists. + } + } + + // Construct a new API key domain object. + apiKey, err := domain.NewApiKey( + key.IsRootKey, + key.Services, + domain.RateLimit{ + RequestsPerRange: key.RequestsPerRange, + RangeInSeconds: key.RangeInSeconds, + }, + ) + if err != nil { + return "", err + } + + // Save the new API key to the repository. + if err = a.apiKeyRepository.CreateApiKey(ctx, *apiKey); err != nil { + return "", err + } + + a.insertKey(newApiKeyInfo(*apiKey)) // Cache the new key for quick lookup. + + log.Debugf("Created new API key %s", apiKey.ID) + + return apiKey.ID, nil +} + +// AllowRequest checks if a given API key is allowed to access a specified path. +func (a *apiKeyService) AllowRequest(apiKey string, path string) bool { + // Check if the key exists and if it's allowed to access the given path. + key, exists := a.getKey(apiKey) + if !exists || !key.canAccessServicePath(path) { + log.Debugf("Api key %s is not allowed to access %s", apiKey, path) + + return false + } + + if key.isRootKey { + return true + } + + // Check if the key has exceeded its rate limit. + isRateLimited, aki := key.isRateLimited(time.Now()) + a.updateKey(aki) + + if isRateLimited { + log.Debugf("Api key %s has exceeded its rate limit", apiKey) + } + log.Debugf("Api key %s is allowed to access %s", apiKey, path) + + return !isRateLimited +} + +// GetServiceApiKey retrieves the API key associated with a specific service. +func (a *apiKeyService) GetServiceApiKey( + ctx context.Context, service string, +) (string, error) { + apiKey, err := a.apiKeyRepository.GetServiceApiKey(ctx, service) + if err != nil { + return "", err + } + + return apiKey.ID, nil +} + +// Below are helper methods for the apiKeyService. + +func (a *apiKeyService) insertKey(key apiKeyInfo) { + a.keysMtx.Lock() + defer a.keysMtx.Unlock() + + a.keysDb[key.id] = key +} + +func (a *apiKeyService) getKey(key string) (apiKeyInfo, bool) { + a.keysMtx.RLock() + defer a.keysMtx.RUnlock() + + keyInfo, exists := a.keysDb[key] + return keyInfo, exists +} + +func (a *apiKeyService) isRootKey(key string) bool { + a.rootKeyMtx.RLock() + defer a.rootKeyMtx.RUnlock() + + return a.rootApiKey == key +} + +func (a *apiKeyService) rootKeyExists() bool { + a.rootKeyMtx.RLock() + defer a.rootKeyMtx.RUnlock() + + return a.rootApiKey != "" +} + +func (a *apiKeyService) updateKey(key apiKeyInfo) { + a.keysMtx.Lock() + defer a.keysMtx.Unlock() + + a.keysDb[key.id] = key +} + +// apiKeyInfo represents detailed information about an API key. +type apiKeyInfo struct { + id string // Unique identifier of the API key. + allowedEndpoints map[string]struct{} // List of services or paths the API key has access to. + firstRequestInRange *time.Time // Timestamp of the first request made within the current rate limit range. + requestsPerRange int // Max number of requests allowed within the rate limit range. + rangeInSeconds int // Duration of the rate limit range in seconds. + requestCount int // Number of requests made within the current rate limit range. + isRootKey bool // Flag indicating if the API key is a root key with unrestricted access. +} + +// Construct a new apiKeyInfo from a domain API key. +func newApiKeyInfo(apiKey domain.ApiKey) apiKeyInfo { + allowedEndpoints := make(map[string]struct{}) + for _, endpoint := range apiKey.Services { + allowedEndpoints[endpoint] = struct{}{} + } + + return apiKeyInfo{ + id: apiKey.ID, + allowedEndpoints: allowedEndpoints, + firstRequestInRange: nil, + requestsPerRange: apiKey.RateLimit.RequestsPerRange, + rangeInSeconds: apiKey.RateLimit.RangeInSeconds, + requestCount: 0, + isRootKey: apiKey.IsRoot, + } +} + +// Check if the API key is allowed to access a given service or path. +func (ak apiKeyInfo) canAccessServicePath(servicePath string) bool { + _, exists := ak.allowedEndpoints[servicePath] + + return exists +} + +// Determine if the API key has exceeded its rate limit. +func (ak apiKeyInfo) isRateLimited(now time.Time) (bool, apiKeyInfo) { + aki := ak + + // If it's the first request in the rate limit range + if ak.firstRequestInRange == nil { + aki.firstRequestInRange = &now + } + + // Check if the current request is outside the rate limit range, if so reset the count and timestamp + if now.Sub(*aki.firstRequestInRange).Seconds() >= float64(aki.rangeInSeconds) { + aki.firstRequestInRange = &now + aki.requestCount = 1 + + return false, aki + } + + aki.requestCount++ + + // If the key has already reached its rate limit, just return true without incrementing the count + if aki.requestCount > aki.requestsPerRange { + return true, aki + } + + return false, aki +} diff --git a/auth/internal/core/application/api_key_service_test.go b/auth/internal/core/application/api_key_service_test.go new file mode 100644 index 0000000..1dead7b --- /dev/null +++ b/auth/internal/core/application/api_key_service_test.go @@ -0,0 +1,152 @@ +package application + +import ( + "context" + "prem-gateway/auth/internal/core/domain" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +var ( + ctx = context.Background() +) + +func TestCreateApiKey(t *testing.T) { + repo := new(domain.MockApiKeyRepository) + repo.On("GetAllApiKeys", mock.Anything).Return(nil, nil) + + service, _ := NewApiKeyService(ctx, repo) + + repo.On("CreateApiKey", mock.Anything, mock.Anything).Return(nil) + + keyReq := CreateApiKeyReq{ + IsRootKey: false, + Services: []string{"service1"}, + RequestsPerRange: 5, + RangeInSeconds: 10, + } + + id, err := service.CreateApiKey(context.Background(), keyReq) + + assert.NotNil(t, id) + assert.Nil(t, err) +} + +func TestAllowRequest(t *testing.T) { + repo := new(domain.MockApiKeyRepository) + keys := []domain.ApiKey{ + { + ID: "test-key", + Services: []string{"test-service"}, + IsRoot: false, + RateLimit: &domain.RateLimit{ + RequestsPerRange: 5, + RangeInSeconds: 10, + }, + }, + } + repo.On("GetAllApiKeys", mock.Anything).Return(keys, nil) + + service, _ := NewApiKeyService(ctx, repo) + + // Valid key and path + assert.True(t, service.AllowRequest("test-key", "test-service")) + + // Invalid key + assert.False(t, service.AllowRequest("invalid-key", "test-service")) + + // Invalid path for a valid key + assert.False(t, service.AllowRequest("test-key", "invalid-service")) +} + +func TestGetServiceApiKey(t *testing.T) { + repo := new(domain.MockApiKeyRepository) + repo.On("GetAllApiKeys", mock.Anything).Return(nil, nil) + service, _ := NewApiKeyService(ctx, repo) + testServiceKey := &domain.ApiKey{ID: "service-key"} + + repo.On("GetServiceApiKey", mock.Anything, "test-service").Return(testServiceKey, nil) + + keyID, err := service.GetServiceApiKey(context.Background(), "test-service") + + assert.Equal(t, "service-key", keyID) + assert.Nil(t, err) +} + +func TestRateLimit(t *testing.T) { + repo := new(domain.MockApiKeyRepository) + keys := []domain.ApiKey{ + { + ID: "rate-limit-key", + Services: []string{"test-service"}, + IsRoot: false, + RateLimit: &domain.RateLimit{ + RequestsPerRange: 2, + RangeInSeconds: 5, + }, + }, + } + repo.On("GetAllApiKeys", mock.Anything).Return(keys, nil) + + service, _ := NewApiKeyService(ctx, repo) + + assert.True(t, service.AllowRequest("rate-limit-key", "test-service")) + assert.True(t, service.AllowRequest("rate-limit-key", "test-service")) + // Exceeding the rate limit + assert.False(t, service.AllowRequest("rate-limit-key", "test-service")) + + time.Sleep(6 * time.Second) + // Rate limit should reset after the range + assert.True(t, service.AllowRequest("rate-limit-key", "test-service")) +} + +func TestRequestCount(t *testing.T) { + repo := new(domain.MockApiKeyRepository) + repo.On("GetAllApiKeys", mock.Anything).Return(nil, nil) + repo.On("CreateApiKey", mock.Anything, mock.Anything).Return(nil) + + service, err := NewApiKeyService(ctx, repo) + if err != nil { + t.Fatalf("Error initializing the service: %v", err) + } + + // Create a new API key + keyReq := CreateApiKeyReq{ + IsRootKey: false, + Services: []string{"test"}, + RequestsPerRange: 5, + RangeInSeconds: 3, + } + apiKey, err := service.CreateApiKey(context.Background(), keyReq) + assert.NoError(t, err, "Error creating API key") + + // Use the key to its limit + for i := 0; i < 5; i++ { + assert.True(t, service.AllowRequest(apiKey, "test"), "Expected request to be allowed") + } + + // This request should be denied, as it exceeds the limit + assert.False(t, service.AllowRequest(apiKey, "test"), "Expected request to be denied") + + // Fetch the key to check the request count + keyInfo, exists := service.(*apiKeyService).getKey(apiKey) + assert.True(t, exists, "API key should exist") + assert.Equal(t, 6, keyInfo.requestCount, "Request count should not increment after limit") + + time.Sleep(3 * time.Second) + + // Now it should allow requests again + assert.True(t, service.AllowRequest(apiKey, "test"), "Expected request to be allowed after rate limit reset") + keyInfo, exists = service.(*apiKeyService).getKey(apiKey) + assert.True(t, exists, "API key should exist") + assert.Equal(t, 1, keyInfo.requestCount, "Request count should not increment after limit") + + for i := 0; i < 4; i++ { + assert.True(t, service.AllowRequest(apiKey, "test"), "Expected request to be allowed") + } + + assert.False(t, service.AllowRequest(apiKey, "test"), "Expected request to be denied") +} diff --git a/auth/internal/core/application/errors.go b/auth/internal/core/application/errors.go new file mode 100644 index 0000000..66bc5a7 --- /dev/null +++ b/auth/internal/core/application/errors.go @@ -0,0 +1,7 @@ +package application + +import "errors" + +var ( + ErrRootKeyExists = errors.New("root key already exists") +) diff --git a/auth/internal/core/application/types.go b/auth/internal/core/application/types.go new file mode 100644 index 0000000..4e412d4 --- /dev/null +++ b/auth/internal/core/application/types.go @@ -0,0 +1,8 @@ +package application + +type CreateApiKeyReq struct { + IsRootKey bool + Services []string + RequestsPerRange int + RangeInSeconds int +} diff --git a/auth/internal/core/domain/api_key.go b/auth/internal/core/domain/api_key.go new file mode 100644 index 0000000..3db07ee --- /dev/null +++ b/auth/internal/core/domain/api_key.go @@ -0,0 +1,77 @@ +package domain + +import ( + "crypto/rand" + "fmt" + "github.com/btcsuite/btcd/btcutil/base58" +) + +const ( + byteLength = 16 +) + +// ApiKey represents an identification token used to authenticate and +// authorize specific endpoints with rate limit constraints. +type ApiKey struct { + ID string // ID is a unique identifier for the API key. + Services []string // Services lists the services that this API key can access. + RateLimit *RateLimit // RateLimit defines the request constraints over a specific time range for this API key. + IsRoot bool // IsRoot specifies whether this API key is a root key. Root key can access all endpoints. +} + +// RateLimit defines the number of requests allowed in a specific time range. +type RateLimit struct { + RequestsPerRange int // RequestsPerRange is the maximum number of requests allowed within the specified time range. + RangeInSeconds int // RangeInSeconds specifies the duration of the time range, in seconds, for rate limiting. +} + +func NewApiKey( + isRootKey bool, services []string, limit RateLimit, +) (*ApiKey, error) { + key, err := genKey() + if err != nil { + return nil, err + } + + rLimit := new(RateLimit) + if limit.RequestsPerRange > 0 { + if isRootKey { + return nil, fmt.Errorf("root keys cannot have rate limits") + } + + if limit.RangeInSeconds <= 0 { + return nil, fmt.Errorf("invalid rate limit range") + } + + rLimit.RequestsPerRange = limit.RequestsPerRange + rLimit.RangeInSeconds = limit.RangeInSeconds + } + + if len(services) > 0 { + if isRootKey { + return nil, fmt.Errorf( + "root keys cannot have endpoints constraints", + ) + } + } + + apiKey := &ApiKey{ + ID: key, + Services: services, + RateLimit: rLimit, + IsRoot: isRootKey, + } + + return apiKey, nil +} + +func genKey() (string, error) { + random := make([]byte, byteLength) + _, err := rand.Read(random) + if err != nil { + return "", fmt.Errorf("unable to read random data") + } + + key := base58.Encode(random) + return key, nil +} diff --git a/auth/internal/core/domain/api_key_repository.go b/auth/internal/core/domain/api_key_repository.go new file mode 100644 index 0000000..7b3abe1 --- /dev/null +++ b/auth/internal/core/domain/api_key_repository.go @@ -0,0 +1,16 @@ +package domain + +import "context" + +type ApiKeyRepository interface { + // CreateApiKey creates a new API key. + CreateApiKey(ctx context.Context, key ApiKey) error + // GetApiKey returns the API key with the given ID. + GetApiKey(ctx context.Context, id string) (*ApiKey, error) + // DeleteApiKey deletes the API key with the given ID. + DeleteApiKey(ctx context.Context, id string) error + // GetAllApiKeys returns all API keys. + GetAllApiKeys(ctx context.Context) ([]ApiKey, error) + // GetServiceApiKey returns the API key for the given service. + GetServiceApiKey(ctx context.Context, service string) (*ApiKey, error) +} diff --git a/auth/internal/core/domain/api_key_repository_mock.go b/auth/internal/core/domain/api_key_repository_mock.go new file mode 100644 index 0000000..57a021c --- /dev/null +++ b/auth/internal/core/domain/api_key_repository_mock.go @@ -0,0 +1,134 @@ +// Code generated by mockery v2.33.1. DO NOT EDIT. + +package domain + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" +) + +// MockApiKeyRepository is an autogenerated mock type for the ApiKeyRepository type +type MockApiKeyRepository struct { + mock.Mock +} + +// CreateApiKey provides a mock function with given fields: ctx, key +func (_m *MockApiKeyRepository) CreateApiKey(ctx context.Context, key ApiKey) error { + ret := _m.Called(ctx, key) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, ApiKey) error); ok { + r0 = rf(ctx, key) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// DeleteApiKey provides a mock function with given fields: ctx, id +func (_m *MockApiKeyRepository) DeleteApiKey(ctx context.Context, id string) error { + ret := _m.Called(ctx, id) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, id) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// GetAllApiKeys provides a mock function with given fields: ctx +func (_m *MockApiKeyRepository) GetAllApiKeys(ctx context.Context) ([]ApiKey, error) { + ret := _m.Called(ctx) + + var r0 []ApiKey + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) ([]ApiKey, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) []ApiKey); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]ApiKey) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetApiKey provides a mock function with given fields: ctx, id +func (_m *MockApiKeyRepository) GetApiKey(ctx context.Context, id string) (*ApiKey, error) { + ret := _m.Called(ctx, id) + + var r0 *ApiKey + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (*ApiKey, error)); ok { + return rf(ctx, id) + } + if rf, ok := ret.Get(0).(func(context.Context, string) *ApiKey); ok { + r0 = rf(ctx, id) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*ApiKey) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetServiceApiKey provides a mock function with given fields: ctx, service +func (_m *MockApiKeyRepository) GetServiceApiKey(ctx context.Context, service string) (*ApiKey, error) { + ret := _m.Called(ctx, service) + + var r0 *ApiKey + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (*ApiKey, error)); ok { + return rf(ctx, service) + } + if rf, ok := ret.Get(0).(func(context.Context, string) *ApiKey); ok { + r0 = rf(ctx, service) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*ApiKey) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, service) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewMockApiKeyRepository creates a new instance of MockApiKeyRepository. 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 NewMockApiKeyRepository(t interface { + mock.TestingT + Cleanup(func()) +}) *MockApiKeyRepository { + mock := &MockApiKeyRepository{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/dns/Makefile b/dns/Makefile index 58cc287..13d6aaa 100644 --- a/dns/Makefile +++ b/dns/Makefile @@ -124,6 +124,7 @@ vet: ## mock: generater mocks mock: + cd ./internal/core/port/; \ mockery --name=ControllerdWrapper --structname=MockControllerdWrapper \ --output=./ --outpkg=port --filename=controllerd_wrapper_mock.go --inpackage; \ mockery --name=IpService --structname=MockIpService \ From 61f2e3b249d4087c044bed5d03dcf333bfa0d509 Mon Sep 17 00:00:00 2001 From: sekulicd Date: Tue, 24 Oct 2023 11:50:49 +0200 Subject: [PATCH 04/39] authd db layer --- auth/internal/config/config.go | 82 +++++++++ auth/internal/core/domain/errors.go | 8 + .../core/domain/repository_service.go | 5 + .../core/domain/repository_service_mock.go | 40 +++++ .../storage/pg/api_key_repository_impl.go | 158 ++++++++++++++++++ .../infrastructure/storage/pg/db_service.go | 149 +++++++++++++++++ ...231023111842_create_api_key_table.down.sql | 3 + ...20231023111842_create_api_key_table.up.sql | 13 ++ .../infrastructure/storage/pg/sqlc.yaml | 8 + .../storage/pg/sqlc/queries/db.go | 32 ++++ .../storage/pg/sqlc/queries/models.go | 22 +++ .../storage/pg/sqlc/queries/query.sql.go | 129 ++++++++++++++ .../infrastructure/storage/pg/sqlc/query.sql | 16 ++ auth/test/db_util.go | 80 +++++++++ auth/test/pg/api_key_repository_test.go | 83 +++++++++ auth/test/pg/run_suite_test.go | 11 ++ auth/test/pg/test_setup.go | 55 ++++++ 17 files changed, 894 insertions(+) create mode 100644 auth/internal/config/config.go create mode 100644 auth/internal/core/domain/errors.go create mode 100644 auth/internal/core/domain/repository_service.go create mode 100644 auth/internal/core/domain/repository_service_mock.go create mode 100644 auth/internal/infrastructure/storage/pg/api_key_repository_impl.go create mode 100644 auth/internal/infrastructure/storage/pg/db_service.go create mode 100644 auth/internal/infrastructure/storage/pg/migration/20231023111842_create_api_key_table.down.sql create mode 100644 auth/internal/infrastructure/storage/pg/migration/20231023111842_create_api_key_table.up.sql create mode 100644 auth/internal/infrastructure/storage/pg/sqlc.yaml create mode 100644 auth/internal/infrastructure/storage/pg/sqlc/queries/db.go create mode 100644 auth/internal/infrastructure/storage/pg/sqlc/queries/models.go create mode 100644 auth/internal/infrastructure/storage/pg/sqlc/queries/query.sql.go create mode 100644 auth/internal/infrastructure/storage/pg/sqlc/query.sql create mode 100644 auth/test/db_util.go create mode 100644 auth/test/pg/api_key_repository_test.go create mode 100644 auth/test/pg/run_suite_test.go create mode 100644 auth/test/pg/test_setup.go diff --git a/auth/internal/config/config.go b/auth/internal/config/config.go new file mode 100644 index 0000000..40f2f64 --- /dev/null +++ b/auth/internal/config/config.go @@ -0,0 +1,82 @@ +package config + +import ( + "errors" + "github.com/btcsuite/btcd/btcutil" + log "github.com/sirupsen/logrus" + "github.com/spf13/viper" +) + +const ( + // PortKey is port on which server is running + PortKey = "PORT_PORT" + // LogLevelKey is log level used by dnsd + LogLevelKey = "LOG_LEVEL" + // DatadirKey is the local data directory to store the internal state of daemon + DatadirKey = "DATADIR" + // DbUserKey is the user name to connect to the database + DbUserKey = "DB_USER" + // DbPassKey is the password to connect to the database + DbPassKey = "DB_PASS" + // DbHostKey is the host address of the database + DbHostKey = "DB_HOST" + // DbPortKey is the port of the database + DbPortKey = "DB_PORT" + // DbNameKey is the name of the database + DbNameKey = "DB_NAME" + // DbMigrationPathKey is the path to the database migration files + DbMigrationPathKey = "DB_MIGRATION_PATH" + // RootApiKey is the root API key with unrestricted access + RootApiKey = "ROOT_API_KEY" + // AdminUserKey is the admin user name + AdminUserKey = "ADMIN_USER" + // AdminPassKey is the admin password + AdminPassKey = "ADMIN_PASS" +) + +var ( + vip *viper.Viper +) + +func LoadConfig() error { + vip = viper.New() + vip.SetEnvPrefix("PREM_GATEWAY_AUTH") + vip.AutomaticEnv() + defaultDataDir := btcutil.AppDataDir("authd", false) + + vip.SetDefault(PortKey, 8080) + vip.SetDefault(LogLevelKey, int(log.DebugLevel)) + vip.SetDefault(DatadirKey, defaultDataDir) + vip.SetDefault(DbUserKey, "root") + vip.SetDefault(DbPassKey, "secret") + vip.SetDefault(DbHostKey, "127.0.0.1") + vip.SetDefault(DbPortKey, 5432) + vip.SetDefault(DbNameKey, "authd-db") + vip.SetDefault(DbMigrationPathKey, "file://dns/internal/infrastructure/storage/pg/migration") + + if vip.GetString(RootApiKey) == "" { + return errors.New("root API key not set") + } + + if vip.GetString(AdminUserKey) == "" { + return errors.New("admin user not set") + } + + if vip.GetString(AdminPassKey) == "" { + return errors.New("admin password not set") + } + + return nil +} + +func GetString(key string) string { + return vip.GetString(key) +} + +func GetInt(key string) int { + return vip.GetInt(key) +} + +func GetServerAddress() string { + return ":" + GetString(PortKey) +} diff --git a/auth/internal/core/domain/errors.go b/auth/internal/core/domain/errors.go new file mode 100644 index 0000000..6a4a8f5 --- /dev/null +++ b/auth/internal/core/domain/errors.go @@ -0,0 +1,8 @@ +package domain + +import "errors" + +var ( + ErrApiKeyExistForService = errors.New("api key already exists for service") + ErrApiKeyNotFound = errors.New("api key not found") +) diff --git a/auth/internal/core/domain/repository_service.go b/auth/internal/core/domain/repository_service.go new file mode 100644 index 0000000..1f28539 --- /dev/null +++ b/auth/internal/core/domain/repository_service.go @@ -0,0 +1,5 @@ +package domain + +type RepositoryService interface { + ApiKeyRepository() ApiKeyRepository +} diff --git a/auth/internal/core/domain/repository_service_mock.go b/auth/internal/core/domain/repository_service_mock.go new file mode 100644 index 0000000..c1934be --- /dev/null +++ b/auth/internal/core/domain/repository_service_mock.go @@ -0,0 +1,40 @@ +// Code generated by mockery v2.33.1. DO NOT EDIT. + +package domain + +import mock "github.com/stretchr/testify/mock" + +// MockRepositoryService is an autogenerated mock type for the RepositoryService type +type MockRepositoryService struct { + mock.Mock +} + +// ApiKeyRepository provides a mock function with given fields: +func (_m *MockRepositoryService) ApiKeyRepository() ApiKeyRepository { + ret := _m.Called() + + var r0 ApiKeyRepository + if rf, ok := ret.Get(0).(func() ApiKeyRepository); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(ApiKeyRepository) + } + } + + return r0 +} + +// NewMockRepositoryService creates a new instance of MockRepositoryService. 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 NewMockRepositoryService(t interface { + mock.TestingT + Cleanup(func()) +}) *MockRepositoryService { + mock := &MockRepositoryService{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/auth/internal/infrastructure/storage/pg/api_key_repository_impl.go b/auth/internal/infrastructure/storage/pg/api_key_repository_impl.go new file mode 100644 index 0000000..b3e6dea --- /dev/null +++ b/auth/internal/infrastructure/storage/pg/api_key_repository_impl.go @@ -0,0 +1,158 @@ +package pgdb + +import ( + "context" + "database/sql" + "github.com/jackc/pgconn" + "prem-gateway/auth/internal/core/domain" + "prem-gateway/auth/internal/infrastructure/storage/pg/sqlc/queries" +) + +type apiKeyRepositoryImpl struct { + querier *queries.Queries + execTx func( + ctx context.Context, + txBody func(*queries.Queries) error, + ) error +} + +func NewIdentityRepositoryImpl( + querier *queries.Queries, + execTx func(ctx context.Context, txBody func(*queries.Queries) error) error, +) domain.ApiKeyRepository { + return &apiKeyRepositoryImpl{ + querier: querier, + execTx: execTx, + } +} + +func (a *apiKeyRepositoryImpl) CreateApiKey( + ctx context.Context, key domain.ApiKey, +) error { + txBody := func(querierWithTx *queries.Queries) error { + rateLimitID := sql.NullInt32{} + if key.RateLimit != nil { + id, err := querierWithTx.InsertRateLimitAndReturnID( + ctx, + queries.InsertRateLimitAndReturnIDParams{ + RequestsPerRange: sql.NullInt32{ + Int32: int32(key.RateLimit.RequestsPerRange), + Valid: true, + }, + RangeInSeconds: sql.NullInt32{ + Int32: int32(key.RateLimit.RangeInSeconds), + Valid: true, + }, + }, + ) + if err != nil { + return err + } + + rateLimitID.Int32 = id + rateLimitID.Valid = true + } + + if err := querierWithTx.InsertApiKey( + ctx, + queries.InsertApiKeyParams{ + ID: key.ID, + IsRoot: sql.NullBool{ + Bool: key.IsRoot, + Valid: true, + }, + RateLimitID: rateLimitID, + ServiceName: sql.NullString{ + String: key.Service, + Valid: true, + }, + }, + ); err != nil { + if pqErr, ok := err.(*pgconn.PgError); pqErr != nil && + ok && pqErr.Code == uniqueViolation { + return domain.ErrApiKeyExistForService + } + } + + return nil + } + + return a.execTx(ctx, txBody) +} + +func (a *apiKeyRepositoryImpl) GetApiKey( + ctx context.Context, id string, +) (*domain.ApiKey, error) { + //TODO implement me + panic("implement me") +} + +func (a *apiKeyRepositoryImpl) DeleteApiKey( + ctx context.Context, id string, +) error { + //TODO implement me + panic("implement me") +} + +func (a *apiKeyRepositoryImpl) GetAllApiKeys(ctx context.Context) ([]domain.ApiKey, error) { + apiKeysRows, err := a.querier.GetAllApiKeys(ctx) + if err != nil { + return nil, err + } + + var resp []*domain.ApiKey + apiKeyMap := make(map[string]*domain.ApiKey) + + for _, row := range apiKeysRows { + apk, exists := apiKeyMap[row.ID] + if !exists { + apk = &domain.ApiKey{ + ID: row.ID, + IsRoot: row.IsRoot.Bool, + } + if !row.IsRoot.Bool { + apk.RateLimit = &domain.RateLimit{ + RequestsPerRange: int(row.RequestsPerRange.Int32), + RangeInSeconds: int(row.RangeInSeconds.Int32), + } + + apk.Service = row.ServiceName.String + } + apiKeyMap[row.ID] = apk + resp = append(resp, apk) + } + } + + result := make([]domain.ApiKey, len(resp)) + for i, apkPtr := range resp { + result[i] = *apkPtr + } + + return result, nil +} + +func (a *apiKeyRepositoryImpl) GetServiceApiKey( + ctx context.Context, service string, +) (*domain.ApiKey, error) { + apiKeysRows, err := a.querier.GetApiKeyForServiceName(ctx, sql.NullString{ + String: service, + Valid: true, + }) + if err != nil { + return nil, err + } + + if len(apiKeysRows) == 0 { + return nil, domain.ErrApiKeyNotFound + } + + return &domain.ApiKey{ + ID: apiKeysRows[0].ID, + IsRoot: apiKeysRows[0].IsRoot.Bool, + Service: apiKeysRows[0].ServiceName.String, + RateLimit: &domain.RateLimit{ + RequestsPerRange: int(apiKeysRows[0].RequestsPerRange.Int32), + RangeInSeconds: int(apiKeysRows[0].RangeInSeconds.Int32), + }, + }, nil +} diff --git a/auth/internal/infrastructure/storage/pg/db_service.go b/auth/internal/infrastructure/storage/pg/db_service.go new file mode 100644 index 0000000..424fe2a --- /dev/null +++ b/auth/internal/infrastructure/storage/pg/db_service.go @@ -0,0 +1,149 @@ +package pgdb + +import ( + "context" + "errors" + "fmt" + "github.com/golang-migrate/migrate/v4" + "github.com/golang-migrate/migrate/v4/database/postgres" + "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v4/pgxpool" + log "github.com/sirupsen/logrus" + "prem-gateway/auth/internal/core/domain" + "prem-gateway/auth/internal/infrastructure/storage/pg/sqlc/queries" + + _ "github.com/golang-migrate/migrate/v4/source/file" +) + +const ( + postgresDriver = "pgx" + insecureDataSourceTemplate = "postgresql://%s:%s@%s:%d/%s?sslmode=disable" + + uniqueViolation = "23505" + pgxNoRows = "no rows in result set" +) + +type repoService struct { + pgxPool *pgxpool.Pool + querier *queries.Queries + + apiKeyRepository domain.ApiKeyRepository +} + +func NewRepoService(dbConfig DbConfig) (domain.RepositoryService, error) { + dataSource := insecureDataSourceStr(dbConfig) + + pgxPool, err := connect(dataSource) + if err != nil { + return nil, err + } + + if err = migrateDb(dataSource, dbConfig.MigrationSourceURL); err != nil { + return nil, err + } + + rm := &repoService{ + pgxPool: pgxPool, + querier: queries.New(pgxPool), + } + + apiKeyRepository := NewIdentityRepositoryImpl(rm.querier, rm.execTx) + rm.apiKeyRepository = apiKeyRepository + + return rm, nil +} + +func (s *repoService) ApiKeyRepository() domain.ApiKeyRepository { + return s.apiKeyRepository +} + +func (s *repoService) Close() { + s.pgxPool.Close() +} + +func (s *repoService) execTx( + ctx context.Context, + txBody func(*queries.Queries) error, +) error { + conn, err := s.pgxPool.Acquire(ctx) + if err != nil { + return err + } + defer conn.Release() + + tx, err := conn.Begin(ctx) + if err != nil { + return err + } + + // Rollback is safe to call even if the tx is already closed, so if + // the tx commits successfully, this is a no-op. + defer func() { + err := tx.Rollback(ctx) + switch { + // If the tx was already closed (it was successfully executed) + // we do not need to log that error. + case errors.Is(err, pgx.ErrTxClosed): + return + + // If this is an unexpected error, log it. + case err != nil: + log.Errorf("unable to rollback db tx: %v", err) + } + }() + + if err := txBody(s.querier.WithTx(tx)); err != nil { + return err + } + + // Commit transaction. + return tx.Commit(ctx) +} + +type DbConfig struct { + DbUser string + DbPassword string + DbHost string + DbPort int + DbName string + MigrationSourceURL string +} + +func connect(dataSource string) (*pgxpool.Pool, error) { + return pgxpool.Connect(context.Background(), dataSource) +} + +func migrateDb(dataSource, migrationSourceUrl string) error { + pg := postgres.Postgres{} + + d, err := pg.Open(dataSource) + if err != nil { + return err + } + + m, err := migrate.NewWithDatabaseInstance( + migrationSourceUrl, + postgresDriver, + d, + ) + if err != nil { + return err + } + + if err := m.Up(); err != nil && err != migrate.ErrNoChange { + return err + } + + return nil +} + +func insecureDataSourceStr(dbConfig DbConfig) string { + return fmt.Sprintf( + insecureDataSourceTemplate, + dbConfig.DbUser, + dbConfig.DbPassword, + dbConfig.DbHost, + dbConfig.DbPort, + dbConfig.DbName, + ) +} diff --git a/auth/internal/infrastructure/storage/pg/migration/20231023111842_create_api_key_table.down.sql b/auth/internal/infrastructure/storage/pg/migration/20231023111842_create_api_key_table.down.sql new file mode 100644 index 0000000..11dc077 --- /dev/null +++ b/auth/internal/infrastructure/storage/pg/migration/20231023111842_create_api_key_table.down.sql @@ -0,0 +1,3 @@ +DROP TABLE IF EXISTS api_key; + +DROP TABLE IF EXISTS rate_limit; \ No newline at end of file diff --git a/auth/internal/infrastructure/storage/pg/migration/20231023111842_create_api_key_table.up.sql b/auth/internal/infrastructure/storage/pg/migration/20231023111842_create_api_key_table.up.sql new file mode 100644 index 0000000..e772b82 --- /dev/null +++ b/auth/internal/infrastructure/storage/pg/migration/20231023111842_create_api_key_table.up.sql @@ -0,0 +1,13 @@ +CREATE TABLE rate_limit ( + id SERIAL PRIMARY KEY, + requests_per_range INT, + range_in_seconds INT +); + +CREATE TABLE api_key ( + id VARCHAR(255) PRIMARY KEY, + is_root BOOLEAN, + rate_limit_id INT, + service_name VARCHAR(255) UNIQUE, + FOREIGN KEY (rate_limit_id) REFERENCES rate_limit(id) +); \ No newline at end of file diff --git a/auth/internal/infrastructure/storage/pg/sqlc.yaml b/auth/internal/infrastructure/storage/pg/sqlc.yaml new file mode 100644 index 0000000..e667b10 --- /dev/null +++ b/auth/internal/infrastructure/storage/pg/sqlc.yaml @@ -0,0 +1,8 @@ +version: 1 +packages: + - path: "sqlc/queries" + name: "queries" + engine: "postgresql" + schema: "migration" + queries: "sqlc/query.sql" + sql_package: "pgx/v4" \ No newline at end of file diff --git a/auth/internal/infrastructure/storage/pg/sqlc/queries/db.go b/auth/internal/infrastructure/storage/pg/sqlc/queries/db.go new file mode 100644 index 0000000..d5fc243 --- /dev/null +++ b/auth/internal/infrastructure/storage/pg/sqlc/queries/db.go @@ -0,0 +1,32 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.16.0 + +package queries + +import ( + "context" + + "github.com/jackc/pgconn" + "github.com/jackc/pgx/v4" +) + +type DBTX interface { + Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) + Query(context.Context, string, ...interface{}) (pgx.Rows, error) + QueryRow(context.Context, string, ...interface{}) pgx.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx pgx.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/auth/internal/infrastructure/storage/pg/sqlc/queries/models.go b/auth/internal/infrastructure/storage/pg/sqlc/queries/models.go new file mode 100644 index 0000000..6aae46c --- /dev/null +++ b/auth/internal/infrastructure/storage/pg/sqlc/queries/models.go @@ -0,0 +1,22 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.16.0 + +package queries + +import ( + "database/sql" +) + +type ApiKey struct { + ID string + IsRoot sql.NullBool + RateLimitID sql.NullInt32 + ServiceName sql.NullString +} + +type RateLimit struct { + ID int32 + RequestsPerRange sql.NullInt32 + RangeInSeconds sql.NullInt32 +} diff --git a/auth/internal/infrastructure/storage/pg/sqlc/queries/query.sql.go b/auth/internal/infrastructure/storage/pg/sqlc/queries/query.sql.go new file mode 100644 index 0000000..fb1e92d --- /dev/null +++ b/auth/internal/infrastructure/storage/pg/sqlc/queries/query.sql.go @@ -0,0 +1,129 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.16.0 +// source: query.sql + +package queries + +import ( + "context" + "database/sql" +) + +const getAllApiKeys = `-- name: GetAllApiKeys :many +SELECT a.id, a.is_root, a.service_name, r.requests_per_range, r.range_in_seconds +FROM api_key a + left join rate_limit r on a.rate_limit_id = r.id ORDER BY a.id +` + +type GetAllApiKeysRow struct { + ID string + IsRoot sql.NullBool + ServiceName sql.NullString + RequestsPerRange sql.NullInt32 + RangeInSeconds sql.NullInt32 +} + +func (q *Queries) GetAllApiKeys(ctx context.Context) ([]GetAllApiKeysRow, error) { + rows, err := q.db.Query(ctx, getAllApiKeys) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetAllApiKeysRow + for rows.Next() { + var i GetAllApiKeysRow + if err := rows.Scan( + &i.ID, + &i.IsRoot, + &i.ServiceName, + &i.RequestsPerRange, + &i.RangeInSeconds, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getApiKeyForServiceName = `-- name: GetApiKeyForServiceName :many +SELECT a.id, a.is_root, a.service_name, r.requests_per_range, r.range_in_seconds +FROM api_key a + inner join rate_limit r on a.rate_limit_id = r.id +WHERE a.service_name = $1 ORDER BY a.id +` + +type GetApiKeyForServiceNameRow struct { + ID string + IsRoot sql.NullBool + ServiceName sql.NullString + RequestsPerRange sql.NullInt32 + RangeInSeconds sql.NullInt32 +} + +func (q *Queries) GetApiKeyForServiceName(ctx context.Context, serviceName sql.NullString) ([]GetApiKeyForServiceNameRow, error) { + rows, err := q.db.Query(ctx, getApiKeyForServiceName, serviceName) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetApiKeyForServiceNameRow + for rows.Next() { + var i GetApiKeyForServiceNameRow + if err := rows.Scan( + &i.ID, + &i.IsRoot, + &i.ServiceName, + &i.RequestsPerRange, + &i.RangeInSeconds, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const insertApiKey = `-- name: InsertApiKey :exec +INSERT INTO api_key (id, is_root, rate_limit_id, service_name) VALUES ($1, $2, $3, $4) +` + +type InsertApiKeyParams struct { + ID string + IsRoot sql.NullBool + RateLimitID sql.NullInt32 + ServiceName sql.NullString +} + +func (q *Queries) InsertApiKey(ctx context.Context, arg InsertApiKeyParams) error { + _, err := q.db.Exec(ctx, insertApiKey, + arg.ID, + arg.IsRoot, + arg.RateLimitID, + arg.ServiceName, + ) + return err +} + +const insertRateLimitAndReturnID = `-- name: InsertRateLimitAndReturnID :one +INSERT INTO rate_limit (requests_per_range, range_in_seconds) VALUES ($1, $2) RETURNING id +` + +type InsertRateLimitAndReturnIDParams struct { + RequestsPerRange sql.NullInt32 + RangeInSeconds sql.NullInt32 +} + +func (q *Queries) InsertRateLimitAndReturnID(ctx context.Context, arg InsertRateLimitAndReturnIDParams) (int32, error) { + row := q.db.QueryRow(ctx, insertRateLimitAndReturnID, arg.RequestsPerRange, arg.RangeInSeconds) + var id int32 + err := row.Scan(&id) + return id, err +} diff --git a/auth/internal/infrastructure/storage/pg/sqlc/query.sql b/auth/internal/infrastructure/storage/pg/sqlc/query.sql new file mode 100644 index 0000000..1a1c72b --- /dev/null +++ b/auth/internal/infrastructure/storage/pg/sqlc/query.sql @@ -0,0 +1,16 @@ +-- name: InsertRateLimitAndReturnID :one +INSERT INTO rate_limit (requests_per_range, range_in_seconds) VALUES ($1, $2) RETURNING id; + +-- name: InsertApiKey :exec +INSERT INTO api_key (id, is_root, rate_limit_id, service_name) VALUES ($1, $2, $3, $4); + +-- name: GetAllApiKeys :many +SELECT a.id, a.is_root, a.service_name, r.requests_per_range, r.range_in_seconds +FROM api_key a + left join rate_limit r on a.rate_limit_id = r.id ORDER BY a.id; + +-- name: GetApiKeyForServiceName :many +SELECT a.id, a.is_root, a.service_name, r.requests_per_range, r.range_in_seconds +FROM api_key a + inner join rate_limit r on a.rate_limit_id = r.id +WHERE a.service_name = $1 ORDER BY a.id; \ No newline at end of file diff --git a/auth/test/db_util.go b/auth/test/db_util.go new file mode 100644 index 0000000..31b5767 --- /dev/null +++ b/auth/test/db_util.go @@ -0,0 +1,80 @@ +package testutil + +import ( + "context" + "database/sql" + "fmt" + + _ "github.com/jackc/pgx/v4/stdlib" +) + +var ( + DB *sql.DB + dbUser = "root" + dbPass = "secret" + dbHost = "127.0.0.1" + dbPort = "5432" + dbName = "authd-db-test" +) + +func SetupDB() error { + db, err := createDBConnection() + if err != nil { + return err + } + + DB = db + return nil +} + +func ShutdownDB() error { + if err := TruncateDB(); err != nil { + return err + } + + return DB.Close() +} + +func TruncateDB() error { + truncateQuery := ` + SELECT truncate_tables('%s') + ` + formattedQuery := fmt.Sprintf(truncateQuery, dbUser) + _, err := DB.ExecContext(context.Background(), formattedQuery) + if err != nil { + return err + } + + return nil +} + +func createDBConnection() (*sql.DB, error) { + formattedURL := fmt.Sprintf("postgres://%s:%s@%s:%s/%s", dbUser, dbPass, dbHost, dbPort, dbName) + db, err := sql.Open("pgx", formattedURL) + if err != nil { + return nil, err + } + + DB = db + + truncateFunctionQuery := ` + CREATE OR REPLACE FUNCTION truncate_tables(username IN VARCHAR) RETURNS void AS $$ + DECLARE + statements CURSOR FOR + SELECT tablename FROM pg_tables + WHERE tableowner = username AND schemaname = 'public' AND tablename NOT LIKE '%migrations'; + BEGIN + FOR stmt IN statements LOOP + EXECUTE 'TRUNCATE TABLE ' || quote_ident(stmt.tablename) || ' CASCADE;'; + END LOOP; + END; + $$ LANGUAGE plpgsql; +` + + _, err = db.ExecContext(context.Background(), truncateFunctionQuery) + if err != nil { + return nil, err + } + + return db, nil +} diff --git a/auth/test/pg/api_key_repository_test.go b/auth/test/pg/api_key_repository_test.go new file mode 100644 index 0000000..400505b --- /dev/null +++ b/auth/test/pg/api_key_repository_test.go @@ -0,0 +1,83 @@ +package pgtest + +import "prem-gateway/auth/internal/core/domain" + +func (p *PgDbTestSuite) TestApiKeyRepository() { + apiKey, err := domain.NewApiKey(true, "", domain.RateLimit{}) + p.NoError(err) + + err = repositorySvc.ApiKeyRepository().CreateApiKey(ctx, *apiKey) + p.NoError(err) + + keys, err := repositorySvc.ApiKeyRepository().GetAllApiKeys(ctx) + p.NoError(err) + + p.Equal(1, len(keys)) + p.Equal(apiKey.ID, keys[0].ID) + p.Equal(true, keys[0].IsRoot) + p.Nil(keys[0].RateLimit) + p.Equal("", keys[0].Service) + + apiKey, err = domain.NewApiKey( + false, "mistral", domain.RateLimit{ + RequestsPerRange: 10, + RangeInSeconds: 60, + }, + ) + p.NoError(err) + err = repositorySvc.ApiKeyRepository().CreateApiKey(ctx, *apiKey) + keyID1 := apiKey.ID + + apiKey, err = domain.NewApiKey( + false, "vicuna", domain.RateLimit{ + RequestsPerRange: 5, + RangeInSeconds: 120, + }, + ) + p.NoError(err) + err = repositorySvc.ApiKeyRepository().CreateApiKey(ctx, *apiKey) + keyID2 := apiKey.ID + + keys, err = repositorySvc.ApiKeyRepository().GetAllApiKeys(ctx) + p.NoError(err) + p.Equal(3, len(keys)) + + for _, v := range keys { + if v.ID == keyID1 { + p.Equal(false, v.IsRoot) + p.Equal(10, v.RateLimit.RequestsPerRange) + p.Equal(60, v.RateLimit.RangeInSeconds) + p.Equal("mistral", v.Service) + } else if v.ID == keyID2 { + p.Equal(false, v.IsRoot) + p.Equal(5, v.RateLimit.RequestsPerRange) + p.Equal(120, v.RateLimit.RangeInSeconds) + p.Equal("vicuna", v.Service) + } + } + + //check unique constraint on service name + apiKey, err = domain.NewApiKey( + false, + "mistral", + domain.RateLimit{ + RequestsPerRange: 10, + RangeInSeconds: 60, + }, + ) + p.NoError(err) + + err = repositorySvc.ApiKeyRepository().CreateApiKey(ctx, *apiKey) + p.Equal(domain.ErrApiKeyExistForService, err) + + apiKey, err = repositorySvc.ApiKeyRepository().GetServiceApiKey(ctx, "mistral") + p.NoError(err) + p.Equal(keyID1, apiKey.ID) + p.Equal(false, apiKey.IsRoot) + p.Equal(10, apiKey.RateLimit.RequestsPerRange) + p.Equal(60, apiKey.RateLimit.RangeInSeconds) + p.Equal("mistral", apiKey.Service) + + apiKey, err = repositorySvc.ApiKeyRepository().GetServiceApiKey(ctx, "dummy") + p.Equal(domain.ErrApiKeyNotFound, err) +} diff --git a/auth/test/pg/run_suite_test.go b/auth/test/pg/run_suite_test.go new file mode 100644 index 0000000..60ccd65 --- /dev/null +++ b/auth/test/pg/run_suite_test.go @@ -0,0 +1,11 @@ +package pgtest + +import ( + "testing" + + "github.com/stretchr/testify/suite" +) + +func TestPgTestSuite(t *testing.T) { + suite.Run(t, new(PgDbTestSuite)) +} diff --git a/auth/test/pg/test_setup.go b/auth/test/pg/test_setup.go new file mode 100644 index 0000000..e2523ac --- /dev/null +++ b/auth/test/pg/test_setup.go @@ -0,0 +1,55 @@ +package pgtest + +import ( + "context" + "prem-gateway/auth/internal/core/domain" + pgdb "prem-gateway/auth/internal/infrastructure/storage/pg" + testutil "prem-gateway/auth/test" + + "github.com/stretchr/testify/suite" +) + +var ( + repositorySvc domain.RepositoryService + ctx = context.Background() +) + +type PgDbTestSuite struct { + suite.Suite +} + +func (p *PgDbTestSuite) SetupSuite() { + rsvc, err := pgdb.NewRepoService(pgdb.DbConfig{ + DbUser: "root", + DbPassword: "secret", + DbHost: "127.0.0.1", + DbPort: 5432, + DbName: "authd-db-test", + MigrationSourceURL: "file://../.." + + "/internal/infrastructure/storage/pg/migration", + }) + if err != nil { + p.FailNow(err.Error()) + } + + repositorySvc = rsvc + + if err := testutil.SetupDB(); err != nil { + p.FailNow(err.Error()) + } +} + +func (p *PgDbTestSuite) TearDownSuite() { + if err := testutil.TruncateDB(); err != nil { + p.FailNow(err.Error()) + } +} + +func (p *PgDbTestSuite) BeforeTest(suiteName, testName string) { + if err := testutil.TruncateDB(); err != nil { + p.FailNow(err.Error()) + } +} + +func (p *PgDbTestSuite) AfterTest(suiteName, testName string) { +} From 6e014d60b0ffc6c22bdbb472b03fe5aac41ae56d Mon Sep 17 00:00:00 2001 From: sekulicd Date: Tue, 24 Oct 2023 22:08:35 +0200 Subject: [PATCH 05/39] http layer --- .../internal/core/application/auth_service.go | 37 +++++ .../interface/http/handler/auth_handler.go | 133 ++++++++++++++++++ .../http/handler/auth_handler_test.go | 34 +++++ auth/internal/interface/http/handler/types.go | 18 +++ auth/internal/interface/http/server.go | 128 +++++++++++++++++ auth/internal/interface/http/server_opts.go | 26 ++++ 6 files changed, 376 insertions(+) create mode 100644 auth/internal/core/application/auth_service.go create mode 100644 auth/internal/interface/http/handler/auth_handler.go create mode 100644 auth/internal/interface/http/handler/auth_handler_test.go create mode 100644 auth/internal/interface/http/handler/types.go create mode 100644 auth/internal/interface/http/server.go create mode 100644 auth/internal/interface/http/server_opts.go diff --git a/auth/internal/core/application/auth_service.go b/auth/internal/core/application/auth_service.go new file mode 100644 index 0000000..c3feab0 --- /dev/null +++ b/auth/internal/core/application/auth_service.go @@ -0,0 +1,37 @@ +package application + +import ( + "context" + "prem-gateway/auth/internal/core/domain" +) + +type AuthService interface { + // AuthAdmin authenticates the admin user and returns a Root Api Key + AuthAdmin(ctx context.Context, user, pass string) (string, error) +} + +func NewAuthService( + adminUser string, adminPass string, repositorySvc domain.RepositoryService, +) AuthService { + return &authService{ + adminUser: adminUser, + adminPass: adminPass, + repositorySvc: repositorySvc, + } +} + +type authService struct { + adminUser string + adminPass string + repositorySvc domain.RepositoryService +} + +func (a authService) AuthAdmin( + ctx context.Context, user, pass string, +) (string, error) { + if user != a.adminUser || pass != a.adminPass { + return "", ErrUnauthorized + } + + return a.repositorySvc.ApiKeyRepository().GetRootApiKey(ctx) +} diff --git a/auth/internal/interface/http/handler/auth_handler.go b/auth/internal/interface/http/handler/auth_handler.go new file mode 100644 index 0000000..e1a8580 --- /dev/null +++ b/auth/internal/interface/http/handler/auth_handler.go @@ -0,0 +1,133 @@ +package httphandler + +import ( + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" + "net" + "net/http" + "net/url" + "prem-gateway/auth/internal/core/application" + "strings" +) + +type AuthHandler interface { + LogIn(c *gin.Context) + CreateApiKey(c *gin.Context) + GetServiceApiKey(c *gin.Context) + IsRequestAllowed(c *gin.Context) +} + +type authHandler struct { + apiKeySvc application.ApiKeyService + authSvc application.AuthService +} + +func NewAuthHandler( + apiKeySvc application.ApiKeyService, + authSvc application.AuthService, +) (AuthHandler, error) { + return &authHandler{ + apiKeySvc: apiKeySvc, + authSvc: authSvc, + }, nil +} + +func (a authHandler) LogIn(c *gin.Context) { + user := c.Query("user") + pass := c.Query("pass") + + apiKey, err := a.authSvc.AuthAdmin(c, user, pass) + if err != nil { + c.JSON(http.StatusUnauthorized, gin.H{ + "error": err.Error(), + }) + return + } + + c.JSON(http.StatusOK, gin.H{ + "api_key": apiKey, + }) +} + +func (a authHandler) CreateApiKey(c *gin.Context) { + var req CreateApiKey + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "error": err.Error(), + }) + return + } + + id, err := a.apiKeySvc.CreateApiKey(c, ToAppCreateApiKeyInfo(req)) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "error": err.Error(), + }) + return + } + + c.JSON(http.StatusCreated, gin.H{ + "api_key": id, + }) +} + +func (a authHandler) GetServiceApiKey(c *gin.Context) { + service := c.Param("service") + + apiKey, err := a.apiKeySvc.GetServiceApiKey(c, service) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "error": err.Error(), + }) + return + } + + c.JSON(http.StatusOK, gin.H{ + "api_key": apiKey, + }) +} + +func (a authHandler) IsRequestAllowed(c *gin.Context) { + apiKey := c.GetHeader("Authorization") + host := c.GetHeader("X-Forwarded-Host") + uri := c.GetHeader("X-Forwarded-Uri") + + log.Infof("Authorization header: %s\n", apiKey) + log.Infof("X-Forwarded-Host header: %s\n", host) + log.Infof("X-Forwarded-Uri header: %s\n", uri) + + service := extractService(host, uri) + if err := a.apiKeySvc.AllowRequest(apiKey, service); err != nil { + c.JSON(http.StatusUnauthorized, gin.H{ + "error": err.Error(), + }) + return + } + + c.JSON(http.StatusOK, gin.H{}) +} + +func extractService(host string, uri string) string { + parsedUri, err := url.Parse(uri) + if err != nil { + return "" // Could not parse URI + } + + // Using the path from parsed URI + path := parsedUri.Path + + if net.ParseIP(host) == nil { + // Extract service name from domain + parts := strings.Split(host, ".") + if len(parts) > 1 { + return parts[len(parts)-2] // Return the last but one segment + } + } else { + // Extract service from the URI path + uriParts := strings.SplitN(path, "/", 4) + if len(uriParts) >= 3 { + return uriParts[1] // Considering the leading '/' splits into an empty initial element + } + } + return "" // Return an empty string if service can't be determined +} diff --git a/auth/internal/interface/http/handler/auth_handler_test.go b/auth/internal/interface/http/handler/auth_handler_test.go new file mode 100644 index 0000000..0b92095 --- /dev/null +++ b/auth/internal/interface/http/handler/auth_handler_test.go @@ -0,0 +1,34 @@ +package httphandler + +import "testing" + +func TestExtractService(t *testing.T) { + tests := []struct { + host string + uri string + expected string + }{ + { + host: "service.prem.com", + uri: "https://service.prem.com/notrelevant/path", + expected: "service", + }, + { + host: "1.1.1.1", + uri: "http://1.1.1.1/service/v1/chat", + expected: "service", + }, + { + host: "service.sub.prem.com", + uri: "https://service.sub.prem.com/notrelevant/path", + expected: "service", + }, + } + + for _, tt := range tests { + result := extractService(tt.host, tt.uri) + if result != tt.expected { + t.Errorf("For host=%s and uri=%s, expected %s but got %s", tt.host, tt.uri, tt.expected, result) + } + } +} diff --git a/auth/internal/interface/http/handler/types.go b/auth/internal/interface/http/handler/types.go new file mode 100644 index 0000000..02f2cd8 --- /dev/null +++ b/auth/internal/interface/http/handler/types.go @@ -0,0 +1,18 @@ +package httphandler + +import "prem-gateway/auth/internal/core/application" + +type CreateApiKey struct { + Service string + RequestsPerRange int + RangeInMinutes int +} + +func ToAppCreateApiKeyInfo(req CreateApiKey) application.CreateApiKeyReq { + rangeInSeconds := req.RangeInMinutes * 60 + return application.CreateApiKeyReq{ + Service: req.Service, + RequestsPerRange: req.RequestsPerRange, + RangeInSeconds: rangeInSeconds, + } +} diff --git a/auth/internal/interface/http/server.go b/auth/internal/interface/http/server.go new file mode 100644 index 0000000..7937e79 --- /dev/null +++ b/auth/internal/interface/http/server.go @@ -0,0 +1,128 @@ +package httpauthd + +import ( + "context" + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" + "net/http" + "prem-gateway/auth/internal/core/application" + "prem-gateway/auth/internal/core/domain" + httphandler "prem-gateway/auth/internal/interface/http/handler" + "time" +) + +const ( + shutdownTimeout = time.Second * 5 +) + +type Server interface { + Start(ctx context.Context, stop context.CancelFunc) <-chan error + Stop() error + Router() http.Handler +} + +type server struct { + serverAddress string + opts serverOptions + authHandler httphandler.AuthHandler +} + +func NewServer( + serverAddress string, + repositorySvc domain.RepositoryService, + adminUser string, + adminPass string, + rootKeyApiKey string, + opts ...ServerOption, +) (Server, error) { + options := defaultServerOptions() + for _, o := range opts { + if err := o.apply(&options); err != nil { + return nil, err + } + } + + apiKeySvc, err := application.NewApiKeyService( + context.Background(), rootKeyApiKey, repositorySvc, + ) + if err != nil { + return nil, err + } + + authSvc := application.NewAuthService(adminUser, adminPass, repositorySvc) + + authHandler, err := httphandler.NewAuthHandler(apiKeySvc, authSvc) + if err != nil { + return nil, err + } + + return &server{ + serverAddress: serverAddress, + opts: options, + authHandler: authHandler, + }, nil +} + +func (s *server) Start(ctx context.Context, stop context.CancelFunc) <-chan error { + errCh := make(chan error) + + httpServer := &http.Server{ + Addr: s.serverAddress, + Handler: s.Router(), + } + + go func() { + <-ctx.Done() + + log.Info("shutdown signal received") + + ctxTimeout, cancel := context.WithTimeout(context.Background(), shutdownTimeout) + + defer func() { + stop() + cancel() + close(errCh) + }() + + httpServer.SetKeepAlivesEnabled(false) + if err := httpServer.Shutdown(ctxTimeout); err != nil { + errCh <- err + } + + log.Info("prem-gateway auth daemon graceful shutdown completed") + }() + + go func() { + log.Infof("prem-gateway auth daemon listening and serving at: %v", s.serverAddress) + + if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { + errCh <- err + } + }() + + return errCh +} + +func (s *server) Stop() error { + return nil +} + +func (s *server) Router() http.Handler { + ginEngine := gin.Default() + ginEngine.Use(func(c *gin.Context) { + c.Writer.Header().Set("Access-Control-Allow-Origin", "http://localhost:1420") // Replace with your frontend origin + c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH") + c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") + if c.Request.Method == "OPTIONS" { + c.AbortWithStatus(http.StatusOK) + return + } + c.Next() + }) + + ginEngine.POST("/auth/login", s.authHandler.LogIn) + ginEngine.POST("/auth/verify", s.authHandler.IsRequestAllowed) + ginEngine.POST("/auth/api-key", s.authHandler.CreateApiKey) + ginEngine.GET("/auth/api-key/service", s.authHandler.GetServiceApiKey) + return ginEngine +} diff --git a/auth/internal/interface/http/server_opts.go b/auth/internal/interface/http/server_opts.go new file mode 100644 index 0000000..208ff3a --- /dev/null +++ b/auth/internal/interface/http/server_opts.go @@ -0,0 +1,26 @@ +package httpauthd + +type ServerOption interface { + apply(*serverOptions) error +} + +type serverOptions struct { +} + +func defaultServerOptions() serverOptions { + return serverOptions{} +} + +type funcServerOption struct { + f func(*serverOptions) error +} + +func (fdo *funcServerOption) apply(do *serverOptions) error { + return fdo.f(do) +} + +func newFuncServerOption(f func(*serverOptions) error) *funcServerOption { + return &funcServerOption{ + f: f, + } +} From ec97b27e2b8a6b62e9446d57b6532aeb842bfdd2 Mon Sep 17 00:00:00 2001 From: sekulicd Date: Tue, 24 Oct 2023 22:10:44 +0200 Subject: [PATCH 06/39] add api key auth traefik lables --- README.md | 2 +- controller/cmd/controllerd/main.go | 34 +++++++++++++++++------------- docker-compose.yml | 8 +++++-- script/docker-compose-box.yml | 3 ++- script/run_all.sh | 1 - 5 files changed, 28 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index b8aeb45..008b369 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ make down #### In order to restart services outside prem-gateway and to assign them with subdomain/tls certificate, use bellow command. ```bash -make up LETSENCRYPT_PROD=true SERVICES=premd,premapp +make up LETSENCRYPT_PROD=true ``` #### Run prem-gateway with prem-app and prem-daemon: diff --git a/controller/cmd/controllerd/main.go b/controller/cmd/controllerd/main.go index 018d243..434cd68 100644 --- a/controller/cmd/controllerd/main.go +++ b/controller/cmd/controllerd/main.go @@ -24,27 +24,16 @@ const ( premappService = "premapp" premdService = "premd" + authdService = "authd" + dnsdService = "dnsd" ) var ( letEncryptProd bool + services = []string{premappService, authdService, dnsdService} ) -type DnsInfo struct { - Domain string `json:"domain"` - SubDomain string `json:"sub_domain"` - NodeName string `json:"node_name"` - Email string `json:"email"` -} - func main() { - serviceNames := os.Getenv("SERVICES") - services := make([]string, 0) - if serviceNames != "" { - services = append(services, strings.Split(serviceNames, ",")...) - } - services = append(services, "dnsd") - letsEncrypt := os.Getenv("LETSENCRYPT_PROD") if letsEncrypt != "" { letEncryptProd = true @@ -248,12 +237,25 @@ func restartServicesWithTls(domain string, services []string, premServices map[s if err := restartContainer(ctx, cli, v, labels, nil); err != nil { return fmt.Errorf("failed to restart container %s: %v", v, err) } - default: + case authdService: + labels := map[string]string{ + "traefik.enable": "true", + fmt.Sprintf("traefik.http.routers.%s.rule", v): fmt.Sprintf("PathPrefix(`/`) && Host(`%s.%s`)", v, domain), + fmt.Sprintf("traefik.http.routers.%s.entrypoints", v): "websecure", + fmt.Sprintf("traefik.http.routers.%s.tls.certresolver", v): "myresolver", + } + + if err := restartContainer(ctx, cli, v, labels, nil); err != nil { + return fmt.Errorf("failed to restart container %s: %v", v, err) + } + case dnsdService, premdService: labels := map[string]string{ "traefik.enable": "true", fmt.Sprintf("traefik.http.routers.%s.rule", v): fmt.Sprintf("PathPrefix(`/`) && Host(`%s.%s`)", v, domain), fmt.Sprintf("traefik.http.routers.%s.entrypoints", v): "websecure", fmt.Sprintf("traefik.http.routers.%s.tls.certresolver", v): "myresolver", + fmt.Sprintf("traefik.http.routers.%s.middlewares", v): "dnsd-strip-prefix,auth", + "traefik.http.middlewares.auth.forwardauth.address": "http://authd:8080/auth/verify", } if err := restartContainer(ctx, cli, v, labels, nil); err != nil { @@ -275,6 +277,8 @@ func restartServicesWithTls(domain string, services []string, premServices map[s "traefik.http.middlewares.http-to-https.redirectscheme.scheme": "https", fmt.Sprintf("traefik.http.routers.%s-http.middlewares", k): "http-to-https", fmt.Sprintf("traefik.http.services.%s.loadbalancer.server.port", k): strconv.Itoa(v), + fmt.Sprintf("traefik.http.routers.%s-https.middlewares", k): "auth", + "traefik.http.middlewares.auth.forwardauth.address": "http://authd:8080/auth/verify", } if err := restartContainer(ctx, cli, k, labels, nil); err != nil { diff --git a/docker-compose.yml b/docker-compose.yml index 0377440..a7b5fe0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -33,7 +33,8 @@ services: - "traefik.enable=true" - "traefik.http.routers.dnsd.rule=PathPrefix(`/dnsd`)" - "traefik.http.middlewares.dnsd-strip-prefix.stripprefix.prefixes=/dnsd" - - "traefik.http.routers.dnsd.middlewares=dnsd-strip-prefix" + - "traefik.http.routers.dnsd.middlewares=dnsd-strip-prefix,auth" + - "traefik.http.middlewares.auth.forwardauth.address=http://authd:8080/auth/verify" depends_on: - dnsd-db-pg - authd @@ -63,6 +64,10 @@ services: build: ./auth networks: - prem-gateway + labels: + - "traefik.enable=true" + - "traefik.http.routers.authd.rule=PathPrefix(`/authd`)" + - "traefik.http.middlewares.authd-strip-prefix.stripprefix.prefixes=/authd" ports: - "8081:8080" restart: unless-stopped @@ -79,7 +84,6 @@ services: user: root environment: LETSENCRYPT_PROD: ${LETSENCRYPT_PROD} - SERVICES: ${SERVICES} restart: unless-stopped networks: diff --git a/script/docker-compose-box.yml b/script/docker-compose-box.yml index 6bdde9d..e9f1580 100644 --- a/script/docker-compose-box.yml +++ b/script/docker-compose-box.yml @@ -17,7 +17,8 @@ services: - "traefik.enable=true" - "traefik.http.routers.premd.rule=PathPrefix(`/premd`)" - "traefik.http.middlewares.premd-strip-prefix.stripprefix.prefixes=/premd" - - "traefik.http.routers.premd.middlewares=premd-strip-prefix" + - "traefik.http.routers.premd.middlewares=premd-strip-prefix,auth" + - "traefik.http.middlewares.auth.forwardauth.address=http://authd:8080/auth/verify" ports: - "8084:8000" restart: unless-stopped diff --git a/script/run_all.sh b/script/run_all.sh index 4a820d4..83d6d38 100644 --- a/script/run_all.sh +++ b/script/run_all.sh @@ -2,7 +2,6 @@ # Run the 'make up' command with environment variables export LETSENCRYPT_PROD=true -export SERVICES=premd,premapp make up # Loop to check for 'OK' from curl command From 293aad635c70757935ac6c6073ae448778124c8a Mon Sep 17 00:00:00 2001 From: sekulicd Date: Tue, 24 Oct 2023 22:11:03 +0200 Subject: [PATCH 07/39] refactor --- auth/Makefile | 4 +- auth/cmd/authd/main.go | 72 +- auth/go.mod | 82 +- auth/go.sum | 726 +++++++++++++++++- .../core/application/api_key_service.go | 82 +- .../core/application/api_key_service_test.go | 72 +- auth/internal/core/application/errors.go | 5 +- auth/internal/core/application/types.go | 3 +- auth/internal/core/domain/api_key.go | 26 +- .../core/domain/api_key_repository.go | 2 + .../core/domain/api_key_repository_mock.go | 24 + .../storage/pg/api_key_repository_impl.go | 11 + .../storage/pg/sqlc/queries/query.sql.go | 11 + .../infrastructure/storage/pg/sqlc/query.sql | 5 +- auth/test/pg/api_key_repository_test.go | 20 +- 15 files changed, 1013 insertions(+), 132 deletions(-) diff --git a/auth/Makefile b/auth/Makefile index 8b21b85..cd6de4e 100644 --- a/auth/Makefile +++ b/auth/Makefile @@ -126,4 +126,6 @@ vet: mock: cd ./internal/core/domain/; \ mockery --name=ApiKeyRepository --structname=MockApiKeyRepository \ - --output=./ --outpkg=port --filename=api_key_repository_mock.go --inpackage; \ No newline at end of file + --output=./ --outpkg=port --filename=api_key_repository_mock.go --inpackage; \ + mockery --name=RepositoryService --structname=MockRepositoryService \ + --output=./ --outpkg=port --filename=repository_service_mock.go --inpackage; \ No newline at end of file diff --git a/auth/cmd/authd/main.go b/auth/cmd/authd/main.go index ed570b5..b97a5cf 100644 --- a/auth/cmd/authd/main.go +++ b/auth/cmd/authd/main.go @@ -1,31 +1,63 @@ package main import ( - "fmt" + "context" log "github.com/sirupsen/logrus" - "net/http" + "os" + "os/signal" + //_ "prem-gateway/auth/docs" + "prem-gateway/auth/internal/config" + pgdb "prem-gateway/auth/internal/infrastructure/storage/pg" + httpauthd "prem-gateway/auth/internal/interface/http" + "syscall" ) -const apiKey = "dummy-api-key" - +// @title Dns Daemon API +// @description DNS Daemon is designed to manage Domain Name System (DNS) records.
It exposes a RESTful API that allows for the creation, modification, retrieval, and deletion of DNS information, as well as checking the status of a DNS entry.
The DNS information includes attributes such as domain, subdomain, A records, and node names. func main() { - http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - log.Info("Authorization header: %s\n", r.Header.Get("Authorization")) - if r.Header.Get("Authorization") == apiKey { - w.WriteHeader(http.StatusOK) - if _, err := fmt.Fprint(w, "Authenticated"); err != nil { - return - } - } else { - w.WriteHeader(http.StatusUnauthorized) - if _, err := fmt.Fprint(w, "Unauthorized"); err != nil { - return - } - } + if err := config.LoadConfig(); err != nil { + log.Fatalf("failed to load config: %s", err) + } + + svc, err := pgdb.NewRepoService(pgdb.DbConfig{ + DbUser: config.GetString(config.DbUserKey), + DbPassword: config.GetString(config.DbPassKey), + DbHost: config.GetString(config.DbHostKey), + DbPort: config.GetInt(config.DbPortKey), + DbName: config.GetString(config.DbNameKey), + MigrationSourceURL: config.GetString(config.DbMigrationPathKey), }) + if err != nil { + log.Fatalf("failed to create pgdb service: %s", err) + } - log.Info("Starting auth daemon on port 8080") - if err := http.ListenAndServe(":8080", nil); err != nil { - fmt.Printf("Auth daemon failed to start: %v", err) + authd, err := httpauthd.NewServer( + config.GetServerAddress(), + svc, + config.GetString(config.AdminUserKey), + config.GetString(config.AdminPassKey), + config.GetString(config.RootApiKey), + ) + if err != nil { + log.Errorf("failed to create prem-gateway auth daemon: %s", err) + } + + ctx, stop := signal.NotifyContext(context.Background(), + os.Interrupt, + syscall.SIGTERM, + syscall.SIGQUIT) + + errC := authd.Start(ctx, stop) + if err := <-errC; err != nil { + log.Panicf("prem-gateway auth daemon noticed error while running: %s", err) } } + +//TODO add swagger, check dnsd swagger +//add more comments in general to auth +//add http tests +//rename NewDBService in dns, check mock +//update docker +//traefik labels +//controllerd labels +//remove basic auth diff --git a/auth/go.mod b/auth/go.mod index 03b1ee6..03e0f21 100644 --- a/auth/go.mod +++ b/auth/go.mod @@ -4,14 +4,84 @@ go 1.20 require ( github.com/btcsuite/btcd/btcutil v1.1.3 + github.com/gin-gonic/gin v1.9.1 + github.com/golang-migrate/migrate/v4 v4.16.2 + github.com/jackc/pgconn v1.14.1 + github.com/jackc/pgx/v4 v4.18.1 github.com/sirupsen/logrus v1.9.3 - github.com/stretchr/testify v1.7.0 + github.com/spf13/viper v1.17.0 + github.com/stretchr/testify v1.8.4 + github.com/swaggo/files v1.0.1 + github.com/swaggo/gin-swagger v1.6.0 ) require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/stretchr/objx v0.1.0 // indirect - golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect - gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect + github.com/KyleBanks/depth v1.2.1 // indirect + github.com/PuerkitoBio/purell v1.1.1 // indirect + github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect + github.com/btcsuite/btcd v0.23.0 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.1.3 // indirect + github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect + github.com/bytedance/sonic v1.9.1 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.19.6 // indirect + github.com/go-openapi/spec v0.20.4 // indirect + github.com/go-openapi/swag v0.19.15 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.14.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/jackc/chunkreader/v2 v2.0.1 // indirect + github.com/jackc/pgio v1.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgproto3/v2 v2.3.2 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgtype v1.14.0 // indirect + github.com/jackc/puddle v1.3.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/leodido/go-urn v1.2.4 // indirect + github.com/lib/pq v1.10.2 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mailru/easyjson v0.7.6 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/sagikazarmark/locafero v0.3.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.10.0 // indirect + github.com/spf13/cast v1.5.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/objx v0.5.0 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/swaggo/swag v1.8.12 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.11 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.9.0 // indirect + golang.org/x/arch v0.3.0 // indirect + golang.org/x/crypto v0.13.0 // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/net v0.15.0 // indirect + golang.org/x/sys v0.12.0 // indirect + golang.org/x/text v0.13.0 // indirect + golang.org/x/tools v0.13.0 // indirect + google.golang.org/protobuf v1.31.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/auth/go.sum b/auth/go.sum index 8c00312..27f73a9 100644 --- a/auth/go.sum +++ b/auth/go.sum @@ -1,14 +1,67 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= +github.com/btcsuite/btcd v0.23.0 h1:V2/ZgjfDFIygAX3ZapeigkVBoVUtOJKSwrhZdlpSvaA= github.com/btcsuite/btcd v0.23.0/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY= github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= +github.com/btcsuite/btcd/btcec/v2 v2.1.3 h1:xM/n3yIhHAhHy04z4i43C8p4ehixJZMsnrVJkgl+MTE= github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= github.com/btcsuite/btcd/btcutil v1.1.3 h1:xfbtw8lwpp0G6NwSHb+UE67ryTFHJAiNuipusjXSohQ= github.com/btcsuite/btcd/btcutil v1.1.3/go.mod h1:UR7dsSJzJUfMmFiiLlIrMq1lS9jh9EdCV7FStZSnpi0= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= @@ -19,31 +72,264 @@ github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= +github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +github.com/dhui/dktest v0.3.16 h1:i6gq2YQEtcrjKbeJpBkWjE8MmLZPYllcjOFbTZuPDnw= +github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= +github.com/docker/docker v20.10.24+incompatible h1:Ugvxm7a8+Gz6vqQYQQ2W7GYq5EUPaAiuPgIfVyI3dYE= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= +github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= +github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= +github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= +github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M= +github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= +github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= +github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/golang-migrate/migrate/v4 v4.16.2 h1:8coYbMKUyInrFk1lfGfRovTLAW7PhWp8qQDT2iKfuoA= +github.com/golang-migrate/migrate/v4 v4.16.2/go.mod h1:pfcJX4nPHaVdc5nmdCikFBWtm+UBpiZjRNNsyBbp0/o= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= +github.com/jackc/pgconn v1.14.1 h1:smbxIaZA08n6YuxEX1sDyjV/qkbtUtkH20qLkR9MUR4= +github.com/jackc/pgconn v1.14.1/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0= +github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= +github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= +github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= +github.com/jackc/pgx/v4 v4.18.1 h1:YP7G1KABtKpB5IHrO9vYwSrCOhs7p3uqhvhhQBptya0= +github.com/jackc/pgx/v4 v4.18.1/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0= +github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= +github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -53,53 +339,479 @@ github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5 github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= +github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= +github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9cJvm4SvQ= +github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= +github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= +github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI= +github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/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.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= +github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= +github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M= +github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo= +github.com/swaggo/swag v1.8.12 h1:pctzkNPu0AlQP2royqX3apjKCQonAnf7KGoxeO4y64w= +github.com/swaggo/swag v1.8.12/go.mod h1:lNfm6Gg+oAq3zRJQNEMBE66LIJKM44mxFqhEEgy2its= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= +go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= +golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/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-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/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= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/auth/internal/core/application/api_key_service.go b/auth/internal/core/application/api_key_service.go index c6aace7..6d27cf2 100644 --- a/auth/internal/core/application/api_key_service.go +++ b/auth/internal/core/application/api_key_service.go @@ -13,7 +13,7 @@ type ApiKeyService interface { // CreateApiKey creates a new API key. CreateApiKey(ctx context.Context, key CreateApiKeyReq) (string, error) // AllowRequest checks if a given API key is allowed to access a specified path. - AllowRequest(apiKey string, path string) bool + AllowRequest(apiKey string, path string) error // GetServiceApiKey fetches the API key for a specific service. GetServiceApiKey(ctx context.Context, service string) (string, error) } @@ -21,10 +21,11 @@ type ApiKeyService interface { // NewApiKeyService constructs a new instance of the ApiKeyService. func NewApiKeyService( ctx context.Context, - apiKeyRepository domain.ApiKeyRepository, + rootApiKey string, + repositorySvc domain.RepositoryService, ) (ApiKeyService, error) { keysDb := make(map[string]apiKeyInfo) - keys, err := apiKeyRepository.GetAllApiKeys(ctx) // Fetch all existing API keys from the repository. + keys, err := repositorySvc.ApiKeyRepository().GetAllApiKeys(ctx) // Fetch all existing API keys from the repository. if err != nil { return nil, err } @@ -33,15 +34,25 @@ func NewApiKeyService( keysDb[key.ID] = newApiKeyInfo(key) // Populate the in-memory cache with the fetched keys. } - return &apiKeyService{ - apiKeyRepository: apiKeyRepository, - keysDb: keysDb, - }, nil + apiKeySvc := &apiKeyService{ + repositorySvc: repositorySvc, + keysDb: keysDb, + rootApiKey: rootApiKey, + } + + if err := apiKeySvc.createRootApiKey(ctx, rootApiKey); err != nil { + return nil, err + } + + return apiKeySvc, nil } // apiKeyService is the concrete implementation of the ApiKeyService interface. type apiKeyService struct { - apiKeyRepository domain.ApiKeyRepository // Repository for interacting with the API key datastore. + adminUser string // Username for the admin user. + adminPass string // Password for the admin user. + + repositorySvc domain.RepositoryService // Service for interacting with the datastore. keysMtx sync.RWMutex // Mutex for safe concurrent access to the `keysDb` map. keysDb map[string]apiKeyInfo // In-memory cache of API keys for fast lookup. @@ -54,16 +65,9 @@ type apiKeyService struct { func (a *apiKeyService) CreateApiKey( ctx context.Context, key CreateApiKeyReq, ) (string, error) { - if key.IsRootKey { - if a.rootKeyExists() { - return "", ErrRootKeyExists // Ensure only one root key exists. - } - } - // Construct a new API key domain object. apiKey, err := domain.NewApiKey( - key.IsRootKey, - key.Services, + key.Service, domain.RateLimit{ RequestsPerRange: key.RequestsPerRange, RangeInSeconds: key.RangeInSeconds, @@ -74,7 +78,7 @@ func (a *apiKeyService) CreateApiKey( } // Save the new API key to the repository. - if err = a.apiKeyRepository.CreateApiKey(ctx, *apiKey); err != nil { + if err = a.repositorySvc.ApiKeyRepository().CreateApiKey(ctx, *apiKey); err != nil { return "", err } @@ -86,17 +90,17 @@ func (a *apiKeyService) CreateApiKey( } // AllowRequest checks if a given API key is allowed to access a specified path. -func (a *apiKeyService) AllowRequest(apiKey string, path string) bool { +func (a *apiKeyService) AllowRequest(apiKey string, path string) error { // Check if the key exists and if it's allowed to access the given path. key, exists := a.getKey(apiKey) if !exists || !key.canAccessServicePath(path) { log.Debugf("Api key %s is not allowed to access %s", apiKey, path) - return false + return ErrUnauthorizedPath } if key.isRootKey { - return true + return nil } // Check if the key has exceeded its rate limit. @@ -105,17 +109,19 @@ func (a *apiKeyService) AllowRequest(apiKey string, path string) bool { if isRateLimited { log.Debugf("Api key %s has exceeded its rate limit", apiKey) + + return ErrRateLimitExceeded } log.Debugf("Api key %s is allowed to access %s", apiKey, path) - return !isRateLimited + return nil } // GetServiceApiKey retrieves the API key associated with a specific service. func (a *apiKeyService) GetServiceApiKey( ctx context.Context, service string, ) (string, error) { - apiKey, err := a.apiKeyRepository.GetServiceApiKey(ctx, service) + apiKey, err := a.repositorySvc.ApiKeyRepository().GetServiceApiKey(ctx, service) if err != nil { return "", err } @@ -123,6 +129,13 @@ func (a *apiKeyService) GetServiceApiKey( return apiKey.ID, nil } +func (a *apiKeyService) createRootApiKey( + ctx context.Context, apiKey string, +) error { + apk := domain.NewRootApiKey(apiKey) + return a.repositorySvc.ApiKeyRepository().CreateApiKey(ctx, *apk) +} + // Below are helper methods for the apiKeyService. func (a *apiKeyService) insertKey(key apiKeyInfo) { @@ -163,25 +176,20 @@ func (a *apiKeyService) updateKey(key apiKeyInfo) { // apiKeyInfo represents detailed information about an API key. type apiKeyInfo struct { - id string // Unique identifier of the API key. - allowedEndpoints map[string]struct{} // List of services or paths the API key has access to. - firstRequestInRange *time.Time // Timestamp of the first request made within the current rate limit range. - requestsPerRange int // Max number of requests allowed within the rate limit range. - rangeInSeconds int // Duration of the rate limit range in seconds. - requestCount int // Number of requests made within the current rate limit range. - isRootKey bool // Flag indicating if the API key is a root key with unrestricted access. + id string // Unique identifier of the API key. + allowedEndpoint string // List of services or paths the API key has access to. + firstRequestInRange *time.Time // Timestamp of the first request made within the current rate limit range. + requestsPerRange int // Max number of requests allowed within the rate limit range. + rangeInSeconds int // Duration of the rate limit range in seconds. + requestCount int // Number of requests made within the current rate limit range. + isRootKey bool // Flag indicating if the API key is a root key with unrestricted access. } // Construct a new apiKeyInfo from a domain API key. func newApiKeyInfo(apiKey domain.ApiKey) apiKeyInfo { - allowedEndpoints := make(map[string]struct{}) - for _, endpoint := range apiKey.Services { - allowedEndpoints[endpoint] = struct{}{} - } - return apiKeyInfo{ id: apiKey.ID, - allowedEndpoints: allowedEndpoints, + allowedEndpoint: apiKey.Service, firstRequestInRange: nil, requestsPerRange: apiKey.RateLimit.RequestsPerRange, rangeInSeconds: apiKey.RateLimit.RangeInSeconds, @@ -192,9 +200,7 @@ func newApiKeyInfo(apiKey domain.ApiKey) apiKeyInfo { // Check if the API key is allowed to access a given service or path. func (ak apiKeyInfo) canAccessServicePath(servicePath string) bool { - _, exists := ak.allowedEndpoints[servicePath] - - return exists + return ak.allowedEndpoint == servicePath } // Determine if the API key has exceeded its rate limit. diff --git a/auth/internal/core/application/api_key_service_test.go b/auth/internal/core/application/api_key_service_test.go index 1dead7b..b8cff24 100644 --- a/auth/internal/core/application/api_key_service_test.go +++ b/auth/internal/core/application/api_key_service_test.go @@ -15,16 +15,16 @@ var ( ) func TestCreateApiKey(t *testing.T) { - repo := new(domain.MockApiKeyRepository) - repo.On("GetAllApiKeys", mock.Anything).Return(nil, nil) + apiKeyRepo := new(domain.MockApiKeyRepository) + apiKeyRepo.On("GetAllApiKeys", mock.Anything).Return(nil, nil) + apiKeyRepo.On("CreateApiKey", mock.Anything, mock.Anything).Return(nil) + repo := new(domain.MockRepositoryService) + repo.On("ApiKeyRepository").Return(apiKeyRepo) - service, _ := NewApiKeyService(ctx, repo) - - repo.On("CreateApiKey", mock.Anything, mock.Anything).Return(nil) + service, _ := NewApiKeyService(ctx, "rootKey", repo) keyReq := CreateApiKeyReq{ - IsRootKey: false, - Services: []string{"service1"}, + Service: "service1", RequestsPerRange: 5, RangeInSeconds: 10, } @@ -36,21 +36,24 @@ func TestCreateApiKey(t *testing.T) { } func TestAllowRequest(t *testing.T) { - repo := new(domain.MockApiKeyRepository) + apiKeyRepo := new(domain.MockApiKeyRepository) keys := []domain.ApiKey{ { - ID: "test-key", - Services: []string{"test-service"}, - IsRoot: false, + ID: "test-key", + Service: "test-service", + IsRoot: false, RateLimit: &domain.RateLimit{ RequestsPerRange: 5, RangeInSeconds: 10, }, }, } - repo.On("GetAllApiKeys", mock.Anything).Return(keys, nil) + apiKeyRepo.On("GetAllApiKeys", mock.Anything).Return(keys, nil) + apiKeyRepo.On("CreateApiKey", mock.Anything, mock.Anything).Return(nil) + repo := new(domain.MockRepositoryService) + repo.On("ApiKeyRepository").Return(apiKeyRepo) - service, _ := NewApiKeyService(ctx, repo) + service, _ := NewApiKeyService(ctx, "rootKey", repo) // Valid key and path assert.True(t, service.AllowRequest("test-key", "test-service")) @@ -63,12 +66,14 @@ func TestAllowRequest(t *testing.T) { } func TestGetServiceApiKey(t *testing.T) { - repo := new(domain.MockApiKeyRepository) - repo.On("GetAllApiKeys", mock.Anything).Return(nil, nil) - service, _ := NewApiKeyService(ctx, repo) + apiKeyRepo := new(domain.MockApiKeyRepository) + apiKeyRepo.On("GetAllApiKeys", mock.Anything).Return(nil, nil) testServiceKey := &domain.ApiKey{ID: "service-key"} - - repo.On("GetServiceApiKey", mock.Anything, "test-service").Return(testServiceKey, nil) + apiKeyRepo.On("GetServiceApiKey", mock.Anything, "test-service").Return(testServiceKey, nil) + repo := new(domain.MockRepositoryService) + repo.On("ApiKeyRepository").Return(apiKeyRepo) + apiKeyRepo.On("CreateApiKey", mock.Anything, mock.Anything).Return(nil) + service, _ := NewApiKeyService(ctx, "rootKey", repo) keyID, err := service.GetServiceApiKey(context.Background(), "test-service") @@ -77,21 +82,23 @@ func TestGetServiceApiKey(t *testing.T) { } func TestRateLimit(t *testing.T) { - repo := new(domain.MockApiKeyRepository) + apiKeyRepo := new(domain.MockApiKeyRepository) keys := []domain.ApiKey{ { - ID: "rate-limit-key", - Services: []string{"test-service"}, - IsRoot: false, + ID: "rate-limit-key", + Service: "test-service", + IsRoot: false, RateLimit: &domain.RateLimit{ RequestsPerRange: 2, RangeInSeconds: 5, }, }, } - repo.On("GetAllApiKeys", mock.Anything).Return(keys, nil) - - service, _ := NewApiKeyService(ctx, repo) + apiKeyRepo.On("GetAllApiKeys", mock.Anything).Return(keys, nil) + repo := new(domain.MockRepositoryService) + repo.On("ApiKeyRepository").Return(apiKeyRepo) + apiKeyRepo.On("CreateApiKey", mock.Anything, mock.Anything).Return(nil) + service, _ := NewApiKeyService(ctx, "rootKey", repo) assert.True(t, service.AllowRequest("rate-limit-key", "test-service")) assert.True(t, service.AllowRequest("rate-limit-key", "test-service")) @@ -104,19 +111,20 @@ func TestRateLimit(t *testing.T) { } func TestRequestCount(t *testing.T) { - repo := new(domain.MockApiKeyRepository) - repo.On("GetAllApiKeys", mock.Anything).Return(nil, nil) - repo.On("CreateApiKey", mock.Anything, mock.Anything).Return(nil) - - service, err := NewApiKeyService(ctx, repo) + apiKeyRepo := new(domain.MockApiKeyRepository) + apiKeyRepo.On("GetAllApiKeys", mock.Anything).Return(nil, nil) + apiKeyRepo.On("CreateApiKey", mock.Anything, mock.Anything).Return(nil) + repo := new(domain.MockRepositoryService) + repo.On("ApiKeyRepository").Return(apiKeyRepo) + apiKeyRepo.On("CreateApiKey", mock.Anything, mock.Anything).Return(nil) + service, err := NewApiKeyService(ctx, "rootKey", repo) if err != nil { t.Fatalf("Error initializing the service: %v", err) } // Create a new API key keyReq := CreateApiKeyReq{ - IsRootKey: false, - Services: []string{"test"}, + Service: "test", RequestsPerRange: 5, RangeInSeconds: 3, } diff --git a/auth/internal/core/application/errors.go b/auth/internal/core/application/errors.go index 66bc5a7..67e35f7 100644 --- a/auth/internal/core/application/errors.go +++ b/auth/internal/core/application/errors.go @@ -3,5 +3,8 @@ package application import "errors" var ( - ErrRootKeyExists = errors.New("root key already exists") + ErrRootKeyExists = errors.New("root key already exists") + ErrUnauthorized = errors.New("unauthorized") + ErrUnauthorizedPath = errors.New("unauthorized path") + ErrRateLimitExceeded = errors.New("rate limit exceeded") ) diff --git a/auth/internal/core/application/types.go b/auth/internal/core/application/types.go index 4e412d4..5444022 100644 --- a/auth/internal/core/application/types.go +++ b/auth/internal/core/application/types.go @@ -1,8 +1,7 @@ package application type CreateApiKeyReq struct { - IsRootKey bool - Services []string + Service string RequestsPerRange int RangeInSeconds int } diff --git a/auth/internal/core/domain/api_key.go b/auth/internal/core/domain/api_key.go index 3db07ee..44a16d5 100644 --- a/auth/internal/core/domain/api_key.go +++ b/auth/internal/core/domain/api_key.go @@ -14,7 +14,7 @@ const ( // authorize specific endpoints with rate limit constraints. type ApiKey struct { ID string // ID is a unique identifier for the API key. - Services []string // Services lists the services that this API key can access. + Service string // Service that this API key can access. RateLimit *RateLimit // RateLimit defines the request constraints over a specific time range for this API key. IsRoot bool // IsRoot specifies whether this API key is a root key. Root key can access all endpoints. } @@ -26,7 +26,7 @@ type RateLimit struct { } func NewApiKey( - isRootKey bool, services []string, limit RateLimit, + service string, limit RateLimit, ) (*ApiKey, error) { key, err := genKey() if err != nil { @@ -35,10 +35,6 @@ func NewApiKey( rLimit := new(RateLimit) if limit.RequestsPerRange > 0 { - if isRootKey { - return nil, fmt.Errorf("root keys cannot have rate limits") - } - if limit.RangeInSeconds <= 0 { return nil, fmt.Errorf("invalid rate limit range") } @@ -47,24 +43,22 @@ func NewApiKey( rLimit.RangeInSeconds = limit.RangeInSeconds } - if len(services) > 0 { - if isRootKey { - return nil, fmt.Errorf( - "root keys cannot have endpoints constraints", - ) - } - } - apiKey := &ApiKey{ ID: key, - Services: services, + Service: service, RateLimit: rLimit, - IsRoot: isRootKey, } return apiKey, nil } +func NewRootApiKey(rootID string) *ApiKey { + return &ApiKey{ + ID: rootID, + IsRoot: true, + } +} + func genKey() (string, error) { random := make([]byte, byteLength) _, err := rand.Read(random) diff --git a/auth/internal/core/domain/api_key_repository.go b/auth/internal/core/domain/api_key_repository.go index 7b3abe1..311a29b 100644 --- a/auth/internal/core/domain/api_key_repository.go +++ b/auth/internal/core/domain/api_key_repository.go @@ -13,4 +13,6 @@ type ApiKeyRepository interface { GetAllApiKeys(ctx context.Context) ([]ApiKey, error) // GetServiceApiKey returns the API key for the given service. GetServiceApiKey(ctx context.Context, service string) (*ApiKey, error) + //GetRootApiKey returns the root API key. + GetRootApiKey(ctx context.Context) (string, error) } diff --git a/auth/internal/core/domain/api_key_repository_mock.go b/auth/internal/core/domain/api_key_repository_mock.go index 57a021c..530c732 100644 --- a/auth/internal/core/domain/api_key_repository_mock.go +++ b/auth/internal/core/domain/api_key_repository_mock.go @@ -93,6 +93,30 @@ func (_m *MockApiKeyRepository) GetApiKey(ctx context.Context, id string) (*ApiK return r0, r1 } +// GetRootApiKey provides a mock function with given fields: ctx +func (_m *MockApiKeyRepository) GetRootApiKey(ctx context.Context) (string, error) { + ret := _m.Called(ctx) + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (string, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) string); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GetServiceApiKey provides a mock function with given fields: ctx, service func (_m *MockApiKeyRepository) GetServiceApiKey(ctx context.Context, service string) (*ApiKey, error) { ret := _m.Called(ctx, service) diff --git a/auth/internal/infrastructure/storage/pg/api_key_repository_impl.go b/auth/internal/infrastructure/storage/pg/api_key_repository_impl.go index b3e6dea..c275f81 100644 --- a/auth/internal/infrastructure/storage/pg/api_key_repository_impl.go +++ b/auth/internal/infrastructure/storage/pg/api_key_repository_impl.go @@ -156,3 +156,14 @@ func (a *apiKeyRepositoryImpl) GetServiceApiKey( }, }, nil } + +func (a *apiKeyRepositoryImpl) GetRootApiKey( + ctx context.Context, +) (string, error) { + id, err := a.querier.GetRootApiKey(ctx) + if err != nil { + return "", err + } + + return id, nil +} diff --git a/auth/internal/infrastructure/storage/pg/sqlc/queries/query.sql.go b/auth/internal/infrastructure/storage/pg/sqlc/queries/query.sql.go index fb1e92d..a09d84b 100644 --- a/auth/internal/infrastructure/storage/pg/sqlc/queries/query.sql.go +++ b/auth/internal/infrastructure/storage/pg/sqlc/queries/query.sql.go @@ -91,6 +91,17 @@ func (q *Queries) GetApiKeyForServiceName(ctx context.Context, serviceName sql.N return items, nil } +const getRootApiKey = `-- name: GetRootApiKey :one +SELECT id FROM api_key WHERE is_root = true +` + +func (q *Queries) GetRootApiKey(ctx context.Context) (string, error) { + row := q.db.QueryRow(ctx, getRootApiKey) + var id string + err := row.Scan(&id) + return id, err +} + const insertApiKey = `-- name: InsertApiKey :exec INSERT INTO api_key (id, is_root, rate_limit_id, service_name) VALUES ($1, $2, $3, $4) ` diff --git a/auth/internal/infrastructure/storage/pg/sqlc/query.sql b/auth/internal/infrastructure/storage/pg/sqlc/query.sql index 1a1c72b..ef0a1b3 100644 --- a/auth/internal/infrastructure/storage/pg/sqlc/query.sql +++ b/auth/internal/infrastructure/storage/pg/sqlc/query.sql @@ -13,4 +13,7 @@ FROM api_key a SELECT a.id, a.is_root, a.service_name, r.requests_per_range, r.range_in_seconds FROM api_key a inner join rate_limit r on a.rate_limit_id = r.id -WHERE a.service_name = $1 ORDER BY a.id; \ No newline at end of file +WHERE a.service_name = $1 ORDER BY a.id; + +-- name: GetRootApiKey :one +SELECT id FROM api_key WHERE is_root = true; \ No newline at end of file diff --git a/auth/test/pg/api_key_repository_test.go b/auth/test/pg/api_key_repository_test.go index 400505b..5ffd769 100644 --- a/auth/test/pg/api_key_repository_test.go +++ b/auth/test/pg/api_key_repository_test.go @@ -3,23 +3,23 @@ package pgtest import "prem-gateway/auth/internal/core/domain" func (p *PgDbTestSuite) TestApiKeyRepository() { - apiKey, err := domain.NewApiKey(true, "", domain.RateLimit{}) - p.NoError(err) + rootApiKey := domain.NewRootApiKey("rootKey") - err = repositorySvc.ApiKeyRepository().CreateApiKey(ctx, *apiKey) + err := repositorySvc.ApiKeyRepository().CreateApiKey(ctx, *rootApiKey) p.NoError(err) keys, err := repositorySvc.ApiKeyRepository().GetAllApiKeys(ctx) p.NoError(err) p.Equal(1, len(keys)) - p.Equal(apiKey.ID, keys[0].ID) + p.Equal(rootApiKey.ID, keys[0].ID) p.Equal(true, keys[0].IsRoot) p.Nil(keys[0].RateLimit) p.Equal("", keys[0].Service) - apiKey, err = domain.NewApiKey( - false, "mistral", domain.RateLimit{ + apiKey, err := domain.NewApiKey( + "mistral", + domain.RateLimit{ RequestsPerRange: 10, RangeInSeconds: 60, }, @@ -29,7 +29,8 @@ func (p *PgDbTestSuite) TestApiKeyRepository() { keyID1 := apiKey.ID apiKey, err = domain.NewApiKey( - false, "vicuna", domain.RateLimit{ + "vicuna", + domain.RateLimit{ RequestsPerRange: 5, RangeInSeconds: 120, }, @@ -58,7 +59,6 @@ func (p *PgDbTestSuite) TestApiKeyRepository() { //check unique constraint on service name apiKey, err = domain.NewApiKey( - false, "mistral", domain.RateLimit{ RequestsPerRange: 10, @@ -80,4 +80,8 @@ func (p *PgDbTestSuite) TestApiKeyRepository() { apiKey, err = repositorySvc.ApiKeyRepository().GetServiceApiKey(ctx, "dummy") p.Equal(domain.ErrApiKeyNotFound, err) + + rapk, err := repositorySvc.ApiKeyRepository().GetRootApiKey(ctx) + p.NoError(err) + p.Equal(rootApiKey.ID, rapk) } From 7ca944f3069bd5da6b13d45657ee51d4f855b6b2 Mon Sep 17 00:00:00 2001 From: sekulicd Date: Tue, 24 Oct 2023 23:10:12 +0200 Subject: [PATCH 08/39] fix --- docker-compose.yml | 4 ++++ script/run_all.sh | 13 ++++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index a7b5fe0..736ef6f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -68,6 +68,10 @@ services: - "traefik.enable=true" - "traefik.http.routers.authd.rule=PathPrefix(`/authd`)" - "traefik.http.middlewares.authd-strip-prefix.stripprefix.prefixes=/authd" + environment: + PREM_GATEWAY_AUTH_ROOT_API_KEY: ${PREM_GATEWAY_AUTH_ROOT_API_KEY} + PREM_GATEWAY_AUTH_ROOT_ADMIN_USER: ${PREM_GATEWAY_AUTH_ROOT_ADMIN_USER} + PREM_GATEWAY_AUTH_ROOT_ADMIN_PASS: ${PREM_GATEWAY_AUTH_ROOT_ADMIN_PASS} ports: - "8081:8080" restart: unless-stopped diff --git a/script/run_all.sh b/script/run_all.sh index 83d6d38..1657f58 100644 --- a/script/run_all.sh +++ b/script/run_all.sh @@ -1,6 +1,15 @@ #!/bin/bash - +BASIC_AUTH_USER="admin" +BASIC_AUTH_PASS=$(openssl rand -base64 4) +ROOT_KEY=$(openssl rand -base64 8) # Run the 'make up' command with environment variables + +PREM_GATEWAY_AUTH_ROOT_API_KEY=$ROOT_KEY +PREM_GATEWAY_AUTH_ROOT_ADMIN_USER=$BASIC_AUTH_USER +PREM_GATEWAY_AUTH_ROOT_ADMIN_PASS=$BASIC_AUTH_PASS +export PREM_GATEWAY_AUTH_ROOT_API_KEY +export PREM_GATEWAY_AUTH_ROOT_ADMIN_USER +export PREM_GATEWAY_AUTH_ROOT_ADMIN_PASS export LETSENCRYPT_PROD=true make up @@ -25,8 +34,6 @@ then sudo apt-get install -y openssl fi -BASIC_AUTH_USER="admin" -BASIC_AUTH_PASS=$(openssl rand -base64 4) HASH=$(openssl passwd -apr1 $BASIC_AUTH_PASS) BASIC_AUTH_CREDENTIALS="$BASIC_AUTH_USER:$HASH" export BASIC_AUTH_CREDENTIALS From e4dd0c32484305cf7ad87d177b51c6ad2427e908 Mon Sep 17 00:00:00 2001 From: sekulicd Date: Tue, 24 Oct 2023 23:16:05 +0200 Subject: [PATCH 09/39] fix --- auth/internal/config/config.go | 2 +- script/run_all.sh | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/auth/internal/config/config.go b/auth/internal/config/config.go index 40f2f64..8148b93 100644 --- a/auth/internal/config/config.go +++ b/auth/internal/config/config.go @@ -28,7 +28,7 @@ const ( DbMigrationPathKey = "DB_MIGRATION_PATH" // RootApiKey is the root API key with unrestricted access RootApiKey = "ROOT_API_KEY" - // AdminUserKey is the admin user name + // AdminUserKey is the admin username AdminUserKey = "ADMIN_USER" // AdminPassKey is the admin password AdminPassKey = "ADMIN_PASS" diff --git a/script/run_all.sh b/script/run_all.sh index 1657f58..f857b9a 100644 --- a/script/run_all.sh +++ b/script/run_all.sh @@ -5,11 +5,11 @@ ROOT_KEY=$(openssl rand -base64 8) # Run the 'make up' command with environment variables PREM_GATEWAY_AUTH_ROOT_API_KEY=$ROOT_KEY -PREM_GATEWAY_AUTH_ROOT_ADMIN_USER=$BASIC_AUTH_USER -PREM_GATEWAY_AUTH_ROOT_ADMIN_PASS=$BASIC_AUTH_PASS +PREM_GATEWAY_AUTH_ADMIN_USER=$BASIC_AUTH_USER +PREM_GATEWAY_AUTH_ADMIN_PASS=$BASIC_AUTH_PASS export PREM_GATEWAY_AUTH_ROOT_API_KEY -export PREM_GATEWAY_AUTH_ROOT_ADMIN_USER -export PREM_GATEWAY_AUTH_ROOT_ADMIN_PASS +export PREM_GATEWAY_AUTH_ADMIN_USER +export PREM_GATEWAY_AUTH_ADMIN_PASS export LETSENCRYPT_PROD=true make up From ac74ac5bf54cd09470dd36a5379a4e970bea29e4 Mon Sep 17 00:00:00 2001 From: sekulicd Date: Tue, 24 Oct 2023 23:20:48 +0200 Subject: [PATCH 10/39] fix --- docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 736ef6f..612b59d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -70,8 +70,8 @@ services: - "traefik.http.middlewares.authd-strip-prefix.stripprefix.prefixes=/authd" environment: PREM_GATEWAY_AUTH_ROOT_API_KEY: ${PREM_GATEWAY_AUTH_ROOT_API_KEY} - PREM_GATEWAY_AUTH_ROOT_ADMIN_USER: ${PREM_GATEWAY_AUTH_ROOT_ADMIN_USER} - PREM_GATEWAY_AUTH_ROOT_ADMIN_PASS: ${PREM_GATEWAY_AUTH_ROOT_ADMIN_PASS} + PREM_GATEWAY_AUTH_ADMIN_USER: ${PREM_GATEWAY_AUTH_ADMIN_USER} + PREM_GATEWAY_AUTH_ADMIN_PASS: ${PREM_GATEWAY_AUTH_ADMIN_PASS} ports: - "8081:8080" restart: unless-stopped From 1f12e063b4ac4f24af4c14e868ab86129b69183a Mon Sep 17 00:00:00 2001 From: sekulicd Date: Tue, 24 Oct 2023 23:23:12 +0200 Subject: [PATCH 11/39] fix --- auth/Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/auth/Dockerfile b/auth/Dockerfile index bd5d39b..41bfa3c 100644 --- a/auth/Dockerfile +++ b/auth/Dockerfile @@ -26,6 +26,8 @@ ARG DIR=/home/authd RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates COPY --from=builder /app/bin/* /usr/local/bin/ +COPY --from=builder /app/internal/infrastructure/storage/pg/migration/* / +ENV PREM_GATEWAY_AUTH_DB_MIGRATION_PATH=file:// # NOTE: Default GID == UID == 1000 RUN adduser --disabled-password \ From 825fee0f0f5af57f7fa0e2231692a746249b9043 Mon Sep 17 00:00:00 2001 From: sekulicd Date: Tue, 24 Oct 2023 23:29:15 +0200 Subject: [PATCH 12/39] fix --- Makefile | 3 ++- docker-compose.yml | 20 +++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 95c7736..d3fb005 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,8 @@ PONY: up down runall stopall up: export POSTGRES_USER=root; \ export POSTGRES_PASSWORD=secret; \ - export POSTGRES_DB=dnsd-db; \ + export DNSD_POSTGRES_DB=dnsd-db; \ + export AUTHD_POSTGRES_DB=authd-db; \ DOCKER_BUILDKIT=0 docker-compose up -d --build ## down: stop prem-gateway diff --git a/docker-compose.yml b/docker-compose.yml index 612b59d..c5c30c3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -54,7 +54,7 @@ services: environment: POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} - POSTGRES_DB: ${POSTGRES_DB} + POSTGRES_DB: ${DNSD_POSTGRES_DB} volumes: - dnsd-pg-data:/var/lib/postgresql/data restart: unless-stopped @@ -72,10 +72,28 @@ services: PREM_GATEWAY_AUTH_ROOT_API_KEY: ${PREM_GATEWAY_AUTH_ROOT_API_KEY} PREM_GATEWAY_AUTH_ADMIN_USER: ${PREM_GATEWAY_AUTH_ADMIN_USER} PREM_GATEWAY_AUTH_ADMIN_PASS: ${PREM_GATEWAY_AUTH_ADMIN_PASS} + PREM_GATEWAY_AUTH_DB_HOST: authd-db-pg + depends_on: + - authd-db-pg ports: - "8081:8080" restart: unless-stopped + authd-db-pg: + container_name: authd-db-pg + image: postgres:14.7 + networks: + - prem-gateway + ports: + - "5432:5432" + environment: + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${AUTHD_POSTGRES_DB} + volumes: + - dnsd-pg-data:/var/lib/postgresql/data + restart: unless-stopped + controllerd: container_name: controllerd build: ./controller From 0f678c0f9a2a4a9c20afd3d7898ddbdbc32a8b02 Mon Sep 17 00:00:00 2001 From: sekulicd Date: Tue, 24 Oct 2023 23:32:23 +0200 Subject: [PATCH 13/39] fix --- docker-compose.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index c5c30c3..f893246 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -73,6 +73,7 @@ services: PREM_GATEWAY_AUTH_ADMIN_USER: ${PREM_GATEWAY_AUTH_ADMIN_USER} PREM_GATEWAY_AUTH_ADMIN_PASS: ${PREM_GATEWAY_AUTH_ADMIN_PASS} PREM_GATEWAY_AUTH_DB_HOST: authd-db-pg + PREM_GATEWAY_AUTH_DB_PORT: 5433 depends_on: - authd-db-pg ports: @@ -85,7 +86,7 @@ services: networks: - prem-gateway ports: - - "5432:5432" + - "5433:5432" environment: POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} From 044c83a8d859c6b52b225705a8cf8fc1b5ea7775 Mon Sep 17 00:00:00 2001 From: sekulicd Date: Tue, 24 Oct 2023 23:35:40 +0200 Subject: [PATCH 14/39] fix --- docker-compose.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index f893246..b5f51a4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -73,7 +73,6 @@ services: PREM_GATEWAY_AUTH_ADMIN_USER: ${PREM_GATEWAY_AUTH_ADMIN_USER} PREM_GATEWAY_AUTH_ADMIN_PASS: ${PREM_GATEWAY_AUTH_ADMIN_PASS} PREM_GATEWAY_AUTH_DB_HOST: authd-db-pg - PREM_GATEWAY_AUTH_DB_PORT: 5433 depends_on: - authd-db-pg ports: From 5974f70bc458c590ba67d75974c49c0140fb1772 Mon Sep 17 00:00:00 2001 From: sekulicd Date: Wed, 25 Oct 2023 00:24:22 +0200 Subject: [PATCH 15/39] fix --- docker-compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose.yml b/docker-compose.yml index b5f51a4..68fae0a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -67,6 +67,7 @@ services: labels: - "traefik.enable=true" - "traefik.http.routers.authd.rule=PathPrefix(`/authd`)" + - "traefik.http.routers.authd.middlewares=authd-strip-prefix" - "traefik.http.middlewares.authd-strip-prefix.stripprefix.prefixes=/authd" environment: PREM_GATEWAY_AUTH_ROOT_API_KEY: ${PREM_GATEWAY_AUTH_ROOT_API_KEY} From b9445095eb648e5c5bb61a387ffbc23e189e8f6f Mon Sep 17 00:00:00 2001 From: sekulicd Date: Wed, 25 Oct 2023 00:28:17 +0200 Subject: [PATCH 16/39] fix --- docker-compose.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 68fae0a..bd7b262 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -92,7 +92,7 @@ services: POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${AUTHD_POSTGRES_DB} volumes: - - dnsd-pg-data:/var/lib/postgresql/data + - authd-pg-data:/var/lib/postgresql/data restart: unless-stopped controllerd: @@ -115,4 +115,5 @@ networks: volumes: dnsd-pg-data: - traefik-letsencrypt: \ No newline at end of file + traefik-letsencrypt: + authd-pg-data: \ No newline at end of file From c20305ebeb73af9125a3fe84eb488283063d8fc0 Mon Sep 17 00:00:00 2001 From: sekulicd Date: Wed, 25 Oct 2023 00:43:57 +0200 Subject: [PATCH 17/39] test --- .../internal/interface/http/handler/auth_handler.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/auth/internal/interface/http/handler/auth_handler.go b/auth/internal/interface/http/handler/auth_handler.go index e1a8580..d911925 100644 --- a/auth/internal/interface/http/handler/auth_handler.go +++ b/auth/internal/interface/http/handler/auth_handler.go @@ -1,6 +1,7 @@ package httphandler import ( + "fmt" "github.com/gin-gonic/gin" log "github.com/sirupsen/logrus" "net" @@ -32,7 +33,7 @@ func NewAuthHandler( }, nil } -func (a authHandler) LogIn(c *gin.Context) { +func (a *authHandler) LogIn(c *gin.Context) { user := c.Query("user") pass := c.Query("pass") @@ -49,7 +50,7 @@ func (a authHandler) LogIn(c *gin.Context) { }) } -func (a authHandler) CreateApiKey(c *gin.Context) { +func (a *authHandler) CreateApiKey(c *gin.Context) { var req CreateApiKey if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{ @@ -71,7 +72,7 @@ func (a authHandler) CreateApiKey(c *gin.Context) { }) } -func (a authHandler) GetServiceApiKey(c *gin.Context) { +func (a *authHandler) GetServiceApiKey(c *gin.Context) { service := c.Param("service") apiKey, err := a.apiKeySvc.GetServiceApiKey(c, service) @@ -87,7 +88,8 @@ func (a authHandler) GetServiceApiKey(c *gin.Context) { }) } -func (a authHandler) IsRequestAllowed(c *gin.Context) { +func (a *authHandler) IsRequestAllowed(c *gin.Context) { + fmt.Println("IsRequestAllowed") apiKey := c.GetHeader("Authorization") host := c.GetHeader("X-Forwarded-Host") uri := c.GetHeader("X-Forwarded-Uri") @@ -95,6 +97,9 @@ func (a authHandler) IsRequestAllowed(c *gin.Context) { log.Infof("Authorization header: %s\n", apiKey) log.Infof("X-Forwarded-Host header: %s\n", host) log.Infof("X-Forwarded-Uri header: %s\n", uri) + fmt.Println(apiKey) + fmt.Println(host) + fmt.Println(uri) service := extractService(host, uri) if err := a.apiKeySvc.AllowRequest(apiKey, service); err != nil { From a22d767589b85fe27e9727a3133408b260fe255d Mon Sep 17 00:00:00 2001 From: sekulicd Date: Wed, 25 Oct 2023 00:55:29 +0200 Subject: [PATCH 18/39] fix --- auth/cmd/authd/main.go | 1 + auth/internal/core/application/api_key_service.go | 2 +- auth/internal/interface/http/server.go | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/auth/cmd/authd/main.go b/auth/cmd/authd/main.go index b97a5cf..49af564 100644 --- a/auth/cmd/authd/main.go +++ b/auth/cmd/authd/main.go @@ -61,3 +61,4 @@ func main() { //traefik labels //controllerd labels //remove basic auth +//handle panic recovery diff --git a/auth/internal/core/application/api_key_service.go b/auth/internal/core/application/api_key_service.go index 6d27cf2..81449bc 100644 --- a/auth/internal/core/application/api_key_service.go +++ b/auth/internal/core/application/api_key_service.go @@ -191,7 +191,7 @@ func newApiKeyInfo(apiKey domain.ApiKey) apiKeyInfo { id: apiKey.ID, allowedEndpoint: apiKey.Service, firstRequestInRange: nil, - requestsPerRange: apiKey.RateLimit.RequestsPerRange, + requestsPerRange: apiKey.RateLimit.RequestsPerRange, //TODO check if nil rangeInSeconds: apiKey.RateLimit.RangeInSeconds, requestCount: 0, isRootKey: apiKey.IsRoot, diff --git a/auth/internal/interface/http/server.go b/auth/internal/interface/http/server.go index 7937e79..18e9028 100644 --- a/auth/internal/interface/http/server.go +++ b/auth/internal/interface/http/server.go @@ -121,7 +121,7 @@ func (s *server) Router() http.Handler { }) ginEngine.POST("/auth/login", s.authHandler.LogIn) - ginEngine.POST("/auth/verify", s.authHandler.IsRequestAllowed) + ginEngine.GET("/auth/verify", s.authHandler.IsRequestAllowed) ginEngine.POST("/auth/api-key", s.authHandler.CreateApiKey) ginEngine.GET("/auth/api-key/service", s.authHandler.GetServiceApiKey) return ginEngine From c1cdf7c57e95d70020abae826bc4af8c29083f4b Mon Sep 17 00:00:00 2001 From: sekulicd Date: Wed, 25 Oct 2023 01:05:11 +0200 Subject: [PATCH 19/39] logging --- .../internal/interface/http/handler/auth_handler.go | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/auth/internal/interface/http/handler/auth_handler.go b/auth/internal/interface/http/handler/auth_handler.go index d911925..68dc40a 100644 --- a/auth/internal/interface/http/handler/auth_handler.go +++ b/auth/internal/interface/http/handler/auth_handler.go @@ -1,7 +1,6 @@ package httphandler import ( - "fmt" "github.com/gin-gonic/gin" log "github.com/sirupsen/logrus" "net" @@ -89,17 +88,15 @@ func (a *authHandler) GetServiceApiKey(c *gin.Context) { } func (a *authHandler) IsRequestAllowed(c *gin.Context) { - fmt.Println("IsRequestAllowed") apiKey := c.GetHeader("Authorization") host := c.GetHeader("X-Forwarded-Host") uri := c.GetHeader("X-Forwarded-Uri") + forwardedFor := c.GetHeader("X-Forwarded-For") - log.Infof("Authorization header: %s\n", apiKey) - log.Infof("X-Forwarded-Host header: %s\n", host) - log.Infof("X-Forwarded-Uri header: %s\n", uri) - fmt.Println(apiKey) - fmt.Println(host) - fmt.Println(uri) + log.Infof("Authorization header: %s", apiKey) + log.Infof("X-Forwarded-Host header: %s", host) + log.Infof("X-Forwarded-Uri header: %s", uri) + log.Infof("X-Forwarded-For header: %s", forwardedFor) service := extractService(host, uri) if err := a.apiKeySvc.AllowRequest(apiKey, service); err != nil { From 51d3f718c9f2b12787dbad4348c05a6a60bc580d Mon Sep 17 00:00:00 2001 From: sekulicd Date: Wed, 25 Oct 2023 09:36:14 +0200 Subject: [PATCH 20/39] fix --- .../interface/http/handler/auth_handler.go | 33 ++++++++++++------- .../http/handler/auth_handler_test.go | 6 ++-- .../internal/interface/http/handler/errors.go | 8 +++++ 3 files changed, 32 insertions(+), 15 deletions(-) create mode 100644 auth/internal/interface/http/handler/errors.go diff --git a/auth/internal/interface/http/handler/auth_handler.go b/auth/internal/interface/http/handler/auth_handler.go index 68dc40a..8051bed 100644 --- a/auth/internal/interface/http/handler/auth_handler.go +++ b/auth/internal/interface/http/handler/auth_handler.go @@ -89,16 +89,28 @@ func (a *authHandler) GetServiceApiKey(c *gin.Context) { func (a *authHandler) IsRequestAllowed(c *gin.Context) { apiKey := c.GetHeader("Authorization") - host := c.GetHeader("X-Forwarded-Host") uri := c.GetHeader("X-Forwarded-Uri") forwardedFor := c.GetHeader("X-Forwarded-For") log.Infof("Authorization header: %s", apiKey) - log.Infof("X-Forwarded-Host header: %s", host) log.Infof("X-Forwarded-Uri header: %s", uri) log.Infof("X-Forwarded-For header: %s", forwardedFor) - service := extractService(host, uri) + service := extractService(forwardedFor, uri) + if service == "" { + c.JSON(http.StatusUnauthorized, gin.H{ + "error": ErrServiceNotFound, + }) + return + } + + if apiKey == "" { + c.JSON(http.StatusUnauthorized, gin.H{ + "error": ErrApiKeyNotProvided, + }) + return + } + if err := a.apiKeySvc.AllowRequest(apiKey, service); err != nil { c.JSON(http.StatusUnauthorized, gin.H{ "error": err.Error(), @@ -115,21 +127,18 @@ func extractService(host string, uri string) string { return "" // Could not parse URI } - // Using the path from parsed URI path := parsedUri.Path - if net.ParseIP(host) == nil { - // Extract service name from domain parts := strings.Split(host, ".") if len(parts) > 1 { - return parts[len(parts)-2] // Return the last but one segment + return parts[0] } } else { - // Extract service from the URI path - uriParts := strings.SplitN(path, "/", 4) - if len(uriParts) >= 3 { - return uriParts[1] // Considering the leading '/' splits into an empty initial element + uriParts := strings.Split(path, "/") + if len(uriParts) > 1 { + return uriParts[1] } } - return "" // Return an empty string if service can't be determined + + return "" } diff --git a/auth/internal/interface/http/handler/auth_handler_test.go b/auth/internal/interface/http/handler/auth_handler_test.go index 0b92095..501e7af 100644 --- a/auth/internal/interface/http/handler/auth_handler_test.go +++ b/auth/internal/interface/http/handler/auth_handler_test.go @@ -10,17 +10,17 @@ func TestExtractService(t *testing.T) { }{ { host: "service.prem.com", - uri: "https://service.prem.com/notrelevant/path", + uri: "/notrelevant/path", expected: "service", }, { host: "1.1.1.1", - uri: "http://1.1.1.1/service/v1/chat", + uri: "/service/v1/chat", expected: "service", }, { host: "service.sub.prem.com", - uri: "https://service.sub.prem.com/notrelevant/path", + uri: "/notrelevant/path", expected: "service", }, } diff --git a/auth/internal/interface/http/handler/errors.go b/auth/internal/interface/http/handler/errors.go new file mode 100644 index 0000000..61b5012 --- /dev/null +++ b/auth/internal/interface/http/handler/errors.go @@ -0,0 +1,8 @@ +package httphandler + +import "errors" + +var ( + ErrServiceNotFound = errors.New("could not extract service from request") + ErrApiKeyNotProvided = errors.New("API key not provided") +) From fc3cceabb5cd2fa334a04cc65f073e2a175121e1 Mon Sep 17 00:00:00 2001 From: sekulicd Date: Wed, 25 Oct 2023 09:53:56 +0200 Subject: [PATCH 21/39] fix --- auth/internal/core/application/api_key_service.go | 4 ++++ auth/internal/interface/http/handler/auth_handler.go | 1 + 2 files changed, 5 insertions(+) diff --git a/auth/internal/core/application/api_key_service.go b/auth/internal/core/application/api_key_service.go index 81449bc..30b875c 100644 --- a/auth/internal/core/application/api_key_service.go +++ b/auth/internal/core/application/api_key_service.go @@ -91,6 +91,10 @@ func (a *apiKeyService) CreateApiKey( // AllowRequest checks if a given API key is allowed to access a specified path. func (a *apiKeyService) AllowRequest(apiKey string, path string) error { + if a.isRootKey(apiKey) { + return nil + } + // Check if the key exists and if it's allowed to access the given path. key, exists := a.getKey(apiKey) if !exists || !key.canAccessServicePath(path) { diff --git a/auth/internal/interface/http/handler/auth_handler.go b/auth/internal/interface/http/handler/auth_handler.go index 8051bed..0eadf87 100644 --- a/auth/internal/interface/http/handler/auth_handler.go +++ b/auth/internal/interface/http/handler/auth_handler.go @@ -103,6 +103,7 @@ func (a *authHandler) IsRequestAllowed(c *gin.Context) { }) return } + log.Infof("Service: %s", service) if apiKey == "" { c.JSON(http.StatusUnauthorized, gin.H{ From 0adedeb3feb89e507e825726986904ade209569e Mon Sep 17 00:00:00 2001 From: sekulicd Date: Wed, 25 Oct 2023 10:00:41 +0200 Subject: [PATCH 22/39] panic recovery --- auth/internal/interface/http/server.go | 1 + dns/internal/interface/http/server.go | 1 + 2 files changed, 2 insertions(+) diff --git a/auth/internal/interface/http/server.go b/auth/internal/interface/http/server.go index 18e9028..dbf6571 100644 --- a/auth/internal/interface/http/server.go +++ b/auth/internal/interface/http/server.go @@ -109,6 +109,7 @@ func (s *server) Stop() error { func (s *server) Router() http.Handler { ginEngine := gin.Default() + ginEngine.Use(gin.Recovery()) ginEngine.Use(func(c *gin.Context) { c.Writer.Header().Set("Access-Control-Allow-Origin", "http://localhost:1420") // Replace with your frontend origin c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH") diff --git a/dns/internal/interface/http/server.go b/dns/internal/interface/http/server.go index 8b598f3..52904b5 100644 --- a/dns/internal/interface/http/server.go +++ b/dns/internal/interface/http/server.go @@ -109,6 +109,7 @@ func (s *server) Stop() error { func (s *server) Router() http.Handler { ginEngine := gin.Default() + ginEngine.Use(gin.Recovery()) ginEngine.Use(func(c *gin.Context) { c.Writer.Header().Set("Access-Control-Allow-Origin", "http://localhost:1420") // Replace with your frontend origin c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH") From d4e893f044323542cbdcbc800cb7c668cf0a6d86 Mon Sep 17 00:00:00 2001 From: sekulicd Date: Wed, 25 Oct 2023 10:37:40 +0200 Subject: [PATCH 23/39] fix --- auth/internal/config/config.go | 2 +- .../core/application/api_key_service.go | 22 ++++++++++++++----- auth/internal/interface/http/server.go | 2 +- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/auth/internal/config/config.go b/auth/internal/config/config.go index 8148b93..ff8b4fa 100644 --- a/auth/internal/config/config.go +++ b/auth/internal/config/config.go @@ -52,7 +52,7 @@ func LoadConfig() error { vip.SetDefault(DbHostKey, "127.0.0.1") vip.SetDefault(DbPortKey, 5432) vip.SetDefault(DbNameKey, "authd-db") - vip.SetDefault(DbMigrationPathKey, "file://dns/internal/infrastructure/storage/pg/migration") + vip.SetDefault(DbMigrationPathKey, "file://auth/internal/infrastructure/storage/pg/migration") if vip.GetString(RootApiKey) == "" { return errors.New("root API key not set") diff --git a/auth/internal/core/application/api_key_service.go b/auth/internal/core/application/api_key_service.go index 30b875c..2871d78 100644 --- a/auth/internal/core/application/api_key_service.go +++ b/auth/internal/core/application/api_key_service.go @@ -30,8 +30,12 @@ func NewApiKeyService( return nil, err } + rootKeyExists := false for _, key := range keys { - keysDb[key.ID] = newApiKeyInfo(key) // Populate the in-memory cache with the fetched keys. + if key.IsRoot { + rootKeyExists = true + } + keysDb[key.ID] = newApiKeyInfo(key) } apiKeySvc := &apiKeyService{ @@ -40,8 +44,10 @@ func NewApiKeyService( rootApiKey: rootApiKey, } - if err := apiKeySvc.createRootApiKey(ctx, rootApiKey); err != nil { - return nil, err + if !rootKeyExists { + if err := apiKeySvc.createRootApiKey(ctx, rootApiKey); err != nil { + return nil, err + } } return apiKeySvc, nil @@ -191,12 +197,18 @@ type apiKeyInfo struct { // Construct a new apiKeyInfo from a domain API key. func newApiKeyInfo(apiKey domain.ApiKey) apiKeyInfo { + requestsPerRange := 0 + rangeInSeconds := 0 + if apiKey.RateLimit != nil { + requestsPerRange = apiKey.RateLimit.RequestsPerRange + rangeInSeconds = apiKey.RateLimit.RangeInSeconds + } return apiKeyInfo{ id: apiKey.ID, allowedEndpoint: apiKey.Service, firstRequestInRange: nil, - requestsPerRange: apiKey.RateLimit.RequestsPerRange, //TODO check if nil - rangeInSeconds: apiKey.RateLimit.RangeInSeconds, + requestsPerRange: requestsPerRange, + rangeInSeconds: rangeInSeconds, requestCount: 0, isRootKey: apiKey.IsRoot, } diff --git a/auth/internal/interface/http/server.go b/auth/internal/interface/http/server.go index dbf6571..6489ed0 100644 --- a/auth/internal/interface/http/server.go +++ b/auth/internal/interface/http/server.go @@ -108,7 +108,7 @@ func (s *server) Stop() error { } func (s *server) Router() http.Handler { - ginEngine := gin.Default() + ginEngine := gin.New() ginEngine.Use(gin.Recovery()) ginEngine.Use(func(c *gin.Context) { c.Writer.Header().Set("Access-Control-Allow-Origin", "http://localhost:1420") // Replace with your frontend origin From dff52475a224ab71002f13227abd1fd993c81eba Mon Sep 17 00:00:00 2001 From: sekulicd Date: Wed, 25 Oct 2023 10:49:22 +0200 Subject: [PATCH 24/39] fix --- auth/internal/interface/http/handler/types.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/auth/internal/interface/http/handler/types.go b/auth/internal/interface/http/handler/types.go index 02f2cd8..efbe56b 100644 --- a/auth/internal/interface/http/handler/types.go +++ b/auth/internal/interface/http/handler/types.go @@ -3,9 +3,9 @@ package httphandler import "prem-gateway/auth/internal/core/application" type CreateApiKey struct { - Service string - RequestsPerRange int - RangeInMinutes int + Service string `json:"service_name"` + RequestsPerRange int `json:"requests_per_range"` + RangeInMinutes int `json:"range_in_minutes"` } func ToAppCreateApiKeyInfo(req CreateApiKey) application.CreateApiKeyReq { From 54a24f5bdef9aca59b82c8941c737e055557995c Mon Sep 17 00:00:00 2001 From: sekulicd Date: Wed, 25 Oct 2023 10:56:33 +0200 Subject: [PATCH 25/39] fix --- script/stop_all.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/script/stop_all.sh b/script/stop_all.sh index 9b408a4..337d1a3 100644 --- a/script/stop_all.sh +++ b/script/stop_all.sh @@ -10,8 +10,8 @@ CONTAINERS=($(docker ps -aq --filter network=prem-gateway)) if [ ${#CONTAINERS[@]} -eq 0 ]; then echo "No containers found on the 'prem-gateway' network." else - PGVOLUME=($(docker inspect dnsd-db-pg | jq -r '.[0].HostConfig.Mounts[0].Source')) - + DNSD_PG_VOLUME=($(docker inspect dnsd-db-pg | jq -r '.[0].HostConfig.Mounts[0].Source')) + AUTH_PG_VOLUME=($(docker inspect auth-db-pg | jq -r '.[0].HostConfig.Mounts[0].Source')) # Stop all containers echo "Stopping containers on 'prem-gateway' network..." for CONTAINER in "${CONTAINERS[@]}"; do @@ -24,9 +24,9 @@ else docker rm "$CONTAINER" done - # Remove dnsd-db-pg volume - echo "Remove dnsd-db-pg volume" - docker volume rm "$PGVOLUME" + # Remove dnsd-db-pg and auth-db-pg volumes + docker volume rm "$DNSD_PG_VOLUME" + docker volume rm "$AUTH_PG_VOLUME" echo "Containers stopped and removed. Volumes cleaned." fi \ No newline at end of file From dfaefdfea01e1af91a228ef5a933e9b6dc8ca2d1 Mon Sep 17 00:00:00 2001 From: sekulicd Date: Wed, 25 Oct 2023 10:58:35 +0200 Subject: [PATCH 26/39] fix --- script/stop_all.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/script/stop_all.sh b/script/stop_all.sh index 337d1a3..dab449c 100644 --- a/script/stop_all.sh +++ b/script/stop_all.sh @@ -11,7 +11,7 @@ if [ ${#CONTAINERS[@]} -eq 0 ]; then echo "No containers found on the 'prem-gateway' network." else DNSD_PG_VOLUME=($(docker inspect dnsd-db-pg | jq -r '.[0].HostConfig.Mounts[0].Source')) - AUTH_PG_VOLUME=($(docker inspect auth-db-pg | jq -r '.[0].HostConfig.Mounts[0].Source')) + AUTHD_PG_VOLUME=($(docker inspect authd-db-pg | jq -r '.[0].HostConfig.Mounts[0].Source')) # Stop all containers echo "Stopping containers on 'prem-gateway' network..." for CONTAINER in "${CONTAINERS[@]}"; do @@ -26,7 +26,7 @@ else # Remove dnsd-db-pg and auth-db-pg volumes docker volume rm "$DNSD_PG_VOLUME" - docker volume rm "$AUTH_PG_VOLUME" + docker volume rm "$AUTHD_PG_VOLUME" echo "Containers stopped and removed. Volumes cleaned." fi \ No newline at end of file From 0fec59a7bf61941e41907fabfc2d8bffa5d29f40 Mon Sep 17 00:00:00 2001 From: sekulicd Date: Wed, 25 Oct 2023 11:11:02 +0200 Subject: [PATCH 27/39] fix --- auth/cmd/authd/main.go | 4 ---- controller/cmd/controllerd/main.go | 2 +- script/docker-compose-box.yml | 2 +- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/auth/cmd/authd/main.go b/auth/cmd/authd/main.go index 49af564..0a76993 100644 --- a/auth/cmd/authd/main.go +++ b/auth/cmd/authd/main.go @@ -57,8 +57,4 @@ func main() { //add more comments in general to auth //add http tests //rename NewDBService in dns, check mock -//update docker -//traefik labels -//controllerd labels //remove basic auth -//handle panic recovery diff --git a/controller/cmd/controllerd/main.go b/controller/cmd/controllerd/main.go index 434cd68..1ed38a7 100644 --- a/controller/cmd/controllerd/main.go +++ b/controller/cmd/controllerd/main.go @@ -254,7 +254,7 @@ func restartServicesWithTls(domain string, services []string, premServices map[s fmt.Sprintf("traefik.http.routers.%s.rule", v): fmt.Sprintf("PathPrefix(`/`) && Host(`%s.%s`)", v, domain), fmt.Sprintf("traefik.http.routers.%s.entrypoints", v): "websecure", fmt.Sprintf("traefik.http.routers.%s.tls.certresolver", v): "myresolver", - fmt.Sprintf("traefik.http.routers.%s.middlewares", v): "dnsd-strip-prefix,auth", + fmt.Sprintf("traefik.http.routers.%s.middlewares", v): "auth,dnsd-strip-prefix", "traefik.http.middlewares.auth.forwardauth.address": "http://authd:8080/auth/verify", } diff --git a/script/docker-compose-box.yml b/script/docker-compose-box.yml index e9f1580..070666b 100644 --- a/script/docker-compose-box.yml +++ b/script/docker-compose-box.yml @@ -17,7 +17,7 @@ services: - "traefik.enable=true" - "traefik.http.routers.premd.rule=PathPrefix(`/premd`)" - "traefik.http.middlewares.premd-strip-prefix.stripprefix.prefixes=/premd" - - "traefik.http.routers.premd.middlewares=premd-strip-prefix,auth" + - "traefik.http.routers.premd.middlewares=auth,premd-strip-prefix" - "traefik.http.middlewares.auth.forwardauth.address=http://authd:8080/auth/verify" ports: - "8084:8000" From effba8d8e77c37d5dac74e30706e3e3251bf6d36 Mon Sep 17 00:00:00 2001 From: sekulicd Date: Wed, 25 Oct 2023 11:41:29 +0200 Subject: [PATCH 28/39] fix test, auth handlers --- .../core/application/api_key_service.go | 20 ++++- .../core/application/api_key_service_test.go | 88 ++++++++++++++----- .../interface/http/handler/auth_handler.go | 7 +- auth/internal/interface/http/server.go | 2 +- 4 files changed, 86 insertions(+), 31 deletions(-) diff --git a/auth/internal/core/application/api_key_service.go b/auth/internal/core/application/api_key_service.go index 2871d78..fd41305 100644 --- a/auth/internal/core/application/api_key_service.go +++ b/auth/internal/core/application/api_key_service.go @@ -11,11 +11,15 @@ import ( // ApiKeyService defines the interface for managing API keys. type ApiKeyService interface { // CreateApiKey creates a new API key. - CreateApiKey(ctx context.Context, key CreateApiKeyReq) (string, error) + CreateApiKey( + ctx context.Context, rootApiKey string, key CreateApiKeyReq, + ) (string, error) // AllowRequest checks if a given API key is allowed to access a specified path. AllowRequest(apiKey string, path string) error // GetServiceApiKey fetches the API key for a specific service. - GetServiceApiKey(ctx context.Context, service string) (string, error) + GetServiceApiKey( + ctx context.Context, rootApiKey, service string, + ) (string, error) } // NewApiKeyService constructs a new instance of the ApiKeyService. @@ -69,8 +73,12 @@ type apiKeyService struct { // CreateApiKey creates a new API key and saves it in the datastore. func (a *apiKeyService) CreateApiKey( - ctx context.Context, key CreateApiKeyReq, + ctx context.Context, rootApiKey string, key CreateApiKeyReq, ) (string, error) { + if !a.isRootKey(rootApiKey) { + return "", ErrUnauthorized + } + // Construct a new API key domain object. apiKey, err := domain.NewApiKey( key.Service, @@ -129,8 +137,12 @@ func (a *apiKeyService) AllowRequest(apiKey string, path string) error { // GetServiceApiKey retrieves the API key associated with a specific service. func (a *apiKeyService) GetServiceApiKey( - ctx context.Context, service string, + ctx context.Context, rootApiKey, service string, ) (string, error) { + if !a.isRootKey(rootApiKey) { + return "", ErrUnauthorized + } + apiKey, err := a.repositorySvc.ApiKeyRepository().GetServiceApiKey(ctx, service) if err != nil { return "", err diff --git a/auth/internal/core/application/api_key_service_test.go b/auth/internal/core/application/api_key_service_test.go index b8cff24..ae03f0e 100644 --- a/auth/internal/core/application/api_key_service_test.go +++ b/auth/internal/core/application/api_key_service_test.go @@ -16,12 +16,18 @@ var ( func TestCreateApiKey(t *testing.T) { apiKeyRepo := new(domain.MockApiKeyRepository) - apiKeyRepo.On("GetAllApiKeys", mock.Anything).Return(nil, nil) + keys := []domain.ApiKey{ + { + ID: "root", + IsRoot: true, + }, + } + apiKeyRepo.On("GetAllApiKeys", mock.Anything).Return(keys, nil) apiKeyRepo.On("CreateApiKey", mock.Anything, mock.Anything).Return(nil) repo := new(domain.MockRepositoryService) repo.On("ApiKeyRepository").Return(apiKeyRepo) - service, _ := NewApiKeyService(ctx, "rootKey", repo) + service, _ := NewApiKeyService(ctx, "root", repo) keyReq := CreateApiKeyReq{ Service: "service1", @@ -29,8 +35,7 @@ func TestCreateApiKey(t *testing.T) { RangeInSeconds: 10, } - id, err := service.CreateApiKey(context.Background(), keyReq) - + id, err := service.CreateApiKey(context.Background(), "root", keyReq) assert.NotNil(t, id) assert.Nil(t, err) } @@ -38,6 +43,10 @@ func TestCreateApiKey(t *testing.T) { func TestAllowRequest(t *testing.T) { apiKeyRepo := new(domain.MockApiKeyRepository) keys := []domain.ApiKey{ + { + ID: "root", + IsRoot: true, + }, { ID: "test-key", Service: "test-service", @@ -53,29 +62,40 @@ func TestAllowRequest(t *testing.T) { repo := new(domain.MockRepositoryService) repo.On("ApiKeyRepository").Return(apiKeyRepo) - service, _ := NewApiKeyService(ctx, "rootKey", repo) + service, _ := NewApiKeyService(ctx, "root", repo) // Valid key and path - assert.True(t, service.AllowRequest("test-key", "test-service")) + err := service.AllowRequest("test-key", "test-service") + assert.Nil(t, err) // Invalid key - assert.False(t, service.AllowRequest("invalid-key", "test-service")) + err = service.AllowRequest("invalid-key", "test-service") + assert.NotNil(t, err) + assert.Equal(t, ErrUnauthorizedPath, err) // Invalid path for a valid key - assert.False(t, service.AllowRequest("test-key", "invalid-service")) + err = service.AllowRequest("test-key", "invalid-service") + assert.NotNil(t, err) + assert.Equal(t, ErrUnauthorizedPath, err) } func TestGetServiceApiKey(t *testing.T) { apiKeyRepo := new(domain.MockApiKeyRepository) - apiKeyRepo.On("GetAllApiKeys", mock.Anything).Return(nil, nil) + keys := []domain.ApiKey{ + { + ID: "root", + IsRoot: true, + }, + } + apiKeyRepo.On("GetAllApiKeys", mock.Anything).Return(keys, nil) testServiceKey := &domain.ApiKey{ID: "service-key"} apiKeyRepo.On("GetServiceApiKey", mock.Anything, "test-service").Return(testServiceKey, nil) repo := new(domain.MockRepositoryService) repo.On("ApiKeyRepository").Return(apiKeyRepo) apiKeyRepo.On("CreateApiKey", mock.Anything, mock.Anything).Return(nil) - service, _ := NewApiKeyService(ctx, "rootKey", repo) + service, _ := NewApiKeyService(ctx, "root", repo) - keyID, err := service.GetServiceApiKey(context.Background(), "test-service") + keyID, err := service.GetServiceApiKey(context.Background(), "root", "test-service") assert.Equal(t, "service-key", keyID) assert.Nil(t, err) @@ -98,26 +118,39 @@ func TestRateLimit(t *testing.T) { repo := new(domain.MockRepositoryService) repo.On("ApiKeyRepository").Return(apiKeyRepo) apiKeyRepo.On("CreateApiKey", mock.Anything, mock.Anything).Return(nil) - service, _ := NewApiKeyService(ctx, "rootKey", repo) + service, _ := NewApiKeyService(ctx, "root", repo) + + err := service.AllowRequest("rate-limit-key", "test-service") + assert.Nil(t, err) + + err = service.AllowRequest("rate-limit-key", "test-service") + assert.Nil(t, err) - assert.True(t, service.AllowRequest("rate-limit-key", "test-service")) - assert.True(t, service.AllowRequest("rate-limit-key", "test-service")) // Exceeding the rate limit - assert.False(t, service.AllowRequest("rate-limit-key", "test-service")) + err = service.AllowRequest("rate-limit-key", "test-service") + assert.NotNil(t, err) + assert.Equal(t, ErrRateLimitExceeded, err) time.Sleep(6 * time.Second) // Rate limit should reset after the range - assert.True(t, service.AllowRequest("rate-limit-key", "test-service")) + err = service.AllowRequest("rate-limit-key", "test-service") + assert.Nil(t, err) } func TestRequestCount(t *testing.T) { apiKeyRepo := new(domain.MockApiKeyRepository) - apiKeyRepo.On("GetAllApiKeys", mock.Anything).Return(nil, nil) + keys := []domain.ApiKey{ + { + ID: "root", + IsRoot: true, + }, + } + apiKeyRepo.On("GetAllApiKeys", mock.Anything).Return(keys, nil) apiKeyRepo.On("CreateApiKey", mock.Anything, mock.Anything).Return(nil) repo := new(domain.MockRepositoryService) repo.On("ApiKeyRepository").Return(apiKeyRepo) apiKeyRepo.On("CreateApiKey", mock.Anything, mock.Anything).Return(nil) - service, err := NewApiKeyService(ctx, "rootKey", repo) + service, err := NewApiKeyService(ctx, "root", repo) if err != nil { t.Fatalf("Error initializing the service: %v", err) } @@ -128,16 +161,19 @@ func TestRequestCount(t *testing.T) { RequestsPerRange: 5, RangeInSeconds: 3, } - apiKey, err := service.CreateApiKey(context.Background(), keyReq) + apiKey, err := service.CreateApiKey(context.Background(), "root", keyReq) assert.NoError(t, err, "Error creating API key") // Use the key to its limit for i := 0; i < 5; i++ { - assert.True(t, service.AllowRequest(apiKey, "test"), "Expected request to be allowed") + err = service.AllowRequest(apiKey, "test") + assert.Nil(t, err) } // This request should be denied, as it exceeds the limit - assert.False(t, service.AllowRequest(apiKey, "test"), "Expected request to be denied") + err = service.AllowRequest(apiKey, "test") + assert.NotNil(t, err) + assert.Equal(t, ErrRateLimitExceeded, err) // Fetch the key to check the request count keyInfo, exists := service.(*apiKeyService).getKey(apiKey) @@ -147,14 +183,18 @@ func TestRequestCount(t *testing.T) { time.Sleep(3 * time.Second) // Now it should allow requests again - assert.True(t, service.AllowRequest(apiKey, "test"), "Expected request to be allowed after rate limit reset") + err = service.AllowRequest(apiKey, "test") + assert.Nil(t, err) keyInfo, exists = service.(*apiKeyService).getKey(apiKey) assert.True(t, exists, "API key should exist") assert.Equal(t, 1, keyInfo.requestCount, "Request count should not increment after limit") for i := 0; i < 4; i++ { - assert.True(t, service.AllowRequest(apiKey, "test"), "Expected request to be allowed") + err = service.AllowRequest(apiKey, "test") + assert.Nil(t, err) } - assert.False(t, service.AllowRequest(apiKey, "test"), "Expected request to be denied") + err = service.AllowRequest(apiKey, "test") + assert.NotNil(t, err) + assert.Equal(t, ErrRateLimitExceeded, err) } diff --git a/auth/internal/interface/http/handler/auth_handler.go b/auth/internal/interface/http/handler/auth_handler.go index 0eadf87..952355a 100644 --- a/auth/internal/interface/http/handler/auth_handler.go +++ b/auth/internal/interface/http/handler/auth_handler.go @@ -50,6 +50,8 @@ func (a *authHandler) LogIn(c *gin.Context) { } func (a *authHandler) CreateApiKey(c *gin.Context) { + apiKey := c.GetHeader("Authorization") + var req CreateApiKey if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{ @@ -58,7 +60,7 @@ func (a *authHandler) CreateApiKey(c *gin.Context) { return } - id, err := a.apiKeySvc.CreateApiKey(c, ToAppCreateApiKeyInfo(req)) + id, err := a.apiKeySvc.CreateApiKey(c, apiKey, ToAppCreateApiKeyInfo(req)) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": err.Error(), @@ -72,9 +74,10 @@ func (a *authHandler) CreateApiKey(c *gin.Context) { } func (a *authHandler) GetServiceApiKey(c *gin.Context) { + apiKey := c.GetHeader("Authorization") service := c.Param("service") - apiKey, err := a.apiKeySvc.GetServiceApiKey(c, service) + apiKey, err := a.apiKeySvc.GetServiceApiKey(c, apiKey, service) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": err.Error(), diff --git a/auth/internal/interface/http/server.go b/auth/internal/interface/http/server.go index 6489ed0..2e658a8 100644 --- a/auth/internal/interface/http/server.go +++ b/auth/internal/interface/http/server.go @@ -121,7 +121,7 @@ func (s *server) Router() http.Handler { c.Next() }) - ginEngine.POST("/auth/login", s.authHandler.LogIn) + ginEngine.GET("/auth/login", s.authHandler.LogIn) ginEngine.GET("/auth/verify", s.authHandler.IsRequestAllowed) ginEngine.POST("/auth/api-key", s.authHandler.CreateApiKey) ginEngine.GET("/auth/api-key/service", s.authHandler.GetServiceApiKey) From 7da44a271580e55e6324450af832cd2cfa9c94f6 Mon Sep 17 00:00:00 2001 From: sekulicd Date: Wed, 25 Oct 2023 12:12:57 +0200 Subject: [PATCH 29/39] fix --- controller/cmd/controllerd/main.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/controller/cmd/controllerd/main.go b/controller/cmd/controllerd/main.go index 1ed38a7..c653397 100644 --- a/controller/cmd/controllerd/main.go +++ b/controller/cmd/controllerd/main.go @@ -210,6 +210,7 @@ func restartServicesWithTls(domain string, services []string, premServices map[s return fmt.Errorf("failed to create docker client: %v", err) } + log.Infof("Restarting internal services: %v", services) for _, v := range services { switch v { case premappService: @@ -266,6 +267,7 @@ func restartServicesWithTls(domain string, services []string, premServices map[s log.Infof("Restarted container %s\n", v) } + log.Infof("Restarting internal services: %v", premServices) for k, v := range premServices { labels := map[string]string{ "traefik.enable": "true", From 17c6aba0efcb3e6936da0a3d877cd7d246e42746 Mon Sep 17 00:00:00 2001 From: sekulicd Date: Wed, 25 Oct 2023 12:25:11 +0200 Subject: [PATCH 30/39] fix --- controller/cmd/controllerd/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controller/cmd/controllerd/main.go b/controller/cmd/controllerd/main.go index c653397..6850793 100644 --- a/controller/cmd/controllerd/main.go +++ b/controller/cmd/controllerd/main.go @@ -30,7 +30,7 @@ const ( var ( letEncryptProd bool - services = []string{premappService, authdService, dnsdService} + services = []string{premappService, premdService, authdService, dnsdService} ) func main() { From ec167c476bdb7fc7e12aa3089d1e81efe9fd2840 Mon Sep 17 00:00:00 2001 From: sekulicd Date: Wed, 25 Oct 2023 12:39:55 +0200 Subject: [PATCH 31/39] fix --- controller/cmd/controllerd/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controller/cmd/controllerd/main.go b/controller/cmd/controllerd/main.go index 6850793..bd0d102 100644 --- a/controller/cmd/controllerd/main.go +++ b/controller/cmd/controllerd/main.go @@ -255,7 +255,7 @@ func restartServicesWithTls(domain string, services []string, premServices map[s fmt.Sprintf("traefik.http.routers.%s.rule", v): fmt.Sprintf("PathPrefix(`/`) && Host(`%s.%s`)", v, domain), fmt.Sprintf("traefik.http.routers.%s.entrypoints", v): "websecure", fmt.Sprintf("traefik.http.routers.%s.tls.certresolver", v): "myresolver", - fmt.Sprintf("traefik.http.routers.%s.middlewares", v): "auth,dnsd-strip-prefix", + fmt.Sprintf("traefik.http.routers.%s.middlewares", v): "auth", "traefik.http.middlewares.auth.forwardauth.address": "http://authd:8080/auth/verify", } From e92fb1462adae2fe85c968aec42648091ac3198c Mon Sep 17 00:00:00 2001 From: sekulicd Date: Wed, 25 Oct 2023 12:57:56 +0200 Subject: [PATCH 32/39] fix --- auth/internal/interface/http/handler/auth_handler.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/auth/internal/interface/http/handler/auth_handler.go b/auth/internal/interface/http/handler/auth_handler.go index 952355a..7a43b4a 100644 --- a/auth/internal/interface/http/handler/auth_handler.go +++ b/auth/internal/interface/http/handler/auth_handler.go @@ -94,10 +94,12 @@ func (a *authHandler) IsRequestAllowed(c *gin.Context) { apiKey := c.GetHeader("Authorization") uri := c.GetHeader("X-Forwarded-Uri") forwardedFor := c.GetHeader("X-Forwarded-For") + host := c.GetHeader("X-Forwarded-Host") log.Infof("Authorization header: %s", apiKey) log.Infof("X-Forwarded-Uri header: %s", uri) log.Infof("X-Forwarded-For header: %s", forwardedFor) + log.Infof("X-Forwarded-Host header: %s", host) service := extractService(forwardedFor, uri) if service == "" { From f889e81ec7956e10af8002a40e609ea5ff7ed40a Mon Sep 17 00:00:00 2001 From: sekulicd Date: Wed, 25 Oct 2023 13:14:00 +0200 Subject: [PATCH 33/39] fix --- .../interface/http/handler/auth_handler.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/auth/internal/interface/http/handler/auth_handler.go b/auth/internal/interface/http/handler/auth_handler.go index 7a43b4a..b61b876 100644 --- a/auth/internal/interface/http/handler/auth_handler.go +++ b/auth/internal/interface/http/handler/auth_handler.go @@ -3,10 +3,10 @@ package httphandler import ( "github.com/gin-gonic/gin" log "github.com/sirupsen/logrus" - "net" "net/http" "net/url" "prem-gateway/auth/internal/core/application" + "regexp" "strings" ) @@ -93,15 +93,13 @@ func (a *authHandler) GetServiceApiKey(c *gin.Context) { func (a *authHandler) IsRequestAllowed(c *gin.Context) { apiKey := c.GetHeader("Authorization") uri := c.GetHeader("X-Forwarded-Uri") - forwardedFor := c.GetHeader("X-Forwarded-For") host := c.GetHeader("X-Forwarded-Host") log.Infof("Authorization header: %s", apiKey) log.Infof("X-Forwarded-Uri header: %s", uri) - log.Infof("X-Forwarded-For header: %s", forwardedFor) log.Infof("X-Forwarded-Host header: %s", host) - service := extractService(forwardedFor, uri) + service := extractService(host, uri) if service == "" { c.JSON(http.StatusUnauthorized, gin.H{ "error": ErrServiceNotFound, @@ -134,7 +132,7 @@ func extractService(host string, uri string) string { } path := parsedUri.Path - if net.ParseIP(host) == nil { + if !isValidIP(host) { parts := strings.Split(host, ".") if len(parts) > 1 { return parts[0] @@ -148,3 +146,9 @@ func extractService(host string, uri string) string { return "" } + +func isValidIP(ip string) bool { + regex := `^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$` + match, _ := regexp.MatchString(regex, ip) + return match +} From e64e90c152f7f9a8fbf758b4763b030e6c5197e1 Mon Sep 17 00:00:00 2001 From: sekulicd Date: Wed, 25 Oct 2023 13:30:55 +0200 Subject: [PATCH 34/39] fix --- auth/internal/core/application/api_key_service.go | 1 + .../infrastructure/storage/pg/api_key_repository_impl.go | 6 ++++-- auth/internal/interface/http/handler/auth_handler.go | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/auth/internal/core/application/api_key_service.go b/auth/internal/core/application/api_key_service.go index fd41305..e79768b 100644 --- a/auth/internal/core/application/api_key_service.go +++ b/auth/internal/core/application/api_key_service.go @@ -143,6 +143,7 @@ func (a *apiKeyService) GetServiceApiKey( return "", ErrUnauthorized } + log.Infof("Fetching API key for service %s", service) apiKey, err := a.repositorySvc.ApiKeyRepository().GetServiceApiKey(ctx, service) if err != nil { return "", err diff --git a/auth/internal/infrastructure/storage/pg/api_key_repository_impl.go b/auth/internal/infrastructure/storage/pg/api_key_repository_impl.go index c275f81..c1fd528 100644 --- a/auth/internal/infrastructure/storage/pg/api_key_repository_impl.go +++ b/auth/internal/infrastructure/storage/pg/api_key_repository_impl.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "github.com/jackc/pgconn" + log "github.com/sirupsen/logrus" "prem-gateway/auth/internal/core/domain" "prem-gateway/auth/internal/infrastructure/storage/pg/sqlc/queries" ) @@ -132,16 +133,17 @@ func (a *apiKeyRepositoryImpl) GetAllApiKeys(ctx context.Context) ([]domain.ApiK } func (a *apiKeyRepositoryImpl) GetServiceApiKey( - ctx context.Context, service string, + ctx context.Context, serviceName string, ) (*domain.ApiKey, error) { apiKeysRows, err := a.querier.GetApiKeyForServiceName(ctx, sql.NullString{ - String: service, + String: serviceName, Valid: true, }) if err != nil { return nil, err } + log.Infof("Found %d API keys for service %s", len(apiKeysRows), serviceName) if len(apiKeysRows) == 0 { return nil, domain.ErrApiKeyNotFound } diff --git a/auth/internal/interface/http/handler/auth_handler.go b/auth/internal/interface/http/handler/auth_handler.go index b61b876..444f8c5 100644 --- a/auth/internal/interface/http/handler/auth_handler.go +++ b/auth/internal/interface/http/handler/auth_handler.go @@ -75,9 +75,9 @@ func (a *authHandler) CreateApiKey(c *gin.Context) { func (a *authHandler) GetServiceApiKey(c *gin.Context) { apiKey := c.GetHeader("Authorization") - service := c.Param("service") + serviceName := c.Param("name") - apiKey, err := a.apiKeySvc.GetServiceApiKey(c, apiKey, service) + apiKey, err := a.apiKeySvc.GetServiceApiKey(c, apiKey, serviceName) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": err.Error(), From 7af218426207885a8e8871f41c6b5de66cea1e2a Mon Sep 17 00:00:00 2001 From: sekulicd Date: Wed, 25 Oct 2023 13:38:31 +0200 Subject: [PATCH 35/39] fix --- auth/internal/core/application/api_key_service.go | 1 - .../infrastructure/storage/pg/api_key_repository_impl.go | 2 -- auth/internal/interface/http/handler/auth_handler.go | 2 +- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/auth/internal/core/application/api_key_service.go b/auth/internal/core/application/api_key_service.go index e79768b..fd41305 100644 --- a/auth/internal/core/application/api_key_service.go +++ b/auth/internal/core/application/api_key_service.go @@ -143,7 +143,6 @@ func (a *apiKeyService) GetServiceApiKey( return "", ErrUnauthorized } - log.Infof("Fetching API key for service %s", service) apiKey, err := a.repositorySvc.ApiKeyRepository().GetServiceApiKey(ctx, service) if err != nil { return "", err diff --git a/auth/internal/infrastructure/storage/pg/api_key_repository_impl.go b/auth/internal/infrastructure/storage/pg/api_key_repository_impl.go index c1fd528..6002263 100644 --- a/auth/internal/infrastructure/storage/pg/api_key_repository_impl.go +++ b/auth/internal/infrastructure/storage/pg/api_key_repository_impl.go @@ -4,7 +4,6 @@ import ( "context" "database/sql" "github.com/jackc/pgconn" - log "github.com/sirupsen/logrus" "prem-gateway/auth/internal/core/domain" "prem-gateway/auth/internal/infrastructure/storage/pg/sqlc/queries" ) @@ -143,7 +142,6 @@ func (a *apiKeyRepositoryImpl) GetServiceApiKey( return nil, err } - log.Infof("Found %d API keys for service %s", len(apiKeysRows), serviceName) if len(apiKeysRows) == 0 { return nil, domain.ErrApiKeyNotFound } diff --git a/auth/internal/interface/http/handler/auth_handler.go b/auth/internal/interface/http/handler/auth_handler.go index 444f8c5..c6b6db5 100644 --- a/auth/internal/interface/http/handler/auth_handler.go +++ b/auth/internal/interface/http/handler/auth_handler.go @@ -75,7 +75,7 @@ func (a *authHandler) CreateApiKey(c *gin.Context) { func (a *authHandler) GetServiceApiKey(c *gin.Context) { apiKey := c.GetHeader("Authorization") - serviceName := c.Param("name") + serviceName := c.Query("name") apiKey, err := a.apiKeySvc.GetServiceApiKey(c, apiKey, serviceName) if err != nil { From 8652ff2a3b9a3b41f081fdec22d02ddcccefe381 Mon Sep 17 00:00:00 2001 From: sekulicd Date: Wed, 25 Oct 2023 14:50:05 +0200 Subject: [PATCH 36/39] auth readme --- auth/README.md | 56 +++++++++++++++++++++++++++++++++++++++--- auth/cmd/authd/main.go | 8 ++---- 2 files changed, 54 insertions(+), 10 deletions(-) diff --git a/auth/README.md b/auth/README.md index 3d8726b..ed3aae1 100644 --- a/auth/README.md +++ b/auth/README.md @@ -1,6 +1,54 @@ -# Auth Daemon +# Auth Daemon Microservice ## Description -Auth Daemon is a microservice which provides api key authentication. -Calls coming to prem-gateway are routed by traefik forward-auth middleware to auth daemon. -Auth daemon checks if the api key is valid and if it is, it forwards the request to the appropriate service. \ No newline at end of file +Auth Daemon is a microservice designed to provide API key authentication. +Incoming calls to the `prem-gateway` are rerouted by the traefik forward-auth middleware to the Auth Daemon. +Once here, the Auth Daemon verifies the API key's validity. +If the key is found to be valid, the request is forwarded to the appropriate service. + +## Exposed Paths + +### 1. Login +- **Path**: `/auth/login` +- **Method**: `GET` +- **Description**: Endpoint for logging in. +- **Query Parameters**: + - `user`: The username. + - `pass`: The password. +- **Response**: + - `200 OK`: Contains the root `api_key`. + - `401 Unauthorized`: Contains error message. + +### 2. Verify Request +- **Path**: `/auth/verify` +- **Method**: `GET` +- **Description**: Verifies if the request is allowed. +- **Headers**: + - `Authorization`: The root API key. +- **Response**: + - `200 OK`: If the request is authorized. + - `401 Unauthorized`: Contains error message. + +### 3. Create API Key +- **Path**: `/auth/api-key` +- **Method**: `POST` +- **Description**: Endpoint to create a new API key. +- **Headers**: + - `Authorization`: The root API key. +- **Body**: JSON object. +- **Response**: + - `201 Created`: Contains the `api_key`. + - `400 Bad Request`: Contains error message. + - `500 Internal Server Error`: Contains error message. + +### 4. Get Service API Key +- **Path**: `/auth/api-key/service` +- **Method**: `GET` +- **Description**: Retrieves the API key for a given service. +- **Headers**: + - `Authorization`: The root API key. +- **Query Parameters**: + - `name`: The service name. +- **Response**: + - `200 OK`: Contains the `api_key`. + - `500 Internal Server Error`: Contains error message. \ No newline at end of file diff --git a/auth/cmd/authd/main.go b/auth/cmd/authd/main.go index 0a76993..00a75bf 100644 --- a/auth/cmd/authd/main.go +++ b/auth/cmd/authd/main.go @@ -5,15 +5,12 @@ import ( log "github.com/sirupsen/logrus" "os" "os/signal" - //_ "prem-gateway/auth/docs" "prem-gateway/auth/internal/config" pgdb "prem-gateway/auth/internal/infrastructure/storage/pg" httpauthd "prem-gateway/auth/internal/interface/http" "syscall" ) -// @title Dns Daemon API -// @description DNS Daemon is designed to manage Domain Name System (DNS) records.
It exposes a RESTful API that allows for the creation, modification, retrieval, and deletion of DNS information, as well as checking the status of a DNS entry.
The DNS information includes attributes such as domain, subdomain, A records, and node names. func main() { if err := config.LoadConfig(); err != nil { log.Fatalf("failed to load config: %s", err) @@ -53,8 +50,7 @@ func main() { } } -//TODO add swagger, check dnsd swagger -//add more comments in general to auth +//TODO +//e2e test //add http tests -//rename NewDBService in dns, check mock //remove basic auth From a6f362acb137b2d375548b75dd9210bf5a3a1b67 Mon Sep 17 00:00:00 2001 From: sekulicd Date: Thu, 26 Oct 2023 13:06:46 +0200 Subject: [PATCH 37/39] update e2e test --- e2e/{dns_test.go => e2e_test.go} | 126 +++++++++++++++++++++++++------ script/stop_all.sh | 3 - 2 files changed, 104 insertions(+), 25 deletions(-) rename e2e/{dns_test.go => e2e_test.go} (57%) diff --git a/e2e/dns_test.go b/e2e/e2e_test.go similarity index 57% rename from e2e/dns_test.go rename to e2e/e2e_test.go index e47a0ac..e61b27a 100644 --- a/e2e/dns_test.go +++ b/e2e/e2e_test.go @@ -12,8 +12,12 @@ import ( "time" ) -// TestDnsFeature is an end-to-end test with purpose of documenting DNS/TLS -// feature and traefik routing. +var ( + client = &http.Client{} +) + +// TestE2eFeatures is an end-to-end test with purpose of documenting DNS/TLS/Auth +// features and traefik routing. // First it queries premd service to get all services and their base urls and it // shows how traefik routes requests to by path. // Then it creates DNS record for domain and it shows how traefik routes requests @@ -25,18 +29,55 @@ import ( // Eg. considering domain example.com and prem-gateway IP address // 1. Create A record for example.com with value prem-gateway IP address // 2. Create A record for *.example.com with value prem-gateway IP address -func TestDnsFeature(t *testing.T) { +// Env. variables: +// PREM_GATEWAY_IP - IP address of prem-gateway +// USER_NAME - username for basic auth +// PASSWORD - password for basic auth +// DOMAIN - domain for DNS record +// EMAIL - email for DNS record +func TestE2eFeatures(t *testing.T) { premGatewayIP := os.Getenv("PREM_GATEWAY_IP") if premGatewayIP == "" { t.Fatal("PREM_GATEWAY_IP environment variable not set") } - servicesUrls := make([]ExtractedFields, 0) + userName := os.Getenv("USER_NAME") + if userName == "" { + t.Fatal("USER_NAME environment variable not set") + } + + password := os.Getenv("PASSWORD") + if password == "" { + t.Fatal("PASSWORD environment variable not set") + } - resp, err := http.Get(fmt.Sprintf("http://%s/%s", premGatewayIP, "premd/v1/services/")) + //GET ROOT API KEY + resp, err := http.Get( + fmt.Sprintf( + "http://%s/%s?user=%s&pass=%s", + premGatewayIP, + "authd/auth/login", + userName, + password, + ), + ) + assert.NoError(t, err) + apiKey := make(map[string]string) + err = json.NewDecoder(resp.Body).Decode(&apiKey) assert.NoError(t, err) assert.Equal(t, http.StatusOK, resp.StatusCode) + rootApiKey := apiKey["api_key"] + assert.NotEmpty(t, rootApiKey) + servicesUrls := make([]ExtractedFields, 0) + + url := fmt.Sprintf("http://%s/%s", premGatewayIP, "premd/v1/services/") + req, err := http.NewRequest("GET", url, nil) + assert.NoError(t, err) + req.Header.Add("Authorization", rootApiKey) + resp, err = client.Do(req) + assert.NoError(t, err) + assert.Equal(t, http.StatusOK, resp.StatusCode) err = json.NewDecoder(resp.Body).Decode(&servicesUrls) assert.NoError(t, err) @@ -44,10 +85,13 @@ func TestDnsFeature(t *testing.T) { assert.Equal(t, v.BaseUrl, fmt.Sprintf("http://%s/%s", premGatewayIP, v.ServiceId)) } - resp, err = http.Get(fmt.Sprintf("http://%s/%s", premGatewayIP, "dnsd/dns/existing")) + url = fmt.Sprintf("http://%s/%s", premGatewayIP, "dnsd/dns/existing") + req, err = http.NewRequest("GET", url, nil) + assert.NoError(t, err) + req.Header.Add("Authorization", rootApiKey) + resp, err = client.Do(req) assert.NoError(t, err) assert.Equal(t, http.StatusOK, resp.StatusCode) - bodyBytes, err := io.ReadAll(resp.Body) resp.Body.Close() assert.Equal(t, string(bodyBytes), "null") @@ -72,21 +116,24 @@ func TestDnsFeature(t *testing.T) { jsonValue, err := json.Marshal(dnsCreateReq) assert.NoError(t, err) - resp, err = http.Post( - fmt.Sprintf("http://%s/%s", premGatewayIP, "dnsd/dns"), - "application/json", - bytes.NewBuffer(jsonValue), - ) + url = fmt.Sprintf("http://%s/%s", premGatewayIP, "dnsd/dns") + req, err = http.NewRequest("POST", url, bytes.NewBuffer(jsonValue)) + assert.NoError(t, err) + req.Header.Add("Authorization", rootApiKey) + resp, err = client.Do(req) assert.NoError(t, err) assert.Equal(t, http.StatusCreated, resp.StatusCode) time.Sleep(10 * time.Second) // Wait for controller to restart services // GET PREMD SERVICES VIA SUBDOMAIN - resp, err = http.Get(fmt.Sprintf("https://%s.%s/%s", "premd", dnsCreateReq.Domain, "v1/services/")) + url = fmt.Sprintf("https://%s.%s/%s", "premd", dnsCreateReq.Domain, "v1/services/") + req, err = http.NewRequest("GET", url, nil) + assert.NoError(t, err) + req.Header.Add("Authorization", rootApiKey) + resp, err = client.Do(req) assert.NoError(t, err) assert.Equal(t, http.StatusOK, resp.StatusCode) - servicesUrls = make([]ExtractedFields, 0) err = json.NewDecoder(resp.Body).Decode(&servicesUrls) assert.NoError(t, err) @@ -95,10 +142,13 @@ func TestDnsFeature(t *testing.T) { assert.Equal(t, v.BaseUrl, fmt.Sprintf("https://%s.%s", v.ServiceId, dnsCreateReq.Domain)) } - resp, err = http.Get(fmt.Sprintf("https://%s.%s/%s", "dnsd", dnsCreateReq.Domain, "dns/existing")) + url = fmt.Sprintf("https://%s.%s/%s", "dnsd", dnsCreateReq.Domain, "dns/existing") + req, err = http.NewRequest("GET", url, nil) + assert.NoError(t, err) + req.Header.Add("Authorization", rootApiKey) + resp, err = client.Do(req) assert.NoError(t, err) assert.Equal(t, http.StatusOK, resp.StatusCode) - checkDns := DnsInfo{} bodyBytes, err = io.ReadAll(resp.Body) resp.Body.Close() @@ -115,11 +165,11 @@ func TestDnsFeature(t *testing.T) { jsonValue, err = json.Marshal(runService) assert.NoError(t, err) - resp, err = http.Post( - fmt.Sprintf("https://%s.%s/%s", "premd", dnsCreateReq.Domain, "v1/run-service/"), - "application/json", - bytes.NewBuffer(jsonValue), - ) + url = fmt.Sprintf("https://%s.%s/%s", "premd", dnsCreateReq.Domain, "v1/run-service/") + req, err = http.NewRequest("POST", url, bytes.NewBuffer(jsonValue)) + assert.NoError(t, err) + req.Header.Add("Authorization", rootApiKey) + resp, err = client.Do(req) assert.NoError(t, err) assert.Equal(t, http.StatusOK, resp.StatusCode) @@ -151,7 +201,33 @@ func TestDnsFeature(t *testing.T) { bytes.NewBuffer(jsonValue), ) assert.NoError(t, err) - assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.Equal(t, http.StatusUnauthorized, resp.StatusCode) + + createApiKeyReq := CreateApiKey{ + ServiceName: "gpt4all-lora-q4", + RequestsPerRange: 10, + RangeInMinutes: 1, + } + + jsonValue, err = json.Marshal(createApiKeyReq) + assert.NoError(t, err) + url = fmt.Sprintf("https://%s.%s/%s", "authd", dnsCreateReq.Domain, "auth/api-key") + req, err = http.NewRequest("POST", url, bytes.NewBuffer(jsonValue)) + assert.NoError(t, err) + req.Header.Add("Authorization", rootApiKey) + resp, err = client.Do(req) + assert.Equal(t, http.StatusCreated, resp.StatusCode) + apiKey = make(map[string]string) + err = json.NewDecoder(resp.Body).Decode(&apiKey) + assert.NoError(t, err) + assert.Equal(t, http.StatusCreated, resp.StatusCode) + serviceApiKey := apiKey["api_key"] + + url = fmt.Sprintf("https://%s.%s/%s", "gpt4all-lora-q4", dnsCreateReq.Domain, "v1/chat/completions") + req, err = http.NewRequest("POST", url, bytes.NewBuffer(jsonValue)) + assert.NoError(t, err) + req.Header.Add("Authorization", serviceApiKey) + resp, err = client.Do(req) bodyBytes, err = io.ReadAll(resp.Body) resp.Body.Close() t.Log(string(bodyBytes)) @@ -191,3 +267,9 @@ type Message struct { Role string `json:"role"` Content string `json:"content"` } + +type CreateApiKey struct { + ServiceName string `json:"service_name"` + RequestsPerRange int `json:"requests_per_range"` + RangeInMinutes int `json:"range_in_minutes"` +} diff --git a/script/stop_all.sh b/script/stop_all.sh index dab449c..ca44961 100644 --- a/script/stop_all.sh +++ b/script/stop_all.sh @@ -1,8 +1,5 @@ #!/bin/bash -# Ensure the script stops on first error -set -e - # Get all container IDs connected to the 'prem-gateway' network CONTAINERS=($(docker ps -aq --filter network=prem-gateway)) From 43e699cde08208fd95907373507f32a46db635cd Mon Sep 17 00:00:00 2001 From: sekulicd Date: Thu, 26 Oct 2023 13:11:35 +0200 Subject: [PATCH 38/39] remove basic auth --- Makefile | 1 - README.md | 4 +-- auth/cmd/authd/main.go | 2 -- controller/cmd/controllerd/main.go | 44 ------------------------------ script/docker-compose-box.yml | 2 -- script/run_all.sh | 4 --- 6 files changed, 2 insertions(+), 55 deletions(-) diff --git a/Makefile b/Makefile index d3fb005..a6197ce 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,6 @@ runall: chmod +x ./script/run_all.sh export PREMD_IMAGE=$(PREMD_IMAGE); \ export PREMAPP_IMAGE=$(PREMAPP_IMAGE); \ - export BASIC_AUTH_CREDENTIALS=$(BASIC_AUTH_CREDENTIALS); \ ./script/run_all.sh ## stopall: stop prem-gateway and prem-box diff --git a/README.md b/README.md index 008b369..e5f5b88 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,10 @@ It is responsible for routing requests from the frontend `prem-app` to either th ## Features - [x] API Gateway -- [ ] Authentication/Authorization +- [x] Authentication/Authorization - [x] Domain Management - [x] TLS -- [ ] Rate Limiting +- [x] Rate Limiting - [ ] Logging - [ ] Metrics diff --git a/auth/cmd/authd/main.go b/auth/cmd/authd/main.go index 00a75bf..ce94eea 100644 --- a/auth/cmd/authd/main.go +++ b/auth/cmd/authd/main.go @@ -51,6 +51,4 @@ func main() { } //TODO -//e2e test //add http tests -//remove basic auth diff --git a/controller/cmd/controllerd/main.go b/controller/cmd/controllerd/main.go index bd0d102..8070c8a 100644 --- a/controller/cmd/controllerd/main.go +++ b/controller/cmd/controllerd/main.go @@ -214,13 +214,6 @@ func restartServicesWithTls(domain string, services []string, premServices map[s for _, v := range services { switch v { case premappService: - basicAuthMiddlewareLabelKey, basicAuthMiddlewareLabelValue, basicAuthName, err := getPremServiceBasicAuthInfo(ctx, cli) - if err != nil { - return err - } - - // TODO handle restart of prem-gateway with dns exists - labels := map[string]string{ "traefik.enable": "true", "traefik.http.routers.premapp-http.rule": fmt.Sprintf("PathPrefix(`/`) && Host(`%s`)", domain), @@ -229,10 +222,7 @@ func restartServicesWithTls(domain string, services []string, premServices map[s "traefik.http.routers.premapp-https.entrypoints": "websecure", "traefik.http.routers.premapp-https.tls.certresolver": "myresolver", "traefik.http.middlewares.http-to-https.redirectscheme.scheme": "https", - "traefik.http.routers.premapp-http.middlewares": fmt.Sprintf("http-to-https, %s", basicAuthName), - "traefik.http.routers.premapp-https.middlewares": basicAuthName, "traefik.http.services.premapp.loadbalancer.server.port": "8080", - basicAuthMiddlewareLabelKey: basicAuthMiddlewareLabelValue, } if err := restartContainer(ctx, cli, v, labels, nil); err != nil { @@ -363,37 +353,3 @@ type PremService struct { SendTo string `json:"baseUrl"` } `json:"invokeMethod"` } - -func getPremServiceBasicAuthInfo( - ctx context.Context, cli *client.Client, -) (string, string, string, error) { - var ( - basicAuthMiddlewareLabelKey string - basicAuthMiddlewareLabelValue string - basicAuthName string - ) - - containerJson, err := cli.ContainerInspect(ctx, premappService) - if err != nil { - return "", "", "", fmt.Errorf("failed to inspect container %s: %v", premappService, err) - } - - for k, v := range containerJson.Config.Labels { - if strings.Contains(k, "basicauth") { - basicAuthMiddlewareLabelKey = k - basicAuthMiddlewareLabelValue = v - - parts := strings.Split(k, ".") - for i, part := range parts { - if part == "middlewares" && i+1 < len(parts) { - basicAuthName = parts[i+1] - break - } - } - - return basicAuthMiddlewareLabelKey, basicAuthMiddlewareLabelValue, basicAuthName, nil - } - } - - return "", "", "", nil -} diff --git a/script/docker-compose-box.yml b/script/docker-compose-box.yml index 070666b..e7e04cb 100644 --- a/script/docker-compose-box.yml +++ b/script/docker-compose-box.yml @@ -37,8 +37,6 @@ services: - "traefik.http.routers.premapp-http.rule=PathPrefix(`/`)" - "traefik.http.routers.premapp-http.entrypoints=web" - "traefik.http.services.premapp.loadbalancer.server.port=8080" - - "traefik.http.middlewares.mybasicauth.basicauth.users=${BASIC_AUTH_CREDENTIALS}" - - "traefik.http.routers.premapp-http.middlewares=mybasicauth" ports: - "8085:8080" restart: unless-stopped diff --git a/script/run_all.sh b/script/run_all.sh index f857b9a..2faa66e 100644 --- a/script/run_all.sh +++ b/script/run_all.sh @@ -34,10 +34,6 @@ then sudo apt-get install -y openssl fi -HASH=$(openssl passwd -apr1 $BASIC_AUTH_PASS) -BASIC_AUTH_CREDENTIALS="$BASIC_AUTH_USER:$HASH" -export BASIC_AUTH_CREDENTIALS - # Run the 'docker-compose' command with environment variables export PREMD_IMAGE export PREMAPP_IMAGE From e0a853178b60c74a72c51f2deaee5c9e6365fae9 Mon Sep 17 00:00:00 2001 From: sekulicd Date: Thu, 26 Oct 2023 14:06:25 +0200 Subject: [PATCH 39/39] auth http test --- auth/cmd/authd/main.go | 3 - .../interface/http/handler/auth_handler.go | 9 +- auth/test/http/router_test.go | 130 ++++++++++++++++++ 3 files changed, 136 insertions(+), 6 deletions(-) create mode 100644 auth/test/http/router_test.go diff --git a/auth/cmd/authd/main.go b/auth/cmd/authd/main.go index ce94eea..9d4659b 100644 --- a/auth/cmd/authd/main.go +++ b/auth/cmd/authd/main.go @@ -49,6 +49,3 @@ func main() { log.Panicf("prem-gateway auth daemon noticed error while running: %s", err) } } - -//TODO -//add http tests diff --git a/auth/internal/interface/http/handler/auth_handler.go b/auth/internal/interface/http/handler/auth_handler.go index c6b6db5..309161e 100644 --- a/auth/internal/interface/http/handler/auth_handler.go +++ b/auth/internal/interface/http/handler/auth_handler.go @@ -116,9 +116,12 @@ func (a *authHandler) IsRequestAllowed(c *gin.Context) { } if err := a.apiKeySvc.AllowRequest(apiKey, service); err != nil { - c.JSON(http.StatusUnauthorized, gin.H{ - "error": err.Error(), - }) + if err == application.ErrRateLimitExceeded { + c.JSON(http.StatusTooManyRequests, gin.H{}) + return + } + + c.JSON(http.StatusUnauthorized, gin.H{}) return } diff --git a/auth/test/http/router_test.go b/auth/test/http/router_test.go new file mode 100644 index 0000000..27100c3 --- /dev/null +++ b/auth/test/http/router_test.go @@ -0,0 +1,130 @@ +package http + +import ( + "bytes" + "encoding/json" + "fmt" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "net/http" + "net/http/httptest" + pgdb "prem-gateway/auth/internal/infrastructure/storage/pg" + authdhttp "prem-gateway/auth/internal/interface/http" + httphandler "prem-gateway/auth/internal/interface/http/handler" + testutil "prem-gateway/auth/test" + "testing" +) + +func TestRouter(t *testing.T) { + err := testutil.SetupDB() + require.NoError(t, err) + defer testutil.TruncateDB() + + svc, err := pgdb.NewRepoService(pgdb.DbConfig{ + DbUser: "root", + DbPassword: "secret", + DbHost: "127.0.0.1", + DbPort: 5432, + DbName: "authd-db-test", + MigrationSourceURL: "file://../.." + + "/internal/infrastructure/storage/pg/migration", + }) + require.NoError(t, err) + + rootKey := "root-key" + user := "user" + pass := "pass" + + serverAddress := ":8080" + authd, err := authdhttp.NewServer(serverAddress, svc, user, pass, rootKey) + require.NoError(t, err) + ginRouter := authd.Router() + + //LOGIN + w := httptest.NewRecorder() + url := fmt.Sprintf( + "http://localhost:8080/%s?user=%s&pass=%s", + "auth/login", + user, + pass, + ) + req, err := http.NewRequest(http.MethodGet, url, nil) + assert.NoError(t, err) + + ginRouter.ServeHTTP(w, req) + require.NoError(t, err) + + apiKey := make(map[string]string) + err = json.NewDecoder(w.Body).Decode(&apiKey) + rootApiKey := apiKey["api_key"] + assert.NotEmpty(t, rootApiKey) + assert.Equal(t, rootApiKey, rootKey) + + //CREATE API KEY + w = httptest.NewRecorder() + url = fmt.Sprintf( + "http://localhost:8080/%s", + "auth/api-key", + ) + + createApiReq := httphandler.CreateApiKey{ + Service: "service", + RequestsPerRange: 5, + RangeInMinutes: 1, + } + createApiReqBytes, err := json.Marshal(createApiReq) + require.NoError(t, err) + req, err = http.NewRequest( + http.MethodPost, url, bytes.NewBuffer(createApiReqBytes), + ) + assert.NoError(t, err) + req.Header.Add("Authorization", rootApiKey) + ginRouter.ServeHTTP(w, req) + require.NoError(t, err) + assert.Equal(t, http.StatusCreated, w.Code) + apiKey = make(map[string]string) + err = json.NewDecoder(w.Body).Decode(&apiKey) + serviceApiKey := apiKey["api_key"] + assert.NotEmpty(t, serviceApiKey) + + //GET API KEY + w = httptest.NewRecorder() + url = fmt.Sprintf( + "http://localhost:8080/%s", + fmt.Sprintf("auth/api-key/service?name=%s", createApiReq.Service), + ) + req, err = http.NewRequest(http.MethodGet, url, nil) + assert.NoError(t, err) + req.Header.Add("Authorization", rootApiKey) + ginRouter.ServeHTTP(w, req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, w.Code) + apiKey = make(map[string]string) + err = json.NewDecoder(w.Body).Decode(&apiKey) + apk := apiKey["api_key"] + assert.NotEmpty(t, serviceApiKey) + assert.Equal(t, serviceApiKey, apk) + + //VERIFY + w = httptest.NewRecorder() + url = fmt.Sprintf( + "http://localhost:8080/%s", + "auth/verify", + ) + req, err = http.NewRequest(http.MethodGet, url, nil) + assert.NoError(t, err) + req.Header.Add("Authorization", serviceApiKey) + req.Header.Add("X-Forwarded-Uri", "/service/v1/chat/completions") + req.Header.Add("X-Forwarded-Host", "1.1.1.1") + for i := 0; i < createApiReq.RequestsPerRange; i++ { + w = httptest.NewRecorder() + ginRouter.ServeHTTP(w, req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, w.Code) + } + + w = httptest.NewRecorder() + ginRouter.ServeHTTP(w, req) + require.NoError(t, err) + assert.Equal(t, http.StatusTooManyRequests, w.Code) +}