feat: initial commit
All checks were successful
Cadoles/bouncer/pipeline/head This commit looks good
All checks were successful
Cadoles/bouncer/pipeline/head This commit looks good
This commit is contained in:
commit
e66938f1d3
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
/.env
|
||||
/bin
|
||||
/dist
|
||||
/tools
|
||||
/.gitea-release
|
||||
/config.yml
|
||||
/admin-key.json
|
||||
/.bouncer-token
|
||||
/data
|
136
.goreleaser.yaml
Normal file
136
.goreleaser.yaml
Normal file
@ -0,0 +1,136 @@
|
||||
project_name: bouncer
|
||||
before:
|
||||
hooks:
|
||||
- go mod tidy
|
||||
- go generate ./...
|
||||
builds:
|
||||
- id: bouncer
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
ldflags:
|
||||
- -s
|
||||
- -w
|
||||
- -X 'main.GitRef={{ .Commit }}'
|
||||
- -X 'main.ProjectVersion={{ .Version }}'
|
||||
- -X 'main.BuildDate={{ .Date }}'
|
||||
- -X 'main.DefaultConfigPath=/etc/bouncer/config.yml'
|
||||
gcflags:
|
||||
- -trimpath="${PWD}"
|
||||
asmflags:
|
||||
- -trimpath="${PWD}"
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
- "386"
|
||||
main: ./cmd/bouncer
|
||||
archives:
|
||||
- id: bouncer
|
||||
builds: ["bouncer"]
|
||||
name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
||||
files:
|
||||
- README.md
|
||||
- misc/packaging/common/config.yml
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
snapshot:
|
||||
name_template: "{{ .Version }}"
|
||||
changelog:
|
||||
sort: asc
|
||||
filters:
|
||||
exclude:
|
||||
- '^docs:'
|
||||
- '^test:'
|
||||
nfpms:
|
||||
- id: bouncer-bin
|
||||
builds:
|
||||
- "bouncer"
|
||||
package_name: bouncer-bin
|
||||
homepage: https://forge.cadoles.com/Cadoles/bouncer
|
||||
maintainer: Cadoles <contact@cadoles.com>
|
||||
description: |-
|
||||
reverse proxy server with dynamic queuing management - binaries
|
||||
license: AGPL-3.0
|
||||
formats:
|
||||
- apk
|
||||
- deb
|
||||
- rpm
|
||||
contents:
|
||||
- src: misc/packaging/common/config.yml
|
||||
dst: /etc/bouncer/config.yml
|
||||
type: config
|
||||
- id: bouncer-admin
|
||||
meta: true
|
||||
package_name: bouncer-admin
|
||||
homepage: https://forge.cadoles.com/Cadoles/bouncer
|
||||
maintainer: Cadoles <contact@cadoles.com>
|
||||
dependencies:
|
||||
- bouncer-bin
|
||||
description: |-
|
||||
reverse proxy server with dynamic queuing management - administration service
|
||||
license: AGPL-3.0
|
||||
formats:
|
||||
- apk
|
||||
- deb
|
||||
- rpm
|
||||
contents:
|
||||
- src: misc/packaging/systemd/bouncer-admin.systemd.service
|
||||
dst: /usr/lib/systemd/system/bouncer-admin.service
|
||||
packager: deb
|
||||
- src: misc/packaging/systemd/bouncer-admin.systemd.service
|
||||
dst: /usr/lib/systemd/system/bouncer-admin.service
|
||||
packager: rpm
|
||||
- src: misc/packaging/openrc/bouncer-admin.openrc.sh
|
||||
dst: /etc/init.d/bouncer-admin
|
||||
file_info:
|
||||
mode: 0755
|
||||
packager: apk
|
||||
- dst: /usr/share/bouncer
|
||||
type: dir
|
||||
file_info:
|
||||
mode: 0700
|
||||
- dst: /var/log/bouncer
|
||||
type: dir
|
||||
file_info:
|
||||
mode: 0700
|
||||
packager: apk
|
||||
scripts:
|
||||
postinstall: "misc/packaging/common/postinstall-bouncer-admin.sh"
|
||||
- id: bouncer-proxy
|
||||
meta: true
|
||||
dependencies:
|
||||
- bouncer-bin
|
||||
package_name: bouncer-proxy
|
||||
homepage: https://forge.cadoles.com/Cadoles/bouncer
|
||||
maintainer: Cadoles <contact@cadoles.com>
|
||||
description: |-
|
||||
reverse proxy server with dynamic queuing management - proxy service
|
||||
license: AGPL-3.0
|
||||
formats:
|
||||
- apk
|
||||
- deb
|
||||
- rpm
|
||||
contents:
|
||||
- src: misc/packaging/systemd/bouncer-proxy.systemd.service
|
||||
dst: /usr/lib/systemd/system/bouncer-proxy.service
|
||||
packager: deb
|
||||
- src: misc/packaging/systemd/bouncer-proxy.systemd.service
|
||||
dst: /usr/lib/systemd/system/bouncer-proxy.service
|
||||
packager: rpm
|
||||
- src: misc/packaging/openrc/bouncer-proxy.openrc.sh
|
||||
dst: /etc/init.d/bouncer-proxy
|
||||
file_info:
|
||||
mode: 0755
|
||||
packager: apk
|
||||
- dst: /usr/share/bouncer
|
||||
type: dir
|
||||
file_info:
|
||||
mode: 0700
|
||||
- dst: /var/log/bouncer
|
||||
type: dir
|
||||
file_info:
|
||||
mode: 0700
|
||||
packager: apk
|
||||
scripts:
|
||||
postinstall: "misc/packaging/common/postinstall-bouncer-proxy.sh"
|
30
Dockerfile
Normal file
30
Dockerfile
Normal file
@ -0,0 +1,30 @@
|
||||
FROM golang:1.19 AS BUILD
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y make
|
||||
|
||||
COPY . /src
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
RUN make GORELEASER_ARGS='build --rm-dist --single-target --snapshot' goreleaser
|
||||
|
||||
FROM busybox:latest AS RUNTIME
|
||||
|
||||
ARG DUMB_INIT_VERSION=1.2.5
|
||||
|
||||
RUN mkdir -p /usr/local/bin \
|
||||
&& wget -O /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v${DUMB_INIT_VERSION}/dumb-init_${DUMB_INIT_VERSION}_x86_64 \
|
||||
&& chmod +x /usr/local/bin/dumb-init
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/dumb-init", "--"]
|
||||
|
||||
COPY --from=BUILD /src/dist/bouncer_linux_amd64_v1 /app
|
||||
COPY --from=BUILD /src/config.yml /etc/bouncer/config.yml
|
||||
|
||||
EXPOSE 8080
|
||||
EXPOSE 8081
|
||||
|
||||
ENTRYPOINT ["/app/bouncer"]
|
||||
|
||||
CMD ["bouncer", "run", "-c", "/etc/bouncer/config.yml"]
|
60
Jenkinsfile
vendored
Normal file
60
Jenkinsfile
vendored
Normal file
@ -0,0 +1,60 @@
|
||||
@Library('cadoles') _
|
||||
|
||||
pipeline {
|
||||
agent {
|
||||
dockerfile {
|
||||
label 'docker'
|
||||
filename 'Dockerfile'
|
||||
dir 'misc/jenkins'
|
||||
args '-v /var/run/docker.sock:/var/run/docker.sock --network host'
|
||||
}
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('Cancel older jobs') {
|
||||
steps {
|
||||
script {
|
||||
def buildNumber = env.BUILD_NUMBER as int
|
||||
if (buildNumber > 1) milestone(buildNumber - 1)
|
||||
milestone(buildNumber)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Run unit tests') {
|
||||
steps {
|
||||
script {
|
||||
sh 'make test'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Release') {
|
||||
when {
|
||||
anyOf {
|
||||
branch 'master'
|
||||
branch 'develop'
|
||||
}
|
||||
}
|
||||
steps {
|
||||
script {
|
||||
withCredentials([
|
||||
usernamePassword([
|
||||
credentialsId: 'forge-jenkins',
|
||||
usernameVariable: 'GITEA_RELEASE_USERNAME',
|
||||
passwordVariable: 'GITEA_RELEASE_PASSWORD'
|
||||
])
|
||||
]) {
|
||||
sh 'make gitea-release'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post {
|
||||
always {
|
||||
cleanWs()
|
||||
}
|
||||
}
|
||||
}
|
142
Makefile
Normal file
142
Makefile
Normal file
@ -0,0 +1,142 @@
|
||||
LINT_ARGS ?= --timeout 5m
|
||||
GORELEASER_VERSION ?= v1.13.1
|
||||
GORELEASER_ARGS ?= release --snapshot --rm-dist
|
||||
GITCHLOG_ARGS ?=
|
||||
SHELL := /bin/bash
|
||||
|
||||
BOUNCER_VERSION ?=
|
||||
GIT_VERSION := $(shell git describe --always)
|
||||
DATE_VERSION := $(shell date +%Y.%-m.%-d)
|
||||
FULL_VERSION := v$(DATE_VERSION)-$(GIT_VERSION)$(if $(shell git diff --stat),-dirty,)
|
||||
|
||||
DOCKER_IMAGE_NAME ?= cadoles/bouncer
|
||||
DOCKER_IMAGE_TAG ?= $(FULL_VERSION)
|
||||
|
||||
GOTEST_ARGS ?= -short
|
||||
|
||||
OPENWRT_DEVICE ?= 192.168.1.1
|
||||
|
||||
watch: tools/modd/bin/modd deps ## Watching updated files - live reload
|
||||
( set -o allexport && source .env && set +o allexport && tools/modd/bin/modd )
|
||||
|
||||
.PHONY: test
|
||||
test: test-go ## Executing tests
|
||||
|
||||
test-go: deps
|
||||
( set -o allexport && source .env && set +o allexport && go test -v -count=1 $(GOTEST_ARGS) ./... )
|
||||
|
||||
test-install-script: tools/bin/bash_unit
|
||||
tools/bin/bash_unit ./misc/script/test_install.sh
|
||||
|
||||
tools/bin/bash_unit:
|
||||
mkdir -p tools/bin
|
||||
cd tools/bin && bash <(curl -s https://raw.githubusercontent.com/pgrange/bash_unit/master/install.sh)
|
||||
|
||||
lint: ## Lint sources code
|
||||
golangci-lint run --enable-all $(LINT_ARGS)
|
||||
|
||||
build: build-bouncer ## Build artefacts
|
||||
|
||||
build-bouncer: deps ## Build executable
|
||||
CGO_ENABLED=0 go build \
|
||||
-v \
|
||||
-ldflags "\
|
||||
-X 'main.GitRef=$(shell git rev-parse --short HEAD)' \
|
||||
-X 'main.ProjectVersion=$(FULL_VERSION)' \
|
||||
-X 'main.BuildDate=$(shell date --utc --rfc-3339=seconds)' \
|
||||
" \
|
||||
-o ./bin/bouncer \
|
||||
./cmd/bouncer
|
||||
|
||||
.env:
|
||||
cp -f .env.dist .env
|
||||
|
||||
config.yml:
|
||||
bin/bouncer config dump > config.yml
|
||||
|
||||
run: .env
|
||||
( set -o allexport && source .env && set +o allexport && bin/bouncer $(BOUNCER_CMD))
|
||||
|
||||
.PHONY: deps
|
||||
deps: .env
|
||||
|
||||
.PHONY: goreleaser
|
||||
goreleaser: deps
|
||||
( set -o allexport && source .env && set +o allexport && VERSION=$(GORELEASER_VERSION) curl -sfL https://goreleaser.com/static/run | GORELEASER_CURRENT_TAG="$(FULL_VERSION)" bash /dev/stdin $(GORELEASER_ARGS) )
|
||||
|
||||
.PHONY: start-release
|
||||
start-release:
|
||||
if [ -z "$(BOUNCER_VERSION)" ]; then echo "You must define environment variable BOUNCER_VERSION"; exit 1; fi
|
||||
|
||||
git flow release start $(BOUNCER_VERSION)
|
||||
|
||||
# Update package.json version
|
||||
jq '.version = "$(BOUNCER_VERSION)"' package.json | sponge package.json
|
||||
git add package.json
|
||||
git commit -m "chore: bump to version $(BOUNCER_VERSION)" --allow-empty
|
||||
|
||||
echo "Commit you additional modifications then execute 'make finish-release'"
|
||||
|
||||
.PHONY: finish-release
|
||||
finish-release:
|
||||
git flow release finish -m "v$(BOUNCER_VERSION)"
|
||||
git push --all
|
||||
git push --tags
|
||||
|
||||
install-git-hooks:
|
||||
git config core.hooksPath .githooks
|
||||
|
||||
docker-build:
|
||||
docker build -t $(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG) .
|
||||
|
||||
docker-release:
|
||||
docker push $(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG)
|
||||
|
||||
gitea-release: tools/gitea-release/bin/gitea-release.sh goreleaser
|
||||
mkdir -p .gitea-release
|
||||
rm -rf .gitea-release/*
|
||||
|
||||
cp dist/*.tar.gz .gitea-release/
|
||||
cp dist/*.apk .gitea-release/
|
||||
cp dist/*.deb .gitea-release/
|
||||
cp dist/*.rpm .gitea-release/
|
||||
|
||||
GITEA_RELEASE_PROJECT="bouncer" \
|
||||
GITEA_RELEASE_ORG="Cadoles" \
|
||||
GITEA_RELEASE_BASE_URL="https://forge.cadoles.com" \
|
||||
GITEA_RELEASE_VERSION="$(FULL_VERSION)" \
|
||||
GITEA_RELEASE_NAME="$(FULL_VERSION)" \
|
||||
GITEA_RELEASE_COMMITISH_TARGET="$(GIT_VERSION)" \
|
||||
GITEA_RELEASE_IS_DRAFT="false" \
|
||||
GITEA_RELEASE_BODY="" \
|
||||
GITEA_RELEASE_ATTACHMENTS="$$(find .gitea-release/* -type f)" \
|
||||
tools/gitea-release/bin/gitea-release.sh
|
||||
|
||||
tools/gitea-release/bin/gitea-release.sh:
|
||||
mkdir -p tools/gitea-release/bin
|
||||
curl --output tools/gitea-release/bin/gitea-release.sh https://forge.cadoles.com/Cadoles/Jenkins/raw/branch/master/resources/com/cadoles/gitea/gitea-release.sh
|
||||
chmod +x tools/gitea-release/bin/gitea-release.sh
|
||||
|
||||
tools/modd/bin/modd:
|
||||
mkdir -p tools/modd/bin
|
||||
GOBIN=$(PWD)/tools/modd/bin go install github.com/cortesi/modd/cmd/modd@latest
|
||||
|
||||
full-version:
|
||||
@echo $(FULL_VERSION)
|
||||
|
||||
.bouncer-token:
|
||||
bin/bouncer auth create-token --role writer --subject dev-writer > .bouncer-token
|
||||
|
||||
run-redis:
|
||||
docker kill bouncer-redis || exit 0
|
||||
docker run --rm -t \
|
||||
--name bouncer-redis \
|
||||
-v $(PWD)/data/redis:/data \
|
||||
-p 6379:6379 \
|
||||
redis:alpine3.17 \
|
||||
redis-server --save 60 1 --loglevel warning
|
||||
|
||||
redis-shell:
|
||||
docker exec -it \
|
||||
bouncer-redis \
|
||||
redis-cli
|
15
README.md
Normal file
15
README.md
Normal file
@ -0,0 +1,15 @@
|
||||
<p align="center">
|
||||
<img src="./misc/logo/bouncer.svg" width="250px" />
|
||||
</p>
|
||||
|
||||
# Bouncer
|
||||
|
||||
Serveur mandataire inverse (_"reverse proxy"_) filtrant avec gestion de files d'attente dynamiques.
|
||||
|
||||
## Documentation
|
||||
|
||||
[Voir le répertoire `doc/`](./doc)
|
||||
|
||||
## Licence
|
||||
|
||||
AGPL-3.0
|
31
cmd/bouncer/main.go
Normal file
31
cmd/bouncer/main.go
Normal file
@ -0,0 +1,31 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/command"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/command/admin"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/command/auth"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/command/config"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/command/server"
|
||||
|
||||
_ "forge.cadoles.com/cadoles/bouncer/internal/imports/format"
|
||||
)
|
||||
|
||||
// nolint: gochecknoglobals
|
||||
var (
|
||||
GitRef = "unknown"
|
||||
ProjectVersion = "unknown"
|
||||
DefaultConfigPath = ""
|
||||
BuildDate = time.Now().UTC().Format(time.RFC3339)
|
||||
)
|
||||
|
||||
func main() {
|
||||
command.Main(
|
||||
BuildDate, ProjectVersion, GitRef, DefaultConfigPath,
|
||||
server.Root(),
|
||||
auth.Root(),
|
||||
admin.Root(),
|
||||
config.Root(),
|
||||
)
|
||||
}
|
1
commitlint.config.js
Normal file
1
commitlint.config.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = {extends: ['@commitlint/config-conventional']}
|
13
doc/README.md
Normal file
13
doc/README.md
Normal file
@ -0,0 +1,13 @@
|
||||
# Documentation
|
||||
|
||||
## Guide d'utilisation
|
||||
|
||||
> TODO
|
||||
|
||||
## Référence
|
||||
|
||||
> TODO
|
||||
|
||||
## Tutoriels
|
||||
|
||||
> TODO
|
92
go.mod
Normal file
92
go.mod
Normal file
@ -0,0 +1,92 @@
|
||||
module forge.cadoles.com/cadoles/bouncer
|
||||
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
forge.cadoles.com/Cadoles/go-proxy v0.0.0-20230512083245-e2dc3e1a0333
|
||||
github.com/btcsuite/btcd/btcutil v1.1.3
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/go-chi/chi/v5 v5.0.8
|
||||
github.com/jedib0t/go-pretty/v6 v6.4.6
|
||||
github.com/ory/dockertest/v3 v3.10.0
|
||||
github.com/redis/go-redis/v9 v9.0.4
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.99.0 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.0 // indirect
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/containerd/continuity v0.3.0 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/docker/cli v20.10.17+incompatible // indirect
|
||||
github.com/docker/docker v20.10.13+incompatible // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-units v0.4.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/kr/pretty v0.3.0 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.1 // indirect
|
||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.2 // indirect
|
||||
github.com/opencontainers/runc v1.1.5 // indirect
|
||||
github.com/qri-io/jsonpointer v0.1.1 // indirect
|
||||
github.com/qri-io/jsonschema v0.2.1 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.10.0 // indirect
|
||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
cdr.dev/slog v1.4.2 // indirect
|
||||
github.com/alecthomas/chroma v0.10.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
|
||||
github.com/dlclark/regexp2 v1.9.0 // indirect
|
||||
github.com/fatih/color v1.15.0 // indirect
|
||||
github.com/go-chi/cors v1.2.1
|
||||
github.com/go-playground/locales v0.12.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.16.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/leodido/go-urn v1.1.0 // indirect
|
||||
github.com/lestrrat-go/blackmagic v1.0.1 // indirect
|
||||
github.com/lestrrat-go/httpcc v1.0.1 // indirect
|
||||
github.com/lestrrat-go/httprc v1.0.4 // indirect
|
||||
github.com/lestrrat-go/iter v1.0.2 // indirect
|
||||
github.com/lestrrat-go/jwx/v2 v2.0.9
|
||||
github.com/lestrrat-go/option v1.0.1 // indirect
|
||||
github.com/lib/pq v1.10.0 // indirect
|
||||
github.com/lithammer/shortuuid/v4 v4.0.0
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.18 // indirect
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/urfave/cli/v2 v2.25.3
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||
gitlab.com/wpetit/goweb v0.0.0-20230419082146-a94d9ed7202b
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
golang.org/x/crypto v0.8.0 // indirect
|
||||
golang.org/x/mod v0.9.0 // indirect
|
||||
golang.org/x/sys v0.7.0 // indirect
|
||||
golang.org/x/term v0.7.0 // indirect
|
||||
golang.org/x/tools v0.7.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
gopkg.in/go-playground/validator.v9 v9.29.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
// replace forge.cadoles.com/Cadoles/go-proxy => ../go-proxy
|
903
go.sum
Normal file
903
go.sum
Normal file
@ -0,0 +1,903 @@
|
||||
cdr.dev/slog v1.4.0/go.mod h1:C5OL99WyuOK8YHZdYY57dAPN1jK2WJlCdq2VP6xeQns=
|
||||
cdr.dev/slog v1.4.2 h1:fIfiqASYQFJBZiASwL825atyzeA96NsqSxx2aL61P8I=
|
||||
cdr.dev/slog v1.4.2/go.mod h1:0EkH+GkFNxizNR+GAXUEdUHanxUH5t9zqPILmPM/Vn8=
|
||||
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.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.49.0/go.mod h1:hGvAdzcWNbyuxS3nWhD7H2cIJxjRRTRLQVB0bdputVY=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
||||
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
|
||||
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
|
||||
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
|
||||
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
|
||||
cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
|
||||
cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
|
||||
cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
|
||||
cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
|
||||
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
|
||||
cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
|
||||
cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
|
||||
cloud.google.com/go v0.99.0 h1:y/cM2iqGgGi5D5DQZl6D9STN/3dR/Vx5Mp8s752oJTY=
|
||||
cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
|
||||
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=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
forge.cadoles.com/Cadoles/go-proxy v0.0.0-20230512083245-e2dc3e1a0333 h1:dAajr9wX8WuFPrwjbKNXRmbF+4AaAT7bUj66G7gdZ+c=
|
||||
forge.cadoles.com/Cadoles/go-proxy v0.0.0-20230512083245-e2dc3e1a0333/go.mod h1:o8ZK5v/3J1dRmklFVn1l6WHAyQ3LgegyHjRIT8KLAFw=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/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/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
|
||||
github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0=
|
||||
github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg=
|
||||
github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
||||
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
|
||||
github.com/alecthomas/chroma v0.7.0/go.mod h1:1U/PfCsTALWWYHDnsIQkxEBM0+6LLe0v8+RSVMOwxeY=
|
||||
github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
|
||||
github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
|
||||
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
|
||||
github.com/alecthomas/kong v0.1.17-0.20190424132513-439c674f7ae0/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI=
|
||||
github.com/alecthomas/kong v0.2.1-0.20190708041108-0548c6b1afae/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI=
|
||||
github.com/alecthomas/kong-hcl v0.1.8-0.20190615233001-b21fea9723c8/go.mod h1:MRgZdU3vrFd05IQ89AxUZ0aYdF39BYoNFa324SodPCA=
|
||||
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao=
|
||||
github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y=
|
||||
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/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4=
|
||||
github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E=
|
||||
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/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
|
||||
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/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
|
||||
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
|
||||
github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg=
|
||||
github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM=
|
||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
|
||||
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||
github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E=
|
||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk=
|
||||
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/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc=
|
||||
github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/dlclark/regexp2 v1.9.0 h1:pTK/l/3qYIKaRXuHnEnIf7Y5NxfRPfpb7dis6/gdlVI=
|
||||
github.com/dlclark/regexp2 v1.9.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/docker/cli v20.10.17+incompatible h1:eO2KS7ZFeov5UJeaDmIs1NFEDRf32PaqRpvoEkKBy5M=
|
||||
github.com/docker/cli v20.10.17+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/docker v20.10.13+incompatible h1:5s7uxnKZG+b8hYWlPYUi6x1Sjpq2MSt96d15eLZeHyw=
|
||||
github.com/docker/docker v20.10.13+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.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
|
||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
|
||||
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
|
||||
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/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
|
||||
github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
||||
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||
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-playground/locales v0.12.1 h1:2FITxuFt/xuCNP1Acdhv62OzaCiviiE4kotfhkmOqEc=
|
||||
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
|
||||
github.com/go-playground/universal-translator v0.16.0 h1:X++omBR/4cE2MNg91AoC3rmGrCjJ8eAeUP/K/EKx4DM=
|
||||
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
|
||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||
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/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/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-20191027212112-611e8accdfc9/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/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
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.3.2-0.20191216170541-340f1ebe299e/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
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/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
|
||||
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
|
||||
github.com/gorilla/csrf v1.6.0/go.mod h1:7tSf8kmjNYr7IWDCYhd3U8Ck34iQ/Yw5CJu7bAkHEGI=
|
||||
github.com/gorilla/handlers v1.4.1/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
|
||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
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/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/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
|
||||
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/jedib0t/go-pretty/v6 v6.4.6 h1:v6aG9h6Uby3IusSSEjHaZNXpHFhzqMmjXcPq1Rjl9Jw=
|
||||
github.com/jedib0t/go-pretty/v6 v6.4.6/go.mod h1:Ndk3ase2CkQbXLLNf5QDHoYb6J9WtVfmHZu9n8rk2xs=
|
||||
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/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/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
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/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
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.1.0 h1:Sm1gr51B1kKyfD2BlRcLSiEkffoG96g6TPv6eRoEiB8=
|
||||
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
|
||||
github.com/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80=
|
||||
github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
|
||||
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
|
||||
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
|
||||
github.com/lestrrat-go/httprc v1.0.4 h1:bAZymwoZQb+Oq8MEbyipag7iSq6YIga8Wj6GOiJGdI8=
|
||||
github.com/lestrrat-go/httprc v1.0.4/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo=
|
||||
github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI=
|
||||
github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4=
|
||||
github.com/lestrrat-go/jwx/v2 v2.0.9 h1:TRX4Q630UXxPVLvP5vGaqVJO7S+0PE6msRZUsFSBoC8=
|
||||
github.com/lestrrat-go/jwx/v2 v2.0.9/go.mod h1:K68euYaR95FnL0hIQB8VvzL70vB7pSifbJUydCTPmgM=
|
||||
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
||||
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
|
||||
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
||||
github.com/lib/pq v1.10.0 h1:Zx5DJFEYQXio93kgXnQ09fXNiUKsqv4OUEu2UtGcB1E=
|
||||
github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
|
||||
github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
|
||||
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
|
||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU=
|
||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc=
|
||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw=
|
||||
github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
|
||||
github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E=
|
||||
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/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/opencontainers/runc v1.1.5 h1:L44KXEpKmfWDcS02aeGm8QNTFXTo2D+8MYGDIJ/GDEs=
|
||||
github.com/opencontainers/runc v1.1.5/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg=
|
||||
github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI=
|
||||
github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4=
|
||||
github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg=
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
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/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
|
||||
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/qri-io/jsonpointer v0.1.1 h1:prVZBZLL6TW5vsSB9fFHFAMBLI4b0ri5vribQlTJiBA=
|
||||
github.com/qri-io/jsonpointer v0.1.1/go.mod h1:DnJPaYgiKu56EuDp8TU5wFLdZIcAnb/uH9v37ZaMV64=
|
||||
github.com/qri-io/jsonschema v0.2.1 h1:NNFoKms+kut6ABPf6xiKNM5214jzxAhDBrPHCJ97Wg0=
|
||||
github.com/qri-io/jsonschema v0.2.1/go.mod h1:g7DPkiOsK1xv6T/Ao5scXRkd+yTFygcANPBaaqW+VrI=
|
||||
github.com/redis/go-redis/v9 v9.0.4 h1:FC82T+CHJ/Q/PdyLW++GeCO+Ol59Y4T7R4jbgjvktgc=
|
||||
github.com/redis/go-redis/v9 v9.0.4/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/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.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
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 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/urfave/cli/v2 v2.25.3 h1:VJkt6wvEBOoSjPFQvOkv6iWIrsJyCrKGtCtxXWwmGeY=
|
||||
github.com/urfave/cli/v2 v2.25.3/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
gitlab.com/wpetit/goweb v0.0.0-20230419082146-a94d9ed7202b h1:nkvOl8TCj/mErADnwFFynjxBtC+hHsrESw6rw56JGmg=
|
||||
gitlab.com/wpetit/goweb v0.0.0-20230419082146-a94d9ed7202b/go.mod h1:3sus4zjoUv1GB7eDLL60QaPkUnXJCWBpjvbe0jWifeY=
|
||||
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.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
|
||||
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-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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
|
||||
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||
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/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
|
||||
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
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-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-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/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.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/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-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/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-20190412213103-97732733099d/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-20190606203320-7fc4e5ec1444/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-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-20191115151921-52ab43148777/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-20191210023423-ac6580df4449/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-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/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-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/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.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
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.5/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.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
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-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-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
|
||||
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
|
||||
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=
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
||||
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
||||
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
||||
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
|
||||
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
|
||||
google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
|
||||
google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
|
||||
google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
|
||||
google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
|
||||
google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
|
||||
google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
|
||||
google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
|
||||
google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
|
||||
google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
|
||||
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-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
|
||||
google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
|
||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
|
||||
google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
|
||||
google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
|
||||
google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
|
||||
google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
|
||||
google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
|
||||
google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106 h1:ErU+UA6wxadoU8nWrsy5MZUVBs75K17zUCsUCIfrXCE=
|
||||
google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
|
||||
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.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
|
||||
google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
|
||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M=
|
||||
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
|
||||
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
||||
gopkg.in/go-playground/validator.v9 v9.29.1 h1:SvGtYmN60a5CVKTOzMSyfzWDeZRxRuGvRQyEAKbw1xc=
|
||||
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
|
||||
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.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
||||
gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo=
|
||||
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/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
101
internal/admin/authz.go
Normal file
101
internal/admin/authz.go
Normal file
@ -0,0 +1,101 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/auth"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/auth/jwt"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/api"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
)
|
||||
|
||||
var ErrCodeForbidden api.ErrorCode = "forbidden"
|
||||
|
||||
func assertReadAccess(h http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
reqUser, ok := assertRequestUser(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
switch user := reqUser.(type) {
|
||||
case *jwt.User:
|
||||
role := user.Role()
|
||||
if role == jwt.RoleReader || role == jwt.RoleWriter {
|
||||
h.ServeHTTP(w, r)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
default:
|
||||
logUnexpectedUserType(r.Context(), reqUser)
|
||||
}
|
||||
|
||||
forbidden(w, r)
|
||||
}
|
||||
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
|
||||
func assertWriteAccess(h http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
reqUser, ok := assertRequestUser(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
switch user := reqUser.(type) {
|
||||
case *jwt.User:
|
||||
role := user.Role()
|
||||
if role == jwt.RoleWriter {
|
||||
h.ServeHTTP(w, r)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
default:
|
||||
logUnexpectedUserType(r.Context(), reqUser)
|
||||
}
|
||||
|
||||
forbidden(w, r)
|
||||
}
|
||||
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
|
||||
func assertRequestUser(w http.ResponseWriter, r *http.Request) (auth.User, bool) {
|
||||
ctx := r.Context()
|
||||
user, err := auth.CtxUser(ctx)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "could not retrieve user", logger.E(errors.WithStack(err)))
|
||||
|
||||
forbidden(w, r)
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
forbidden(w, r)
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return user, true
|
||||
}
|
||||
|
||||
func forbidden(w http.ResponseWriter, r *http.Request) {
|
||||
logger.Warn(r.Context(), "forbidden", logger.F("path", r.URL.Path))
|
||||
|
||||
api.ErrorResponse(w, http.StatusForbidden, ErrCodeForbidden, nil)
|
||||
}
|
||||
|
||||
func logUnexpectedUserType(ctx context.Context, user auth.User) {
|
||||
logger.Error(
|
||||
ctx, "unexpected user type",
|
||||
logger.F("subject", user.Subject()),
|
||||
logger.F("type", fmt.Sprintf("%T", user)),
|
||||
)
|
||||
}
|
31
internal/admin/error.go
Normal file
31
internal/admin/error.go
Normal file
@ -0,0 +1,31 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/schema"
|
||||
"gitlab.com/wpetit/goweb/api"
|
||||
)
|
||||
|
||||
const ErrCodeAlreadyExist api.ErrorCode = "already-exist"
|
||||
|
||||
func invalidDataErrorResponse(w http.ResponseWriter, r *http.Request, err *schema.InvalidDataError) {
|
||||
keyErrors := err.KeyErrors()
|
||||
|
||||
message := ""
|
||||
for idx, err := range keyErrors {
|
||||
if idx != 0 {
|
||||
message += ", "
|
||||
}
|
||||
message += fmt.Sprintf("Path [%s]: %s", err.PropertyPath, err.Message)
|
||||
}
|
||||
|
||||
api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeInvalidRequest, &struct {
|
||||
Message string `json:"message"`
|
||||
}{
|
||||
Message: message,
|
||||
})
|
||||
|
||||
return
|
||||
}
|
42
internal/admin/init.go
Normal file
42
internal/admin/init.go
Normal file
@ -0,0 +1,42 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/setup"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (s *Server) initRepositories(ctx context.Context) error {
|
||||
if err := s.initLayerRepository(ctx); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if err := s.initProxyRepository(ctx); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) initLayerRepository(ctx context.Context) error {
|
||||
layerRepository, err := setup.NewLayerRepository(ctx, s.redisConfig)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
s.layerRepository = layerRepository
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) initProxyRepository(ctx context.Context) error {
|
||||
proxyRepository, err := setup.NewProxyRepository(ctx, s.redisConfig)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
s.proxyRepository = proxyRepository
|
||||
|
||||
return nil
|
||||
}
|
302
internal/admin/layer_route.go
Normal file
302
internal/admin/layer_route.go
Normal file
@ -0,0 +1,302 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sort"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/schema"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/api"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
)
|
||||
|
||||
type QueryLayerResponse struct {
|
||||
Layers []*store.LayerHeader `json:"layers"`
|
||||
}
|
||||
|
||||
func (s *Server) queryLayer(w http.ResponseWriter, r *http.Request) {
|
||||
proxyName, ok := getProxyName(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
options := []store.QueryLayerOptionFunc{}
|
||||
|
||||
name := r.URL.Query().Get("name")
|
||||
if name != "" {
|
||||
options = append(options, store.WithLayerQueryName(store.LayerName(name)))
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
layers, err := s.layerRepository.QueryLayers(
|
||||
ctx,
|
||||
proxyName,
|
||||
options...,
|
||||
)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "could not list layers", logger.E(errors.WithStack(err)))
|
||||
api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
sort.Sort(store.ByLayerWeight(layers))
|
||||
|
||||
api.DataResponse(w, http.StatusOK, QueryLayerResponse{
|
||||
Layers: layers,
|
||||
})
|
||||
}
|
||||
|
||||
func validateLayerName(v string) (store.LayerName, error) {
|
||||
name, err := store.ValidateName(v)
|
||||
if err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
return store.LayerName(name), nil
|
||||
}
|
||||
|
||||
type GetLayerResponse struct {
|
||||
Layer *store.Layer `json:"layer"`
|
||||
}
|
||||
|
||||
func (s *Server) getLayer(w http.ResponseWriter, r *http.Request) {
|
||||
proxyName, ok := getProxyName(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
layerName, ok := getLayerName(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
layer, err := s.layerRepository.GetLayer(ctx, proxyName, layerName)
|
||||
if err != nil {
|
||||
if errors.Is(err, store.ErrNotFound) {
|
||||
api.ErrorResponse(w, http.StatusNotFound, api.ErrCodeNotFound, nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
logger.Error(ctx, "could not get layer", logger.E(errors.WithStack(err)))
|
||||
api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
api.DataResponse(w, http.StatusOK, GetLayerResponse{
|
||||
Layer: layer,
|
||||
})
|
||||
}
|
||||
|
||||
type DeleteLayerResponse struct {
|
||||
LayerName store.LayerName `json:"layerName"`
|
||||
}
|
||||
|
||||
func (s *Server) deleteLayer(w http.ResponseWriter, r *http.Request) {
|
||||
proxyName, ok := getProxyName(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
layerName, ok := getLayerName(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
if err := s.layerRepository.DeleteLayer(ctx, proxyName, layerName); err != nil {
|
||||
if errors.Is(err, store.ErrNotFound) {
|
||||
api.ErrorResponse(w, http.StatusNotFound, api.ErrCodeNotFound, nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
logger.Error(ctx, "could not delete layer", logger.E(errors.WithStack(err)))
|
||||
api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
api.DataResponse(w, http.StatusOK, DeleteLayerResponse{
|
||||
LayerName: layerName,
|
||||
})
|
||||
}
|
||||
|
||||
type CreateLayerRequest struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Type string `json:"type" validate:"required"`
|
||||
Options map[string]any `json:"options"`
|
||||
}
|
||||
|
||||
type CreateLayerResponse struct {
|
||||
Layer *store.Layer `json:"layer"`
|
||||
}
|
||||
|
||||
func (s *Server) createLayer(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
proxyName, ok := getProxyName(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
createLayerReq := &CreateLayerRequest{}
|
||||
if ok := api.Bind(w, r, createLayerReq); !ok {
|
||||
return
|
||||
}
|
||||
|
||||
layerName, err := store.ValidateName(createLayerReq.Name)
|
||||
if err != nil {
|
||||
logger.Error(r.Context(), "could not parse 'name' parameter", logger.E(errors.WithStack(err)))
|
||||
api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeMalformedRequest, nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
layer, err := s.layerRepository.CreateLayer(ctx, proxyName, store.LayerName(layerName), store.LayerType(createLayerReq.Type), createLayerReq.Options)
|
||||
if err != nil {
|
||||
if errors.Is(err, store.ErrAlreadyExist) {
|
||||
api.ErrorResponse(w, http.StatusConflict, ErrCodeAlreadyExist, nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
logger.Error(ctx, "could not create layer", logger.E(errors.WithStack(err)))
|
||||
api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
api.DataResponse(w, http.StatusOK, struct {
|
||||
Layer *store.Layer `json:"layer"`
|
||||
}{
|
||||
Layer: layer,
|
||||
})
|
||||
}
|
||||
|
||||
type UpdateLayerRequest struct {
|
||||
Enabled *bool `json:"enabled"`
|
||||
Weight *int `json:"weight"`
|
||||
Options *store.LayerOptions `json:"options"`
|
||||
}
|
||||
|
||||
type UpdateLayerResponse struct {
|
||||
Layer *store.Layer `json:"layer"`
|
||||
}
|
||||
|
||||
func (s *Server) updateLayer(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
proxyName, ok := getProxyName(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
layerName, ok := getLayerName(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
layer, err := s.layerRepository.GetLayer(ctx, proxyName, layerName)
|
||||
if err != nil {
|
||||
if errors.Is(err, store.ErrNotFound) {
|
||||
api.ErrorResponse(w, http.StatusNotFound, api.ErrCodeNotFound, nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
logger.Error(ctx, "could not get layer", logger.E(errors.WithStack(err)))
|
||||
api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
updateLayerReq := &UpdateLayerRequest{}
|
||||
if ok := api.Bind(w, r, updateLayerReq); !ok {
|
||||
return
|
||||
}
|
||||
|
||||
options := make([]store.UpdateLayerOptionFunc, 0)
|
||||
|
||||
if updateLayerReq.Enabled != nil {
|
||||
options = append(options, store.WithLayerUpdateEnabled(*updateLayerReq.Enabled))
|
||||
}
|
||||
|
||||
if updateLayerReq.Weight != nil {
|
||||
options = append(options, store.WithLayerUpdateWeight(*updateLayerReq.Weight))
|
||||
}
|
||||
|
||||
if updateLayerReq.Options != nil {
|
||||
if err := schema.ValidateLayerOptions(ctx, layer.Type, updateLayerReq.Options); err != nil {
|
||||
logger.Error(r.Context(), "could not validate layer options", logger.E(errors.WithStack(err)))
|
||||
|
||||
var invalidDataErr *schema.InvalidDataError
|
||||
if errors.As(err, &invalidDataErr) {
|
||||
invalidDataErrorResponse(w, r, invalidDataErr)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
options = append(options, store.WithLayerUpdateOptions(*updateLayerReq.Options))
|
||||
}
|
||||
|
||||
layer, err = s.layerRepository.UpdateLayer(
|
||||
ctx, proxyName, layerName,
|
||||
options...,
|
||||
)
|
||||
if err != nil {
|
||||
if errors.Is(err, store.ErrNotFound) {
|
||||
api.ErrorResponse(w, http.StatusNotFound, api.ErrCodeNotFound, nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
logger.Error(ctx, "could not update layer", logger.E(errors.WithStack(err)))
|
||||
api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
api.DataResponse(w, http.StatusOK, UpdateLayerResponse{Layer: layer})
|
||||
}
|
||||
|
||||
func getLayerName(w http.ResponseWriter, r *http.Request) (store.LayerName, bool) {
|
||||
rawLayerName := chi.URLParam(r, "layerName")
|
||||
|
||||
name, err := store.ValidateName(rawLayerName)
|
||||
if err != nil {
|
||||
logger.Error(r.Context(), "could not parse layer name", logger.E(errors.WithStack(err)))
|
||||
api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeMalformedRequest, nil)
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
return store.LayerName(name), true
|
||||
}
|
||||
|
||||
func geLayerName(w http.ResponseWriter, r *http.Request) (store.LayerName, bool) {
|
||||
rawLayerName := chi.URLParam(r, "layerName")
|
||||
|
||||
name, err := store.ValidateName(rawLayerName)
|
||||
if err != nil {
|
||||
logger.Error(r.Context(), "could not parse layer name", logger.E(errors.WithStack(err)))
|
||||
api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeMalformedRequest, nil)
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
return store.LayerName(name), true
|
||||
}
|
31
internal/admin/option.go
Normal file
31
internal/admin/option.go
Normal file
@ -0,0 +1,31 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/config"
|
||||
)
|
||||
|
||||
type Option struct {
|
||||
ServerConfig config.AdminServerConfig
|
||||
RedisConfig config.RedisConfig
|
||||
}
|
||||
|
||||
type OptionFunc func(*Option)
|
||||
|
||||
func defaultOption() *Option {
|
||||
return &Option{
|
||||
ServerConfig: config.NewDefaultAdminServerConfig(),
|
||||
RedisConfig: config.NewDefaultRedisConfig(),
|
||||
}
|
||||
}
|
||||
|
||||
func WithServerConfig(conf config.AdminServerConfig) OptionFunc {
|
||||
return func(opt *Option) {
|
||||
opt.ServerConfig = conf
|
||||
}
|
||||
}
|
||||
|
||||
func WithRedisConfig(conf config.RedisConfig) OptionFunc {
|
||||
return func(opt *Option) {
|
||||
opt.RedisConfig = conf
|
||||
}
|
||||
}
|
312
internal/admin/proxy_route.go
Normal file
312
internal/admin/proxy_route.go
Normal file
@ -0,0 +1,312 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/api"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
)
|
||||
|
||||
type QueryProxyResponse struct {
|
||||
Proxies []*store.ProxyHeader `json:"proxies"`
|
||||
}
|
||||
|
||||
func (s *Server) queryProxy(w http.ResponseWriter, r *http.Request) {
|
||||
options := []store.QueryProxyOptionFunc{}
|
||||
|
||||
names, ok := getStringableSliceValues(w, r, "names", nil, validateProxyName)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if names != nil {
|
||||
options = append(options, store.WithProxyQueryNames(names...))
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
proxies, err := s.proxyRepository.QueryProxy(
|
||||
ctx,
|
||||
options...,
|
||||
)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "could not list proxies", logger.E(errors.WithStack(err)))
|
||||
api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
sort.Sort(store.ByProxyWeight(proxies))
|
||||
|
||||
api.DataResponse(w, http.StatusOK, QueryProxyResponse{
|
||||
Proxies: proxies,
|
||||
})
|
||||
}
|
||||
|
||||
func validateProxyName(v string) (store.ProxyName, error) {
|
||||
name, err := store.ValidateName(v)
|
||||
if err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
return store.ProxyName(name), nil
|
||||
}
|
||||
|
||||
type GetProxyResponse struct {
|
||||
Proxy *store.Proxy `json:"proxy"`
|
||||
}
|
||||
|
||||
func (s *Server) getProxy(w http.ResponseWriter, r *http.Request) {
|
||||
proxyName, ok := getProxyName(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
proxy, err := s.proxyRepository.GetProxy(ctx, proxyName)
|
||||
if err != nil {
|
||||
if errors.Is(err, store.ErrNotFound) {
|
||||
api.ErrorResponse(w, http.StatusNotFound, api.ErrCodeNotFound, nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
logger.Error(ctx, "could not get proxy", logger.E(errors.WithStack(err)))
|
||||
api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
api.DataResponse(w, http.StatusOK, GetProxyResponse{
|
||||
Proxy: proxy,
|
||||
})
|
||||
}
|
||||
|
||||
type DeleteProxyResponse struct {
|
||||
ProxyName store.ProxyName `json:"proxyName"`
|
||||
}
|
||||
|
||||
func (s *Server) deleteProxy(w http.ResponseWriter, r *http.Request) {
|
||||
proxyName, ok := getProxyName(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
if err := s.proxyRepository.DeleteProxy(ctx, proxyName); err != nil {
|
||||
if errors.Is(err, store.ErrNotFound) {
|
||||
api.ErrorResponse(w, http.StatusNotFound, api.ErrCodeNotFound, nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
logger.Error(ctx, "could not delete proxy", logger.E(errors.WithStack(err)))
|
||||
api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
api.DataResponse(w, http.StatusOK, DeleteProxyResponse{
|
||||
ProxyName: proxyName,
|
||||
})
|
||||
}
|
||||
|
||||
type CreateProxyRequest struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
To string `json:"to" validate:"required"`
|
||||
From []string `json:"from" validate:"required"`
|
||||
}
|
||||
|
||||
type CreateProxyResponse struct {
|
||||
Proxy *store.Proxy `json:"proxy"`
|
||||
}
|
||||
|
||||
func (s *Server) createProxy(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
createProxyReq := &CreateProxyRequest{}
|
||||
if ok := api.Bind(w, r, createProxyReq); !ok {
|
||||
return
|
||||
}
|
||||
|
||||
name, err := store.ValidateName(createProxyReq.Name)
|
||||
if err != nil {
|
||||
logger.Error(r.Context(), "could not parse 'name' parameter", logger.E(errors.WithStack(err)))
|
||||
api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeMalformedRequest, nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := url.Parse(createProxyReq.To); err != nil {
|
||||
logger.Error(r.Context(), "could not parse 'to' parameter", logger.E(errors.WithStack(err)))
|
||||
api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeMalformedRequest, nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
proxy, err := s.proxyRepository.CreateProxy(ctx, store.ProxyName(name), createProxyReq.To, createProxyReq.From...)
|
||||
if err != nil {
|
||||
if errors.Is(err, store.ErrAlreadyExist) {
|
||||
api.ErrorResponse(w, http.StatusConflict, ErrCodeAlreadyExist, nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
logger.Error(ctx, "could not create proxy", logger.E(errors.WithStack(err)))
|
||||
api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
api.DataResponse(w, http.StatusOK, struct {
|
||||
Proxy *store.Proxy `json:"proxy"`
|
||||
}{
|
||||
Proxy: proxy,
|
||||
})
|
||||
}
|
||||
|
||||
type UpdateProxyRequest struct {
|
||||
Enabled *bool `json:"enabled"`
|
||||
Weight *int `json:"weight"`
|
||||
To *string `json:"to"`
|
||||
From []string `json:"from"`
|
||||
}
|
||||
|
||||
type UpdateProxyResponse struct {
|
||||
Proxy *store.Proxy `json:"proxy"`
|
||||
}
|
||||
|
||||
func (s *Server) updateProxy(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
proxyName, ok := getProxyName(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
updateProxyReq := &UpdateProxyRequest{}
|
||||
if ok := api.Bind(w, r, updateProxyReq); !ok {
|
||||
return
|
||||
}
|
||||
|
||||
options := make([]store.UpdateProxyOptionFunc, 0)
|
||||
|
||||
if updateProxyReq.Enabled != nil {
|
||||
options = append(options, store.WithProxyUpdateEnabled(*updateProxyReq.Enabled))
|
||||
}
|
||||
|
||||
if updateProxyReq.To != nil {
|
||||
_, err := url.Parse(*updateProxyReq.To)
|
||||
if err != nil {
|
||||
logger.Error(r.Context(), "could not parse 'to' parameter", logger.E(errors.WithStack(err)))
|
||||
api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeMalformedRequest, nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
options = append(options, store.WithProxyUpdateTo(*updateProxyReq.To))
|
||||
}
|
||||
|
||||
if updateProxyReq.From != nil {
|
||||
options = append(options, store.WithProxyUpdateFrom(updateProxyReq.From...))
|
||||
}
|
||||
|
||||
if updateProxyReq.Weight != nil {
|
||||
options = append(options, store.WithProxyUpdateWeight(*updateProxyReq.Weight))
|
||||
}
|
||||
|
||||
proxy, err := s.proxyRepository.UpdateProxy(
|
||||
ctx, proxyName,
|
||||
options...,
|
||||
)
|
||||
if err != nil {
|
||||
if errors.Is(err, store.ErrNotFound) {
|
||||
api.ErrorResponse(w, http.StatusNotFound, api.ErrCodeNotFound, nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
logger.Error(ctx, "could not update proxy", logger.E(errors.WithStack(err)))
|
||||
api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
api.DataResponse(w, http.StatusOK, UpdateProxyResponse{Proxy: proxy})
|
||||
}
|
||||
|
||||
func getProxyName(w http.ResponseWriter, r *http.Request) (store.ProxyName, bool) {
|
||||
rawProxyName := chi.URLParam(r, "proxyName")
|
||||
|
||||
name, err := store.ValidateName(rawProxyName)
|
||||
if err != nil {
|
||||
logger.Error(r.Context(), "could not parse proxy name", logger.E(errors.WithStack(err)))
|
||||
api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeMalformedRequest, nil)
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
return store.ProxyName(name), true
|
||||
}
|
||||
|
||||
func getIntQueryParam(w http.ResponseWriter, r *http.Request, param string, defaultValue int64) (int64, bool) {
|
||||
rawValue := r.URL.Query().Get(param)
|
||||
if rawValue != "" {
|
||||
value, err := strconv.ParseInt(rawValue, 10, 64)
|
||||
if err != nil {
|
||||
logger.Error(r.Context(), "could not parse int param", logger.F("param", param), logger.E(errors.WithStack(err)))
|
||||
api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeMalformedRequest, nil)
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return value, true
|
||||
}
|
||||
|
||||
return defaultValue, true
|
||||
}
|
||||
|
||||
func getStringSliceValues(w http.ResponseWriter, r *http.Request, param string, defaultValue []string) ([]string, bool) {
|
||||
rawValue := r.URL.Query().Get(param)
|
||||
if rawValue != "" {
|
||||
values := strings.Split(rawValue, ",")
|
||||
|
||||
return values, true
|
||||
}
|
||||
|
||||
return defaultValue, true
|
||||
}
|
||||
|
||||
func getStringableSliceValues[T ~string](w http.ResponseWriter, r *http.Request, param string, defaultValue []T, validate func(string) (T, error)) ([]T, bool) {
|
||||
rawValue := r.URL.Query().Get(param)
|
||||
|
||||
if rawValue != "" {
|
||||
rawValues := strings.Split(rawValue, ",")
|
||||
values := make([]T, 0, len(rawValues))
|
||||
|
||||
for _, rv := range rawValues {
|
||||
v, err := validate(rv)
|
||||
if err != nil {
|
||||
logger.Error(r.Context(), "could not parse ids slice param", logger.F("param", param), logger.E(errors.WithStack(err)))
|
||||
api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeMalformedRequest, nil)
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
values = append(values, v)
|
||||
}
|
||||
|
||||
return values, true
|
||||
}
|
||||
|
||||
return defaultValue, true
|
||||
}
|
145
internal/admin/server.go
Normal file
145
internal/admin/server.go
Normal file
@ -0,0 +1,145 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/auth"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/auth/jwt"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/config"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/jwk"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/go-chi/cors"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
serverConfig config.AdminServerConfig
|
||||
redisConfig config.RedisConfig
|
||||
proxyRepository store.ProxyRepository
|
||||
layerRepository store.LayerRepository
|
||||
}
|
||||
|
||||
func (s *Server) Start(ctx context.Context) (<-chan net.Addr, <-chan error) {
|
||||
errs := make(chan error)
|
||||
addrs := make(chan net.Addr)
|
||||
|
||||
go s.run(ctx, addrs, errs)
|
||||
|
||||
return addrs, errs
|
||||
}
|
||||
|
||||
func (s *Server) run(parentCtx context.Context, addrs chan net.Addr, errs chan error) {
|
||||
defer func() {
|
||||
close(errs)
|
||||
close(addrs)
|
||||
}()
|
||||
|
||||
ctx, cancel := context.WithCancel(parentCtx)
|
||||
defer cancel()
|
||||
|
||||
if err := s.initRepositories(ctx); err != nil {
|
||||
errs <- errors.WithStack(err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", s.serverConfig.HTTP.Host, s.serverConfig.HTTP.Port))
|
||||
if err != nil {
|
||||
errs <- errors.WithStack(err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
addrs <- listener.Addr()
|
||||
|
||||
defer func() {
|
||||
if err := listener.Close(); err != nil && !errors.Is(err, net.ErrClosed) {
|
||||
errs <- errors.WithStack(err)
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
|
||||
if err := listener.Close(); err != nil && !errors.Is(err, net.ErrClosed) {
|
||||
log.Printf("%+v", errors.WithStack(err))
|
||||
}
|
||||
}()
|
||||
|
||||
key, err := jwk.LoadOrGenerate(string(s.serverConfig.Auth.PrivateKey), jwk.DefaultKeySize)
|
||||
if err != nil {
|
||||
errs <- errors.WithStack(err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
keys, err := jwk.PublicKeySet(key)
|
||||
if err != nil {
|
||||
errs <- errors.WithStack(err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
router := chi.NewRouter()
|
||||
|
||||
router.Use(middleware.Logger)
|
||||
|
||||
corsMiddleware := cors.New(cors.Options{
|
||||
AllowedOrigins: s.serverConfig.CORS.AllowedOrigins,
|
||||
AllowedMethods: s.serverConfig.CORS.AllowedMethods,
|
||||
AllowCredentials: bool(s.serverConfig.CORS.AllowCredentials),
|
||||
AllowedHeaders: s.serverConfig.CORS.AllowedHeaders,
|
||||
Debug: bool(s.serverConfig.CORS.Debug),
|
||||
})
|
||||
|
||||
router.Use(corsMiddleware.Handler)
|
||||
|
||||
router.Route("/api/v1", func(r chi.Router) {
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(auth.Middleware(
|
||||
jwt.NewAuthenticator(keys, string(s.serverConfig.Auth.Issuer), jwt.DefaultAcceptableSkew),
|
||||
))
|
||||
|
||||
r.Route("/proxies", func(r chi.Router) {
|
||||
r.With(assertReadAccess).Get("/", s.queryProxy)
|
||||
r.With(assertWriteAccess).Post("/", s.createProxy)
|
||||
r.With(assertReadAccess).Get("/{proxyName}", s.getProxy)
|
||||
r.With(assertWriteAccess).Put("/{proxyName}", s.updateProxy)
|
||||
r.With(assertWriteAccess).Delete("/{proxyName}", s.deleteProxy)
|
||||
|
||||
r.With(assertReadAccess).Get("/{proxyName}/layers", s.queryLayer)
|
||||
r.With(assertWriteAccess).Post("/{proxyName}/layers", s.createLayer)
|
||||
r.With(assertReadAccess).Get("/{proxyName}/layers/{layerName}", s.getLayer)
|
||||
r.With(assertWriteAccess).Put("/{proxyName}/layers/{layerName}", s.updateLayer)
|
||||
r.With(assertWriteAccess).Delete("/{proxyName}/layers/{layerName}", s.deleteLayer)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
logger.Info(ctx, "http server listening")
|
||||
|
||||
if err := http.Serve(listener, router); err != nil && !errors.Is(err, net.ErrClosed) {
|
||||
errs <- errors.WithStack(err)
|
||||
}
|
||||
|
||||
logger.Info(ctx, "http server exiting")
|
||||
}
|
||||
|
||||
func NewServer(funcs ...OptionFunc) *Server {
|
||||
opt := defaultOption()
|
||||
for _, fn := range funcs {
|
||||
fn(opt)
|
||||
}
|
||||
|
||||
return &Server{
|
||||
serverConfig: opt.ServerConfig,
|
||||
redisConfig: opt.RedisConfig,
|
||||
}
|
||||
}
|
72
internal/auth/jwt/authenticator.go
Normal file
72
internal/auth/jwt/authenticator.go
Normal file
@ -0,0 +1,72 @@
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/auth"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/jwk"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
)
|
||||
|
||||
const DefaultAcceptableSkew = 5 * time.Minute
|
||||
|
||||
type Authenticator struct {
|
||||
keys jwk.Set
|
||||
issuer string
|
||||
acceptableSkew time.Duration
|
||||
}
|
||||
|
||||
// Authenticate implements auth.Authenticator.
|
||||
func (a *Authenticator) Authenticate(ctx context.Context, r *http.Request) (auth.User, error) {
|
||||
ctx = logger.With(r.Context(), logger.F("remoteAddr", r.RemoteAddr))
|
||||
|
||||
authorization := r.Header.Get("Authorization")
|
||||
if authorization == "" {
|
||||
return nil, errors.WithStack(auth.ErrUnauthenticated)
|
||||
}
|
||||
|
||||
rawToken := strings.TrimPrefix(authorization, "Bearer ")
|
||||
if rawToken == "" {
|
||||
return nil, errors.WithStack(auth.ErrUnauthenticated)
|
||||
}
|
||||
|
||||
token, err := parseToken(ctx, a.keys, a.issuer, rawToken, a.acceptableSkew)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
rawRole, exists := token.Get(keyRole)
|
||||
if !exists {
|
||||
return nil, errors.New("could not find 'thumbprint' claim")
|
||||
}
|
||||
|
||||
role, ok := rawRole.(string)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("unexpected '%s' claim value: '%v'", keyRole, rawRole)
|
||||
}
|
||||
|
||||
if !isValidRole(role) {
|
||||
return nil, errors.Errorf("invalid role '%s'", role)
|
||||
}
|
||||
|
||||
user := &User{
|
||||
subject: token.Subject(),
|
||||
role: Role(role),
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func NewAuthenticator(keys jwk.Set, issuer string, acceptableSkew time.Duration) *Authenticator {
|
||||
return &Authenticator{
|
||||
keys: keys,
|
||||
issuer: issuer,
|
||||
acceptableSkew: acceptableSkew,
|
||||
}
|
||||
}
|
||||
|
||||
var _ auth.Authenticator = &Authenticator{}
|
62
internal/auth/jwt/jwt.go
Normal file
62
internal/auth/jwt/jwt.go
Normal file
@ -0,0 +1,62 @@
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/jwk"
|
||||
"github.com/lestrrat-go/jwx/v2/jwa"
|
||||
"github.com/lestrrat-go/jwx/v2/jws"
|
||||
"github.com/lestrrat-go/jwx/v2/jwt"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const keyRole = "role"
|
||||
|
||||
func parseToken(ctx context.Context, keys jwk.Set, issuer string, rawToken string, acceptableSkew time.Duration) (jwt.Token, error) {
|
||||
token, err := jwt.Parse(
|
||||
[]byte(rawToken),
|
||||
jwt.WithKeySet(keys, jws.WithRequireKid(false)),
|
||||
jwt.WithIssuer(issuer),
|
||||
jwt.WithValidate(true),
|
||||
jwt.WithAcceptableSkew(acceptableSkew),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func GenerateToken(ctx context.Context, key jwk.Key, issuer, subject string, role Role) (string, error) {
|
||||
token := jwt.New()
|
||||
|
||||
if err := token.Set(jwt.SubjectKey, subject); err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
if err := token.Set(jwt.IssuerKey, issuer); err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
if err := token.Set(keyRole, role); err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
|
||||
if err := token.Set(jwt.NotBeforeKey, now); err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
if err := token.Set(jwt.IssuedAtKey, now); err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
rawToken, err := jwt.Sign(token, jwt.WithKey(jwa.RS256, key))
|
||||
if err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
return string(rawToken), nil
|
||||
}
|
32
internal/auth/jwt/user.go
Normal file
32
internal/auth/jwt/user.go
Normal file
@ -0,0 +1,32 @@
|
||||
package jwt
|
||||
|
||||
import "forge.cadoles.com/cadoles/bouncer/internal/auth"
|
||||
|
||||
type Role string
|
||||
|
||||
const (
|
||||
RoleWriter Role = "writer"
|
||||
RoleReader Role = "reader"
|
||||
)
|
||||
|
||||
func isValidRole(r string) bool {
|
||||
rr := Role(r)
|
||||
|
||||
return rr == RoleWriter || rr == RoleReader
|
||||
}
|
||||
|
||||
type User struct {
|
||||
subject string
|
||||
role Role
|
||||
}
|
||||
|
||||
// Subject implements auth.User
|
||||
func (u *User) Subject() string {
|
||||
return u.subject
|
||||
}
|
||||
|
||||
func (u *User) Role() Role {
|
||||
return u.role
|
||||
}
|
||||
|
||||
var _ auth.User = &User{}
|
79
internal/auth/middleware.go
Normal file
79
internal/auth/middleware.go
Normal file
@ -0,0 +1,79 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/api"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
)
|
||||
|
||||
const (
|
||||
ErrCodeUnauthorized api.ErrorCode = "unauthorized"
|
||||
ErrCodeForbidden api.ErrorCode = "forbidden"
|
||||
)
|
||||
|
||||
type contextKey string
|
||||
|
||||
const (
|
||||
contextKeyUser contextKey = "user"
|
||||
)
|
||||
|
||||
func CtxUser(ctx context.Context) (User, error) {
|
||||
user, ok := ctx.Value(contextKeyUser).(User)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("unexpected user type: expected '%T', got '%T'", new(User), ctx.Value(contextKeyUser))
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
var ErrUnauthenticated = errors.New("unauthenticated")
|
||||
|
||||
type User interface {
|
||||
Subject() string
|
||||
}
|
||||
|
||||
type Authenticator interface {
|
||||
Authenticate(context.Context, *http.Request) (User, error)
|
||||
}
|
||||
|
||||
func Middleware(authenticators ...Authenticator) func(http.Handler) http.Handler {
|
||||
return func(h http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := logger.With(r.Context(), logger.F("remoteAddr", r.RemoteAddr))
|
||||
|
||||
var (
|
||||
user User
|
||||
err error
|
||||
)
|
||||
|
||||
for _, auth := range authenticators {
|
||||
user, err = auth.Authenticate(ctx, r)
|
||||
if err != nil {
|
||||
logger.Debug(ctx, "could not authenticate request", logger.E(errors.WithStack(err)))
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if user != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
api.ErrorResponse(w, http.StatusUnauthorized, ErrCodeUnauthorized, nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
ctx = logger.With(ctx, logger.F("user", user.Subject()))
|
||||
ctx = context.WithValue(ctx, contextKeyUser, user)
|
||||
|
||||
h.ServeHTTP(w, r.WithContext(ctx))
|
||||
}
|
||||
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
}
|
53
internal/chi/log_formatter.go
Normal file
53
internal/chi/log_formatter.go
Normal file
@ -0,0 +1,53 @@
|
||||
package chi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
)
|
||||
|
||||
type LogFormatter struct{}
|
||||
|
||||
// NewLogEntry implements middleware.LogFormatter
|
||||
func (*LogFormatter) NewLogEntry(r *http.Request) middleware.LogEntry {
|
||||
return &LogEntry{
|
||||
method: r.Method,
|
||||
path: r.URL.Path,
|
||||
ctx: r.Context(),
|
||||
}
|
||||
}
|
||||
|
||||
func NewLogFormatter() *LogFormatter {
|
||||
return &LogFormatter{}
|
||||
}
|
||||
|
||||
var _ middleware.LogFormatter = &LogFormatter{}
|
||||
|
||||
type LogEntry struct {
|
||||
method string
|
||||
path string
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// Panic implements middleware.LogEntry
|
||||
func (e *LogEntry) Panic(v interface{}, stack []byte) {
|
||||
logger.Error(e.ctx, fmt.Sprintf("%s %s", e.method, e.path), logger.F("stack", string(stack)))
|
||||
}
|
||||
|
||||
// Write implements middleware.LogEntry
|
||||
func (e *LogEntry) Write(status int, bytes int, header http.Header, elapsed time.Duration, extra interface{}) {
|
||||
logger.Info(e.ctx, fmt.Sprintf("%s %s - %d", e.method, e.path, status),
|
||||
logger.F("status", status),
|
||||
logger.F("bytes", bytes),
|
||||
logger.F("elapsed", elapsed),
|
||||
logger.F("method", e.method),
|
||||
logger.F("path", e.path),
|
||||
logger.F("extra", extra),
|
||||
)
|
||||
}
|
||||
|
||||
var _ middleware.LogEntry = &LogEntry{}
|
144
internal/client/client.go
Normal file
144
internal/client/client.go
Normal file
@ -0,0 +1,144 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/api"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
http *http.Client
|
||||
defaultOpts Options
|
||||
serverURL string
|
||||
}
|
||||
|
||||
func (c *Client) apiGet(ctx context.Context, path string, result any, funcs ...OptionFunc) error {
|
||||
if err := c.apiDo(ctx, http.MethodGet, path, nil, result, funcs...); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) apiPost(ctx context.Context, path string, payload any, result any, funcs ...OptionFunc) error {
|
||||
if err := c.apiDo(ctx, http.MethodPost, path, payload, result, funcs...); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) apiPut(ctx context.Context, path string, payload any, result any, funcs ...OptionFunc) error {
|
||||
if err := c.apiDo(ctx, http.MethodPut, path, payload, result, funcs...); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) apiDelete(ctx context.Context, path string, payload any, result any, funcs ...OptionFunc) error {
|
||||
if err := c.apiDo(ctx, http.MethodDelete, path, payload, result, funcs...); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) apiDo(ctx context.Context, method string, path string, payload any, response any, funcs ...OptionFunc) error {
|
||||
opts := c.defaultOptions()
|
||||
for _, fn := range funcs {
|
||||
fn(opts)
|
||||
}
|
||||
|
||||
url := c.serverURL + path
|
||||
|
||||
logger.Debug(
|
||||
ctx, "new http request",
|
||||
logger.F("method", method),
|
||||
logger.F("url", url),
|
||||
logger.F("payload", payload),
|
||||
)
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
encoder := json.NewEncoder(&buf)
|
||||
|
||||
if err := encoder.Encode(payload); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, url, &buf)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
for key, values := range opts.Headers {
|
||||
for _, v := range values {
|
||||
req.Header.Add(key, v)
|
||||
}
|
||||
}
|
||||
|
||||
res, err := c.http.Do(req)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
defer res.Body.Close()
|
||||
|
||||
decoder := json.NewDecoder(res.Body)
|
||||
|
||||
if err := decoder.Decode(&response); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) defaultOptions() *Options {
|
||||
return &Options{
|
||||
Headers: c.defaultOpts.Headers,
|
||||
}
|
||||
}
|
||||
|
||||
func withResponse[T any]() struct {
|
||||
Data T
|
||||
Error *api.Error
|
||||
} {
|
||||
return struct {
|
||||
Data T
|
||||
Error *api.Error
|
||||
}{}
|
||||
}
|
||||
|
||||
func joinSlice[T any](items []T) string {
|
||||
str := ""
|
||||
|
||||
for idx, item := range items {
|
||||
if idx != 0 {
|
||||
str += ","
|
||||
}
|
||||
|
||||
str += fmt.Sprintf("%v", item)
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
func New(serverURL string, funcs ...OptionFunc) *Client {
|
||||
opts := Options{}
|
||||
for _, fn := range funcs {
|
||||
fn(&opts)
|
||||
}
|
||||
|
||||
return &Client{
|
||||
serverURL: serverURL,
|
||||
http: &http.Client{},
|
||||
defaultOpts: opts,
|
||||
}
|
||||
}
|
32
internal/client/create_layer.go
Normal file
32
internal/client/create_layer.go
Normal file
@ -0,0 +1,32 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/admin"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (c *Client) CreateLayer(ctx context.Context, proxyName store.ProxyName, layerName store.LayerName, layerType store.LayerType, options store.LayerOptions, funcs ...OptionFunc) (*store.Layer, error) {
|
||||
request := admin.CreateLayerRequest{
|
||||
Name: string(layerName),
|
||||
Type: string(layerType),
|
||||
Options: options,
|
||||
}
|
||||
|
||||
response := withResponse[admin.CreateLayerResponse]()
|
||||
|
||||
path := fmt.Sprintf("/api/v1/proxies/%s/layers", proxyName)
|
||||
|
||||
if err := c.apiPost(ctx, path, request, &response); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
if response.Error != nil {
|
||||
return nil, errors.WithStack(response.Error)
|
||||
}
|
||||
|
||||
return response.Data.Layer, nil
|
||||
}
|
30
internal/client/create_proxy.go
Normal file
30
internal/client/create_proxy.go
Normal file
@ -0,0 +1,30 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/admin"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (c *Client) CreateProxy(ctx context.Context, name store.ProxyName, to *url.URL, from []string, funcs ...OptionFunc) (*store.Proxy, error) {
|
||||
request := admin.CreateProxyRequest{
|
||||
Name: string(name),
|
||||
To: to.String(),
|
||||
From: from,
|
||||
}
|
||||
|
||||
response := withResponse[admin.CreateProxyResponse]()
|
||||
|
||||
if err := c.apiPost(ctx, "/api/v1/proxies", request, &response); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
if response.Error != nil {
|
||||
return nil, errors.WithStack(response.Error)
|
||||
}
|
||||
|
||||
return response.Data.Proxy, nil
|
||||
}
|
26
internal/client/delete_layer.go
Normal file
26
internal/client/delete_layer.go
Normal file
@ -0,0 +1,26 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/admin"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (c *Client) DeleteLayer(ctx context.Context, proxyName store.ProxyName, layerName store.LayerName, funcs ...OptionFunc) (store.LayerName, error) {
|
||||
response := withResponse[admin.DeleteLayerResponse]()
|
||||
|
||||
path := fmt.Sprintf("/api/v1/proxies/%s/layers/%s", proxyName, layerName)
|
||||
|
||||
if err := c.apiDelete(ctx, path, nil, &response, funcs...); err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
if response.Error != nil {
|
||||
return "", errors.WithStack(response.Error)
|
||||
}
|
||||
|
||||
return response.Data.LayerName, nil
|
||||
}
|
26
internal/client/delete_proxy.go
Normal file
26
internal/client/delete_proxy.go
Normal file
@ -0,0 +1,26 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/admin"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (c *Client) DeleteProxy(ctx context.Context, proxyName store.ProxyName, funcs ...OptionFunc) (store.ProxyName, error) {
|
||||
response := withResponse[admin.DeleteProxyResponse]()
|
||||
|
||||
path := fmt.Sprintf("/api/v1/proxies/%s", proxyName)
|
||||
|
||||
if err := c.apiDelete(ctx, path, nil, &response, funcs...); err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
if response.Error != nil {
|
||||
return "", errors.WithStack(response.Error)
|
||||
}
|
||||
|
||||
return response.Data.ProxyName, nil
|
||||
}
|
26
internal/client/get_layer.go
Normal file
26
internal/client/get_layer.go
Normal file
@ -0,0 +1,26 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/admin"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (c *Client) GetLayer(ctx context.Context, proxyName store.ProxyName, layerName store.LayerName, funcs ...OptionFunc) (*store.Layer, error) {
|
||||
response := withResponse[admin.GetLayerResponse]()
|
||||
|
||||
path := fmt.Sprintf("/api/v1/proxies/%s/layers/%s", proxyName, layerName)
|
||||
|
||||
if err := c.apiGet(ctx, path, &response, funcs...); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
if response.Error != nil {
|
||||
return nil, errors.WithStack(response.Error)
|
||||
}
|
||||
|
||||
return response.Data.Layer, nil
|
||||
}
|
26
internal/client/get_proxy.go
Normal file
26
internal/client/get_proxy.go
Normal file
@ -0,0 +1,26 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/admin"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (c *Client) GetProxy(ctx context.Context, proxyName store.ProxyName, funcs ...OptionFunc) (*store.Proxy, error) {
|
||||
response := withResponse[admin.GetProxyResponse]()
|
||||
|
||||
path := fmt.Sprintf("/api/v1/proxies/%s", proxyName)
|
||||
|
||||
if err := c.apiGet(ctx, path, &response, funcs...); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
if response.Error != nil {
|
||||
return nil, errors.WithStack(response.Error)
|
||||
}
|
||||
|
||||
return response.Data.Proxy, nil
|
||||
}
|
24
internal/client/options.go
Normal file
24
internal/client/options.go
Normal file
@ -0,0 +1,24 @@
|
||||
package client
|
||||
|
||||
import "net/http"
|
||||
|
||||
type Options struct {
|
||||
Headers http.Header
|
||||
}
|
||||
|
||||
type OptionFunc func(*Options)
|
||||
|
||||
func WithToken(token string) OptionFunc {
|
||||
return func(o *Options) {
|
||||
if o.Headers == nil {
|
||||
o.Headers = http.Header{}
|
||||
}
|
||||
o.Headers.Set("Authorization", "Bearer "+token)
|
||||
}
|
||||
}
|
||||
|
||||
func WithHeaders(headers http.Header) OptionFunc {
|
||||
return func(o *Options) {
|
||||
o.Headers = headers
|
||||
}
|
||||
}
|
75
internal/client/query_layer.go
Normal file
75
internal/client/query_layer.go
Normal file
@ -0,0 +1,75 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/admin"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type QueryLayerOptionFunc func(*QueryLayerOptions)
|
||||
|
||||
type QueryLayerOptions struct {
|
||||
Options []OptionFunc
|
||||
Offset *int
|
||||
Limit *int
|
||||
Names []store.LayerName
|
||||
}
|
||||
|
||||
func WithQueryLayerOptions(funcs ...OptionFunc) QueryLayerOptionFunc {
|
||||
return func(opts *QueryLayerOptions) {
|
||||
opts.Options = funcs
|
||||
}
|
||||
}
|
||||
|
||||
func WithQueryLayerLimit(limit int) QueryLayerOptionFunc {
|
||||
return func(opts *QueryLayerOptions) {
|
||||
opts.Limit = &limit
|
||||
}
|
||||
}
|
||||
|
||||
func WithQueryLayerOffset(offset int) QueryLayerOptionFunc {
|
||||
return func(opts *QueryLayerOptions) {
|
||||
opts.Offset = &offset
|
||||
}
|
||||
}
|
||||
|
||||
func WithQueryLayerNames(names ...store.LayerName) QueryLayerOptionFunc {
|
||||
return func(opts *QueryLayerOptions) {
|
||||
opts.Names = names
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) QueryLayer(ctx context.Context, proxyName store.ProxyName, funcs ...QueryLayerOptionFunc) ([]*store.LayerHeader, error) {
|
||||
options := &QueryLayerOptions{}
|
||||
for _, fn := range funcs {
|
||||
fn(options)
|
||||
}
|
||||
|
||||
query := url.Values{}
|
||||
|
||||
if options.Names != nil && len(options.Names) > 0 {
|
||||
query.Set("names", joinSlice(options.Names))
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("/api/v1/proxies/%s/layers?%s", proxyName, query.Encode())
|
||||
|
||||
response := withResponse[admin.QueryLayerResponse]()
|
||||
|
||||
if options.Options == nil {
|
||||
options.Options = make([]OptionFunc, 0)
|
||||
}
|
||||
|
||||
if err := c.apiGet(ctx, path, &response, options.Options...); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
if response.Error != nil {
|
||||
return nil, errors.WithStack(response.Error)
|
||||
}
|
||||
|
||||
return response.Data.Layers, nil
|
||||
}
|
75
internal/client/query_proxy.go
Normal file
75
internal/client/query_proxy.go
Normal file
@ -0,0 +1,75 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/admin"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type QueryProxyOptionFunc func(*QueryProxyOptions)
|
||||
|
||||
type QueryProxyOptions struct {
|
||||
Options []OptionFunc
|
||||
Limit *int
|
||||
Offset *int
|
||||
Names []store.ProxyName
|
||||
}
|
||||
|
||||
func WithQueryProxyOptions(funcs ...OptionFunc) QueryProxyOptionFunc {
|
||||
return func(opts *QueryProxyOptions) {
|
||||
opts.Options = funcs
|
||||
}
|
||||
}
|
||||
|
||||
func WithQueryProxyLimit(limit int) QueryProxyOptionFunc {
|
||||
return func(opts *QueryProxyOptions) {
|
||||
opts.Limit = &limit
|
||||
}
|
||||
}
|
||||
|
||||
func WithQueryProxyOffset(offset int) QueryProxyOptionFunc {
|
||||
return func(opts *QueryProxyOptions) {
|
||||
opts.Offset = &offset
|
||||
}
|
||||
}
|
||||
|
||||
func WithQueryProxyNames(names ...store.ProxyName) QueryProxyOptionFunc {
|
||||
return func(opts *QueryProxyOptions) {
|
||||
opts.Names = names
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) QueryProxy(ctx context.Context, funcs ...QueryProxyOptionFunc) ([]*store.ProxyHeader, error) {
|
||||
options := &QueryProxyOptions{}
|
||||
for _, fn := range funcs {
|
||||
fn(options)
|
||||
}
|
||||
|
||||
query := url.Values{}
|
||||
|
||||
if options.Names != nil && len(options.Names) > 0 {
|
||||
query.Set("names", joinSlice(options.Names))
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("/api/v1/proxies?%s", query.Encode())
|
||||
|
||||
response := withResponse[admin.QueryProxyResponse]()
|
||||
|
||||
if options.Options == nil {
|
||||
options.Options = make([]OptionFunc, 0)
|
||||
}
|
||||
|
||||
if err := c.apiGet(ctx, path, &response, options.Options...); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
if response.Error != nil {
|
||||
return nil, errors.WithStack(response.Error)
|
||||
}
|
||||
|
||||
return response.Data.Proxies, nil
|
||||
}
|
28
internal/client/update_layer.go
Normal file
28
internal/client/update_layer.go
Normal file
@ -0,0 +1,28 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/admin"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type UpdateLayerOptions = admin.UpdateLayerRequest
|
||||
|
||||
func (c *Client) UpdateLayer(ctx context.Context, proxyName store.ProxyName, layerName store.LayerName, opts *UpdateLayerOptions, funcs ...OptionFunc) (*store.Layer, error) {
|
||||
response := withResponse[admin.UpdateLayerResponse]()
|
||||
|
||||
path := fmt.Sprintf("/api/v1/proxies/%s/layers/%s", proxyName, layerName)
|
||||
|
||||
if err := c.apiPut(ctx, path, opts, &response); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
if response.Error != nil {
|
||||
return nil, errors.WithStack(response.Error)
|
||||
}
|
||||
|
||||
return response.Data.Layer, nil
|
||||
}
|
28
internal/client/update_proxy.go
Normal file
28
internal/client/update_proxy.go
Normal file
@ -0,0 +1,28 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/admin"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type UpdateProxyOptions = admin.UpdateProxyRequest
|
||||
|
||||
func (c *Client) UpdateProxy(ctx context.Context, proxyName store.ProxyName, opts *UpdateProxyOptions, funcs ...OptionFunc) (*store.Proxy, error) {
|
||||
response := withResponse[admin.UpdateProxyResponse]()
|
||||
|
||||
path := fmt.Sprintf("/api/v1/proxies/%s", proxyName)
|
||||
|
||||
if err := c.apiPut(ctx, path, opts, &response); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
if response.Error != nil {
|
||||
return nil, errors.WithStack(response.Error)
|
||||
}
|
||||
|
||||
return response.Data.Proxy, nil
|
||||
}
|
91
internal/command/admin/apierr/wrap.go
Normal file
91
internal/command/admin/apierr/wrap.go
Normal file
@ -0,0 +1,91 @@
|
||||
package apierr
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/api"
|
||||
)
|
||||
|
||||
func Wrap(err error) error {
|
||||
apiErr := &api.Error{}
|
||||
if !errors.As(err, &apiErr) {
|
||||
return err
|
||||
}
|
||||
|
||||
switch apiErr.Code {
|
||||
case api.ErrCodeInvalidFieldValue:
|
||||
return wrapInvalidFieldValueErr(apiErr)
|
||||
|
||||
default:
|
||||
return wrapApiErrorWithMessage(apiErr)
|
||||
}
|
||||
}
|
||||
|
||||
func wrapApiErrorWithMessage(err *api.Error) error {
|
||||
data, ok := err.Data.(map[string]any)
|
||||
if !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
rawMessage, exists := data["message"]
|
||||
if !exists {
|
||||
return err
|
||||
}
|
||||
|
||||
message, ok := rawMessage.(string)
|
||||
if !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
return errors.Wrapf(err, message)
|
||||
}
|
||||
|
||||
func wrapInvalidFieldValueErr(err *api.Error) error {
|
||||
data, ok := err.Data.(map[string]any)
|
||||
if !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
rawFields, exists := data["Fields"]
|
||||
if !exists {
|
||||
return err
|
||||
}
|
||||
|
||||
fields, ok := rawFields.([]any)
|
||||
if !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
field string
|
||||
rule string
|
||||
)
|
||||
|
||||
if len(fields) == 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
firstField, ok := fields[0].(map[string]any)
|
||||
if !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
param, ok := firstField["Param"].(string)
|
||||
if !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
tag, ok := firstField["Tag"].(string)
|
||||
if !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
fieldName, ok := firstField["Field"].(string)
|
||||
if !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
field = fieldName
|
||||
rule = tag + "=" + param
|
||||
|
||||
return errors.Wrapf(err, "server expected field '%s' to match rule '%s'", field, rule)
|
||||
}
|
98
internal/command/admin/flag/flag.go
Normal file
98
internal/command/admin/flag/flag.go
Normal file
@ -0,0 +1,98 @@
|
||||
package flag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/format"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/format/table"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func ComposeFlags(flags ...cli.Flag) []cli.Flag {
|
||||
baseFlags := []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "server",
|
||||
Aliases: []string{"s"},
|
||||
Usage: "use `SERVER` as server url",
|
||||
Value: "http://127.0.0.1:8081",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "format",
|
||||
Aliases: []string{"f"},
|
||||
Usage: fmt.Sprintf("use `FORMAT` as output format (available: %s)", format.Available()),
|
||||
Value: string(table.Format),
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "output-mode",
|
||||
Aliases: []string{"m"},
|
||||
Usage: fmt.Sprintf("use `MODE` as output mode (available: %s)", []format.OutputMode{format.OutputModeCompact, format.OutputModeWide}),
|
||||
Value: string(format.OutputModeCompact),
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "token",
|
||||
Aliases: []string{"t"},
|
||||
EnvVars: []string{`BOUNCER_TOKEN`},
|
||||
Usage: "use `TOKEN` as authentication token",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "token-file",
|
||||
EnvVars: []string{`BOUNCER_TOKEN_FILE`},
|
||||
Usage: "use `TOKEN_FILE` as file containing the authentication token",
|
||||
Value: ".bouncer-token",
|
||||
TakesFile: true,
|
||||
},
|
||||
}
|
||||
|
||||
flags = append(flags, baseFlags...)
|
||||
|
||||
return flags
|
||||
}
|
||||
|
||||
type BaseFlags struct {
|
||||
ServerURL string
|
||||
Format format.Format
|
||||
OutputMode format.OutputMode
|
||||
Token string
|
||||
TokenFile string
|
||||
}
|
||||
|
||||
func GetBaseFlags(ctx *cli.Context) *BaseFlags {
|
||||
serverURL := ctx.String("server")
|
||||
rawFormat := ctx.String("format")
|
||||
rawOutputMode := ctx.String("output-mode")
|
||||
tokenFile := ctx.String("token-file")
|
||||
token := ctx.String("token")
|
||||
|
||||
return &BaseFlags{
|
||||
ServerURL: serverURL,
|
||||
Format: format.Format(rawFormat),
|
||||
OutputMode: format.OutputMode(rawOutputMode),
|
||||
Token: token,
|
||||
TokenFile: tokenFile,
|
||||
}
|
||||
}
|
||||
|
||||
func GetToken(flags *BaseFlags) (string, error) {
|
||||
if flags.Token != "" {
|
||||
return flags.Token, nil
|
||||
}
|
||||
|
||||
if flags.TokenFile == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
rawToken, err := ioutil.ReadFile(flags.TokenFile)
|
||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
if rawToken == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return strings.TrimSpace(string(rawToken)), nil
|
||||
}
|
11
internal/command/admin/flag/util.go
Normal file
11
internal/command/admin/flag/util.go
Normal file
@ -0,0 +1,11 @@
|
||||
package flag
|
||||
|
||||
func AsAnySlice[T any](src []T) []any {
|
||||
dst := make([]any, len(src))
|
||||
|
||||
for i, s := range src {
|
||||
dst[i] = s
|
||||
}
|
||||
|
||||
return dst
|
||||
}
|
72
internal/command/admin/layer/create.go
Normal file
72
internal/command/admin/layer/create.go
Normal file
@ -0,0 +1,72 @@
|
||||
package layer
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/client"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/command/admin/apierr"
|
||||
clientFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/flag"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/command/admin/layer/flag"
|
||||
layerFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/layer/flag"
|
||||
proxyFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/proxy/flag"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/format"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func CreateCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "create",
|
||||
Usage: "Create layer",
|
||||
Flags: layerFlag.WithLayerCreateFlags(),
|
||||
Action: func(ctx *cli.Context) error {
|
||||
baseFlags := clientFlag.GetBaseFlags(ctx)
|
||||
|
||||
token, err := clientFlag.GetToken(baseFlags)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
layerName, err := flag.AssertLayerName(ctx)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
proxyName, err := proxyFlag.AssertProxyName(ctx)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
layerType, err := flag.AssertLayerType(ctx)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
layerOptions, err := flag.AssertLayerOptions(ctx)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
client := client.New(baseFlags.ServerURL, client.WithToken(token))
|
||||
|
||||
layer, err := client.CreateLayer(
|
||||
ctx.Context,
|
||||
proxyName,
|
||||
layerName,
|
||||
layerType,
|
||||
layerOptions,
|
||||
)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
hints := layerHints(baseFlags.OutputMode)
|
||||
|
||||
if err := format.Write(baseFlags.Format, os.Stdout, hints, layer); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
62
internal/command/admin/layer/delete.go
Normal file
62
internal/command/admin/layer/delete.go
Normal file
@ -0,0 +1,62 @@
|
||||
package layer
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/client"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/command/admin/apierr"
|
||||
clientFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/flag"
|
||||
layerFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/layer/flag"
|
||||
proxyFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/proxy/flag"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/format"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func DeleteCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "delete",
|
||||
Usage: "Delete layer",
|
||||
Flags: layerFlag.WithLayerFlags(),
|
||||
Action: func(ctx *cli.Context) error {
|
||||
baseFlags := clientFlag.GetBaseFlags(ctx)
|
||||
|
||||
token, err := clientFlag.GetToken(baseFlags)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
proxyName, err := proxyFlag.AssertProxyName(ctx)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
layerName, err := layerFlag.AssertLayerName(ctx)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
client := client.New(baseFlags.ServerURL, client.WithToken(token))
|
||||
|
||||
layerName, err = client.DeleteLayer(ctx.Context, proxyName, layerName)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
hints := format.Hints{
|
||||
OutputMode: baseFlags.OutputMode,
|
||||
}
|
||||
|
||||
if err := format.Write(baseFlags.Format, os.Stdout, hints, struct {
|
||||
Name store.LayerName `json:"id"`
|
||||
}{
|
||||
Name: layerName,
|
||||
}); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
76
internal/command/admin/layer/flag/flag.go
Normal file
76
internal/command/admin/layer/flag/flag.go
Normal file
@ -0,0 +1,76 @@
|
||||
package flag
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
proxyFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/proxy/flag"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
FlagLayerName = "layer-name"
|
||||
FlagLayerType = "layer-type"
|
||||
FlagLayerOptions = "layer-options"
|
||||
)
|
||||
|
||||
func WithLayerFlags(flags ...cli.Flag) []cli.Flag {
|
||||
baseFlags := proxyFlag.WithProxyFlags(
|
||||
&cli.StringFlag{
|
||||
Name: FlagLayerName,
|
||||
Usage: "use `LAYER_NAME` as targeted layer",
|
||||
Value: "",
|
||||
Required: true,
|
||||
},
|
||||
)
|
||||
|
||||
flags = append(flags, baseFlags...)
|
||||
|
||||
return flags
|
||||
}
|
||||
|
||||
func WithLayerCreateFlags(flags ...cli.Flag) []cli.Flag {
|
||||
return WithLayerFlags(
|
||||
&cli.StringFlag{
|
||||
Name: FlagLayerType,
|
||||
Usage: "Set `LAYER_TYPE` as layer's type",
|
||||
Value: "",
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: FlagLayerOptions,
|
||||
Usage: "Set `LAYER_OPTIONS` as layer's options",
|
||||
Value: "{}",
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func AssertLayerName(ctx *cli.Context) (store.LayerName, error) {
|
||||
rawLayerName := ctx.String(FlagLayerName)
|
||||
|
||||
name, err := store.ValidateName(rawLayerName)
|
||||
if err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
return store.LayerName(name), nil
|
||||
}
|
||||
|
||||
func AssertLayerType(ctx *cli.Context) (store.LayerType, error) {
|
||||
rawLayerType := ctx.String(FlagLayerType)
|
||||
|
||||
return store.LayerType(rawLayerType), nil
|
||||
}
|
||||
|
||||
func AssertLayerOptions(ctx *cli.Context) (store.LayerOptions, error) {
|
||||
rawLayerOptions := ctx.String(FlagLayerOptions)
|
||||
|
||||
layerOptions := store.LayerOptions{}
|
||||
|
||||
if err := json.Unmarshal([]byte(rawLayerOptions), &layerOptions); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return layerOptions, nil
|
||||
}
|
55
internal/command/admin/layer/get.go
Normal file
55
internal/command/admin/layer/get.go
Normal file
@ -0,0 +1,55 @@
|
||||
package layer
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/client"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/command/admin/apierr"
|
||||
clientFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/flag"
|
||||
layerFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/layer/flag"
|
||||
proxyFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/proxy/flag"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/format"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func GetCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "get",
|
||||
Usage: "Get layer",
|
||||
Flags: layerFlag.WithLayerFlags(),
|
||||
Action: func(ctx *cli.Context) error {
|
||||
baseFlags := clientFlag.GetBaseFlags(ctx)
|
||||
|
||||
token, err := clientFlag.GetToken(baseFlags)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
proxyName, err := proxyFlag.AssertProxyName(ctx)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
layerName, err := layerFlag.AssertLayerName(ctx)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
client := client.New(baseFlags.ServerURL, client.WithToken(token))
|
||||
|
||||
layer, err := client.GetLayer(ctx.Context, proxyName, layerName)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
hints := layerHints(baseFlags.OutputMode)
|
||||
|
||||
if err := format.Write(baseFlags.Format, os.Stdout, hints, layer); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
69
internal/command/admin/layer/query.go
Normal file
69
internal/command/admin/layer/query.go
Normal file
@ -0,0 +1,69 @@
|
||||
package layer
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/client"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/command/admin/apierr"
|
||||
clientFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/flag"
|
||||
proxyFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/proxy/flag"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/format"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func QueryCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "query",
|
||||
Usage: "Query layers",
|
||||
Flags: proxyFlag.WithProxyFlags(
|
||||
&cli.StringSliceFlag{
|
||||
Name: "with-name",
|
||||
Usage: "use `WITH_NAME` as query filter",
|
||||
},
|
||||
),
|
||||
Action: func(ctx *cli.Context) error {
|
||||
baseFlags := clientFlag.GetBaseFlags(ctx)
|
||||
|
||||
token, err := clientFlag.GetToken(baseFlags)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
proxyName, err := proxyFlag.AssertProxyName(ctx)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
options := make([]client.QueryLayerOptionFunc, 0)
|
||||
|
||||
rawNames := ctx.StringSlice("with-name")
|
||||
if rawNames != nil {
|
||||
layerNames := func(names []string) []store.LayerName {
|
||||
layerNames := make([]store.LayerName, len(names))
|
||||
for i, name := range names {
|
||||
layerNames[i] = store.LayerName(name)
|
||||
}
|
||||
return layerNames
|
||||
}(rawNames)
|
||||
options = append(options, client.WithQueryLayerNames(layerNames...))
|
||||
}
|
||||
|
||||
client := client.New(baseFlags.ServerURL, client.WithToken(token))
|
||||
|
||||
proxies, err := client.QueryLayer(ctx.Context, proxyName, options...)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
hints := layerHeaderHints(baseFlags.OutputMode)
|
||||
|
||||
if err := format.Write(baseFlags.Format, os.Stdout, hints, clientFlag.AsAnySlice(proxies)...); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
19
internal/command/admin/layer/root.go
Normal file
19
internal/command/admin/layer/root.go
Normal file
@ -0,0 +1,19 @@
|
||||
package layer
|
||||
|
||||
import (
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func Root() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "layer",
|
||||
Usage: "Execute actions related to layers",
|
||||
Subcommands: []*cli.Command{
|
||||
CreateCommand(),
|
||||
GetCommand(),
|
||||
QueryCommand(),
|
||||
UpdateCommand(),
|
||||
DeleteCommand(),
|
||||
},
|
||||
}
|
||||
}
|
91
internal/command/admin/layer/update.go
Normal file
91
internal/command/admin/layer/update.go
Normal file
@ -0,0 +1,91 @@
|
||||
package layer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/client"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/command/admin/apierr"
|
||||
clientFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/flag"
|
||||
layerFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/layer/flag"
|
||||
proxyFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/proxy/flag"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/format"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func UpdateCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "update",
|
||||
Usage: "Update layer",
|
||||
Flags: layerFlag.WithLayerFlags(
|
||||
&cli.BoolFlag{
|
||||
Name: "enabled",
|
||||
Usage: "Enable or disable proxy",
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "weight",
|
||||
Usage: "Set `WEIGHT` as proxy's weight",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "options",
|
||||
Usage: "Set `OPTIONS` as proxy's options",
|
||||
},
|
||||
),
|
||||
Action: func(ctx *cli.Context) error {
|
||||
baseFlags := clientFlag.GetBaseFlags(ctx)
|
||||
|
||||
token, err := clientFlag.GetToken(baseFlags)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
proxyName, err := proxyFlag.AssertProxyName(ctx)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
layerName, err := layerFlag.AssertLayerName(ctx)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
opts := &client.UpdateLayerOptions{}
|
||||
|
||||
if ctx.IsSet("options") {
|
||||
var options store.LayerOptions
|
||||
if err := json.Unmarshal([]byte(ctx.String("options")), &options); err != nil {
|
||||
return errors.Wrap(err, "could not parse options")
|
||||
}
|
||||
|
||||
opts.Options = &options
|
||||
}
|
||||
|
||||
if ctx.IsSet("weight") {
|
||||
weight := ctx.Int("weight")
|
||||
opts.Weight = &weight
|
||||
}
|
||||
|
||||
if ctx.IsSet("enabled") {
|
||||
enabled := ctx.Bool("enabled")
|
||||
opts.Enabled = &enabled
|
||||
}
|
||||
|
||||
client := client.New(baseFlags.ServerURL, client.WithToken(token))
|
||||
|
||||
proxy, err := client.UpdateLayer(ctx.Context, proxyName, layerName, opts)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
hints := layerHints(baseFlags.OutputMode)
|
||||
|
||||
if err := format.Write(baseFlags.Format, os.Stdout, hints, proxy); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
33
internal/command/admin/layer/util.go
Normal file
33
internal/command/admin/layer/util.go
Normal file
@ -0,0 +1,33 @@
|
||||
package layer
|
||||
|
||||
import (
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/format"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/format/table"
|
||||
)
|
||||
|
||||
func layerHeaderHints(outputMode format.OutputMode) format.Hints {
|
||||
return format.Hints{
|
||||
OutputMode: outputMode,
|
||||
Props: []format.Prop{
|
||||
format.NewProp("Name", "Name"),
|
||||
format.NewProp("Type", "Type"),
|
||||
format.NewProp("Enabled", "Enabled"),
|
||||
format.NewProp("Weight", "Weight"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func layerHints(outputMode format.OutputMode) format.Hints {
|
||||
return format.Hints{
|
||||
OutputMode: outputMode,
|
||||
Props: []format.Prop{
|
||||
format.NewProp("Name", "Name"),
|
||||
format.NewProp("Type", "Type"),
|
||||
format.NewProp("Enabled", "Enabled"),
|
||||
format.NewProp("Weight", "Weight"),
|
||||
format.NewProp("Options", "Options"),
|
||||
format.NewProp("CreatedAt", "CreatedAt", table.WithCompactModeMaxColumnWidth(20)),
|
||||
format.NewProp("UpdatedAt", "UpdatedAt", table.WithCompactModeMaxColumnWidth(20)),
|
||||
},
|
||||
}
|
||||
}
|
69
internal/command/admin/proxy/create.go
Normal file
69
internal/command/admin/proxy/create.go
Normal file
@ -0,0 +1,69 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/client"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/command/admin/apierr"
|
||||
clientFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/flag"
|
||||
proxyFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/proxy/flag"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/format"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func CreateCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "create",
|
||||
Usage: "Create proxy",
|
||||
Flags: proxyFlag.WithProxyFlags(
|
||||
&cli.StringFlag{
|
||||
Name: "to",
|
||||
Usage: "Set `TO` as proxy's destination url",
|
||||
Value: "",
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "from",
|
||||
Usage: "Set `FROM` as proxy's patterns to match incoming requests",
|
||||
Value: cli.NewStringSlice("*"),
|
||||
},
|
||||
),
|
||||
Action: func(ctx *cli.Context) error {
|
||||
baseFlags := clientFlag.GetBaseFlags(ctx)
|
||||
|
||||
token, err := clientFlag.GetToken(baseFlags)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
proxyName, err := proxyFlag.AssertProxyName(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "'to' parameter should be a valid url")
|
||||
}
|
||||
|
||||
to, err := url.Parse(ctx.String("to"))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "'to' parameter should be a valid url")
|
||||
}
|
||||
|
||||
from := ctx.StringSlice("from")
|
||||
|
||||
client := client.New(baseFlags.ServerURL, client.WithToken(token))
|
||||
|
||||
proxy, err := client.CreateProxy(ctx.Context, proxyName, to, from)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
hints := proxyHints(baseFlags.OutputMode)
|
||||
|
||||
if err := format.Write(baseFlags.Format, os.Stdout, hints, proxy); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
56
internal/command/admin/proxy/delete.go
Normal file
56
internal/command/admin/proxy/delete.go
Normal file
@ -0,0 +1,56 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/client"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/command/admin/apierr"
|
||||
clientFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/flag"
|
||||
proxyFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/proxy/flag"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/format"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func DeleteCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "delete",
|
||||
Usage: "Delete proxy",
|
||||
Flags: proxyFlag.WithProxyFlags(),
|
||||
Action: func(ctx *cli.Context) error {
|
||||
baseFlags := clientFlag.GetBaseFlags(ctx)
|
||||
|
||||
token, err := clientFlag.GetToken(baseFlags)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
proxyName, err := proxyFlag.AssertProxyName(ctx)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
client := client.New(baseFlags.ServerURL, client.WithToken(token))
|
||||
|
||||
proxyName, err = client.DeleteProxy(ctx.Context, proxyName)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
hints := format.Hints{
|
||||
OutputMode: baseFlags.OutputMode,
|
||||
}
|
||||
|
||||
if err := format.Write(baseFlags.Format, os.Stdout, hints, struct {
|
||||
Name store.ProxyName `json:"id"`
|
||||
}{
|
||||
Name: proxyName,
|
||||
}); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
36
internal/command/admin/proxy/flag/flag.go
Normal file
36
internal/command/admin/proxy/flag/flag.go
Normal file
@ -0,0 +1,36 @@
|
||||
package flag
|
||||
|
||||
import (
|
||||
clientFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/flag"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
const FlagProxyName = "proxy-name"
|
||||
|
||||
func WithProxyFlags(flags ...cli.Flag) []cli.Flag {
|
||||
baseFlags := clientFlag.ComposeFlags(
|
||||
&cli.StringFlag{
|
||||
Name: FlagProxyName,
|
||||
Usage: "use `PROXY_NAME` as targeted proxy",
|
||||
Value: "",
|
||||
Required: true,
|
||||
},
|
||||
)
|
||||
|
||||
flags = append(flags, baseFlags...)
|
||||
|
||||
return flags
|
||||
}
|
||||
|
||||
func AssertProxyName(ctx *cli.Context) (store.ProxyName, error) {
|
||||
rawProxyName := ctx.String(FlagProxyName)
|
||||
|
||||
name, err := store.ValidateName(rawProxyName)
|
||||
if err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
return store.ProxyName(name), nil
|
||||
}
|
49
internal/command/admin/proxy/get.go
Normal file
49
internal/command/admin/proxy/get.go
Normal file
@ -0,0 +1,49 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/client"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/command/admin/apierr"
|
||||
clientFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/flag"
|
||||
proxyFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/proxy/flag"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/format"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func GetCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "get",
|
||||
Usage: "Get proxy",
|
||||
Flags: proxyFlag.WithProxyFlags(),
|
||||
Action: func(ctx *cli.Context) error {
|
||||
baseFlags := clientFlag.GetBaseFlags(ctx)
|
||||
|
||||
token, err := clientFlag.GetToken(baseFlags)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
proxyName, err := proxyFlag.AssertProxyName(ctx)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
client := client.New(baseFlags.ServerURL, client.WithToken(token))
|
||||
|
||||
proxy, err := client.GetProxy(ctx.Context, proxyName)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
hints := proxyHints(baseFlags.OutputMode)
|
||||
|
||||
if err := format.Write(baseFlags.Format, os.Stdout, hints, proxy); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
63
internal/command/admin/proxy/query.go
Normal file
63
internal/command/admin/proxy/query.go
Normal file
@ -0,0 +1,63 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/client"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/command/admin/apierr"
|
||||
clientFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/flag"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/format"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func QueryCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "query",
|
||||
Usage: "Query proxies",
|
||||
Flags: clientFlag.ComposeFlags(
|
||||
&cli.Int64SliceFlag{
|
||||
Name: "ids",
|
||||
Usage: "use `IDS` as query filter",
|
||||
},
|
||||
),
|
||||
Action: func(ctx *cli.Context) error {
|
||||
baseFlags := clientFlag.GetBaseFlags(ctx)
|
||||
|
||||
token, err := clientFlag.GetToken(baseFlags)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
options := make([]client.QueryProxyOptionFunc, 0)
|
||||
|
||||
rawNames := ctx.StringSlice("ids")
|
||||
if rawNames != nil {
|
||||
proxyNames := func(names []string) []store.ProxyName {
|
||||
proxyNames := make([]store.ProxyName, len(names))
|
||||
for i, name := range names {
|
||||
proxyNames[i] = store.ProxyName(name)
|
||||
}
|
||||
return proxyNames
|
||||
}(rawNames)
|
||||
options = append(options, client.WithQueryProxyNames(proxyNames...))
|
||||
}
|
||||
|
||||
client := client.New(baseFlags.ServerURL, client.WithToken(token))
|
||||
|
||||
proxies, err := client.QueryProxy(ctx.Context, options...)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
hints := proxyHeaderHints(baseFlags.OutputMode)
|
||||
|
||||
if err := format.Write(baseFlags.Format, os.Stdout, hints, clientFlag.AsAnySlice(proxies)...); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
19
internal/command/admin/proxy/root.go
Normal file
19
internal/command/admin/proxy/root.go
Normal file
@ -0,0 +1,19 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func Root() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "proxy",
|
||||
Usage: "Execute actions related to proxies",
|
||||
Subcommands: []*cli.Command{
|
||||
GetCommand(),
|
||||
CreateCommand(),
|
||||
QueryCommand(),
|
||||
DeleteCommand(),
|
||||
UpdateCommand(),
|
||||
},
|
||||
}
|
||||
}
|
93
internal/command/admin/proxy/update.go
Normal file
93
internal/command/admin/proxy/update.go
Normal file
@ -0,0 +1,93 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/client"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/command/admin/apierr"
|
||||
clientFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/flag"
|
||||
proxyFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/proxy/flag"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/format"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func UpdateCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "update",
|
||||
Usage: "Update proxy",
|
||||
Flags: proxyFlag.WithProxyFlags(
|
||||
&cli.StringFlag{
|
||||
Name: "to",
|
||||
Usage: "Set `TO` as proxy's destination url",
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "from",
|
||||
Usage: "Set `FROM` as proxy's patterns to match incoming requests",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "enabled",
|
||||
Usage: "Enable or disable proxy",
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "weight",
|
||||
Usage: "Set `WEIGHT` as proxy's weight",
|
||||
},
|
||||
),
|
||||
Action: func(ctx *cli.Context) error {
|
||||
baseFlags := clientFlag.GetBaseFlags(ctx)
|
||||
|
||||
token, err := clientFlag.GetToken(baseFlags)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
proxyName, err := proxyFlag.AssertProxyName(ctx)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
opts := &client.UpdateProxyOptions{}
|
||||
|
||||
if ctx.IsSet("to") {
|
||||
to := ctx.String("to")
|
||||
if _, err := url.Parse(to); err != nil {
|
||||
return errors.Wrap(err, "'to' parameter should be a valid url")
|
||||
}
|
||||
|
||||
opts.To = &to
|
||||
}
|
||||
|
||||
from := ctx.StringSlice("from")
|
||||
if from != nil {
|
||||
opts.From = from
|
||||
}
|
||||
|
||||
if ctx.IsSet("weight") {
|
||||
weight := ctx.Int("weight")
|
||||
opts.Weight = &weight
|
||||
}
|
||||
|
||||
if ctx.IsSet("enabled") {
|
||||
enabled := ctx.Bool("enabled")
|
||||
opts.Enabled = &enabled
|
||||
}
|
||||
|
||||
client := client.New(baseFlags.ServerURL, client.WithToken(token))
|
||||
|
||||
proxy, err := client.UpdateProxy(ctx.Context, proxyName, opts)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
hints := proxyHints(baseFlags.OutputMode)
|
||||
|
||||
if err := format.Write(baseFlags.Format, os.Stdout, hints, proxy); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
32
internal/command/admin/proxy/util.go
Normal file
32
internal/command/admin/proxy/util.go
Normal file
@ -0,0 +1,32 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/format"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/format/table"
|
||||
)
|
||||
|
||||
func proxyHeaderHints(outputMode format.OutputMode) format.Hints {
|
||||
return format.Hints{
|
||||
OutputMode: outputMode,
|
||||
Props: []format.Prop{
|
||||
format.NewProp("Name", "Name"),
|
||||
format.NewProp("Enabled", "Enabled"),
|
||||
format.NewProp("Weight", "Weight"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func proxyHints(outputMode format.OutputMode) format.Hints {
|
||||
return format.Hints{
|
||||
OutputMode: outputMode,
|
||||
Props: []format.Prop{
|
||||
format.NewProp("Name", "Name"),
|
||||
format.NewProp("From", "From"),
|
||||
format.NewProp("To", "To"),
|
||||
format.NewProp("Enabled", "Enabled"),
|
||||
format.NewProp("Weight", "Weight"),
|
||||
format.NewProp("CreatedAt", "CreatedAt", table.WithCompactModeMaxColumnWidth(20)),
|
||||
format.NewProp("UpdatedAt", "UpdatedAt", table.WithCompactModeMaxColumnWidth(20)),
|
||||
},
|
||||
}
|
||||
}
|
18
internal/command/admin/root.go
Normal file
18
internal/command/admin/root.go
Normal file
@ -0,0 +1,18 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/command/admin/layer"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/command/admin/proxy"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func Root() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "admin",
|
||||
Usage: "Admin related commands",
|
||||
Subcommands: []*cli.Command{
|
||||
proxy.Root(),
|
||||
layer.Root(),
|
||||
},
|
||||
}
|
||||
}
|
54
internal/command/auth/create_token.go
Normal file
54
internal/command/auth/create_token.go
Normal file
@ -0,0 +1,54 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/auth/jwt"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/command/common"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/jwk"
|
||||
"github.com/lithammer/shortuuid/v4"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func CreateTokenCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "create-token",
|
||||
Usage: "Create a new authentication token",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "role",
|
||||
Usage: fmt.Sprintf("associate `ROLE` to the token (available: %v)", []jwt.Role{jwt.RoleReader, jwt.RoleWriter}),
|
||||
Value: string(jwt.RoleReader),
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "subject",
|
||||
Usage: "associate `SUBJECT` to the token",
|
||||
Value: fmt.Sprintf("user-%s", shortuuid.New()),
|
||||
},
|
||||
},
|
||||
Action: func(ctx *cli.Context) error {
|
||||
conf, err := common.LoadConfig(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Could not load configuration")
|
||||
}
|
||||
|
||||
subject := ctx.String("subject")
|
||||
role := ctx.String("role")
|
||||
|
||||
key, err := jwk.LoadOrGenerate(string(conf.Admin.Auth.PrivateKey), jwk.DefaultKeySize)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
token, err := jwt.GenerateToken(ctx.Context, key, string(conf.Admin.Auth.Issuer), subject, jwt.Role(role))
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
fmt.Println(token)
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
15
internal/command/auth/root.go
Normal file
15
internal/command/auth/root.go
Normal file
@ -0,0 +1,15 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func Root() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "auth",
|
||||
Usage: "Authentication related commands",
|
||||
Subcommands: []*cli.Command{
|
||||
CreateTokenCommand(),
|
||||
},
|
||||
}
|
||||
}
|
7
internal/command/common/flags.go
Normal file
7
internal/command/common/flags.go
Normal file
@ -0,0 +1,7 @@
|
||||
package common
|
||||
|
||||
import "github.com/urfave/cli/v2"
|
||||
|
||||
func Flags() []cli.Flag {
|
||||
return []cli.Flag{}
|
||||
}
|
27
internal/command/common/load_config.go
Normal file
27
internal/command/common/load_config.go
Normal file
@ -0,0 +1,27 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/config"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func LoadConfig(ctx *cli.Context) (*config.Config, error) {
|
||||
configFile := ctx.String("config")
|
||||
|
||||
var (
|
||||
conf *config.Config
|
||||
err error
|
||||
)
|
||||
|
||||
if configFile != "" {
|
||||
conf, err = config.NewFromFile(configFile)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Could not load config file '%s'", configFile)
|
||||
}
|
||||
} else {
|
||||
conf = config.NewDefault()
|
||||
}
|
||||
|
||||
return conf, nil
|
||||
}
|
36
internal/command/config/dump.go
Normal file
36
internal/command/config/dump.go
Normal file
@ -0,0 +1,36 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/command/common"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/config"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
)
|
||||
|
||||
func Dump() *cli.Command {
|
||||
flags := common.Flags()
|
||||
|
||||
return &cli.Command{
|
||||
Name: "dump",
|
||||
Usage: "Dump the current configuration",
|
||||
Flags: flags,
|
||||
Action: func(ctx *cli.Context) error {
|
||||
conf, err := common.LoadConfig(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Could not load configuration")
|
||||
}
|
||||
|
||||
logger.SetFormat(logger.Format(conf.Logger.Format))
|
||||
logger.SetLevel(logger.Level(conf.Logger.Level))
|
||||
|
||||
if err := config.Dump(conf, os.Stdout); err != nil {
|
||||
return errors.Wrap(err, "Could not dump configuration")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
13
internal/command/config/root.go
Normal file
13
internal/command/config/root.go
Normal file
@ -0,0 +1,13 @@
|
||||
package config
|
||||
|
||||
import "github.com/urfave/cli/v2"
|
||||
|
||||
func Root() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "config",
|
||||
Usage: "Config related commands",
|
||||
Subcommands: []*cli.Command{
|
||||
Dump(),
|
||||
},
|
||||
}
|
||||
}
|
107
internal/command/main.go
Normal file
107
internal/command/main.go
Normal file
@ -0,0 +1,107 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func Main(buildDate, projectVersion, gitRef, defaultConfigPath string, commands ...*cli.Command) {
|
||||
ctx := context.Background()
|
||||
|
||||
compiled, err := time.Parse(time.RFC3339, buildDate)
|
||||
if err != nil {
|
||||
panic(errors.Wrapf(err, "could not parse build date '%s'", buildDate))
|
||||
}
|
||||
|
||||
app := &cli.App{
|
||||
Version: fmt.Sprintf("%s (%s, %s)", projectVersion, gitRef, buildDate),
|
||||
Compiled: compiled,
|
||||
Name: "bouncer",
|
||||
Usage: "reverse proxy server with dynamic queuing management",
|
||||
Commands: commands,
|
||||
Before: func(ctx *cli.Context) error {
|
||||
workdir := ctx.String("workdir")
|
||||
// Switch to new working directory if defined
|
||||
if workdir != "" {
|
||||
if err := os.Chdir(workdir); err != nil {
|
||||
return errors.Wrap(err, "could not change working directory")
|
||||
}
|
||||
}
|
||||
|
||||
if err := ctx.Set("projectVersion", projectVersion); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if err := ctx.Set("gitRef", gitRef); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if err := ctx.Set("buildDate", buildDate); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "workdir",
|
||||
Value: "",
|
||||
Usage: "The working directory",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "projectVersion",
|
||||
Value: "",
|
||||
Hidden: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "gitRef",
|
||||
Value: "",
|
||||
Hidden: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "buildDate",
|
||||
Value: "",
|
||||
Hidden: true,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "debug",
|
||||
EnvVars: []string{"BOUNCER_DEBUG"},
|
||||
Value: false,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "config",
|
||||
Aliases: []string{"c"},
|
||||
EnvVars: []string{"BOUNCER_CONFIG"},
|
||||
Value: defaultConfigPath,
|
||||
TakesFile: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
app.ExitErrHandler = func(ctx *cli.Context, err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
debug := ctx.Bool("debug")
|
||||
|
||||
if !debug {
|
||||
fmt.Printf("[ERROR] %v\n", err)
|
||||
} else {
|
||||
fmt.Printf("%+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(cli.FlagsByName(app.Flags))
|
||||
sort.Sort(cli.CommandsByName(app.Commands))
|
||||
|
||||
if err := app.RunContext(ctx, os.Args); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
15
internal/command/server/admin/root.go
Normal file
15
internal/command/server/admin/root.go
Normal file
@ -0,0 +1,15 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func Root() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "admin",
|
||||
Usage: "Admin server related commands",
|
||||
Subcommands: []*cli.Command{
|
||||
RunCommand(),
|
||||
},
|
||||
}
|
||||
}
|
54
internal/command/server/admin/run.go
Normal file
54
internal/command/server/admin/run.go
Normal file
@ -0,0 +1,54 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/admin"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/command/common"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
)
|
||||
|
||||
func RunCommand() *cli.Command {
|
||||
flags := common.Flags()
|
||||
|
||||
return &cli.Command{
|
||||
Name: "run",
|
||||
Usage: "Run the admin server",
|
||||
Flags: flags,
|
||||
Action: func(ctx *cli.Context) error {
|
||||
conf, err := common.LoadConfig(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not load configuration")
|
||||
}
|
||||
|
||||
logger.SetFormat(logger.Format(conf.Logger.Format))
|
||||
logger.SetLevel(logger.Level(conf.Logger.Level))
|
||||
|
||||
srv := admin.NewServer(
|
||||
admin.WithServerConfig(conf.Admin),
|
||||
admin.WithRedisConfig(conf.Redis),
|
||||
)
|
||||
|
||||
addrs, srvErrs := srv.Start(ctx.Context)
|
||||
|
||||
select {
|
||||
case addr := <-addrs:
|
||||
url := fmt.Sprintf("http://%s", addr.String())
|
||||
url = strings.Replace(url, "0.0.0.0", "127.0.0.1", 1)
|
||||
|
||||
logger.Info(ctx.Context, "listening", logger.F("url", url))
|
||||
case err = <-srvErrs:
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if err = <-srvErrs; err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
15
internal/command/server/proxy/root.go
Normal file
15
internal/command/server/proxy/root.go
Normal file
@ -0,0 +1,15 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func Root() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "proxy",
|
||||
Usage: "Proxy server related commands",
|
||||
Subcommands: []*cli.Command{
|
||||
RunCommand(),
|
||||
},
|
||||
}
|
||||
}
|
87
internal/command/server/proxy/run.go
Normal file
87
internal/command/server/proxy/run.go
Normal file
@ -0,0 +1,87 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/command/common"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/config"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/proxy"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/queue"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/setup"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
)
|
||||
|
||||
func RunCommand() *cli.Command {
|
||||
flags := common.Flags()
|
||||
|
||||
return &cli.Command{
|
||||
Name: "run",
|
||||
Usage: "Run the proxy server",
|
||||
Flags: flags,
|
||||
Action: func(ctx *cli.Context) error {
|
||||
conf, err := common.LoadConfig(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not load configuration")
|
||||
}
|
||||
|
||||
logger.SetFormat(logger.Format(conf.Logger.Format))
|
||||
logger.SetLevel(logger.Level(conf.Logger.Level))
|
||||
|
||||
layers, err := initDirectorLayers(ctx.Context, conf)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not initialize director layers")
|
||||
}
|
||||
|
||||
srv := proxy.NewServer(
|
||||
proxy.WithServerConfig(conf.Proxy),
|
||||
proxy.WithRedisConfig(conf.Redis),
|
||||
proxy.WithDirectorLayers(layers...),
|
||||
)
|
||||
|
||||
addrs, srvErrs := srv.Start(ctx.Context)
|
||||
|
||||
select {
|
||||
case addr := <-addrs:
|
||||
url := fmt.Sprintf("http://%s", addr.String())
|
||||
url = strings.Replace(url, "0.0.0.0", "127.0.0.1", 1)
|
||||
|
||||
logger.Info(ctx.Context, "listening", logger.F("url", url))
|
||||
case err = <-srvErrs:
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if err = <-srvErrs; err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func initDirectorLayers(ctx context.Context, conf *config.Config) ([]director.Layer, error) {
|
||||
layers := make([]director.Layer, 0)
|
||||
|
||||
queue, err := initQueueLayer(ctx, conf)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not initialize queue layer")
|
||||
}
|
||||
|
||||
layers = append(layers, queue)
|
||||
|
||||
return layers, nil
|
||||
}
|
||||
|
||||
func initQueueLayer(ctx context.Context, conf *config.Config) (*queue.Queue, error) {
|
||||
adapter, err := setup.NewQueueAdapter(ctx, conf.Redis)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return queue.New(adapter), nil
|
||||
}
|
18
internal/command/server/root.go
Normal file
18
internal/command/server/root.go
Normal file
@ -0,0 +1,18 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/command/server/admin"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/command/server/proxy"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func Root() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "server",
|
||||
Usage: "Server related commands",
|
||||
Subcommands: []*cli.Command{
|
||||
proxy.Root(),
|
||||
admin.Root(),
|
||||
},
|
||||
}
|
||||
}
|
27
internal/config/admin_server.go
Normal file
27
internal/config/admin_server.go
Normal file
@ -0,0 +1,27 @@
|
||||
package config
|
||||
|
||||
type AdminServerConfig struct {
|
||||
HTTP HTTPConfig `yaml:"http"`
|
||||
CORS CORSConfig `yaml:"cors"`
|
||||
Auth AuthConfig `yaml:"auth"`
|
||||
}
|
||||
|
||||
func NewDefaultAdminServerConfig() AdminServerConfig {
|
||||
return AdminServerConfig{
|
||||
HTTP: NewHTTPConfig("127.0.0.1", 8081),
|
||||
CORS: NewDefaultCORSConfig(),
|
||||
Auth: NewDefaultAuthConfig(),
|
||||
}
|
||||
}
|
||||
|
||||
type AuthConfig struct {
|
||||
Issuer InterpolatedString `yaml:"issuer"`
|
||||
PrivateKey InterpolatedString `yaml:"privateKey"`
|
||||
}
|
||||
|
||||
func NewDefaultAuthConfig() AuthConfig {
|
||||
return AuthConfig{
|
||||
Issuer: "http://127.0.0.1:8081",
|
||||
PrivateKey: "admin-key.json",
|
||||
}
|
||||
}
|
64
internal/config/config.go
Normal file
64
internal/config/config.go
Normal file
@ -0,0 +1,64 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Config definition
|
||||
type Config struct {
|
||||
Admin AdminServerConfig `yaml:"admin"`
|
||||
Proxy ProxyServerConfig `yaml:"proxy"`
|
||||
Redis RedisConfig `yaml:"redis"`
|
||||
Logger LoggerConfig `yaml:"logger"`
|
||||
}
|
||||
|
||||
// NewFromFile retrieves the configuration from the given file
|
||||
func NewFromFile(path string) (*Config, error) {
|
||||
config := NewDefault()
|
||||
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not read file '%s'", path)
|
||||
}
|
||||
|
||||
if err := yaml.Unmarshal(data, config); err != nil {
|
||||
return nil, errors.Wrapf(err, "could not unmarshal configuration")
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// NewDumpDefault dump the new default configuration
|
||||
func NewDumpDefault() *Config {
|
||||
config := NewDefault()
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
// NewDefault return new default configuration
|
||||
func NewDefault() *Config {
|
||||
return &Config{
|
||||
Admin: NewDefaultAdminServerConfig(),
|
||||
Proxy: NewDefaultProxyServerConfig(),
|
||||
Logger: NewDefaultLoggerConfig(),
|
||||
Redis: NewDefaultRedisConfig(),
|
||||
}
|
||||
}
|
||||
|
||||
// Dump the given configuration in the given writer
|
||||
func Dump(config *Config, w io.Writer) error {
|
||||
data, err := yaml.Marshal(config)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not dump config")
|
||||
}
|
||||
|
||||
if _, err := w.Write(data); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
16
internal/config/config_test.go
Normal file
16
internal/config/config_test.go
Normal file
@ -0,0 +1,16 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func TestConfigLoad(t *testing.T) {
|
||||
filepath := "./testdata/config.yml"
|
||||
|
||||
_, err := NewFromFile(filepath)
|
||||
if err != nil {
|
||||
t.Fatal(errors.WithStack(err))
|
||||
}
|
||||
}
|
20
internal/config/cors.go
Normal file
20
internal/config/cors.go
Normal file
@ -0,0 +1,20 @@
|
||||
package config
|
||||
|
||||
type CORSConfig struct {
|
||||
AllowedOrigins InterpolatedStringSlice `yaml:"allowedOrigins"`
|
||||
AllowCredentials InterpolatedBool `yaml:"allowCredentials"`
|
||||
AllowedMethods InterpolatedStringSlice `yaml:"allowMethods"`
|
||||
AllowedHeaders InterpolatedStringSlice `yaml:"allowedHeaders"`
|
||||
Debug InterpolatedBool `yaml:"debug"`
|
||||
}
|
||||
|
||||
// NewDefaultCorsConfig return the default CORS configuration.
|
||||
func NewDefaultCORSConfig() CORSConfig {
|
||||
return CORSConfig{
|
||||
AllowedOrigins: InterpolatedStringSlice{"http://localhost:3001"},
|
||||
AllowCredentials: true,
|
||||
AllowedMethods: InterpolatedStringSlice{"POST", "GET", "PUT", "DELETE"},
|
||||
AllowedHeaders: InterpolatedStringSlice{"Origin", "Accept", "Content-Type", "Authorization", "Sentry-Trace"},
|
||||
Debug: false,
|
||||
}
|
||||
}
|
125
internal/config/environment.go
Normal file
125
internal/config/environment.go
Normal file
@ -0,0 +1,125 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var reVar = regexp.MustCompile(`^\${(\w+)}$`)
|
||||
|
||||
type InterpolatedString string
|
||||
|
||||
func (is *InterpolatedString) UnmarshalYAML(value *yaml.Node) error {
|
||||
var str string
|
||||
|
||||
if err := value.Decode(&str); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if match := reVar.FindStringSubmatch(str); len(match) > 0 {
|
||||
*is = InterpolatedString(os.Getenv(match[1]))
|
||||
} else {
|
||||
*is = InterpolatedString(str)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type InterpolatedInt int
|
||||
|
||||
func (ii *InterpolatedInt) UnmarshalYAML(value *yaml.Node) error {
|
||||
var str string
|
||||
|
||||
if err := value.Decode(&str); err != nil {
|
||||
return errors.Wrapf(err, "could not decode value '%v' (line '%d') into string", value.Value, value.Line)
|
||||
}
|
||||
|
||||
if match := reVar.FindStringSubmatch(str); len(match) > 0 {
|
||||
str = os.Getenv(match[1])
|
||||
}
|
||||
|
||||
intVal, err := strconv.ParseInt(str, 10, 32)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not parse int '%v', line '%d'", str, value.Line)
|
||||
}
|
||||
|
||||
*ii = InterpolatedInt(int(intVal))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type InterpolatedBool bool
|
||||
|
||||
func (ib *InterpolatedBool) UnmarshalYAML(value *yaml.Node) error {
|
||||
var str string
|
||||
|
||||
if err := value.Decode(&str); err != nil {
|
||||
return errors.Wrapf(err, "could not decode value '%v' (line '%d') into string", value.Value, value.Line)
|
||||
}
|
||||
|
||||
if match := reVar.FindStringSubmatch(str); len(match) > 0 {
|
||||
str = os.Getenv(match[1])
|
||||
}
|
||||
|
||||
boolVal, err := strconv.ParseBool(str)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not parse bool '%v', line '%d'", str, value.Line)
|
||||
}
|
||||
|
||||
*ib = InterpolatedBool(boolVal)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type InterpolatedMap map[string]interface{}
|
||||
|
||||
func (im *InterpolatedMap) UnmarshalYAML(value *yaml.Node) error {
|
||||
var data map[string]interface{}
|
||||
|
||||
if err := value.Decode(&data); err != nil {
|
||||
return errors.Wrapf(err, "could not decode value '%v' (line '%d') into map", value.Value, value.Line)
|
||||
}
|
||||
|
||||
for key, value := range data {
|
||||
strVal, ok := value.(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if match := reVar.FindStringSubmatch(strVal); len(match) > 0 {
|
||||
strVal = os.Getenv(match[1])
|
||||
}
|
||||
|
||||
data[key] = strVal
|
||||
}
|
||||
|
||||
*im = data
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type InterpolatedStringSlice []string
|
||||
|
||||
func (iss *InterpolatedStringSlice) UnmarshalYAML(value *yaml.Node) error {
|
||||
var data []string
|
||||
|
||||
if err := value.Decode(&data); err != nil {
|
||||
return errors.Wrapf(err, "could not decode value '%v' (line '%d') into map", value.Value, value.Line)
|
||||
}
|
||||
|
||||
for index, value := range data {
|
||||
if match := reVar.FindStringSubmatch(value); len(match) > 0 {
|
||||
value = os.Getenv(match[1])
|
||||
}
|
||||
|
||||
data[index] = value
|
||||
}
|
||||
|
||||
*iss = data
|
||||
|
||||
return nil
|
||||
}
|
13
internal/config/http.go
Normal file
13
internal/config/http.go
Normal file
@ -0,0 +1,13 @@
|
||||
package config
|
||||
|
||||
type HTTPConfig struct {
|
||||
Host InterpolatedString `yaml:"host"`
|
||||
Port InterpolatedInt `yaml:"port"`
|
||||
}
|
||||
|
||||
func NewHTTPConfig(host string, port int) HTTPConfig {
|
||||
return HTTPConfig{
|
||||
Host: InterpolatedString(host),
|
||||
Port: InterpolatedInt(port),
|
||||
}
|
||||
}
|
15
internal/config/logger.go
Normal file
15
internal/config/logger.go
Normal file
@ -0,0 +1,15 @@
|
||||
package config
|
||||
|
||||
import "gitlab.com/wpetit/goweb/logger"
|
||||
|
||||
type LoggerConfig struct {
|
||||
Level InterpolatedInt `yaml:"level"`
|
||||
Format InterpolatedString `yaml:"format"`
|
||||
}
|
||||
|
||||
func NewDefaultLoggerConfig() LoggerConfig {
|
||||
return LoggerConfig{
|
||||
Level: InterpolatedInt(logger.LevelInfo),
|
||||
Format: InterpolatedString(logger.FormatHuman),
|
||||
}
|
||||
}
|
11
internal/config/proxy_server.go
Normal file
11
internal/config/proxy_server.go
Normal file
@ -0,0 +1,11 @@
|
||||
package config
|
||||
|
||||
type ProxyServerConfig struct {
|
||||
HTTP HTTPConfig `yaml:"http"`
|
||||
}
|
||||
|
||||
func NewDefaultProxyServerConfig() ProxyServerConfig {
|
||||
return ProxyServerConfig{
|
||||
HTTP: NewHTTPConfig("0.0.0.0", 8080),
|
||||
}
|
||||
}
|
19
internal/config/redis.go
Normal file
19
internal/config/redis.go
Normal file
@ -0,0 +1,19 @@
|
||||
package config
|
||||
|
||||
const (
|
||||
RedisModeSimple = "simple"
|
||||
RedisModeSentinel = "sentinel"
|
||||
RedisModeCluster = "cluster"
|
||||
)
|
||||
|
||||
type RedisConfig struct {
|
||||
Adresses InterpolatedStringSlice `yaml:"addresses"`
|
||||
Master InterpolatedString `yaml:"master"`
|
||||
}
|
||||
|
||||
func NewDefaultRedisConfig() RedisConfig {
|
||||
return RedisConfig{
|
||||
Adresses: InterpolatedStringSlice{"localhost:6379"},
|
||||
Master: "",
|
||||
}
|
||||
}
|
6
internal/config/testdata/config.yml
vendored
Normal file
6
internal/config/testdata/config.yml
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
logger:
|
||||
level: 0
|
||||
format: human
|
||||
http:
|
||||
host: "0.0.0.0"
|
||||
port: 3000
|
38
internal/format/json/writer.go
Normal file
38
internal/format/json/writer.go
Normal file
@ -0,0 +1,38 @@
|
||||
package json
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/format"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const Format format.Format = "json"
|
||||
|
||||
func init() {
|
||||
format.Register(Format, NewWriter())
|
||||
}
|
||||
|
||||
type Writer struct{}
|
||||
|
||||
// Format implements format.Writer.
|
||||
func (*Writer) Write(writer io.Writer, hints format.Hints, data ...any) error {
|
||||
encoder := json.NewEncoder(writer)
|
||||
|
||||
if hints.OutputMode == format.OutputModeWide {
|
||||
encoder.SetIndent("", " ")
|
||||
}
|
||||
|
||||
if err := encoder.Encode(data); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewWriter() *Writer {
|
||||
return &Writer{}
|
||||
}
|
||||
|
||||
var _ format.Writer = &Writer{}
|
49
internal/format/prop.go
Normal file
49
internal/format/prop.go
Normal file
@ -0,0 +1,49 @@
|
||||
package format
|
||||
|
||||
type PropHintName string
|
||||
|
||||
type PropHintFunc func() (PropHintName, any)
|
||||
|
||||
type Prop struct {
|
||||
name string
|
||||
label string
|
||||
hints map[PropHintName]any
|
||||
}
|
||||
|
||||
func (p *Prop) Name() string {
|
||||
return p.name
|
||||
}
|
||||
|
||||
func (p *Prop) Label() string {
|
||||
return p.label
|
||||
}
|
||||
|
||||
func NewProp(name, label string, funcs ...PropHintFunc) Prop {
|
||||
hints := make(map[PropHintName]any)
|
||||
for _, fn := range funcs {
|
||||
name, value := fn()
|
||||
hints[name] = value
|
||||
}
|
||||
|
||||
return Prop{name, label, hints}
|
||||
}
|
||||
|
||||
func WithPropHint(name PropHintName, value any) PropHintFunc {
|
||||
return func() (PropHintName, any) {
|
||||
return name, value
|
||||
}
|
||||
}
|
||||
|
||||
func PropHint[T any](p Prop, name PropHintName, defaultValue T) T {
|
||||
rawValue, exists := p.hints[name]
|
||||
if !exists {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
value, ok := rawValue.(T)
|
||||
if !ok {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
46
internal/format/registry.go
Normal file
46
internal/format/registry.go
Normal file
@ -0,0 +1,46 @@
|
||||
package format
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Format string
|
||||
|
||||
type Registry map[Format]Writer
|
||||
|
||||
var defaultRegistry = Registry{}
|
||||
|
||||
var ErrUnknownFormat = errors.New("unknown format")
|
||||
|
||||
func Write(format Format, writer io.Writer, hints Hints, data ...any) error {
|
||||
formatWriter, exists := defaultRegistry[format]
|
||||
if !exists {
|
||||
return errors.WithStack(ErrUnknownFormat)
|
||||
}
|
||||
|
||||
if hints.OutputMode == "" {
|
||||
hints.OutputMode = OutputModeCompact
|
||||
}
|
||||
|
||||
if err := formatWriter.Write(writer, hints, data...); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Available() []Format {
|
||||
formats := make([]Format, 0, len(defaultRegistry))
|
||||
|
||||
for f := range defaultRegistry {
|
||||
formats = append(formats, f)
|
||||
}
|
||||
|
||||
return formats
|
||||
}
|
||||
|
||||
func Register(format Format, writer Writer) {
|
||||
defaultRegistry[format] = writer
|
||||
}
|
61
internal/format/table/prop.go
Normal file
61
internal/format/table/prop.go
Normal file
@ -0,0 +1,61 @@
|
||||
package table
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/format"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
hintCompactModeMaxColumnWidth format.PropHintName = "compactModeMaxColumnWidth"
|
||||
)
|
||||
|
||||
func WithCompactModeMaxColumnWidth(max int) format.PropHintFunc {
|
||||
return format.WithPropHint(hintCompactModeMaxColumnWidth, max)
|
||||
}
|
||||
|
||||
func getProps(d any) []format.Prop {
|
||||
props := make([]format.Prop, 0)
|
||||
|
||||
v := reflect.Indirect(reflect.ValueOf(d))
|
||||
typeOf := v.Type()
|
||||
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
name := typeOf.Field(i).Name
|
||||
props = append(props, format.NewProp(name, name))
|
||||
}
|
||||
|
||||
return props
|
||||
}
|
||||
|
||||
func getFieldValue(obj any, name string) string {
|
||||
v := reflect.Indirect(reflect.ValueOf(obj))
|
||||
|
||||
fieldValue := v.FieldByName(name)
|
||||
|
||||
if !fieldValue.IsValid() {
|
||||
return ""
|
||||
}
|
||||
|
||||
switch fieldValue.Kind() {
|
||||
case reflect.Map:
|
||||
fallthrough
|
||||
case reflect.Struct:
|
||||
fallthrough
|
||||
case reflect.Slice:
|
||||
fallthrough
|
||||
case reflect.Interface:
|
||||
json, err := json.Marshal(fieldValue.Interface())
|
||||
if err != nil {
|
||||
panic(errors.WithStack(err))
|
||||
}
|
||||
|
||||
return string(json)
|
||||
|
||||
default:
|
||||
return fmt.Sprintf("%v", fieldValue)
|
||||
}
|
||||
}
|
80
internal/format/table/writer.go
Normal file
80
internal/format/table/writer.go
Normal file
@ -0,0 +1,80 @@
|
||||
package table
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/format"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
)
|
||||
|
||||
const Format format.Format = "table"
|
||||
|
||||
const DefaultCompactModeMaxColumnWidth = 30
|
||||
|
||||
func init() {
|
||||
format.Register(Format, NewWriter(DefaultCompactModeMaxColumnWidth))
|
||||
}
|
||||
|
||||
type Writer struct {
|
||||
compactModeMaxColumnWidth int
|
||||
}
|
||||
|
||||
// Write implements format.Writer.
|
||||
func (w *Writer) Write(writer io.Writer, hints format.Hints, data ...any) error {
|
||||
t := table.NewWriter()
|
||||
|
||||
t.SetOutputMirror(writer)
|
||||
|
||||
var props []format.Prop
|
||||
|
||||
if hints.Props != nil {
|
||||
props = hints.Props
|
||||
} else {
|
||||
if len(data) > 0 {
|
||||
props = getProps(data[0])
|
||||
} else {
|
||||
props = make([]format.Prop, 0)
|
||||
}
|
||||
}
|
||||
|
||||
labels := table.Row{}
|
||||
|
||||
for _, p := range props {
|
||||
labels = append(labels, p.Label())
|
||||
}
|
||||
|
||||
t.AppendHeader(labels)
|
||||
|
||||
isCompactMode := hints.OutputMode == format.OutputModeCompact
|
||||
|
||||
for _, d := range data {
|
||||
row := table.Row{}
|
||||
|
||||
for _, p := range props {
|
||||
value := getFieldValue(d, p.Name())
|
||||
|
||||
compactModeMaxColumnWidth := format.PropHint(p,
|
||||
hintCompactModeMaxColumnWidth,
|
||||
w.compactModeMaxColumnWidth,
|
||||
)
|
||||
|
||||
if isCompactMode && len(value) > compactModeMaxColumnWidth {
|
||||
value = value[:compactModeMaxColumnWidth] + "..."
|
||||
}
|
||||
|
||||
row = append(row, value)
|
||||
}
|
||||
|
||||
t.AppendRow(row)
|
||||
}
|
||||
|
||||
t.Render()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewWriter(compactModeMaxColumnWidth int) *Writer {
|
||||
return &Writer{compactModeMaxColumnWidth}
|
||||
}
|
||||
|
||||
var _ format.Writer = &Writer{}
|
86
internal/format/table/writer_test.go
Normal file
86
internal/format/table/writer_test.go
Normal file
@ -0,0 +1,86 @@
|
||||
package table
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/format"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type dummyItem struct {
|
||||
MyString string
|
||||
MyInt int
|
||||
MySub subItem
|
||||
}
|
||||
|
||||
type subItem struct {
|
||||
MyBool bool
|
||||
}
|
||||
|
||||
var dummyItems = []any{
|
||||
dummyItem{
|
||||
MyString: "Foo",
|
||||
MyInt: 1,
|
||||
MySub: subItem{
|
||||
MyBool: false,
|
||||
},
|
||||
},
|
||||
dummyItem{
|
||||
MyString: "Bar",
|
||||
MyInt: 0,
|
||||
MySub: subItem{
|
||||
MyBool: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestWriterNoHints(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
writer := NewWriter(DefaultCompactModeMaxColumnWidth)
|
||||
|
||||
if err := writer.Write(&buf, format.Hints{}, dummyItems...); err != nil {
|
||||
t.Fatalf("%+v", errors.WithStack(err))
|
||||
}
|
||||
|
||||
expected := `+----------+-------+------------------+
|
||||
| MYSTRING | MYINT | MYSUB |
|
||||
+----------+-------+------------------+
|
||||
| Foo | 1 | {"MyBool":false} |
|
||||
| Bar | 0 | {"MyBool":true} |
|
||||
+----------+-------+------------------+`
|
||||
|
||||
if e, g := strings.TrimSpace(expected), strings.TrimSpace(buf.String()); e != g {
|
||||
t.Errorf("buf.String(): expected \n%v\ngot\n%v", e, g)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriterWithPropHints(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
writer := NewWriter(DefaultCompactModeMaxColumnWidth)
|
||||
|
||||
hints := format.Hints{
|
||||
Props: []format.Prop{
|
||||
format.NewProp("MyString", "MyString"),
|
||||
format.NewProp("MyInt", "MyInt"),
|
||||
},
|
||||
}
|
||||
|
||||
if err := writer.Write(&buf, hints, dummyItems...); err != nil {
|
||||
t.Fatalf("%+v", errors.WithStack(err))
|
||||
}
|
||||
|
||||
expected := `+----------+-------+
|
||||
| MYSTRING | MYINT |
|
||||
+----------+-------+
|
||||
| Foo | 1 |
|
||||
| Bar | 0 |
|
||||
+----------+-------+`
|
||||
|
||||
if e, g := strings.TrimSpace(expected), strings.TrimSpace(buf.String()); e != g {
|
||||
t.Errorf("buf.String(): expected \n%v\ngot\n%v", e, g)
|
||||
}
|
||||
}
|
19
internal/format/writer.go
Normal file
19
internal/format/writer.go
Normal file
@ -0,0 +1,19 @@
|
||||
package format
|
||||
|
||||
import "io"
|
||||
|
||||
type OutputMode string
|
||||
|
||||
const (
|
||||
OutputModeWide OutputMode = "wide"
|
||||
OutputModeCompact OutputMode = "compact"
|
||||
)
|
||||
|
||||
type Hints struct {
|
||||
Props []Prop
|
||||
OutputMode OutputMode
|
||||
}
|
||||
|
||||
type Writer interface {
|
||||
Write(writer io.Writer, hints Hints, data ...any) error
|
||||
}
|
6
internal/imports/format/format_import.go
Normal file
6
internal/imports/format/format_import.go
Normal file
@ -0,0 +1,6 @@
|
||||
package format
|
||||
|
||||
import (
|
||||
_ "forge.cadoles.com/cadoles/bouncer/internal/format/json"
|
||||
_ "forge.cadoles.com/cadoles/bouncer/internal/format/table"
|
||||
)
|
140
internal/jwk/jwk.go
Normal file
140
internal/jwk/jwk.go
Normal file
@ -0,0 +1,140 @@
|
||||
package jwk
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil/base58"
|
||||
"github.com/lestrrat-go/jwx/v2/jwa"
|
||||
"github.com/lestrrat-go/jwx/v2/jwk"
|
||||
"github.com/lestrrat-go/jwx/v2/jws"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const DefaultKeySize = 2048
|
||||
|
||||
type (
|
||||
Key = jwk.Key
|
||||
Set = jwk.Set
|
||||
ParseOption = jwk.ParseOption
|
||||
)
|
||||
|
||||
var (
|
||||
FromRaw = jwk.FromRaw
|
||||
NewSet = jwk.NewSet
|
||||
)
|
||||
|
||||
const AlgorithmKey = jwk.AlgorithmKey
|
||||
|
||||
func Parse(src []byte, options ...jwk.ParseOption) (Set, error) {
|
||||
return jwk.Parse(src, options...)
|
||||
}
|
||||
|
||||
func PublicKeySet(keys ...jwk.Key) (jwk.Set, error) {
|
||||
set := jwk.NewSet()
|
||||
|
||||
for _, k := range keys {
|
||||
pubkey, err := k.PublicKey()
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
if err := pubkey.Set(jwk.AlgorithmKey, jwa.RS256); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
if err := set.AddKey(pubkey); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
|
||||
return set, nil
|
||||
}
|
||||
|
||||
func LoadOrGenerate(path string, size int) (jwk.Key, error) {
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
key, err := Generate(size)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
data, err = json.Marshal(key)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(path, data, 0o640); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
|
||||
key, err := jwk.ParseKey(data)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func Generate(size int) (jwk.Key, error) {
|
||||
privKey, err := rsa.GenerateKey(rand.Reader, size)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
key, err := jwk.FromRaw(privKey)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func Sign(key jwk.Key, payload ...any) (string, error) {
|
||||
json, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
rawSignature, err := jws.Sign(
|
||||
nil,
|
||||
jws.WithKey(jwa.RS256, key),
|
||||
jws.WithDetachedPayload(json),
|
||||
)
|
||||
if err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
signature := base58.Encode(rawSignature)
|
||||
|
||||
return signature, nil
|
||||
}
|
||||
|
||||
func Verify(jwks jwk.Set, signature string, payload ...any) (bool, error) {
|
||||
json, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return false, errors.WithStack(err)
|
||||
}
|
||||
|
||||
decoded := base58.Decode(signature)
|
||||
|
||||
_, err = jws.Verify(
|
||||
decoded,
|
||||
jws.WithKeySet(jwks, jws.WithRequireKid(false)),
|
||||
jws.WithDetachedPayload(json),
|
||||
)
|
||||
if err != nil {
|
||||
return false, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
40
internal/jwk/jwk_test.go
Normal file
40
internal/jwk/jwk_test.go
Normal file
@ -0,0 +1,40 @@
|
||||
package jwk
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func TestJWK(t *testing.T) {
|
||||
privateKey, err := Generate(DefaultKeySize)
|
||||
if err != nil {
|
||||
t.Fatalf("%+v", errors.WithStack(err))
|
||||
}
|
||||
|
||||
keySet, err := PublicKeySet(privateKey)
|
||||
if err != nil {
|
||||
t.Fatalf("%+v", errors.WithStack(err))
|
||||
}
|
||||
|
||||
metadata := map[string]any{
|
||||
"Foo": "bar",
|
||||
"Test": 1,
|
||||
}
|
||||
|
||||
signature, err := Sign(privateKey, metadata)
|
||||
if err != nil {
|
||||
t.Fatalf("%+v", errors.WithStack(err))
|
||||
}
|
||||
|
||||
t.Logf("Signature: %s", signature)
|
||||
|
||||
matches, err := Verify(keySet, signature, metadata)
|
||||
if err != nil {
|
||||
t.Fatalf("%+v", errors.WithStack(err))
|
||||
}
|
||||
|
||||
if !matches {
|
||||
t.Error("signature should match")
|
||||
}
|
||||
}
|
60
internal/proxy/director/context.go
Normal file
60
internal/proxy/director/context.go
Normal file
@ -0,0 +1,60 @@
|
||||
package director
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type contextKey string
|
||||
|
||||
const (
|
||||
contextKeyProxy contextKey = "proxy"
|
||||
contextKeyLayers contextKey = "layers"
|
||||
)
|
||||
|
||||
var (
|
||||
errContextKeyNotFound = errors.New("context key not found")
|
||||
errUnexpectedContextValue = errors.New("unexpected context value")
|
||||
)
|
||||
|
||||
func withProxy(ctx context.Context, proxy *store.Proxy) context.Context {
|
||||
return context.WithValue(ctx, contextKeyProxy, proxy)
|
||||
}
|
||||
|
||||
func ctxProxy(ctx context.Context) (*store.Proxy, error) {
|
||||
proxy, err := ctxValue[*store.Proxy](ctx, contextKeyProxy)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return proxy, nil
|
||||
}
|
||||
|
||||
func withLayers(ctx context.Context, layers []*store.Layer) context.Context {
|
||||
return context.WithValue(ctx, contextKeyLayers, layers)
|
||||
}
|
||||
|
||||
func ctxLayers(ctx context.Context) ([]*store.Layer, error) {
|
||||
layers, err := ctxValue[[]*store.Layer](ctx, contextKeyLayers)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return layers, nil
|
||||
}
|
||||
|
||||
func ctxValue[T any](ctx context.Context, key contextKey) (T, error) {
|
||||
raw := ctx.Value(key)
|
||||
if raw == nil {
|
||||
return *new(T), errors.WithStack(errContextKeyNotFound)
|
||||
}
|
||||
|
||||
value, ok := raw.(T)
|
||||
if !ok {
|
||||
return *new(T), errors.WithStack(errUnexpectedContextValue)
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
231
internal/proxy/director/director.go
Normal file
231
internal/proxy/director/director.go
Normal file
@ -0,0 +1,231 @@
|
||||
package director
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
|
||||
"forge.cadoles.com/Cadoles/go-proxy"
|
||||
"forge.cadoles.com/Cadoles/go-proxy/wildcard"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
)
|
||||
|
||||
type Director struct {
|
||||
proxyRepository store.ProxyRepository
|
||||
layerRepository store.LayerRepository
|
||||
layerRegistry *LayerRegistry
|
||||
}
|
||||
|
||||
func (d *Director) rewriteRequest(r *http.Request) (*http.Request, error) {
|
||||
ctx := r.Context()
|
||||
|
||||
proxies, err := d.getProxies(ctx)
|
||||
if err != nil {
|
||||
return r, errors.WithStack(err)
|
||||
}
|
||||
|
||||
var match *store.Proxy
|
||||
|
||||
MAIN:
|
||||
for _, p := range proxies {
|
||||
for _, from := range p.From {
|
||||
if matches := wildcard.Match(r.Host, from); !matches {
|
||||
continue
|
||||
}
|
||||
|
||||
match = p
|
||||
break MAIN
|
||||
}
|
||||
}
|
||||
|
||||
if match == nil {
|
||||
return r, nil
|
||||
}
|
||||
|
||||
toURL, err := url.Parse(match.To)
|
||||
if err != nil {
|
||||
return r, errors.WithStack(err)
|
||||
}
|
||||
|
||||
r.URL.Host = toURL.Host
|
||||
r.URL.Scheme = toURL.Scheme
|
||||
|
||||
ctx = logger.With(ctx,
|
||||
logger.F("proxy", match.Name),
|
||||
logger.F("host", r.Host),
|
||||
logger.F("remoteAddr", r.RemoteAddr),
|
||||
)
|
||||
|
||||
ctx = withProxy(ctx, match)
|
||||
|
||||
layers, err := d.getLayers(ctx, match.Name)
|
||||
if err != nil {
|
||||
return r, errors.WithStack(err)
|
||||
}
|
||||
|
||||
ctx = withLayers(ctx, layers)
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (d *Director) getProxies(ctx context.Context) ([]*store.Proxy, error) {
|
||||
headers, err := d.proxyRepository.QueryProxy(ctx, store.WithProxyQueryEnabled(true))
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
sort.Sort(store.ByProxyWeight(headers))
|
||||
|
||||
proxies := make([]*store.Proxy, 0, len(headers))
|
||||
|
||||
for _, h := range headers {
|
||||
if !h.Enabled {
|
||||
continue
|
||||
}
|
||||
|
||||
proxy, err := d.proxyRepository.GetProxy(ctx, h.Name)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
proxies = append(proxies, proxy)
|
||||
}
|
||||
|
||||
return proxies, nil
|
||||
}
|
||||
|
||||
func (d *Director) getLayers(ctx context.Context, proxyName store.ProxyName) ([]*store.Layer, error) {
|
||||
headers, err := d.layerRepository.QueryLayers(ctx, proxyName, store.WithLayerQueryEnabled(true))
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
sort.Sort(store.ByLayerWeight(headers))
|
||||
|
||||
layers := make([]*store.Layer, 0, len(headers))
|
||||
|
||||
for _, h := range headers {
|
||||
if !h.Enabled {
|
||||
continue
|
||||
}
|
||||
|
||||
layer, err := d.layerRepository.GetLayer(ctx, proxyName, h.Name)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
layers = append(layers, layer)
|
||||
}
|
||||
|
||||
return layers, nil
|
||||
}
|
||||
|
||||
func (d *Director) RequestTransformer() proxy.RequestTransformer {
|
||||
return func(r *http.Request) {
|
||||
ctx := r.Context()
|
||||
layers, err := ctxLayers(ctx)
|
||||
if err != nil {
|
||||
if errors.Is(err, errContextKeyNotFound) {
|
||||
return
|
||||
}
|
||||
|
||||
logger.Error(ctx, "could not retrieve layers from context", logger.E(errors.WithStack(err)))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
for _, layer := range layers {
|
||||
transformerLayer, ok := d.layerRegistry.GetRequestTransformer(layer.Type)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
transformer := transformerLayer.RequestTransformer(layer)
|
||||
|
||||
transformer(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Director) ResponseTransformer() proxy.ResponseTransformer {
|
||||
return func(r *http.Response) error {
|
||||
ctx := r.Request.Context()
|
||||
layers, err := ctxLayers(ctx)
|
||||
if err != nil {
|
||||
if errors.Is(err, errContextKeyNotFound) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
for _, layer := range layers {
|
||||
transformerLayer, ok := d.layerRegistry.GetResponseTransformer(layer.Type)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
transformer := transformerLayer.ResponseTransformer(layer)
|
||||
|
||||
if err := transformer(r); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Director) Middleware() proxy.Middleware {
|
||||
return func(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
r, err := d.rewriteRequest(r)
|
||||
if err != nil {
|
||||
logger.Error(r.Context(), "could not rewrite request", logger.E(errors.WithStack(err)))
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
layers, err := ctxLayers(ctx)
|
||||
if err != nil {
|
||||
if errors.Is(err, errContextKeyNotFound) {
|
||||
return
|
||||
}
|
||||
|
||||
logger.Error(ctx, "could not retrieve proxy and layers from context", logger.E(errors.WithStack(err)))
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
httpMiddlewares := make([]proxy.Middleware, 0)
|
||||
for _, layer := range layers {
|
||||
middleware, ok := d.layerRegistry.GetMiddleware(layer.Type)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
httpMiddlewares = append(httpMiddlewares, middleware.Middleware(layer))
|
||||
}
|
||||
|
||||
handler := createMiddlewareChain(next, httpMiddlewares)
|
||||
|
||||
handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
}
|
||||
|
||||
func New(proxyRepository store.ProxyRepository, layerRepository store.LayerRepository, layers ...Layer) *Director {
|
||||
registry := NewLayerRegistry(layers...)
|
||||
|
||||
return &Director{proxyRepository, layerRepository, registry}
|
||||
}
|
104
internal/proxy/director/layer_registry.go
Normal file
104
internal/proxy/director/layer_registry.go
Normal file
@ -0,0 +1,104 @@
|
||||
package director
|
||||
|
||||
import (
|
||||
"forge.cadoles.com/Cadoles/go-proxy"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
)
|
||||
|
||||
type Layer interface {
|
||||
LayerType() store.LayerType
|
||||
}
|
||||
|
||||
type MiddlewareLayer interface {
|
||||
Layer
|
||||
Middleware(layer *store.Layer) proxy.Middleware
|
||||
}
|
||||
|
||||
type RequestTransformerLayer interface {
|
||||
Layer
|
||||
RequestTransformer(layer *store.Layer) proxy.RequestTransformer
|
||||
}
|
||||
|
||||
type ResponseTransformerLayer interface {
|
||||
Layer
|
||||
ResponseTransformer(layer *store.Layer) proxy.ResponseTransformer
|
||||
}
|
||||
|
||||
type LayerRegistry struct {
|
||||
index map[store.LayerType]Layer
|
||||
}
|
||||
|
||||
func (r *LayerRegistry) GetLayer(layerType store.LayerType) (Layer, bool) {
|
||||
layer, exists := r.index[layerType]
|
||||
if !exists {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return layer, true
|
||||
}
|
||||
|
||||
func (r *LayerRegistry) getLayerAsAny(layerType store.LayerType) (any, bool) {
|
||||
return r.GetLayer(layerType)
|
||||
}
|
||||
|
||||
func (r *LayerRegistry) GetMiddleware(layerType store.LayerType) (MiddlewareLayer, bool) {
|
||||
layer, exists := r.getLayerAsAny(layerType)
|
||||
if !exists {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
middleware, ok := layer.(MiddlewareLayer)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return middleware, true
|
||||
}
|
||||
|
||||
func (r *LayerRegistry) GetResponseTransformer(layerType store.LayerType) (ResponseTransformerLayer, bool) {
|
||||
layer, exists := r.getLayerAsAny(layerType)
|
||||
if !exists {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
transformer, ok := layer.(ResponseTransformerLayer)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return transformer, true
|
||||
}
|
||||
|
||||
func (r *LayerRegistry) GetRequestTransformer(layerType store.LayerType) (RequestTransformerLayer, bool) {
|
||||
layer, exists := r.getLayerAsAny(layerType)
|
||||
if !exists {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
transformer, ok := layer.(RequestTransformerLayer)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return transformer, true
|
||||
}
|
||||
|
||||
func (r *LayerRegistry) Load(layers ...Layer) {
|
||||
index := make(map[store.LayerType]Layer)
|
||||
|
||||
for _, l := range layers {
|
||||
layerType := l.LayerType()
|
||||
index[layerType] = l
|
||||
}
|
||||
|
||||
r.index = index
|
||||
}
|
||||
|
||||
func NewLayerRegistry(layers ...Layer) *LayerRegistry {
|
||||
registry := &LayerRegistry{
|
||||
index: make(map[store.LayerType]Layer),
|
||||
}
|
||||
registry.Load(layers...)
|
||||
|
||||
return registry
|
||||
}
|
18
internal/proxy/director/util.go
Normal file
18
internal/proxy/director/util.go
Normal file
@ -0,0 +1,18 @@
|
||||
package director
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"forge.cadoles.com/Cadoles/go-proxy"
|
||||
"forge.cadoles.com/Cadoles/go-proxy/util"
|
||||
)
|
||||
|
||||
func createMiddlewareChain(handler http.Handler, middlewares []proxy.Middleware) http.Handler {
|
||||
util.Reverse(middlewares)
|
||||
|
||||
for _, m := range middlewares {
|
||||
handler = m(handler)
|
||||
}
|
||||
|
||||
return handler
|
||||
}
|
42
internal/proxy/init.go
Normal file
42
internal/proxy/init.go
Normal file
@ -0,0 +1,42 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/setup"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (s *Server) initRepositories(ctx context.Context) error {
|
||||
if err := s.initProxyRepository(ctx); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if err := s.initLayerRepository(ctx); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) initProxyRepository(ctx context.Context) error {
|
||||
proxyRepository, err := setup.NewProxyRepository(ctx, s.redisConfig)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
s.proxyRepository = proxyRepository
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) initLayerRepository(ctx context.Context) error {
|
||||
layerRepository, err := setup.NewLayerRepository(ctx, s.redisConfig)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
s.layerRepository = layerRepository
|
||||
|
||||
return nil
|
||||
}
|
40
internal/proxy/option.go
Normal file
40
internal/proxy/option.go
Normal file
@ -0,0 +1,40 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/config"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director"
|
||||
)
|
||||
|
||||
type Option struct {
|
||||
ServerConfig config.ProxyServerConfig
|
||||
RedisConfig config.RedisConfig
|
||||
DirectorLayers []director.Layer
|
||||
}
|
||||
|
||||
type OptionFunc func(*Option)
|
||||
|
||||
func defaultOption() *Option {
|
||||
return &Option{
|
||||
ServerConfig: config.NewDefaultProxyServerConfig(),
|
||||
RedisConfig: config.NewDefaultRedisConfig(),
|
||||
DirectorLayers: make([]director.Layer, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func WithServerConfig(conf config.ProxyServerConfig) OptionFunc {
|
||||
return func(opt *Option) {
|
||||
opt.ServerConfig = conf
|
||||
}
|
||||
}
|
||||
|
||||
func WithRedisConfig(conf config.RedisConfig) OptionFunc {
|
||||
return func(opt *Option) {
|
||||
opt.RedisConfig = conf
|
||||
}
|
||||
}
|
||||
|
||||
func WithDirectorLayers(layers ...director.Layer) OptionFunc {
|
||||
return func(opt *Option) {
|
||||
opt.DirectorLayers = layers
|
||||
}
|
||||
}
|
118
internal/proxy/server.go
Normal file
118
internal/proxy/server.go
Normal file
@ -0,0 +1,118 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"forge.cadoles.com/Cadoles/go-proxy"
|
||||
bouncerChi "forge.cadoles.com/cadoles/bouncer/internal/chi"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/config"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
serverConfig config.ProxyServerConfig
|
||||
redisConfig config.RedisConfig
|
||||
directorLayers []director.Layer
|
||||
proxyRepository store.ProxyRepository
|
||||
layerRepository store.LayerRepository
|
||||
}
|
||||
|
||||
func (s *Server) Start(ctx context.Context) (<-chan net.Addr, <-chan error) {
|
||||
errs := make(chan error)
|
||||
addrs := make(chan net.Addr)
|
||||
|
||||
go s.run(ctx, addrs, errs)
|
||||
|
||||
return addrs, errs
|
||||
}
|
||||
|
||||
func (s *Server) run(parentCtx context.Context, addrs chan net.Addr, errs chan error) {
|
||||
defer func() {
|
||||
close(errs)
|
||||
close(addrs)
|
||||
}()
|
||||
|
||||
ctx, cancel := context.WithCancel(parentCtx)
|
||||
defer cancel()
|
||||
|
||||
if err := s.initRepositories(ctx); err != nil {
|
||||
errs <- errors.WithStack(err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", s.serverConfig.HTTP.Host, s.serverConfig.HTTP.Port))
|
||||
if err != nil {
|
||||
errs <- errors.WithStack(err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
addrs <- listener.Addr()
|
||||
|
||||
defer func() {
|
||||
if err := listener.Close(); err != nil && !errors.Is(err, net.ErrClosed) {
|
||||
errs <- errors.WithStack(err)
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
|
||||
if err := listener.Close(); err != nil && !errors.Is(err, net.ErrClosed) {
|
||||
log.Printf("%+v", errors.WithStack(err))
|
||||
}
|
||||
}()
|
||||
|
||||
router := chi.NewRouter()
|
||||
|
||||
logger.Info(ctx, "http server listening")
|
||||
|
||||
director := director.New(
|
||||
s.proxyRepository,
|
||||
s.layerRepository,
|
||||
s.directorLayers...,
|
||||
)
|
||||
|
||||
router.Use(middleware.RequestLogger(bouncerChi.NewLogFormatter()))
|
||||
router.Use(director.Middleware())
|
||||
|
||||
handler := proxy.New(
|
||||
proxy.WithRequestTransformers(
|
||||
director.RequestTransformer(),
|
||||
),
|
||||
proxy.WithResponseTransformers(
|
||||
director.ResponseTransformer(),
|
||||
),
|
||||
)
|
||||
|
||||
router.Handle("/*", handler)
|
||||
|
||||
if err := http.Serve(listener, router); err != nil && !errors.Is(err, net.ErrClosed) {
|
||||
errs <- errors.WithStack(err)
|
||||
}
|
||||
|
||||
logger.Info(ctx, "http server exiting")
|
||||
}
|
||||
|
||||
func NewServer(funcs ...OptionFunc) *Server {
|
||||
opt := defaultOption()
|
||||
for _, fn := range funcs {
|
||||
fn(opt)
|
||||
}
|
||||
|
||||
return &Server{
|
||||
serverConfig: opt.ServerConfig,
|
||||
redisConfig: opt.RedisConfig,
|
||||
directorLayers: opt.DirectorLayers,
|
||||
}
|
||||
}
|
16
internal/queue/adapter.go
Normal file
16
internal/queue/adapter.go
Normal file
@ -0,0 +1,16 @@
|
||||
package queue
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Status struct {
|
||||
Sessions int64
|
||||
}
|
||||
|
||||
type Adapter interface {
|
||||
Touch(ctx context.Context, queueName string, sessionId string) (int64, error)
|
||||
Status(ctx context.Context, queueName string) (*Status, error)
|
||||
Refresh(ctx context.Context, queueName string, keepAlive time.Duration) error
|
||||
}
|
48
internal/queue/layer_options.go
Normal file
48
internal/queue/layer_options.go
Normal file
@ -0,0 +1,48 @@
|
||||
package queue
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type LayerOptions struct {
|
||||
Capacity int64 `mapstructure:"capacity"`
|
||||
Matchers []string `mapstructure:"matchers"`
|
||||
KeepAlive time.Duration `mapstructure:"keepAlive"`
|
||||
}
|
||||
|
||||
func fromStoreOptions(storeOptions store.LayerOptions) (*LayerOptions, error) {
|
||||
layerOptions := LayerOptions{
|
||||
Capacity: 1000,
|
||||
Matchers: []string{"*"},
|
||||
KeepAlive: 30 * time.Second,
|
||||
}
|
||||
|
||||
config := mapstructure.DecoderConfig{
|
||||
DecodeHook: stringToDurationHook,
|
||||
Result: &layerOptions,
|
||||
}
|
||||
|
||||
decoder, err := mapstructure.NewDecoder(&config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := decoder.Decode(storeOptions); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return &layerOptions, nil
|
||||
}
|
||||
|
||||
func stringToDurationHook(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) {
|
||||
if t == reflect.TypeOf(*new(time.Duration)) && f == reflect.TypeOf("") {
|
||||
return time.ParseDuration(data.(string))
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
9
internal/queue/options.go
Normal file
9
internal/queue/options.go
Normal file
@ -0,0 +1,9 @@
|
||||
package queue
|
||||
|
||||
type Options struct{}
|
||||
|
||||
type OptionFunc func(*Options)
|
||||
|
||||
func defaultOptions() *Options {
|
||||
return &Options{}
|
||||
}
|
143
internal/queue/queue.go
Normal file
143
internal/queue/queue.go
Normal file
@ -0,0 +1,143 @@
|
||||
package queue
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"forge.cadoles.com/Cadoles/go-proxy"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
)
|
||||
|
||||
const LayerType store.LayerType = "queue"
|
||||
|
||||
type Queue struct {
|
||||
adapter Adapter
|
||||
refreshJobRunning uint32
|
||||
}
|
||||
|
||||
// LayerType implements director.MiddlewareLayer
|
||||
func (q *Queue) LayerType() store.LayerType {
|
||||
return LayerType
|
||||
}
|
||||
|
||||
// Middleware implements director.MiddlewareLayer
|
||||
func (q *Queue) Middleware(layer *store.Layer) proxy.Middleware {
|
||||
return func(h http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
options, err := fromStoreOptions(layer.Options)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "could not parse layer options", logger.E(errors.WithStack(err)))
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
cookieName := q.getCookieName(layer.Name)
|
||||
|
||||
cookie, err := r.Cookie(cookieName)
|
||||
if err != nil && !errors.Is(err, http.ErrNoCookie) {
|
||||
logger.Error(ctx, "could not retrieve cookie", logger.E(errors.WithStack(err)))
|
||||
}
|
||||
|
||||
if cookie == nil {
|
||||
cookie = &http.Cookie{
|
||||
Name: cookieName,
|
||||
Value: uuid.NewString(),
|
||||
Path: "/",
|
||||
}
|
||||
|
||||
w.Header().Add("Set-Cookie", cookie.String())
|
||||
}
|
||||
|
||||
sessionID := cookie.Value
|
||||
queueName := string(layer.Name)
|
||||
|
||||
q.refreshQueue(queueName, options.KeepAlive)
|
||||
|
||||
rank, err := q.adapter.Touch(ctx, queueName, sessionID)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "could not retrieve session rank", logger.E(errors.WithStack(err)))
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if rank >= options.Capacity {
|
||||
q.renderQueuePage(w, r, queueName, options, rank)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
ctx = logger.With(ctx,
|
||||
logger.F("queueSessionId", sessionID),
|
||||
logger.F("queueName", queueName),
|
||||
logger.F("queueSessionRank", rank),
|
||||
)
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
h.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
}
|
||||
|
||||
func (q *Queue) renderQueuePage(w http.ResponseWriter, r *http.Request, queueName string, options *LayerOptions, rank int64) {
|
||||
ctx := r.Context()
|
||||
|
||||
status, err := q.adapter.Status(ctx, queueName)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "could not retrieve queue status", logger.E(errors.WithStack(err)))
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
http.Error(w, fmt.Sprintf("queued (rank: %d, status: %d/%d)", rank+1, status.Sessions, options.Capacity), http.StatusServiceUnavailable)
|
||||
}
|
||||
|
||||
func (q *Queue) refreshQueue(queueName string, keepAlive time.Duration) {
|
||||
if !atomic.CompareAndSwapUint32(&q.refreshJobRunning, 0, 1) {
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer atomic.StoreUint32(&q.refreshJobRunning, 0)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), keepAlive*2)
|
||||
defer cancel()
|
||||
|
||||
if err := q.adapter.Refresh(ctx, queueName, keepAlive); err != nil {
|
||||
logger.Error(ctx, "could not refresh queue",
|
||||
logger.E(errors.WithStack(err)),
|
||||
logger.F("queue", queueName),
|
||||
)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (q *Queue) getCookieName(layerName store.LayerName) string {
|
||||
return fmt.Sprintf("_%s_%s", LayerType, layerName)
|
||||
}
|
||||
|
||||
func New(adapter Adapter, funcs ...OptionFunc) *Queue {
|
||||
opts := defaultOptions()
|
||||
for _, fn := range funcs {
|
||||
fn(opts)
|
||||
}
|
||||
|
||||
return &Queue{
|
||||
adapter: adapter,
|
||||
}
|
||||
}
|
||||
|
||||
var _ director.MiddlewareLayer = &Queue{}
|
167
internal/queue/redis/adapter.go
Normal file
167
internal/queue/redis/adapter.go
Normal file
@ -0,0 +1,167 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/queue"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
const (
|
||||
keyPrefixQueue = "queue"
|
||||
)
|
||||
|
||||
type Adapter struct {
|
||||
client redis.UniversalClient
|
||||
txMaxRetry int
|
||||
}
|
||||
|
||||
// Refresh implements queue.Adapter
|
||||
func (a *Adapter) Refresh(ctx context.Context, queueName string, keepAlive time.Duration) error {
|
||||
lastSeenKey := lastSeenKey(queueName)
|
||||
rankKey := rankKey(queueName)
|
||||
|
||||
err := withTx(ctx, a.client, func(ctx context.Context, tx *redis.Tx) error {
|
||||
expires := time.Now().UTC().Add(-keepAlive)
|
||||
|
||||
cmd := tx.ZRangeByScore(ctx, lastSeenKey, &redis.ZRangeBy{
|
||||
Min: "0",
|
||||
Max: strconv.FormatInt(expires.Unix(), 10),
|
||||
})
|
||||
|
||||
members, err := cmd.Result()
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if len(members) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
anyMembers := make([]any, len(members))
|
||||
for i, m := range members {
|
||||
anyMembers[i] = m
|
||||
}
|
||||
|
||||
if err := tx.ZRem(ctx, rankKey, anyMembers...).Err(); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if err := tx.ZRem(ctx, lastSeenKey, anyMembers...).Err(); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}, rankKey, lastSeenKey)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Touch implements queue.Adapter
|
||||
func (a *Adapter) Touch(ctx context.Context, queueName string, sessionId string) (int64, error) {
|
||||
lastSeenKey := lastSeenKey(queueName)
|
||||
rankKey := rankKey(queueName)
|
||||
|
||||
var rank int64
|
||||
|
||||
retry := a.txMaxRetry
|
||||
|
||||
for retry > 0 {
|
||||
err := withTx(ctx, a.client, func(ctx context.Context, tx *redis.Tx) error {
|
||||
now := time.Now().UTC().Unix()
|
||||
|
||||
err := tx.ZAddNX(ctx, rankKey, redis.Z{Score: float64(now), Member: sessionId}).Err()
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
err = tx.ZAdd(ctx, lastSeenKey, redis.Z{Score: float64(now), Member: sessionId}).Err()
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
val, err := tx.ZRank(ctx, rankKey, sessionId).Result()
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
rank = val
|
||||
|
||||
return nil
|
||||
}, rankKey, lastSeenKey)
|
||||
if err != nil {
|
||||
if errors.Is(err, redis.Nil) && retry > 0 {
|
||||
retry--
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
return 0, errors.WithStack(err)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
return rank, nil
|
||||
}
|
||||
|
||||
// Status implements queue.Adapter
|
||||
func (a *Adapter) Status(ctx context.Context, queueName string) (*queue.Status, error) {
|
||||
rankKey := rankKey(queueName)
|
||||
|
||||
status := &queue.Status{}
|
||||
|
||||
cmd := a.client.ZCard(ctx, rankKey)
|
||||
if err := cmd.Err(); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
status.Sessions = cmd.Val()
|
||||
|
||||
return status, nil
|
||||
}
|
||||
|
||||
func NewAdapter(client redis.UniversalClient, txMaxRetry int) *Adapter {
|
||||
return &Adapter{
|
||||
client: client,
|
||||
txMaxRetry: txMaxRetry,
|
||||
}
|
||||
}
|
||||
|
||||
var _ queue.Adapter = &Adapter{}
|
||||
|
||||
func key(parts ...string) string {
|
||||
return strings.Join(parts, ":")
|
||||
}
|
||||
|
||||
func rankKey(queueName string) string {
|
||||
return key(keyPrefixQueue, queueName, "rank")
|
||||
}
|
||||
|
||||
func lastSeenKey(queueName string) string {
|
||||
return key(keyPrefixQueue, queueName, "last_seen")
|
||||
}
|
||||
|
||||
func withTx(ctx context.Context, client redis.UniversalClient, fn func(ctx context.Context, tx *redis.Tx) error, keys ...string) error {
|
||||
txf := func(tx *redis.Tx) error {
|
||||
if err := fn(ctx, tx); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
err := client.Watch(ctx, txf, keys...)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user