Compare commits
26 Commits
088b684
...
sysupgrade
Author | SHA1 | Date | |
---|---|---|---|
0b783c374a | |||
97a60e2617 | |||
cd796fff89 | |||
8ada7e3b84 | |||
fa71986d37 | |||
1261beb812 | |||
1b9914c306 | |||
fbcd3ca806 | |||
fa36d55163 | |||
55db21ad23 | |||
258d0bc158 | |||
0a3760b48a | |||
b53842cbb7 | |||
d6aab44bee | |||
5e4d9eb819 | |||
a297821f3c | |||
9aca9a6c58 | |||
0fb0d234d6 | |||
bd0d5a621a | |||
e13de5bd0d | |||
813b1519e4 | |||
f9b8d4f243 | |||
1ff29ae1fb | |||
3310c09320 | |||
2a828afc89 | |||
c049cf8047 |
6
.gitignore
vendored
6
.gitignore
vendored
@ -5,4 +5,8 @@ dist/
|
|||||||
/tmp
|
/tmp
|
||||||
/state.json
|
/state.json
|
||||||
/emissary.sqlite
|
/emissary.sqlite
|
||||||
/.gitea-release
|
/.gitea-release
|
||||||
|
/agent-key.json
|
||||||
|
/apps
|
||||||
|
/server-key.json
|
||||||
|
/.emissary-token
|
@ -2,6 +2,7 @@ project_name: emissary
|
|||||||
before:
|
before:
|
||||||
hooks:
|
hooks:
|
||||||
- go mod tidy
|
- go mod tidy
|
||||||
|
- go generate ./...
|
||||||
builds:
|
builds:
|
||||||
- id: emissary-server
|
- id: emissary-server
|
||||||
env:
|
env:
|
||||||
@ -22,6 +23,7 @@ builds:
|
|||||||
goarch:
|
goarch:
|
||||||
- amd64
|
- amd64
|
||||||
- arm64
|
- arm64
|
||||||
|
- "386"
|
||||||
main: ./cmd/server
|
main: ./cmd/server
|
||||||
- id: emissary-agent
|
- id: emissary-agent
|
||||||
env:
|
env:
|
||||||
@ -43,6 +45,7 @@ builds:
|
|||||||
- amd64
|
- amd64
|
||||||
- arm64
|
- arm64
|
||||||
- arm
|
- arm
|
||||||
|
- "386"
|
||||||
main: ./cmd/agent
|
main: ./cmd/agent
|
||||||
archives:
|
archives:
|
||||||
- id: server
|
- id: server
|
||||||
@ -51,15 +54,17 @@ archives:
|
|||||||
files:
|
files:
|
||||||
- README.md
|
- README.md
|
||||||
- migrations
|
- migrations
|
||||||
|
- misc/packaging/common/config-server.yml
|
||||||
- id: agent
|
- id: agent
|
||||||
builds: ["emissary-agent"]
|
builds: ["emissary-agent"]
|
||||||
name_template: '{{ .ProjectName }}-agent_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
name_template: '{{ .ProjectName }}-agent_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
||||||
files:
|
files:
|
||||||
- README.md
|
- README.md
|
||||||
|
- misc/packaging/common/config-agent.yml
|
||||||
checksum:
|
checksum:
|
||||||
name_template: 'checksums.txt'
|
name_template: 'checksums.txt'
|
||||||
snapshot:
|
snapshot:
|
||||||
name_template: "{{ incpatch .Version }}-next"
|
name_template: "{{ .Version }}"
|
||||||
changelog:
|
changelog:
|
||||||
sort: asc
|
sort: asc
|
||||||
filters:
|
filters:
|
||||||
@ -97,6 +102,18 @@ nfpms:
|
|||||||
file_info:
|
file_info:
|
||||||
mode: 0755
|
mode: 0755
|
||||||
packager: apk
|
packager: apk
|
||||||
|
- dst: /var/lib/emissary
|
||||||
|
type: dir
|
||||||
|
file_info:
|
||||||
|
mode: 0700
|
||||||
|
- dst: /usr/share/emissary
|
||||||
|
type: dir
|
||||||
|
file_info:
|
||||||
|
mode: 0700
|
||||||
|
- dst: /var/log/emissary
|
||||||
|
type: dir
|
||||||
|
file_info:
|
||||||
|
mode: 0700
|
||||||
scripts:
|
scripts:
|
||||||
postinstall: "misc/packaging/common/postinstall-server.sh"
|
postinstall: "misc/packaging/common/postinstall-server.sh"
|
||||||
- id: emissary-agent
|
- id: emissary-agent
|
||||||
|
21
Makefile
21
Makefile
@ -1,14 +1,16 @@
|
|||||||
LINT_ARGS ?= --timeout 5m
|
LINT_ARGS ?= --timeout 5m
|
||||||
GORELEASER_VERSION ?= v1.13.1
|
GORELEASER_VERSION ?= v1.13.1
|
||||||
GORELEASER_ARGS ?= release --auto-snapshot --snapshot --rm-dist
|
GORELEASER_ARGS ?= release --snapshot --rm-dist
|
||||||
GITCHLOG_ARGS ?=
|
GITCHLOG_ARGS ?=
|
||||||
SHELL := /bin/bash
|
SHELL := /bin/bash
|
||||||
|
|
||||||
EMISSARY_VERSION ?=
|
EMISSARY_VERSION ?=
|
||||||
GIT_VERSION ?= $(shell git describe --always)
|
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 ?= bornholm/emissary
|
DOCKER_IMAGE_NAME ?= bornholm/emissary
|
||||||
DOCKER_IMAGE_TAG ?= $(GIT_VERSION)$(if $(shell git diff --stat),-dirty,)
|
DOCKER_IMAGE_TAG ?= $(FULL_VERSION)
|
||||||
|
|
||||||
GOTEST_ARGS ?= -short
|
GOTEST_ARGS ?= -short
|
||||||
|
|
||||||
@ -43,7 +45,7 @@ build-emissary-%: deps ## Build executable
|
|||||||
-v \
|
-v \
|
||||||
-ldflags "\
|
-ldflags "\
|
||||||
-X 'main.GitRef=$(shell git rev-parse --short HEAD)' \
|
-X 'main.GitRef=$(shell git rev-parse --short HEAD)' \
|
||||||
-X 'main.ProjectVersion=$(shell git describe --always)' \
|
-X 'main.ProjectVersion=$(FULL_VERSION)' \
|
||||||
-X 'main.BuildDate=$(shell date --utc --rfc-3339=seconds)' \
|
-X 'main.BuildDate=$(shell date --utc --rfc-3339=seconds)' \
|
||||||
" \
|
" \
|
||||||
-o ./bin/$* \
|
-o ./bin/$* \
|
||||||
@ -73,7 +75,7 @@ dump-config: build-emissary
|
|||||||
|
|
||||||
.PHONY: goreleaser
|
.PHONY: goreleaser
|
||||||
goreleaser: deps
|
goreleaser: deps
|
||||||
( set -o allexport && source .env && set +o allexport && VERSION=$(GORELEASER_VERSION) curl -sfL https://goreleaser.com/static/run | bash /dev/stdin $(GORELEASER_ARGS) )
|
( 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
|
.PHONY: start-release
|
||||||
start-release:
|
start-release:
|
||||||
@ -126,8 +128,8 @@ gitea-release: tools/gitea-release/bin/gitea-release.sh goreleaser
|
|||||||
GITEA_RELEASE_PROJECT="emissary" \
|
GITEA_RELEASE_PROJECT="emissary" \
|
||||||
GITEA_RELEASE_ORG="arcad" \
|
GITEA_RELEASE_ORG="arcad" \
|
||||||
GITEA_RELEASE_BASE_URL="https://forge.cadoles.com" \
|
GITEA_RELEASE_BASE_URL="https://forge.cadoles.com" \
|
||||||
GITEA_RELEASE_VERSION="$(GIT_VERSION)" \
|
GITEA_RELEASE_VERSION="$(FULL_VERSION)" \
|
||||||
GITEA_RELEASE_NAME="$(GIT_VERSION)" \
|
GITEA_RELEASE_NAME="$(FULL_VERSION)" \
|
||||||
GITEA_RELEASE_COMMITISH_TARGET="$(GIT_VERSION)" \
|
GITEA_RELEASE_COMMITISH_TARGET="$(GIT_VERSION)" \
|
||||||
GITEA_RELEASE_IS_DRAFT="false" \
|
GITEA_RELEASE_IS_DRAFT="false" \
|
||||||
GITEA_RELEASE_BODY="" \
|
GITEA_RELEASE_BODY="" \
|
||||||
@ -137,4 +139,7 @@ gitea-release: tools/gitea-release/bin/gitea-release.sh goreleaser
|
|||||||
tools/gitea-release/bin/gitea-release.sh:
|
tools/gitea-release/bin/gitea-release.sh:
|
||||||
mkdir -p tools/gitea-release/bin
|
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
|
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
|
chmod +x tools/gitea-release/bin/gitea-release.sh
|
||||||
|
|
||||||
|
.emissary-token:
|
||||||
|
$(MAKE) run-emissary-server EMISSARY_CMD="--debug --config tmp/server.yml server auth create-token --role writer > .emissary-token"
|
@ -5,6 +5,9 @@ import (
|
|||||||
|
|
||||||
"forge.cadoles.com/Cadoles/emissary/internal/command"
|
"forge.cadoles.com/Cadoles/emissary/internal/command"
|
||||||
"forge.cadoles.com/Cadoles/emissary/internal/command/agent"
|
"forge.cadoles.com/Cadoles/emissary/internal/command/agent"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/command/api"
|
||||||
|
|
||||||
|
_ "forge.cadoles.com/Cadoles/emissary/internal/imports/spec"
|
||||||
)
|
)
|
||||||
|
|
||||||
// nolint: gochecknoglobals
|
// nolint: gochecknoglobals
|
||||||
@ -16,5 +19,5 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
command.Main(BuildDate, ProjectVersion, GitRef, DefaultConfigPath, agent.Root())
|
command.Main(BuildDate, ProjectVersion, GitRef, DefaultConfigPath, agent.Root(), api.Root())
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,12 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"forge.cadoles.com/Cadoles/emissary/internal/command"
|
"forge.cadoles.com/Cadoles/emissary/internal/command"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/command/api"
|
||||||
"forge.cadoles.com/Cadoles/emissary/internal/command/server"
|
"forge.cadoles.com/Cadoles/emissary/internal/command/server"
|
||||||
|
|
||||||
_ "github.com/jackc/pgx/v5/stdlib"
|
_ "forge.cadoles.com/Cadoles/emissary/internal/imports/format"
|
||||||
_ "modernc.org/sqlite"
|
_ "forge.cadoles.com/Cadoles/emissary/internal/imports/spec"
|
||||||
|
_ "forge.cadoles.com/Cadoles/emissary/internal/imports/sql"
|
||||||
)
|
)
|
||||||
|
|
||||||
// nolint: gochecknoglobals
|
// nolint: gochecknoglobals
|
||||||
@ -19,5 +21,5 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
command.Main(BuildDate, ProjectVersion, GitRef, DefaultConfigPath, server.Root())
|
command.Main(BuildDate, ProjectVersion, GitRef, DefaultConfigPath, server.Root(), api.Root())
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,12 @@
|
|||||||
|
|
||||||
[See `misc/rest/server.rest`](../misc/rest/server.rest)
|
[See `misc/rest/server.rest`](../misc/rest/server.rest)
|
||||||
|
|
||||||
|
### Spécifications
|
||||||
|
|
||||||
|
- [Schéma `app.emissary.cadoles.com`](../internal/spec/app/schema.json)
|
||||||
|
- [Schéma `uci.emissary.cadoles.com`](../internal/spec/uci/schema.json)
|
||||||
|
- [Schéma `gateway.emissary.cadoles.com`](../internal/spec/gateway/schema.json)
|
||||||
|
|
||||||
### Configuration
|
### Configuration
|
||||||
|
|
||||||
See:
|
See:
|
||||||
|
82
go.mod
82
go.mod
@ -3,71 +3,107 @@ module forge.cadoles.com/Cadoles/emissary
|
|||||||
go 1.19
|
go 1.19
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
forge.cadoles.com/arcad/edge v0.0.0-20230322170544-cf8a3f8ac077
|
||||||
github.com/alecthomas/participle/v2 v2.0.0-beta.5
|
github.com/alecthomas/participle/v2 v2.0.0-beta.5
|
||||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883
|
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883
|
||||||
github.com/btcsuite/btcd/btcutil v1.1.3
|
github.com/btcsuite/btcd/btcutil v1.1.3
|
||||||
github.com/davecgh/go-spew v1.1.1
|
github.com/davecgh/go-spew v1.1.1
|
||||||
github.com/denisbrodbeck/machineid v1.0.1
|
github.com/denisbrodbeck/machineid v1.0.1
|
||||||
|
github.com/dop251/goja v0.0.0-20230304130813-e2f543bf4b4c
|
||||||
|
github.com/evanphx/json-patch/v5 v5.6.0
|
||||||
github.com/go-chi/chi v4.1.2+incompatible
|
github.com/go-chi/chi v4.1.2+incompatible
|
||||||
github.com/go-chi/cors v1.2.1
|
github.com/go-chi/cors v1.2.1
|
||||||
github.com/golang-migrate/migrate/v4 v4.15.2
|
github.com/golang-migrate/migrate/v4 v4.15.2
|
||||||
github.com/jackc/pgx/v5 v5.2.0
|
github.com/jackc/pgx/v5 v5.3.1
|
||||||
|
github.com/jedib0t/go-pretty/v6 v6.4.4
|
||||||
|
github.com/lestrrat-go/jwx/v2 v2.0.9
|
||||||
|
github.com/lithammer/shortuuid/v4 v4.0.0
|
||||||
|
github.com/mitchellh/hashstructure/v2 v2.0.2
|
||||||
github.com/mitchellh/mapstructure v1.5.0
|
github.com/mitchellh/mapstructure v1.5.0
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/santhosh-tekuri/jsonschema/v5 v5.1.1
|
github.com/qri-io/jsonschema v0.2.1
|
||||||
github.com/urfave/cli/v2 v2.23.7
|
github.com/urfave/cli/v2 v2.24.4
|
||||||
gitlab.com/wpetit/goweb v0.0.0-20230206085656-dec695f0e2e9
|
gitlab.com/wpetit/goweb v0.0.0-20230227162855-a1f09bafccb3
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
modernc.org/sqlite v1.20.3
|
modernc.org/sqlite v1.21.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require google.golang.org/genproto v0.0.0-20220728213248-dd149ef739b9 // indirect
|
require (
|
||||||
|
github.com/barnybug/go-cast v0.0.0-20201201064555-a87ccbc26692 // indirect
|
||||||
|
github.com/dop251/goja_nodejs v0.0.0-20230320130059-dcf93ba651dd // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.1 // indirect
|
||||||
|
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
|
||||||
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
|
github.com/google/pprof v0.0.0-20230309165930-d61513b1440d // indirect
|
||||||
|
github.com/gorilla/websocket v1.5.0 // indirect
|
||||||
|
github.com/hashicorp/mdns v1.0.5 // indirect
|
||||||
|
github.com/igm/sockjs-go/v3 v3.0.2 // indirect
|
||||||
|
github.com/miekg/dns v1.1.51 // indirect
|
||||||
|
github.com/oklog/ulid/v2 v2.1.0 // indirect
|
||||||
|
github.com/orcaman/concurrent-map v1.0.0 // indirect
|
||||||
|
golang.org/x/net v0.8.0 // indirect
|
||||||
|
google.golang.org/genproto v0.0.0-20220728213248-dd149ef739b9 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cdr.dev/slog v1.4.1 // indirect
|
cdr.dev/slog v1.4.2 // indirect
|
||||||
github.com/alecthomas/chroma v0.10.0 // indirect
|
github.com/alecthomas/chroma v0.10.0 // indirect
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||||
github.com/dlclark/regexp2 v1.8.0 // indirect
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
github.com/dlclark/regexp2 v1.8.1 // indirect
|
||||||
github.com/fatih/color v1.13.0 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
|
github.com/fatih/color v1.15.0 // indirect
|
||||||
|
github.com/go-chi/chi/v5 v5.0.8
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
github.com/google/uuid v1.3.0 // indirect
|
github.com/google/uuid v1.3.0 // indirect
|
||||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||||
github.com/jackc/puddle/v2 v2.1.2 // indirect
|
github.com/jackc/puddle/v2 v2.2.0 // indirect
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||||
github.com/leodido/go-urn v1.2.1 // indirect
|
github.com/leodido/go-urn v1.2.2 // 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/option v1.0.1 // indirect
|
||||||
github.com/lib/pq v1.10.7 // indirect
|
github.com/lib/pq v1.10.7 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||||
|
github.com/qri-io/jsonpointer v0.1.1 // indirect
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.9.0 // indirect
|
github.com/rogpeppe/go-internal v1.9.0 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/sergi/go-diff v1.3.1 // indirect
|
github.com/sergi/go-diff v1.3.1 // indirect
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||||
go.opencensus.io v0.24.0 // indirect
|
go.opencensus.io v0.24.0 // indirect
|
||||||
go.uber.org/atomic v1.10.0 // indirect
|
go.uber.org/atomic v1.10.0 // indirect
|
||||||
golang.org/x/crypto v0.5.0 // indirect
|
golang.org/x/crypto v0.7.0 // indirect
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
golang.org/x/mod v0.8.0 // indirect
|
||||||
golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7 // indirect
|
golang.org/x/sync v0.1.0 // indirect
|
||||||
golang.org/x/sys v0.4.0 // indirect
|
golang.org/x/sys v0.6.0 // indirect
|
||||||
golang.org/x/term v0.4.0 // indirect
|
golang.org/x/term v0.6.0 // indirect
|
||||||
golang.org/x/text v0.6.0 // indirect
|
golang.org/x/text v0.8.0 // indirect
|
||||||
golang.org/x/tools v0.1.12 // indirect
|
golang.org/x/tools v0.6.0 // indirect
|
||||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||||
google.golang.org/protobuf v1.28.1 // indirect
|
google.golang.org/protobuf v1.28.1 // indirect
|
||||||
gopkg.in/go-playground/validator.v9 v9.31.0 // indirect
|
gopkg.in/go-playground/validator.v9 v9.31.0 // indirect
|
||||||
lukechampine.com/uint128 v1.2.0 // indirect
|
lukechampine.com/uint128 v1.2.0 // indirect
|
||||||
modernc.org/cc/v3 v3.40.0 // indirect
|
modernc.org/cc/v3 v3.40.0 // indirect
|
||||||
modernc.org/ccgo/v3 v3.16.13 // indirect
|
modernc.org/ccgo/v3 v3.16.13 // indirect
|
||||||
modernc.org/libc v1.22.2 // indirect
|
modernc.org/libc v1.22.3 // indirect
|
||||||
modernc.org/mathutil v1.5.0 // indirect
|
modernc.org/mathutil v1.5.0 // indirect
|
||||||
modernc.org/memory v1.4.0 // indirect
|
modernc.org/memory v1.5.0 // indirect
|
||||||
modernc.org/opt v0.1.3 // indirect
|
modernc.org/opt v0.1.3 // indirect
|
||||||
modernc.org/strutil v1.1.3 // indirect
|
modernc.org/strutil v1.1.3 // indirect
|
||||||
modernc.org/token v1.0.1 // indirect
|
modernc.org/token v1.1.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// replace forge.cadoles.com/arcad/edge => ../edge
|
||||||
|
210
go.sum
210
go.sum
@ -1,8 +1,8 @@
|
|||||||
bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8=
|
bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8=
|
||||||
bazil.org/fuse v0.0.0-20200407214033-5883e5a4b512/go.mod h1:FbcW6z/2VytnFDhZfumh8Ss8zxHE6qpMP5sHTRe0EaM=
|
bazil.org/fuse v0.0.0-20200407214033-5883e5a4b512/go.mod h1:FbcW6z/2VytnFDhZfumh8Ss8zxHE6qpMP5sHTRe0EaM=
|
||||||
cdr.dev/slog v1.4.0/go.mod h1:C5OL99WyuOK8YHZdYY57dAPN1jK2WJlCdq2VP6xeQns=
|
cdr.dev/slog v1.4.0/go.mod h1:C5OL99WyuOK8YHZdYY57dAPN1jK2WJlCdq2VP6xeQns=
|
||||||
cdr.dev/slog v1.4.1 h1:Q8+X63m8/WB4geelMTDO8t4CTwVh1f7+5Cxi7kS/SZg=
|
cdr.dev/slog v1.4.2 h1:fIfiqASYQFJBZiASwL825atyzeA96NsqSxx2aL61P8I=
|
||||||
cdr.dev/slog v1.4.1/go.mod h1:O76C6gZJxa5HK1SXMrjd48V2kJxYZKFRTcFfn/V9OhA=
|
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.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.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.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||||
@ -54,6 +54,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
|
|||||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
cloud.google.com/go/storage v1.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=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
|
forge.cadoles.com/arcad/edge v0.0.0-20230322170544-cf8a3f8ac077 h1:vsYcNHZevZrs0VeOTasvJoqvPynb8OvH+MMpIUvNT6Q=
|
||||||
|
forge.cadoles.com/arcad/edge v0.0.0-20230322170544-cf8a3f8ac077/go.mod h1:ONd6vyQ0IM0vHi1i+bmZBRc1Fd0BoXMuDdY/+0sZefw=
|
||||||
gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8=
|
gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8=
|
||||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20210715213245-6c3934b029d8/go.mod h1:CzsSbkDixRphAF5hS6wbMKq0eI6ccJRb7/A0M6JBnwg=
|
github.com/AdaLogics/go-fuzz-headers v0.0.0-20210715213245-6c3934b029d8/go.mod h1:CzsSbkDixRphAF5hS6wbMKq0eI6ccJRb7/A0M6JBnwg=
|
||||||
github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k=
|
github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k=
|
||||||
@ -121,13 +123,11 @@ github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm
|
|||||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
|
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
|
||||||
github.com/alecthomas/assert/v2 v2.0.3 h1:WKqJODfOiQG0nEJKFKzDIG3E29CN2/4zR9XGJzKIkbg=
|
github.com/alecthomas/assert/v2 v2.0.3 h1:WKqJODfOiQG0nEJKFKzDIG3E29CN2/4zR9XGJzKIkbg=
|
||||||
github.com/alecthomas/chroma v0.7.0/go.mod h1:1U/PfCsTALWWYHDnsIQkxEBM0+6LLe0v8+RSVMOwxeY=
|
github.com/alecthomas/chroma v0.7.0/go.mod h1:1U/PfCsTALWWYHDnsIQkxEBM0+6LLe0v8+RSVMOwxeY=
|
||||||
github.com/alecthomas/chroma v0.9.1/go.mod h1:eMuEnpA18XbG/WhOWtCzJHS7WqEtDAI+HxdwoW0nVSk=
|
|
||||||
github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
|
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/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/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.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 v0.2.1-0.20190708041108-0548c6b1afae/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI=
|
||||||
github.com/alecthomas/kong v0.2.4/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE=
|
|
||||||
github.com/alecthomas/kong-hcl v0.1.8-0.20190615233001-b21fea9723c8/go.mod h1:MRgZdU3vrFd05IQ89AxUZ0aYdF39BYoNFa324SodPCA=
|
github.com/alecthomas/kong-hcl v0.1.8-0.20190615233001-b21fea9723c8/go.mod h1:MRgZdU3vrFd05IQ89AxUZ0aYdF39BYoNFa324SodPCA=
|
||||||
github.com/alecthomas/participle/v2 v2.0.0-beta.5 h1:y6dsSYVb1G5eK6mgmy+BgI3Mw35a3WghArZ/Hbebrjo=
|
github.com/alecthomas/participle/v2 v2.0.0-beta.5 h1:y6dsSYVb1G5eK6mgmy+BgI3Mw35a3WghArZ/Hbebrjo=
|
||||||
github.com/alecthomas/participle/v2 v2.0.0-beta.5/go.mod h1:RC764t6n4L8D8ITAJv0qdokritYSNR3wV5cVwmIEaMM=
|
github.com/alecthomas/participle/v2 v2.0.0-beta.5/go.mod h1:RC764t6n4L8D8ITAJv0qdokritYSNR3wV5cVwmIEaMM=
|
||||||
@ -178,6 +178,8 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.6.1/go.mod h1:hLZ/AnkIKHLuPGjEiyghNE
|
|||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.7.2/go.mod h1:8EzeIqfWt2wWT4rJVu3f21TfrhJ8AEMzVybRNSb/b4g=
|
github.com/aws/aws-sdk-go-v2/service/sts v1.7.2/go.mod h1:8EzeIqfWt2wWT4rJVu3f21TfrhJ8AEMzVybRNSb/b4g=
|
||||||
github.com/aws/smithy-go v1.7.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
|
github.com/aws/smithy-go v1.7.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
|
||||||
github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
|
github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
|
||||||
|
github.com/barnybug/go-cast v0.0.0-20201201064555-a87ccbc26692 h1:JW4WZlqyaNWUUahfr7MigeDW6jmtam5cTzzo1lwsFhE=
|
||||||
|
github.com/barnybug/go-cast v0.0.0-20201201064555-a87ccbc26692/go.mod h1:Au0ipPuCBA7zsOC61SnyrYetm8VT3vo1UJtwHeYke44=
|
||||||
github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=
|
github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=
|
||||||
github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
@ -232,8 +234,11 @@ github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOo
|
|||||||
github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M=
|
github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M=
|
||||||
github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E=
|
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/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
|
github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY=
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
|
github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic=
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
|
github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg=
|
github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg=
|
||||||
github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc=
|
github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc=
|
||||||
github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs=
|
github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs=
|
||||||
@ -390,11 +395,14 @@ github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjI
|
|||||||
github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E=
|
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/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 v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.0.1-0.20160907170601-6d212800a42e/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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.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/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.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/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
|
||||||
github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ=
|
github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ=
|
||||||
github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI=
|
github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI=
|
||||||
@ -408,8 +416,10 @@ github.com/dhui/dktest v0.3.10/go.mod h1:h5Enh0nG3Qbo9WjNFRrwmKUaePEBhXMOygbz3Ww
|
|||||||
github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
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.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.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||||
github.com/dlclark/regexp2 v1.8.0 h1:rJD5HeGIT/2b5CDk63FVCwZA3qgYElfg+oQK7uH5pfE=
|
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||||
github.com/dlclark/regexp2 v1.8.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
|
github.com/dlclark/regexp2 v1.8.1 h1:6Lcdwya6GjPUNsBct8Lg/yRPwMhABj269AAzdGSiR+0=
|
||||||
|
github.com/dlclark/regexp2 v1.8.1/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
|
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
|
||||||
github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||||
github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY=
|
github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY=
|
||||||
@ -432,9 +442,18 @@ github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD
|
|||||||
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
|
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
|
||||||
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
||||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||||
|
github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
|
||||||
|
github.com/dop251/goja v0.0.0-20221118162653-d4bf6fde1b86/go.mod h1:yRkwfj0CBpOGre+TwBsqPV0IH0Pk73e4PXJOeNDboGs=
|
||||||
|
github.com/dop251/goja v0.0.0-20230304130813-e2f543bf4b4c h1:/utv6nmTctV6OVgfk5+O6lEMEWL+6KJy4h9NZ5fnkQQ=
|
||||||
|
github.com/dop251/goja v0.0.0-20230304130813-e2f543bf4b4c/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4=
|
||||||
|
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
|
||||||
|
github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM=
|
||||||
|
github.com/dop251/goja_nodejs v0.0.0-20230320130059-dcf93ba651dd h1:8FguYHL/davT0sAfVoi84iRI4MCVTVFtlnmZqIoAXDQ=
|
||||||
|
github.com/dop251/goja_nodejs v0.0.0-20230320130059-dcf93ba651dd/go.mod h1:0tlktQL7yHfYEtjcRGi/eiOkbDR5XF7gyFFvbC5//E0=
|
||||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
|
||||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
||||||
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||||
@ -452,10 +471,12 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
|
|||||||
github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws=
|
github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws=
|
||||||
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||||
github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||||
|
github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww=
|
||||||
|
github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4=
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
|
||||||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
|
||||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
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/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||||
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||||
@ -469,6 +490,8 @@ github.com/fsouza/fake-gcs-server v1.17.0/go.mod h1:D1rTE4YCyHFNa99oyJJ5HyclvN/0
|
|||||||
github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA=
|
github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA=
|
||||||
github.com/gabriel-vasile/mimetype v1.3.1/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8=
|
github.com/gabriel-vasile/mimetype v1.3.1/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.0/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8=
|
github.com/gabriel-vasile/mimetype v1.4.0/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.1 h1:TRWk7se+TOjCYgRth7+1/OYLNiRNIotknkFtf/dnN7Q=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.1/go.mod h1:05Vi0w3Y9c/lNvJOdmIwvrrAhX3rYhfQQCaf9VJcv7M=
|
||||||
github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
|
github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
|
||||||
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
|
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
|
||||||
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
@ -476,6 +499,8 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME
|
|||||||
github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||||
github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec=
|
github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec=
|
||||||
github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
github.com/go-chi/chi v4.1.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 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
||||||
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||||
github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g=
|
github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g=
|
||||||
@ -521,6 +546,8 @@ github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/Nu
|
|||||||
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
|
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
|
||||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
|
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
|
||||||
|
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
|
||||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
@ -550,6 +577,8 @@ github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWe
|
|||||||
github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ=
|
github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ=
|
||||||
github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0=
|
github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0=
|
||||||
github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
|
github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
|
||||||
|
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/gocql/gocql v0.0.0-20210515062232-b7ef815b4556/go.mod h1:DL0ekTmBSTdlNF25Orwt/JMzqIq3EJ4MVa/J/uK64OY=
|
github.com/gocql/gocql v0.0.0-20210515062232-b7ef815b4556/go.mod h1:DL0ekTmBSTdlNF25Orwt/JMzqIq3EJ4MVa/J/uK64OY=
|
||||||
github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
||||||
github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
||||||
@ -561,6 +590,7 @@ github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRx
|
|||||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU=
|
github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU=
|
||||||
github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c=
|
github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c=
|
||||||
|
github.com/gogo/protobuf v0.0.0-20161014173244-50d1bd39ce4e/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||||
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||||
@ -658,7 +688,9 @@ github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLe
|
|||||||
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/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-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg=
|
||||||
|
github.com/google/pprof v0.0.0-20230309165930-d61513b1440d h1:um9/pc7tKMINFfP1eE7Wv6PRGXlcCSJkVajF7KJw3uQ=
|
||||||
|
github.com/google/pprof v0.0.0-20230309165930-d61513b1440d/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk=
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
@ -686,6 +718,8 @@ github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/z
|
|||||||
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||||
|
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||||
@ -713,12 +747,16 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX
|
|||||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
|
github.com/hashicorp/go.net v0.0.0-20151006203346-104dcad90073/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
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/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/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||||
|
github.com/hashicorp/mdns v0.0.0-20151206042412-9d85cf22f9f8/go.mod h1:aa76Av3qgPeIQp9Y3qIkTBPieQYNkQ13Kxe7pze9Wb0=
|
||||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||||
|
github.com/hashicorp/mdns v1.0.5 h1:1M5hW1cunYeoXOqHwEb/GBDDHAFo0Yqb/uz/beC6LbE=
|
||||||
|
github.com/hashicorp/mdns v1.0.5/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
|
||||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||||
@ -726,6 +764,9 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO
|
|||||||
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
|
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
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/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
|
github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
|
||||||
|
github.com/igm/sockjs-go/v3 v3.0.2 h1:2m0k53w0DBiGozeQUIEPR6snZFmpFpYvVsGnfLPNXbE=
|
||||||
|
github.com/igm/sockjs-go/v3 v3.0.2/go.mod h1:UqchsOjeagIBFHvd+RZpLaVRbCwGilEC08EDHsD1jYE=
|
||||||
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||||
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||||
github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||||
@ -776,15 +817,17 @@ github.com/jackc/pgx/v4 v4.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXg
|
|||||||
github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o=
|
github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o=
|
||||||
github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg=
|
github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg=
|
||||||
github.com/jackc/pgx/v4 v4.10.1/go.mod h1:QlrWebbs3kqEZPHCTGyxecvzG6tvIsYu+A5b1raylkA=
|
github.com/jackc/pgx/v4 v4.10.1/go.mod h1:QlrWebbs3kqEZPHCTGyxecvzG6tvIsYu+A5b1raylkA=
|
||||||
github.com/jackc/pgx/v5 v5.2.0 h1:NdPpngX0Y6z6XDFKqmFQaE+bCtkqzvQIOt1wvBlAqs8=
|
github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU=
|
||||||
github.com/jackc/pgx/v5 v5.2.0/go.mod h1:Ptn7zmohNsWEsdxRawMzk3gaKma2obW+NWTnKa0S4nk=
|
github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8=
|
||||||
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
github.com/jackc/puddle/v2 v2.1.2 h1:0f7vaaXINONKTsxYDn4otOAiJanX/BMeAtY//BXqzlg=
|
github.com/jackc/puddle/v2 v2.2.0 h1:RdcDk92EJBuBS55nQMMYFXTxwstHug4jkhT5pq8VxPk=
|
||||||
github.com/jackc/puddle/v2 v2.1.2/go.mod h1:2lpufsF5mRHO6SuZkm0fNYxM6SWHfvyFj62KwNzgels=
|
github.com/jackc/puddle/v2 v2.2.0/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
|
github.com/jedib0t/go-pretty/v6 v6.4.4 h1:N+gz6UngBPF4M288kiMURPHELDMIhF/Em35aYuKrsSc=
|
||||||
|
github.com/jedib0t/go-pretty/v6 v6.4.4/go.mod h1:MgmISkTWDSFu0xOqiZ0mKNntMQ2mDgOcwOkwBEkMDJI=
|
||||||
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
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/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
@ -842,6 +885,7 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
|
|||||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
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 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/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
|
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
|
||||||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||||
@ -850,8 +894,21 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/ktrysmt/go-bitbucket v0.6.4/go.mod h1:9u0v3hsd2rqCHRIpbir1oP7F58uo5dq19sBYvuMoyQ4=
|
github.com/ktrysmt/go-bitbucket v0.6.4/go.mod h1:9u0v3hsd2rqCHRIpbir1oP7F58uo5dq19sBYvuMoyQ4=
|
||||||
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
|
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
|
||||||
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
|
github.com/leodido/go-urn v1.2.2 h1:7z68G0FCGvDk646jz1AelTYNYWrTNm0bEcFAo147wt4=
|
||||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
github.com/leodido/go-urn v1.2.2/go.mod h1:kUaIbLZWttglzwNuG0pgsh5vuV6u2YcGBYz1hIPjtOQ=
|
||||||
|
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.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
@ -861,6 +918,8 @@ github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
|||||||
github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
|
github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
|
||||||
github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo=
|
github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo=
|
||||||
|
github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
|
||||||
|
github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
|
||||||
github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=
|
github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=
|
||||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
@ -878,7 +937,6 @@ github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcncea
|
|||||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
|
||||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
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 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
@ -896,17 +954,23 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
|
|||||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
|
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/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
||||||
github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
||||||
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||||
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||||
github.com/mattn/go-sqlite3 v1.14.10/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
github.com/mattn/go-sqlite3 v1.14.10/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||||
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
|
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||||
github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY=
|
github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY=
|
||||||
|
github.com/miekg/dns v0.0.0-20161006100029-fc4e1e2843d8/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||||
|
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
||||||
|
github.com/miekg/dns v1.1.51 h1:0+Xg7vObnhrz/4ZCZcZh7zPXlmU0aveS2HDBd0m0qSo=
|
||||||
|
github.com/miekg/dns v1.1.51/go.mod h1:2Z9d3CP1LQWihRZUf29mQ19yDThaI4DAYzte2CaQW5c=
|
||||||
github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
|
github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
|
||||||
github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4=
|
github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4=
|
||||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||||
@ -914,6 +978,8 @@ github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk
|
|||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||||
|
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
|
||||||
|
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
||||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
@ -956,6 +1022,8 @@ github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYX
|
|||||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||||
|
github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU=
|
||||||
|
github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
|
||||||
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||||
github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
@ -1011,8 +1079,11 @@ github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3
|
|||||||
github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8=
|
github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8=
|
||||||
github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI=
|
github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI=
|
||||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||||
|
github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
|
||||||
|
github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
|
||||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
|
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
|
||||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||||
|
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
|
||||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||||
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
|
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
|
||||||
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
|
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
|
||||||
@ -1029,7 +1100,9 @@ github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV
|
|||||||
github.com/pkg/errors v0.8.1/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 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
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/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
||||||
|
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||||
@ -1067,14 +1140,22 @@ github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
|
|||||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||||
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||||
|
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/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
|
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
|
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 v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||||
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
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.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||||
@ -1084,11 +1165,10 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
|
|||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
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/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
|
github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
|
||||||
|
github.com/rwtodd/Go.Sed v0.0.0-20210816025313-55464686f9ef/go.mod h1:8AEUvGVi2uQ5b24BIhcr0GCcpd/RNAFWaN2CJFrWIIQ=
|
||||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||||
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
|
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
|
||||||
github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
|
github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
|
||||||
github.com/santhosh-tekuri/jsonschema/v5 v5.1.1 h1:lEOLY2vyGIqKWUI9nzsOJRV3mb3WC9dXYORsLEUcoeY=
|
|
||||||
github.com/santhosh-tekuri/jsonschema/v5 v5.1.1/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0=
|
|
||||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||||
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
||||||
github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U=
|
github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U=
|
||||||
@ -1145,6 +1225,7 @@ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoH
|
|||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.1.5-0.20160925220609-976c720a22c8/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
@ -1152,9 +1233,11 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
|
|||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
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.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.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.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
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/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||||
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||||
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||||
@ -1171,8 +1254,8 @@ github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/
|
|||||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||||
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||||
github.com/urfave/cli/v2 v2.23.7 h1:YHDQ46s3VghFHFf1DdF+Sh7H4RqhcM+t0TmZRJx4oJY=
|
github.com/urfave/cli/v2 v2.24.4 h1:0gyJJEBYtCV87zI/x2nZCPyDxD51K6xM8SkwjHFCNEU=
|
||||||
github.com/urfave/cli/v2 v2.23.7/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
|
github.com/urfave/cli/v2 v2.24.4/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
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/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||||
github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
|
github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
|
||||||
@ -1202,13 +1285,14 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
|||||||
github.com/yuin/goldmark v1.1.32/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.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
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=
|
||||||
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
|
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
|
||||||
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA=
|
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA=
|
||||||
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg=
|
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg=
|
||||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||||
gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE=
|
gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE=
|
||||||
gitlab.com/wpetit/goweb v0.0.0-20230206085656-dec695f0e2e9 h1:6JlkcdjYVQglPWYuemK2MoZAtRE4vFx85zLXflGIyI8=
|
gitlab.com/wpetit/goweb v0.0.0-20230227162855-a1f09bafccb3 h1:ddXRTeqEr7LcHQEtkd6gogZOh9tI1Y6Gappr0a1oa2I=
|
||||||
gitlab.com/wpetit/goweb v0.0.0-20230206085656-dec695f0e2e9/go.mod h1:3sus4zjoUv1GB7eDLL60QaPkUnXJCWBpjvbe0jWifeY=
|
gitlab.com/wpetit/goweb v0.0.0-20230227162855-a1f09bafccb3/go.mod h1:3sus4zjoUv1GB7eDLL60QaPkUnXJCWBpjvbe0jWifeY=
|
||||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||||
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||||
@ -1261,6 +1345,7 @@ go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
|||||||
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
|
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
|
||||||
go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||||
|
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
|
||||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||||
@ -1294,8 +1379,8 @@ golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWP
|
|||||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
|
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
|
||||||
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
|
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
@ -1343,8 +1428,11 @@ 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.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
|
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
|
||||||
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
|
golang.org/x/net v0.0.0-20161013035702-8b4af36cd21a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
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-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
@ -1401,6 +1489,7 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v
|
|||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/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-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-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
|
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
|
||||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||||
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
@ -1413,7 +1502,13 @@ golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qx
|
|||||||
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220111093109-d55c255bac03/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20220111093109-d55c255bac03/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
|
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||||
|
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||||
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
|
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
||||||
|
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||||
golang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
@ -1444,8 +1539,9 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/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-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-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7 h1:ZrnxWX62AgTKOSagEqxvb3ffipvEDX2pl7E1TdqLqIc=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7/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-20180224232135-f6cff0780e54/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180224232135-f6cff0780e54/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@ -1503,7 +1599,6 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/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-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-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/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-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-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@ -1535,6 +1630,7 @@ golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/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-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-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/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-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@ -1568,17 +1664,26 @@ golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220317061510-51cd9980dadf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220317061510-51cd9980dadf/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.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.3.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 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg=
|
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||||
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
|
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||||
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
|
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
|
||||||
|
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
@ -1588,8 +1693,12 @@ 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.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/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.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
|
||||||
|
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
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-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
@ -1677,8 +1786,10 @@ 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.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.1.4/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.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
|
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
|
||||||
|
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
|
||||||
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
@ -1893,6 +2004,7 @@ gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|||||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.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.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
@ -1975,8 +2087,8 @@ modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
|
|||||||
modernc.org/internal v1.0.0/go.mod h1:VUD/+JAkhCpvkUitlEOnhpVxCgsBI90oTzSCRcqQVSM=
|
modernc.org/internal v1.0.0/go.mod h1:VUD/+JAkhCpvkUitlEOnhpVxCgsBI90oTzSCRcqQVSM=
|
||||||
modernc.org/libc v1.7.13-0.20210308123627-12f642a52bb8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
|
modernc.org/libc v1.7.13-0.20210308123627-12f642a52bb8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
|
||||||
modernc.org/libc v1.9.5/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
|
modernc.org/libc v1.9.5/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
|
||||||
modernc.org/libc v1.22.2 h1:4U7v51GyhlWqQmwCHj28Rdq2Yzwk55ovjFrdPjs8Hb0=
|
modernc.org/libc v1.22.3 h1:D/g6O5ftAfavceqlLOFwaZuA5KYafKwmr30A6iSqoyY=
|
||||||
modernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug=
|
modernc.org/libc v1.22.3/go.mod h1:MQrloYP209xa2zHome2a8HLiLm6k0UT8CoHpV74tOFw=
|
||||||
modernc.org/lldb v1.0.0/go.mod h1:jcRvJGWfCGodDZz8BPwiKMJxGJngQ/5DrRapkQnLob8=
|
modernc.org/lldb v1.0.0/go.mod h1:jcRvJGWfCGodDZz8BPwiKMJxGJngQ/5DrRapkQnLob8=
|
||||||
modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=
|
modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=
|
||||||
modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||||
@ -1984,24 +2096,24 @@ modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6
|
|||||||
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
||||||
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||||
modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc=
|
modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc=
|
||||||
modernc.org/memory v1.4.0 h1:crykUfNSnMAXaOJnnxcSzbUGMqkLWjklJKkBK2nwZwk=
|
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
|
||||||
modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
||||||
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||||
modernc.org/ql v1.0.0/go.mod h1:xGVyrLIatPcO2C1JvI/Co8c0sr6y91HKFNy4pt9JXEY=
|
modernc.org/ql v1.0.0/go.mod h1:xGVyrLIatPcO2C1JvI/Co8c0sr6y91HKFNy4pt9JXEY=
|
||||||
modernc.org/sortutil v1.1.0/go.mod h1:ZyL98OQHJgH9IEfN71VsamvJgrtRX9Dj2gX+vH86L1k=
|
modernc.org/sortutil v1.1.0/go.mod h1:ZyL98OQHJgH9IEfN71VsamvJgrtRX9Dj2gX+vH86L1k=
|
||||||
modernc.org/sqlite v1.10.6/go.mod h1:Z9FEjUtZP4qFEg6/SiADg9XCER7aYy9a/j7Pg9P7CPs=
|
modernc.org/sqlite v1.10.6/go.mod h1:Z9FEjUtZP4qFEg6/SiADg9XCER7aYy9a/j7Pg9P7CPs=
|
||||||
modernc.org/sqlite v1.20.3 h1:SqGJMMxjj1PHusLxdYxeQSodg7Jxn9WWkaAQjKrntZs=
|
modernc.org/sqlite v1.21.0 h1:4aP4MdUf15i3R3M2mx6Q90WHKz3nZLoz96zlB6tNdow=
|
||||||
modernc.org/sqlite v1.20.3/go.mod h1:zKcGyrICaxNTMEHSr1HQ2GUraP0j+845GYw37+EyT6A=
|
modernc.org/sqlite v1.21.0/go.mod h1:XwQ0wZPIh1iKb5mkvCJ3szzbhk+tykC8ZWqTRTgYRwI=
|
||||||
modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=
|
modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=
|
||||||
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
|
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
|
||||||
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
|
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
|
||||||
modernc.org/tcl v1.5.2/go.mod h1:pmJYOLgpiys3oI4AeAafkcUfE+TKKilminxNyU/+Zlo=
|
modernc.org/tcl v1.5.2/go.mod h1:pmJYOLgpiys3oI4AeAafkcUfE+TKKilminxNyU/+Zlo=
|
||||||
modernc.org/tcl v1.15.0 h1:oY+JeD11qVVSgVvodMJsu7Edf8tr5E/7tuhF5cNYz34=
|
modernc.org/tcl v1.15.1 h1:mOQwiEK4p7HruMZcwKTZPw/aqtGM4aY00uzWhlKKYws=
|
||||||
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||||
modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
|
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||||
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||||
modernc.org/z v1.0.1-0.20210308123920-1f282aa71362/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA=
|
modernc.org/z v1.0.1-0.20210308123920-1f282aa71362/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA=
|
||||||
modernc.org/z v1.0.1/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA=
|
modernc.org/z v1.0.1/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA=
|
||||||
modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE=
|
modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE=
|
||||||
|
@ -4,13 +4,22 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/agent/metadata"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/auth/agent"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/client"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/jwk"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"gitlab.com/wpetit/goweb/api"
|
||||||
"gitlab.com/wpetit/goweb/logger"
|
"gitlab.com/wpetit/goweb/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Agent struct {
|
type Agent struct {
|
||||||
|
thumbprint string
|
||||||
|
privateKey jwk.Key
|
||||||
|
serverURL string
|
||||||
controllers []Controller
|
controllers []Controller
|
||||||
interval time.Duration
|
interval time.Duration
|
||||||
|
collectors []metadata.Collector
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Agent) Run(ctx context.Context) error {
|
func (a *Agent) Run(ctx context.Context) error {
|
||||||
@ -21,20 +30,41 @@ func (a *Agent) Run(ctx context.Context) error {
|
|||||||
ticker := time.NewTicker(a.interval)
|
ticker := time.NewTicker(a.interval)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
token, err := agent.GenerateToken(a.privateKey, a.thumbprint)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := client.New(a.serverURL, client.WithToken(token))
|
||||||
|
|
||||||
|
ctx = withClient(ctx, client)
|
||||||
|
|
||||||
|
tick := func() {
|
||||||
|
logger.Debug(ctx, "registering agent")
|
||||||
|
|
||||||
|
if err := a.registerAgent(ctx, client, state); err != nil {
|
||||||
|
logger.Error(ctx, "could not register agent", logger.E(errors.WithStack(err)))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Debug(ctx, "state before reconciliation", logger.F("state", state))
|
||||||
|
|
||||||
|
if err := a.Reconcile(ctx, state); err != nil {
|
||||||
|
logger.Error(ctx, "could not reconcile node with state", logger.E(errors.WithStack(err)))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Debug(ctx, "state after reconciliation", logger.F("state", state))
|
||||||
|
}
|
||||||
|
|
||||||
|
tick()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
|
tick()
|
||||||
logger.Debug(ctx, "state before reconciliation", logger.F("state", state))
|
|
||||||
|
|
||||||
if err := a.Reconcile(ctx, state); err != nil {
|
|
||||||
logger.Error(ctx, "could not reconcile node with state", logger.E(errors.WithStack(err)))
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Debug(ctx, "state after reconciliation", logger.F("state", state))
|
|
||||||
|
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return errors.WithStack(ctx.Err())
|
return errors.WithStack(ctx.Err())
|
||||||
}
|
}
|
||||||
@ -58,14 +88,65 @@ func (a *Agent) Reconcile(ctx context.Context, state *State) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(funcs ...OptionFunc) *Agent {
|
func (a *Agent) registerAgent(ctx context.Context, client *client.Client, state *State) error {
|
||||||
|
meta, err := a.collectMetadata(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sorted := metadata.Sort(meta)
|
||||||
|
|
||||||
|
agent, err := client.RegisterAgent(ctx, a.privateKey, a.thumbprint, sorted)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
state.agentID = agent.ID
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Agent) collectMetadata(ctx context.Context) (map[string]any, error) {
|
||||||
|
metadata := make(map[string]any)
|
||||||
|
|
||||||
|
for _, collector := range a.collectors {
|
||||||
|
name, value, err := collector.Collect(ctx)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(
|
||||||
|
ctx, "could not collect metadata",
|
||||||
|
logger.E(errors.WithStack(err)), logger.F("name", name),
|
||||||
|
)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata[name] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
return metadata, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isAPIError(err error, code api.ErrorCode) (bool, any) {
|
||||||
|
apiError := &api.Error{}
|
||||||
|
if errors.As(err, &apiError) && apiError.Code == code {
|
||||||
|
return true, apiError.Data
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(serverURL string, privateKey jwk.Key, thumbprint string, funcs ...OptionFunc) *Agent {
|
||||||
opt := defaultOption()
|
opt := defaultOption()
|
||||||
for _, fn := range funcs {
|
for _, fn := range funcs {
|
||||||
fn(opt)
|
fn(opt)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Agent{
|
return &Agent{
|
||||||
|
serverURL: serverURL,
|
||||||
|
privateKey: privateKey,
|
||||||
|
thumbprint: thumbprint,
|
||||||
controllers: opt.Controllers,
|
controllers: opt.Controllers,
|
||||||
interval: opt.Interval,
|
interval: opt.Interval,
|
||||||
|
collectors: opt.Collectors,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
27
internal/agent/context.go
Normal file
27
internal/agent/context.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/client"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type contextKey string
|
||||||
|
|
||||||
|
const (
|
||||||
|
contextKeyClient contextKey = "client"
|
||||||
|
)
|
||||||
|
|
||||||
|
func withClient(ctx context.Context, client *client.Client) context.Context {
|
||||||
|
return context.WithValue(ctx, contextKeyClient, client)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Client(ctx context.Context) *client.Client {
|
||||||
|
client, ok := ctx.Value(contextKeyClient).(*client.Client)
|
||||||
|
if !ok {
|
||||||
|
panic(errors.New("could not retrieve client from context"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return client
|
||||||
|
}
|
302
internal/agent/controller/app/controller.go
Normal file
302
internal/agent/controller/app/controller.go
Normal file
@ -0,0 +1,302 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/agent"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/spec/app"
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/bundle"
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/storage/sqlite"
|
||||||
|
"github.com/mitchellh/hashstructure/v2"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"gitlab.com/wpetit/goweb/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type serverEntry struct {
|
||||||
|
SpecHash uint64
|
||||||
|
Server *Server
|
||||||
|
}
|
||||||
|
|
||||||
|
type Controller struct {
|
||||||
|
client *http.Client
|
||||||
|
downloadDir string
|
||||||
|
dataDir string
|
||||||
|
servers map[string]*serverEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name implements node.Controller.
|
||||||
|
func (c *Controller) Name() string {
|
||||||
|
return "app-controller"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reconcile implements node.Controller.
|
||||||
|
func (c *Controller) Reconcile(ctx context.Context, state *agent.State) error {
|
||||||
|
appSpec := app.NewSpec()
|
||||||
|
|
||||||
|
if err := state.GetSpec(app.NameApp, appSpec); err != nil {
|
||||||
|
if errors.Is(err, agent.ErrSpecNotFound) {
|
||||||
|
logger.Info(ctx, "could not find app spec")
|
||||||
|
|
||||||
|
c.stopAllApps(ctx, appSpec)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info(ctx, "retrieved spec", logger.F("spec", appSpec.SpecName()), logger.F("revision", appSpec.SpecRevision()))
|
||||||
|
|
||||||
|
c.updateApps(ctx, appSpec)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) stopAllApps(ctx context.Context, spec *app.Spec) {
|
||||||
|
if len(c.servers) > 0 {
|
||||||
|
logger.Info(ctx, "stopping all apps")
|
||||||
|
}
|
||||||
|
|
||||||
|
for appID, entry := range c.servers {
|
||||||
|
logger.Info(ctx, "stopping app", logger.F("appID", appID))
|
||||||
|
|
||||||
|
if err := entry.Server.Stop(); err != nil {
|
||||||
|
logger.Error(
|
||||||
|
ctx, "error while stopping app",
|
||||||
|
logger.F("appID", appID),
|
||||||
|
logger.E(errors.WithStack(err)),
|
||||||
|
)
|
||||||
|
|
||||||
|
delete(c.servers, appID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) updateApps(ctx context.Context, spec *app.Spec) {
|
||||||
|
// Stop and remove obsolete apps
|
||||||
|
for appID, entry := range c.servers {
|
||||||
|
if _, exists := spec.Apps[appID]; exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info(ctx, "stopping app", logger.F("appID", appID))
|
||||||
|
|
||||||
|
if err := entry.Server.Stop(); err != nil {
|
||||||
|
logger.Error(
|
||||||
|
ctx, "error while stopping app",
|
||||||
|
logger.F("gatewayID", appID),
|
||||||
|
logger.E(errors.WithStack(err)),
|
||||||
|
)
|
||||||
|
|
||||||
|
delete(c.servers, appID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// (Re)start apps
|
||||||
|
for appID, appSpec := range spec.Apps {
|
||||||
|
appCtx := logger.With(ctx, logger.F("appID", appID))
|
||||||
|
|
||||||
|
if err := c.updateApp(ctx, appID, appSpec, spec.Auth); err != nil {
|
||||||
|
logger.Error(appCtx, "could not update app", logger.E(errors.WithStack(err)))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) updateApp(ctx context.Context, appID string, appSpec app.AppEntry, auth *app.Auth) (err error) {
|
||||||
|
newAppSpecHash, err := hashstructure.Hash(appSpec, hashstructure.FormatV2, nil)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bundle, sha256sum, err := c.ensureAppBundle(ctx, appID, appSpec)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not download app bundle")
|
||||||
|
}
|
||||||
|
|
||||||
|
dataDir, err := c.ensureAppDataDir(ctx, appID)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not retrieve app data dir")
|
||||||
|
}
|
||||||
|
|
||||||
|
var entry *serverEntry
|
||||||
|
|
||||||
|
entry, exists := c.servers[appID]
|
||||||
|
if !exists {
|
||||||
|
logger.Info(ctx, "app currently not running")
|
||||||
|
} else if sha256sum != appSpec.SHA256Sum {
|
||||||
|
logger.Info(
|
||||||
|
ctx, "bundle hash mismatch, stopping app",
|
||||||
|
logger.F("currentHash", sha256sum),
|
||||||
|
logger.F("specHash", appSpec.SHA256Sum),
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := entry.Server.Stop(); err != nil {
|
||||||
|
return errors.Wrap(err, "could not stop app")
|
||||||
|
}
|
||||||
|
|
||||||
|
entry = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if entry == nil {
|
||||||
|
dbFile := filepath.Join(dataDir, appID+".sqlite")
|
||||||
|
db, err := sqlite.Open(dbFile)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "could not opend database file '%s'", dbFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
entry = &serverEntry{
|
||||||
|
Server: NewServer(bundle, db, auth),
|
||||||
|
SpecHash: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
c.servers[appID] = entry
|
||||||
|
}
|
||||||
|
|
||||||
|
specChanged := newAppSpecHash != entry.SpecHash
|
||||||
|
|
||||||
|
if entry.Server.Running() && !specChanged {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if specChanged && entry.SpecHash != 0 {
|
||||||
|
logger.Info(
|
||||||
|
ctx, "restarting app",
|
||||||
|
logger.F("address", appSpec.Address),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
logger.Info(
|
||||||
|
ctx, "starting app",
|
||||||
|
logger.F("address", appSpec.Address),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := entry.Server.Start(ctx, appSpec.Address); err != nil {
|
||||||
|
delete(c.servers, appID)
|
||||||
|
|
||||||
|
return errors.Wrap(err, "could not start app")
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.SpecHash = newAppSpecHash
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) ensureAppBundle(ctx context.Context, appID string, spec app.AppEntry) (bundle.Bundle, string, error) {
|
||||||
|
if err := os.MkdirAll(c.downloadDir, os.ModePerm); err != nil {
|
||||||
|
return nil, "", errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bundlePath := filepath.Join(c.downloadDir, appID+"."+spec.Format)
|
||||||
|
|
||||||
|
_, err := os.Stat(bundlePath)
|
||||||
|
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||||
|
return nil, "", errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
if err := c.downloadFile(spec.URL, spec.SHA256Sum, bundlePath); err != nil {
|
||||||
|
return nil, "", errors.WithStack(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sha256sum, err := hash(bundlePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sha256sum == spec.SHA256Sum {
|
||||||
|
bdle, err := bundle.FromPath(bundlePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return bdle, sha256sum, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info(ctx, "bundle hash mismatch, downloading app", logger.F("url", spec.URL))
|
||||||
|
|
||||||
|
if err := c.downloadFile(spec.URL, spec.SHA256Sum, bundlePath); err != nil {
|
||||||
|
return nil, "", errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bdle, err := bundle.FromPath(bundlePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return bdle, "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) downloadFile(url string, sha256sum string, dest string) error {
|
||||||
|
res, err := c.client.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := res.Body.Close(); err != nil && !errors.Is(err, os.ErrClosed) {
|
||||||
|
panic(errors.WithStack(err))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
tmp, err := os.CreateTemp(filepath.Dir(dest), "download_")
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := os.Remove(tmp.Name()); err != nil && !os.IsNotExist(err) {
|
||||||
|
panic(errors.WithStack(err))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if _, err := io.Copy(tmp, res.Body); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpFileHash, err := hash(tmp.Name())
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpFileHash != sha256sum {
|
||||||
|
return errors.Errorf("sha256 sum mismatch: expected '%s', got '%s'", sha256sum, tmpFileHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Rename(tmp.Name(), dest); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) ensureAppDataDir(ctx context.Context, appID string) (string, error) {
|
||||||
|
dataDir := filepath.Join(c.dataDir, appID)
|
||||||
|
|
||||||
|
if err := os.MkdirAll(dataDir, os.ModePerm); err != nil {
|
||||||
|
return "", errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataDir, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewController(funcs ...OptionFunc) *Controller {
|
||||||
|
opts := defaultOptions()
|
||||||
|
for _, fn := range funcs {
|
||||||
|
fn(opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Controller{
|
||||||
|
client: opts.Client,
|
||||||
|
downloadDir: opts.DownloadDir,
|
||||||
|
dataDir: opts.DataDir,
|
||||||
|
servers: make(map[string]*serverEntry),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ agent.Controller = &Controller{}
|
31
internal/agent/controller/app/hash.go
Normal file
31
internal/agent/controller/app/hash.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func hash(path string) (string, error) {
|
||||||
|
file, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
hasher := sha256.New()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := file.Close(); err != nil {
|
||||||
|
panic(errors.WithStack(err))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if _, err := io.Copy(hasher, file); err != nil {
|
||||||
|
return "", errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return hex.EncodeToString(hasher.Sum(nil)), nil
|
||||||
|
}
|
37
internal/agent/controller/app/options.go
Normal file
37
internal/agent/controller/app/options.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
DataDir string
|
||||||
|
DownloadDir string
|
||||||
|
Client *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultOptions() *Options {
|
||||||
|
return &Options{
|
||||||
|
DataDir: "apps/data",
|
||||||
|
DownloadDir: "apps/download",
|
||||||
|
Client: &http.Client{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type OptionFunc func(*Options)
|
||||||
|
|
||||||
|
func WithDataDir(dataDir string) OptionFunc {
|
||||||
|
return func(o *Options) {
|
||||||
|
o.DataDir = dataDir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithDownloadDir(downloadDir string) OptionFunc {
|
||||||
|
return func(o *Options) {
|
||||||
|
o.DownloadDir = downloadDir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithClient(client *http.Client) OptionFunc {
|
||||||
|
return func(o *Options) {
|
||||||
|
o.Client = client
|
||||||
|
}
|
||||||
|
}
|
205
internal/agent/controller/app/server.go
Normal file
205
internal/agent/controller/app/server.go
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
appSpec "forge.cadoles.com/Cadoles/emissary/internal/spec/app"
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/app"
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/bus"
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/bus/memory"
|
||||||
|
edgeHTTP "forge.cadoles.com/arcad/edge/pkg/http"
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/module"
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/module/auth"
|
||||||
|
authHTTP "forge.cadoles.com/arcad/edge/pkg/module/auth/http"
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/module/cast"
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/module/net"
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/storage"
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/storage/sqlite"
|
||||||
|
"gitlab.com/wpetit/goweb/logger"
|
||||||
|
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/bundle"
|
||||||
|
"github.com/dop251/goja"
|
||||||
|
"github.com/go-chi/chi/middleware"
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/lestrrat-go/jwx/v2/jwa"
|
||||||
|
"github.com/lestrrat-go/jwx/v2/jwk"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
_ "forge.cadoles.com/Cadoles/emissary/internal/imports/passwd"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
bundle bundle.Bundle
|
||||||
|
db *sql.DB
|
||||||
|
server *http.Server
|
||||||
|
serverMutex sync.RWMutex
|
||||||
|
auth *appSpec.Auth
|
||||||
|
keySet jwk.Set
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Start(ctx context.Context, addr string) (err error) {
|
||||||
|
if s.server != nil {
|
||||||
|
if err := s.Stop(); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
router := chi.NewRouter()
|
||||||
|
|
||||||
|
router.Use(middleware.Logger)
|
||||||
|
|
||||||
|
bus := memory.NewBus()
|
||||||
|
ds := sqlite.NewDocumentStoreWithDB(s.db)
|
||||||
|
bs := sqlite.NewBlobStoreWithDB(s.db)
|
||||||
|
|
||||||
|
handler := edgeHTTP.NewHandler(
|
||||||
|
edgeHTTP.WithBus(bus),
|
||||||
|
edgeHTTP.WithServerModules(s.getAppModules(bus, ds, bs)...),
|
||||||
|
)
|
||||||
|
if err := handler.Load(s.bundle); err != nil {
|
||||||
|
return errors.Wrap(err, "could not load app bundle")
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.auth != nil {
|
||||||
|
if s.auth.Local != nil {
|
||||||
|
var rawKey any = s.auth.Local.Key
|
||||||
|
if strKey, ok := rawKey.(string); ok {
|
||||||
|
rawKey = []byte(strKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
key, err := jwk.FromRaw(rawKey)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := key.Set(jwk.AlgorithmKey, jwa.HS256); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
keySet := jwk.NewSet()
|
||||||
|
if err := keySet.AddKey(key); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.keySet = keySet
|
||||||
|
|
||||||
|
router.Handle("/auth/*", authHTTP.NewLocalHandler(
|
||||||
|
jwa.HS256, key,
|
||||||
|
authHTTP.WithRoutePrefix("/auth"),
|
||||||
|
authHTTP.WithAccounts(s.auth.Local.Accounts...),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
router.Handle("/*", handler)
|
||||||
|
|
||||||
|
server := &http.Server{
|
||||||
|
Addr: addr,
|
||||||
|
Handler: router,
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
if recovered := recover(); recovered != nil {
|
||||||
|
if err, ok := recovered.(error); ok {
|
||||||
|
logger.Error(ctx, err.Error(), logger.E(errors.WithStack(err)))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
panic(recovered)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := s.Stop(); err != nil {
|
||||||
|
panic(errors.WithStack(err))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
|
panic(errors.WithStack(err))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
s.serverMutex.Lock()
|
||||||
|
s.server = server
|
||||||
|
s.serverMutex.Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Running() bool {
|
||||||
|
s.serverMutex.RLock()
|
||||||
|
defer s.serverMutex.RUnlock()
|
||||||
|
|
||||||
|
return s.server != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Stop() error {
|
||||||
|
if s.server == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
s.serverMutex.Lock()
|
||||||
|
s.server = nil
|
||||||
|
s.serverMutex.Unlock()
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := s.server.Close(); err != nil {
|
||||||
|
panic(errors.WithStack(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) getAppModules(bus bus.Bus, ds storage.DocumentStore, bs storage.BlobStore) []app.ServerModuleFactory {
|
||||||
|
return []app.ServerModuleFactory{
|
||||||
|
module.ContextModuleFactory(),
|
||||||
|
module.ConsoleModuleFactory(),
|
||||||
|
cast.CastModuleFactory(),
|
||||||
|
module.LifecycleModuleFactory(),
|
||||||
|
net.ModuleFactory(bus),
|
||||||
|
module.RPCModuleFactory(bus),
|
||||||
|
module.StoreModuleFactory(ds),
|
||||||
|
module.BlobModuleFactory(bus, bs),
|
||||||
|
module.Extends(
|
||||||
|
auth.ModuleFactory(
|
||||||
|
auth.WithJWT(s.getJWTKeySet),
|
||||||
|
),
|
||||||
|
func(o *goja.Object) {
|
||||||
|
if err := o.Set("CLAIM_TENANT", "arcad_tenant"); err != nil {
|
||||||
|
panic(errors.New("could not set 'CLAIM_TENANT' property"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := o.Set("CLAIM_ENTRYPOINT", "arcad_entrypoint"); err != nil {
|
||||||
|
panic(errors.New("could not set 'CLAIM_ENTRYPOINT' property"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := o.Set("CLAIM_ROLE", "arcad_role"); err != nil {
|
||||||
|
panic(errors.New("could not set 'CLAIM_ROLE' property"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := o.Set("CLAIM_PREFERRED_USERNAME", "preferred_username"); err != nil {
|
||||||
|
panic(errors.New("could not set 'CLAIM_PREFERRED_USERNAME' property"))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) getJWTKeySet() (jwk.Set, error) {
|
||||||
|
return s.keySet, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServer(bundle bundle.Bundle, db *sql.DB, auth *appSpec.Auth) *Server {
|
||||||
|
return &Server{
|
||||||
|
bundle: bundle,
|
||||||
|
db: db,
|
||||||
|
auth: auth,
|
||||||
|
}
|
||||||
|
}
|
@ -1,124 +0,0 @@
|
|||||||
package gateway
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"forge.cadoles.com/Cadoles/emissary/internal/agent"
|
|
||||||
"forge.cadoles.com/Cadoles/emissary/internal/spec"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"gitlab.com/wpetit/goweb/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Controller struct {
|
|
||||||
proxies map[spec.GatewayID]*ReverseProxy
|
|
||||||
currentSpecRevision int
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name implements node.Controller.
|
|
||||||
func (c *Controller) Name() string {
|
|
||||||
return "gateway-controller"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reconcile implements node.Controller.
|
|
||||||
func (c *Controller) Reconcile(ctx context.Context, state *agent.State) error {
|
|
||||||
gatewaySpec := spec.NewGatewaySpec()
|
|
||||||
|
|
||||||
if err := state.GetSpec(spec.NameGateway, gatewaySpec); err != nil {
|
|
||||||
if errors.Is(err, agent.ErrSpecNotFound) {
|
|
||||||
logger.Info(ctx, "could not find gateway spec, stopping all remaining proxies")
|
|
||||||
|
|
||||||
c.stopAllProxies(ctx)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Info(ctx, "retrieved spec", logger.F("spec", gatewaySpec.SpecName()), logger.F("revision", gatewaySpec.SpecRevision()))
|
|
||||||
|
|
||||||
if c.currentSpecRevision == gatewaySpec.SpecRevision() {
|
|
||||||
logger.Info(ctx, "spec revision did not change, doing nothing")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
c.updateProxies(ctx, gatewaySpec)
|
|
||||||
|
|
||||||
c.currentSpecRevision = gatewaySpec.SpecRevision()
|
|
||||||
logger.Info(ctx, "updating current spec revision", logger.F("revision", c.currentSpecRevision))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Controller) stopAllProxies(ctx context.Context) {
|
|
||||||
for gatewayID, proxy := range c.proxies {
|
|
||||||
logger.Info(ctx, "stopping proxy", logger.F("gatewayID", gatewayID))
|
|
||||||
|
|
||||||
if err := proxy.Stop(); err != nil {
|
|
||||||
logger.Error(
|
|
||||||
ctx, "error while stopping proxy",
|
|
||||||
logger.F("gatewayID", gatewayID),
|
|
||||||
logger.E(errors.WithStack(err)),
|
|
||||||
)
|
|
||||||
|
|
||||||
delete(c.proxies, gatewayID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Controller) updateProxies(ctx context.Context, spec *spec.Gateway) {
|
|
||||||
// Stop and remove obsolete gateways
|
|
||||||
for gatewayID, proxy := range c.proxies {
|
|
||||||
if _, exists := spec.Gateways[gatewayID]; exists {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Info(ctx, "stopping proxy", logger.F("gatewayID", gatewayID))
|
|
||||||
|
|
||||||
if err := proxy.Stop(); err != nil {
|
|
||||||
logger.Error(
|
|
||||||
ctx, "error while stopping proxy",
|
|
||||||
logger.F("gatewayID", gatewayID),
|
|
||||||
logger.E(errors.WithStack(err)),
|
|
||||||
)
|
|
||||||
|
|
||||||
delete(c.proxies, gatewayID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// (Re)start gateways
|
|
||||||
for gatewayID, gatewaySpec := range spec.Gateways {
|
|
||||||
proxy, exists := c.proxies[gatewayID]
|
|
||||||
if !exists {
|
|
||||||
proxy = NewReverseProxy()
|
|
||||||
c.proxies[gatewayID] = proxy
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Info(
|
|
||||||
ctx, "starting proxy",
|
|
||||||
logger.F("gatewayID", gatewayID),
|
|
||||||
logger.F("addr", gatewaySpec.Address),
|
|
||||||
logger.F("target", gatewaySpec.Target),
|
|
||||||
)
|
|
||||||
|
|
||||||
if err := proxy.Start(ctx, gatewaySpec.Address, gatewaySpec.Target); err != nil {
|
|
||||||
logger.Error(
|
|
||||||
ctx, "error while starting proxy",
|
|
||||||
logger.F("gatewayID", gatewayID),
|
|
||||||
logger.E(errors.WithStack(err)),
|
|
||||||
)
|
|
||||||
|
|
||||||
delete(c.proxies, gatewayID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewController() *Controller {
|
|
||||||
return &Controller{
|
|
||||||
proxies: make(map[spec.GatewayID]*ReverseProxy),
|
|
||||||
currentSpecRevision: -1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ agent.Controller = &Controller{}
|
|
31
internal/agent/controller/openwrt/hash.go
Normal file
31
internal/agent/controller/openwrt/hash.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package openwrt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func hash(path string) (string, error) {
|
||||||
|
file, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
hasher := sha256.New()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := file.Close(); err != nil {
|
||||||
|
panic(errors.WithStack(err))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if _, err := io.Copy(hasher, file); err != nil {
|
||||||
|
return "", errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return hex.EncodeToString(hasher.Sum(nil)), nil
|
||||||
|
}
|
17
internal/agent/controller/openwrt/spec/sysupgrade/init.go
Normal file
17
internal/agent/controller/openwrt/spec/sysupgrade/init.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package sysupgrade
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/spec"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed schema.json
|
||||||
|
var schema []byte
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if err := spec.Register(Name, schema); err != nil {
|
||||||
|
panic(errors.WithStack(err))
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://sysupgrade.openwrt.emissary.cadoles.com/spec.json",
|
||||||
|
"title": "SysUpgradeSpec",
|
||||||
|
"description": "Emissary 'SysUpgrade' specification",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"url": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"sha256sum": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["url", "sha256sum", "version"],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
38
internal/agent/controller/openwrt/spec/sysupgrade/spec.go
Normal file
38
internal/agent/controller/openwrt/spec/sysupgrade/spec.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package sysupgrade
|
||||||
|
|
||||||
|
import (
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/spec"
|
||||||
|
)
|
||||||
|
|
||||||
|
const Name spec.Name = "sysupgrade.openwrt.emissary.cadoles.com"
|
||||||
|
|
||||||
|
type Spec struct {
|
||||||
|
Revision int `json:"revision"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
SHA256Sum string `json:"sha256sum"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Spec) SpecName() spec.Name {
|
||||||
|
return Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Spec) SpecRevision() int {
|
||||||
|
return s.Revision
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Spec) SpecData() map[string]any {
|
||||||
|
return map[string]any{
|
||||||
|
"url": s.URL,
|
||||||
|
"version": s.Version,
|
||||||
|
"sha256sum": s.SHA256Sum,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSpec() *Spec {
|
||||||
|
return &Spec{
|
||||||
|
Revision: -1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ spec.Spec = &Spec{}
|
9
internal/agent/controller/openwrt/spec/sysupgrade/testdata/spec-ok.json
vendored
Normal file
9
internal/agent/controller/openwrt/spec/sysupgrade/testdata/spec-ok.json
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"name": "sysupgrade.openwrt.emissary.cadoles.com",
|
||||||
|
"data": {
|
||||||
|
"url": "http://example.com/firmware.img",
|
||||||
|
"sha256sum": "58019192dacdae17755707719707db007e26dac856102280583fbd18427dd352",
|
||||||
|
"version": "0.0.0"
|
||||||
|
},
|
||||||
|
"revision": 0
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
package sysupgrade
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/spec"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type validatorTestCase struct {
|
||||||
|
Name string
|
||||||
|
Source string
|
||||||
|
ShouldFail bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var validatorTestCases = []validatorTestCase{
|
||||||
|
{
|
||||||
|
Name: "SpecOK",
|
||||||
|
Source: "testdata/spec-ok.json",
|
||||||
|
ShouldFail: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidator(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
validator := spec.NewValidator()
|
||||||
|
if err := validator.Register(Name, schema); err != nil {
|
||||||
|
t.Fatalf("+%v", errors.WithStack(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range validatorTestCases {
|
||||||
|
func(tc validatorTestCase) {
|
||||||
|
t.Run(tc.Name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
rawSpec, err := ioutil.ReadFile(tc.Source)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("+%v", errors.WithStack(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
var spec spec.RawSpec
|
||||||
|
|
||||||
|
if err := json.Unmarshal(rawSpec, &spec); err != nil {
|
||||||
|
t.Fatalf("+%v", errors.WithStack(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
err = validator.Validate(ctx, &spec)
|
||||||
|
|
||||||
|
if !tc.ShouldFail && err != nil {
|
||||||
|
t.Errorf("+%v", errors.WithStack(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.ShouldFail && err == nil {
|
||||||
|
t.Error("validation should have failed")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}(tc)
|
||||||
|
}
|
||||||
|
}
|
177
internal/agent/controller/openwrt/sysupgrade_controller.go
Normal file
177
internal/agent/controller/openwrt/sysupgrade_controller.go
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
package openwrt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/agent"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/agent/controller/openwrt/spec/sysupgrade"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"gitlab.com/wpetit/goweb/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SysUpgradeController struct {
|
||||||
|
client *http.Client
|
||||||
|
command string
|
||||||
|
args []string
|
||||||
|
firmwareVersion FirmwareVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name implements agent.Controller
|
||||||
|
func (*SysUpgradeController) Name() string {
|
||||||
|
return "sysupgrade-controller"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reconcile implements agent.Controller
|
||||||
|
func (c *SysUpgradeController) Reconcile(ctx context.Context, state *agent.State) error {
|
||||||
|
sysSpec := sysupgrade.NewSpec()
|
||||||
|
|
||||||
|
if err := state.GetSpec(sysupgrade.Name, sysSpec); err != nil {
|
||||||
|
if errors.Is(err, agent.ErrSpecNotFound) {
|
||||||
|
logger.Info(ctx, "could not find sysupgrade spec, doing nothing")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
firmwareVersion, err := c.firmwareVersion.FirmwareVersion(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = logger.With(ctx,
|
||||||
|
logger.F("currentFirmwareVersion", firmwareVersion),
|
||||||
|
logger.F("newFirmwareVersion", sysSpec.Version),
|
||||||
|
)
|
||||||
|
|
||||||
|
if firmwareVersion == sysSpec.Version {
|
||||||
|
logger.Info(ctx, "firmware version did not change, doing nothing")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadDir, err := os.MkdirTemp(os.TempDir(), "emissary_sysupgrade_*")
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := os.RemoveAll(downloadDir); err != nil {
|
||||||
|
logger.Error(
|
||||||
|
ctx, "could not remove download direction",
|
||||||
|
logger.E(errors.WithStack(err)),
|
||||||
|
logger.F("downloadDir", downloadDir),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
firmwareFile := filepath.Join(downloadDir, "firmware.bin")
|
||||||
|
|
||||||
|
logger.Info(
|
||||||
|
ctx, "downloading firmware",
|
||||||
|
logger.F("url", sysSpec.URL), logger.F("sha256sum", sysSpec.SHA256Sum),
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := c.downloadFile(ctx, sysSpec.URL, sysSpec.SHA256Sum, firmwareFile); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info(ctx, "upgrading firmware")
|
||||||
|
|
||||||
|
if err := c.upgradeFirmware(ctx, firmwareFile); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *SysUpgradeController) downloadFile(ctx context.Context, url string, sha256sum string, dest string) error {
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := c.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := res.Body.Close(); err != nil && !errors.Is(err, os.ErrClosed) {
|
||||||
|
panic(errors.WithStack(err))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
tmp, err := os.CreateTemp(filepath.Dir(dest), "download_")
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := os.Remove(tmp.Name()); err != nil && !os.IsNotExist(err) {
|
||||||
|
panic(errors.WithStack(err))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if _, err := io.Copy(tmp, res.Body); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpFileHash, err := hash(tmp.Name())
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpFileHash != sha256sum {
|
||||||
|
return errors.Errorf("sha256 sum mismatch: expected '%s', got '%s'", sha256sum, tmpFileHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Rename(tmp.Name(), dest); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *SysUpgradeController) upgradeFirmware(ctx context.Context, firmwareFile string) error {
|
||||||
|
templatizedArgs := make([]string, len(c.args))
|
||||||
|
for i, a := range c.args {
|
||||||
|
templatizedArgs[i] = strings.Replace(a, FirmwareFileTemplate, firmwareFile, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
command := exec.CommandContext(ctx, c.command, templatizedArgs...)
|
||||||
|
|
||||||
|
command.Stdout = os.Stdout
|
||||||
|
command.Stderr = os.Stderr
|
||||||
|
|
||||||
|
logger.Debug(ctx, "executing command", logger.F("command", c.command), logger.F("args", templatizedArgs))
|
||||||
|
|
||||||
|
if err := command.Run(); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSysUpgradeController(funcs ...SysUpgradeOptionFunc) *SysUpgradeController {
|
||||||
|
opts := defaultSysUpgradeOptions()
|
||||||
|
for _, fn := range funcs {
|
||||||
|
fn(opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &SysUpgradeController{
|
||||||
|
command: opts.Command,
|
||||||
|
args: opts.Args,
|
||||||
|
client: opts.Client,
|
||||||
|
firmwareVersion: opts.FirmwareVersion,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ agent.Controller = &SysUpgradeController{}
|
@ -0,0 +1,46 @@
|
|||||||
|
package openwrt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"gitlab.com/wpetit/goweb/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ShellFirmwareVersion struct {
|
||||||
|
command string
|
||||||
|
args []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// FirmwareVersion implements FirmwareVersion
|
||||||
|
func (fv *ShellFirmwareVersion) FirmwareVersion(ctx context.Context) (string, error) {
|
||||||
|
command := exec.CommandContext(ctx, fv.command, fv.args...)
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
command.Stdout = &buf
|
||||||
|
command.Stderr = os.Stderr
|
||||||
|
|
||||||
|
logger.Debug(ctx, "executing command", logger.F("command", fv.command), logger.F("args", fv.args))
|
||||||
|
|
||||||
|
if err := command.Run(); err != nil {
|
||||||
|
return "", errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
version := strings.TrimSpace(buf.String())
|
||||||
|
|
||||||
|
return version, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewShellFirmwareVersion(command string, args ...string) *ShellFirmwareVersion {
|
||||||
|
return &ShellFirmwareVersion{
|
||||||
|
command: command,
|
||||||
|
args: args,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ FirmwareVersion = &ShellFirmwareVersion{}
|
58
internal/agent/controller/openwrt/sysupgrade_options.go
Normal file
58
internal/agent/controller/openwrt/sysupgrade_options.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package openwrt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const FirmwareFileTemplate = "%FIRMWARE%"
|
||||||
|
|
||||||
|
type FirmwareVersion interface {
|
||||||
|
FirmwareVersion(context.Context) (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type SysUpgradeOptions struct {
|
||||||
|
Command string
|
||||||
|
Args []string
|
||||||
|
FirmwareVersion FirmwareVersion
|
||||||
|
Client *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultSysUpgradeOptions() *SysUpgradeOptions {
|
||||||
|
return &SysUpgradeOptions{
|
||||||
|
Command: `echo`,
|
||||||
|
Args: []string{`[DUMMY UPGRADE]`, FirmwareFileTemplate},
|
||||||
|
Client: &http.Client{
|
||||||
|
Timeout: 30 * time.Second,
|
||||||
|
},
|
||||||
|
FirmwareVersion: NewShellFirmwareVersion(`echo`, "0.0.0-dummy"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type SysUpgradeOptionFunc func(*SysUpgradeOptions)
|
||||||
|
|
||||||
|
func WithSysUpgradeCommand(command string, args ...string) SysUpgradeOptionFunc {
|
||||||
|
return func(opts *SysUpgradeOptions) {
|
||||||
|
opts.Command = command
|
||||||
|
opts.Args = args
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithSysUpgradeFirmwareVersion(firmwareVersion FirmwareVersion) SysUpgradeOptionFunc {
|
||||||
|
return func(opts *SysUpgradeOptions) {
|
||||||
|
opts.FirmwareVersion = firmwareVersion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithSysUpgradeShellFirmwareVersion(command string, args ...string) SysUpgradeOptionFunc {
|
||||||
|
return func(opts *SysUpgradeOptions) {
|
||||||
|
opts.FirmwareVersion = NewShellFirmwareVersion(command, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithSysUpgradeClient(client *http.Client) SysUpgradeOptionFunc {
|
||||||
|
return func(opts *SysUpgradeOptions) {
|
||||||
|
opts.Client = client
|
||||||
|
}
|
||||||
|
}
|
@ -8,7 +8,7 @@ import (
|
|||||||
|
|
||||||
"forge.cadoles.com/Cadoles/emissary/internal/agent"
|
"forge.cadoles.com/Cadoles/emissary/internal/agent"
|
||||||
"forge.cadoles.com/Cadoles/emissary/internal/openwrt/uci"
|
"forge.cadoles.com/Cadoles/emissary/internal/openwrt/uci"
|
||||||
"forge.cadoles.com/Cadoles/emissary/internal/spec"
|
ucispec "forge.cadoles.com/Cadoles/emissary/internal/spec/uci"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gitlab.com/wpetit/goweb/logger"
|
"gitlab.com/wpetit/goweb/logger"
|
||||||
)
|
)
|
||||||
@ -25,9 +25,9 @@ func (*UCIController) Name() string {
|
|||||||
|
|
||||||
// Reconcile implements node.Controller.
|
// Reconcile implements node.Controller.
|
||||||
func (c *UCIController) Reconcile(ctx context.Context, state *agent.State) error {
|
func (c *UCIController) Reconcile(ctx context.Context, state *agent.State) error {
|
||||||
uciSpec := spec.NewUCISpec()
|
uciSpec := ucispec.NewSpec()
|
||||||
|
|
||||||
if err := state.GetSpec(spec.NameUCI, uciSpec); err != nil {
|
if err := state.GetSpec(ucispec.NameUCI, uciSpec); err != nil {
|
||||||
if errors.Is(err, agent.ErrSpecNotFound) {
|
if errors.Is(err, agent.ErrSpecNotFound) {
|
||||||
logger.Info(ctx, "could not find uci spec, doing nothing")
|
logger.Info(ctx, "could not find uci spec, doing nothing")
|
||||||
|
|
||||||
@ -57,7 +57,7 @@ func (c *UCIController) Reconcile(ctx context.Context, state *agent.State) error
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UCIController) updateConfiguration(ctx context.Context, spec *spec.UCI) error {
|
func (c *UCIController) updateConfiguration(ctx context.Context, spec *ucispec.Spec) error {
|
||||||
logger.Info(ctx, "importing uci config")
|
logger.Info(ctx, "importing uci config")
|
||||||
|
|
||||||
if err := c.importConfig(ctx, spec.Config); err != nil {
|
if err := c.importConfig(ctx, spec.Config); err != nil {
|
||||||
@ -91,7 +91,7 @@ func (c *UCIController) importConfig(ctx context.Context, uci *uci.UCI) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UCIController) execPostImportCommands(ctx context.Context, commands []*spec.UCIPostImportCommand) error {
|
func (c *UCIController) execPostImportCommands(ctx context.Context, commands []*ucispec.UCIPostImportCommand) error {
|
||||||
for _, postImportCmd := range commands {
|
for _, postImportCmd := range commands {
|
||||||
cmd := exec.CommandContext(ctx, postImportCmd.Command, postImportCmd.Args...)
|
cmd := exec.CommandContext(ctx, postImportCmd.Command, postImportCmd.Args...)
|
||||||
|
|
||||||
|
180
internal/agent/controller/proxy/controller.go
Normal file
180
internal/agent/controller/proxy/controller.go
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
package proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/agent"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/spec/proxy"
|
||||||
|
edgeProxy "forge.cadoles.com/arcad/edge/pkg/proxy"
|
||||||
|
"github.com/mitchellh/hashstructure/v2"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"gitlab.com/wpetit/goweb/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type proxyEntry struct {
|
||||||
|
SpecHash uint64
|
||||||
|
Proxy *ReverseProxy
|
||||||
|
}
|
||||||
|
|
||||||
|
type Controller struct {
|
||||||
|
proxies map[proxy.ID]*proxyEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name implements node.Controller.
|
||||||
|
func (c *Controller) Name() string {
|
||||||
|
return "proxy-controller"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reconcile implements node.Controller.
|
||||||
|
func (c *Controller) Reconcile(ctx context.Context, state *agent.State) error {
|
||||||
|
proxySpec := proxy.NewSpec()
|
||||||
|
|
||||||
|
if err := state.GetSpec(proxy.NameProxy, proxySpec); err != nil {
|
||||||
|
if errors.Is(err, agent.ErrSpecNotFound) {
|
||||||
|
logger.Info(ctx, "could not find proxy spec")
|
||||||
|
|
||||||
|
c.stopAllProxies(ctx)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info(ctx, "retrieved spec", logger.F("spec", proxySpec.SpecName()), logger.F("revision", proxySpec.SpecRevision()))
|
||||||
|
|
||||||
|
c.updateProxies(ctx, proxySpec)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) stopAllProxies(ctx context.Context) {
|
||||||
|
if len(c.proxies) > 0 {
|
||||||
|
logger.Info(ctx, "stopping all proxies")
|
||||||
|
}
|
||||||
|
|
||||||
|
for proxyID, entry := range c.proxies {
|
||||||
|
logger.Info(ctx, "stopping proxy", logger.F("proxyID", proxyID))
|
||||||
|
|
||||||
|
if err := entry.Proxy.Stop(); err != nil {
|
||||||
|
logger.Error(
|
||||||
|
ctx, "error while stopping proxy",
|
||||||
|
logger.F("proxyID", proxyID),
|
||||||
|
logger.E(errors.WithStack(err)),
|
||||||
|
)
|
||||||
|
|
||||||
|
delete(c.proxies, proxyID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) updateProxies(ctx context.Context, spec *proxy.Spec) {
|
||||||
|
// Stop and remove obsolete proxys
|
||||||
|
for proxyID, entry := range c.proxies {
|
||||||
|
if _, exists := spec.Proxies[proxyID]; exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info(ctx, "stopping proxy", logger.F("proxyID", proxyID))
|
||||||
|
|
||||||
|
if err := entry.Proxy.Stop(); err != nil {
|
||||||
|
logger.Error(
|
||||||
|
ctx, "error while stopping proxy",
|
||||||
|
logger.F("proxyID", proxyID),
|
||||||
|
logger.E(errors.WithStack(err)),
|
||||||
|
)
|
||||||
|
|
||||||
|
delete(c.proxies, proxyID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// (Re)start proxys
|
||||||
|
for proxyID, proxySpec := range spec.Proxies {
|
||||||
|
proxyCtx := logger.With(ctx, logger.F("proxyID", proxyID))
|
||||||
|
|
||||||
|
if err := c.updateProxy(ctx, proxyID, proxySpec); err != nil {
|
||||||
|
logger.Error(proxyCtx, "could not update proxy", logger.E(errors.WithStack(err)))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) updateProxy(ctx context.Context, proxyID proxy.ID, proxySpec proxy.ProxyEntry) (err error) {
|
||||||
|
newProxySpecHash, err := hashstructure.Hash(proxySpec, hashstructure.FormatV2, nil)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var entry *proxyEntry
|
||||||
|
|
||||||
|
entry, exists := c.proxies[proxyID]
|
||||||
|
if !exists {
|
||||||
|
logger.Info(ctx, "proxy currently not running")
|
||||||
|
}
|
||||||
|
|
||||||
|
if entry == nil {
|
||||||
|
entry = &proxyEntry{
|
||||||
|
Proxy: NewReverseProxy(),
|
||||||
|
SpecHash: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
c.proxies[proxyID] = entry
|
||||||
|
}
|
||||||
|
|
||||||
|
specChanged := newProxySpecHash != entry.SpecHash
|
||||||
|
|
||||||
|
if entry.Proxy.Running() && !specChanged {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if specChanged && entry.SpecHash != 0 {
|
||||||
|
logger.Info(
|
||||||
|
ctx, "restarting proxy",
|
||||||
|
logger.F("address", proxySpec.Address),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
logger.Info(
|
||||||
|
ctx, "starting proxy",
|
||||||
|
logger.F("address", proxySpec.Address),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
options := make([]edgeProxy.OptionFunc, 0)
|
||||||
|
allowedHosts := make([]string, len(proxySpec.Mappings))
|
||||||
|
mappings := make(map[string]*url.URL, len(proxySpec.Mappings))
|
||||||
|
|
||||||
|
for _, m := range proxySpec.Mappings {
|
||||||
|
target, err := url.Parse(m.Target)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mappings[m.HostPattern] = target
|
||||||
|
allowedHosts = append(allowedHosts, m.HostPattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
options = append(
|
||||||
|
options,
|
||||||
|
edgeProxy.WithAllowedHosts(allowedHosts...),
|
||||||
|
edgeProxy.WithRewriteHosts(mappings),
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := entry.Proxy.Start(ctx, proxySpec.Address, options...); err != nil {
|
||||||
|
delete(c.proxies, proxyID)
|
||||||
|
|
||||||
|
return errors.Wrap(err, "could not start app")
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.SpecHash = newProxySpecHash
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewController() *Controller {
|
||||||
|
return &Controller{
|
||||||
|
proxies: make(map[proxy.ID]*proxyEntry),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ agent.Controller = &Controller{}
|
@ -1,28 +1,23 @@
|
|||||||
package gateway
|
package proxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"sync"
|
||||||
"net/url"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gitlab.com/wpetit/goweb/logger"
|
"gitlab.com/wpetit/goweb/logger"
|
||||||
|
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/proxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ReverseProxy struct {
|
type ReverseProxy struct {
|
||||||
addr string
|
addr string
|
||||||
target string
|
|
||||||
server *http.Server
|
server *http.Server
|
||||||
|
mutex sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ReverseProxy) Start(ctx context.Context, addr, target string) error {
|
func (p *ReverseProxy) Start(ctx context.Context, addr string, funcs ...proxy.OptionFunc) error {
|
||||||
alreadyRunning := p.server != nil && target == p.target && addr == p.target
|
|
||||||
|
|
||||||
if alreadyRunning {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.server != nil {
|
if p.server != nil {
|
||||||
if err := p.Stop(); err != nil {
|
if err := p.Stop(); err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
@ -33,33 +28,40 @@ func (p *ReverseProxy) Start(ctx context.Context, addr, target string) error {
|
|||||||
Addr: addr,
|
Addr: addr,
|
||||||
}
|
}
|
||||||
|
|
||||||
url, err := url.Parse(target)
|
proxy := proxy.New(funcs...)
|
||||||
if err != nil {
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
proxy := httputil.NewSingleHostReverseProxy(url)
|
|
||||||
|
|
||||||
server.Handler = proxy
|
server.Handler = proxy
|
||||||
|
|
||||||
|
p.mutex.Lock()
|
||||||
p.server = server
|
p.server = server
|
||||||
p.addr = addr
|
p.addr = addr
|
||||||
p.target = target
|
p.mutex.Unlock()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
if err := p.Stop(); err != nil {
|
||||||
|
logger.Error(ctx, "error while stopping gateway", logger.E(errors.WithStack(err)))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
logger.Error(ctx, "error while listening", logger.E(errors.WithStack(err)))
|
logger.Error(ctx, "error while listening", logger.E(errors.WithStack(err)))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := p.Stop(); err != nil {
|
|
||||||
logger.Error(ctx, "error while stopping gateway", logger.E(errors.WithStack(err)))
|
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *ReverseProxy) Running() bool {
|
||||||
|
p.mutex.RLock()
|
||||||
|
defer p.mutex.RUnlock()
|
||||||
|
|
||||||
|
return p.server != nil
|
||||||
|
}
|
||||||
|
|
||||||
func (p *ReverseProxy) Stop() error {
|
func (p *ReverseProxy) Stop() error {
|
||||||
|
p.mutex.Lock()
|
||||||
|
defer p.mutex.Unlock()
|
||||||
|
|
||||||
if p.server == nil {
|
if p.server == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
@ -4,18 +4,13 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
|
|
||||||
"forge.cadoles.com/Cadoles/emissary/internal/agent"
|
"forge.cadoles.com/Cadoles/emissary/internal/agent"
|
||||||
"forge.cadoles.com/Cadoles/emissary/internal/agent/machineid"
|
|
||||||
"forge.cadoles.com/Cadoles/emissary/internal/client"
|
"forge.cadoles.com/Cadoles/emissary/internal/client"
|
||||||
"forge.cadoles.com/Cadoles/emissary/internal/datastore"
|
"forge.cadoles.com/Cadoles/emissary/internal/datastore"
|
||||||
"forge.cadoles.com/Cadoles/emissary/internal/server"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gitlab.com/wpetit/goweb/api"
|
|
||||||
"gitlab.com/wpetit/goweb/logger"
|
"gitlab.com/wpetit/goweb/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Controller struct {
|
type Controller struct{}
|
||||||
client *client.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name implements node.Controller.
|
// Name implements node.Controller.
|
||||||
func (c *Controller) Name() string {
|
func (c *Controller) Name() string {
|
||||||
@ -24,56 +19,24 @@ func (c *Controller) Name() string {
|
|||||||
|
|
||||||
// Reconcile implements node.Controller.
|
// Reconcile implements node.Controller.
|
||||||
func (c *Controller) Reconcile(ctx context.Context, state *agent.State) error {
|
func (c *Controller) Reconcile(ctx context.Context, state *agent.State) error {
|
||||||
machineID, err := machineid.Get()
|
cl := agent.Client(ctx)
|
||||||
|
|
||||||
|
agent, err := cl.GetAgent(
|
||||||
|
ctx,
|
||||||
|
state.AgentID(),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx = logger.With(ctx, logger.F("machineID", machineID))
|
if err := c.reconcileAgent(ctx, cl, state, agent); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
agent, err := c.client.RegisterAgent(ctx, machineID)
|
|
||||||
isAlreadyRegisteredErr, _ := isAPIError(err, server.ErrCodeAlreadyRegistered)
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case isAlreadyRegisteredErr:
|
|
||||||
agents, _, err := c.client.QueryAgents(
|
|
||||||
ctx,
|
|
||||||
client.WithQueryAgentsLimit(1),
|
|
||||||
client.WithQueryAgentsRemoteID(machineID),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(agents) == 0 {
|
|
||||||
logger.Error(ctx, "could not find remote matching agent")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.reconcileAgent(ctx, state, agents[0]); err != nil {
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
|
|
||||||
case agent != nil:
|
|
||||||
if err := c.reconcileAgent(ctx, state, agent); err != nil {
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
|
|
||||||
case err != nil:
|
|
||||||
logger.Error(ctx, "could not contact server", logger.E(errors.WithStack(err)))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) reconcileAgent(ctx context.Context, state *agent.State, agent *datastore.Agent) error {
|
func (c *Controller) reconcileAgent(ctx context.Context, client *client.Client, state *agent.State, agent *datastore.Agent) error {
|
||||||
ctx = logger.With(ctx, logger.F("agentID", agent.ID))
|
ctx = logger.With(ctx, logger.F("agentID", agent.ID))
|
||||||
|
|
||||||
if agent.Status != datastore.AgentStatusAccepted {
|
if agent.Status != datastore.AgentStatusAccepted {
|
||||||
@ -82,7 +45,7 @@ func (c *Controller) reconcileAgent(ctx context.Context, state *agent.State, age
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
specs, err := c.client.GetAgentSpecs(ctx, agent.ID)
|
specs, err := client.GetAgentSpecs(ctx, agent.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(ctx, "could not retrieve agent specs", logger.E(errors.WithStack(err)))
|
logger.Error(ctx, "could not retrieve agent specs", logger.E(errors.WithStack(err)))
|
||||||
|
|
||||||
@ -98,19 +61,8 @@ func (c *Controller) reconcileAgent(ctx context.Context, state *agent.State, age
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewController(serverURL string) *Controller {
|
func NewController() *Controller {
|
||||||
client := client.New(serverURL)
|
return &Controller{}
|
||||||
|
|
||||||
return &Controller{client}
|
|
||||||
}
|
|
||||||
|
|
||||||
func isAPIError(err error, code api.ErrorCode) (bool, any) {
|
|
||||||
apiError := &api.Error{}
|
|
||||||
if errors.As(err, &apiError) && apiError.Code == code {
|
|
||||||
return true, apiError.Data
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ agent.Controller = &Controller{}
|
var _ agent.Controller = &Controller{}
|
||||||
|
12
internal/agent/metadata/collector.go
Normal file
12
internal/agent/metadata/collector.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package metadata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrMetadataNotAvailable = errors.New("metadata not available")
|
||||||
|
|
||||||
|
type Collector interface {
|
||||||
|
Collect(context.Context) (string, string, error)
|
||||||
|
}
|
31
internal/agent/metadata/collector/buildinfo/collector.go
Normal file
31
internal/agent/metadata/collector/buildinfo/collector.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package buildinfo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"runtime/debug"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/agent/metadata"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
MetadataBuildInfo = "buildinfo"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Collector struct{}
|
||||||
|
|
||||||
|
// Collect implements agent.MetadataCollector
|
||||||
|
func (c *Collector) Collect(ctx context.Context) (string, string, error) {
|
||||||
|
buildInfo, ok := debug.ReadBuildInfo()
|
||||||
|
if !ok {
|
||||||
|
return "", "", errors.WithStack(metadata.ErrMetadataNotAvailable)
|
||||||
|
}
|
||||||
|
|
||||||
|
return MetadataBuildInfo, buildInfo.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCollector() *Collector {
|
||||||
|
return &Collector{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ metadata.Collector = &Collector{}
|
46
internal/agent/metadata/collector/shell/collector.go
Normal file
46
internal/agent/metadata/collector/shell/collector.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package shell
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/agent/metadata"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Collector struct {
|
||||||
|
name string
|
||||||
|
command string
|
||||||
|
args []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect implements agent.MetadataCollector
|
||||||
|
func (c *Collector) Collect(ctx context.Context) (string, string, error) {
|
||||||
|
cmd := exec.CommandContext(ctx, c.command, c.args...)
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
cmd.Stdout = &buf
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return "", "", errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
value := strings.TrimSpace(buf.String())
|
||||||
|
|
||||||
|
return c.name, value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCollector(name string, command string, args ...string) *Collector {
|
||||||
|
return &Collector{
|
||||||
|
name: name,
|
||||||
|
command: command,
|
||||||
|
args: args,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ metadata.Collector = &Collector{}
|
3
internal/agent/metadata/metadata.go
Normal file
3
internal/agent/metadata/metadata.go
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
package metadata
|
||||||
|
|
||||||
|
type Metadata map[string]any
|
37
internal/agent/metadata/sort.go
Normal file
37
internal/agent/metadata/sort.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package metadata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Tuple struct {
|
||||||
|
Key string `json:"key"`
|
||||||
|
Value any `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Sort(metadata map[string]any) []Tuple {
|
||||||
|
keys := make([]string, 0, len(metadata))
|
||||||
|
for k := range metadata {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
tuples := make([]Tuple, len(keys))
|
||||||
|
|
||||||
|
for i, k := range keys {
|
||||||
|
tuples[i] = Tuple{k, metadata[k]}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tuples
|
||||||
|
}
|
||||||
|
|
||||||
|
func FromSorted(tuples []Tuple) map[string]any {
|
||||||
|
metadata := make(map[string]any)
|
||||||
|
|
||||||
|
for _, t := range tuples {
|
||||||
|
metadata[t.Key] = t.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
return metadata
|
||||||
|
}
|
@ -1,31 +0,0 @@
|
|||||||
package agent
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Option struct {
|
|
||||||
Interval time.Duration
|
|
||||||
Controllers []Controller
|
|
||||||
}
|
|
||||||
|
|
||||||
type OptionFunc func(*Option)
|
|
||||||
|
|
||||||
func defaultOption() *Option {
|
|
||||||
return &Option{
|
|
||||||
Controllers: make([]Controller, 0),
|
|
||||||
Interval: 10 * time.Second,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithControllers(controllers ...Controller) OptionFunc {
|
|
||||||
return func(opt *Option) {
|
|
||||||
opt.Controllers = controllers
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithInterval(interval time.Duration) OptionFunc {
|
|
||||||
return func(opt *Option) {
|
|
||||||
opt.Interval = interval
|
|
||||||
}
|
|
||||||
}
|
|
43
internal/agent/options.go
Normal file
43
internal/agent/options.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/agent/metadata"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
Client *client.Client
|
||||||
|
Interval time.Duration
|
||||||
|
Controllers []Controller
|
||||||
|
Collectors []metadata.Collector
|
||||||
|
}
|
||||||
|
|
||||||
|
type OptionFunc func(*Options)
|
||||||
|
|
||||||
|
func defaultOption() *Options {
|
||||||
|
return &Options{
|
||||||
|
Controllers: make([]Controller, 0),
|
||||||
|
Interval: 10 * time.Second,
|
||||||
|
Collectors: make([]metadata.Collector, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithControllers(controllers ...Controller) OptionFunc {
|
||||||
|
return func(opt *Options) {
|
||||||
|
opt.Controllers = controllers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithInterval(interval time.Duration) OptionFunc {
|
||||||
|
return func(opt *Options) {
|
||||||
|
opt.Interval = interval
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithCollectors(collectors ...metadata.Collector) OptionFunc {
|
||||||
|
return func(opts *Options) {
|
||||||
|
opts.Collectors = collectors
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@ package agent
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/datastore"
|
||||||
"forge.cadoles.com/Cadoles/emissary/internal/spec"
|
"forge.cadoles.com/Cadoles/emissary/internal/spec"
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -13,7 +14,8 @@ var ErrSpecNotFound = errors.New("spec not found")
|
|||||||
type Specs map[spec.Name]spec.Spec
|
type Specs map[spec.Name]spec.Spec
|
||||||
|
|
||||||
type State struct {
|
type State struct {
|
||||||
specs Specs `json:"specs"`
|
agentID datastore.AgentID
|
||||||
|
specs Specs
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewState() *State {
|
func NewState() *State {
|
||||||
@ -24,8 +26,10 @@ func NewState() *State {
|
|||||||
|
|
||||||
func (s *State) MarshalJSON() ([]byte, error) {
|
func (s *State) MarshalJSON() ([]byte, error) {
|
||||||
state := struct {
|
state := struct {
|
||||||
|
ID datastore.AgentID `json:"agentId"`
|
||||||
Specs map[spec.Name]*spec.RawSpec `json:"specs"`
|
Specs map[spec.Name]*spec.RawSpec `json:"specs"`
|
||||||
}{
|
}{
|
||||||
|
ID: s.agentID,
|
||||||
Specs: func(specs map[spec.Name]spec.Spec) map[spec.Name]*spec.RawSpec {
|
Specs: func(specs map[spec.Name]spec.Spec) map[spec.Name]*spec.RawSpec {
|
||||||
rawSpecs := make(map[spec.Name]*spec.RawSpec)
|
rawSpecs := make(map[spec.Name]*spec.RawSpec)
|
||||||
|
|
||||||
@ -51,7 +55,8 @@ func (s *State) MarshalJSON() ([]byte, error) {
|
|||||||
|
|
||||||
func (s *State) UnmarshalJSON(data []byte) error {
|
func (s *State) UnmarshalJSON(data []byte) error {
|
||||||
state := struct {
|
state := struct {
|
||||||
Specs map[spec.Name]*spec.RawSpec `json:"specs"`
|
AgentID datastore.AgentID `json:"agentId"`
|
||||||
|
Specs map[spec.Name]*spec.RawSpec `json:"specs"`
|
||||||
}{}
|
}{}
|
||||||
|
|
||||||
if err := json.Unmarshal(data, &state); err != nil {
|
if err := json.Unmarshal(data, &state); err != nil {
|
||||||
@ -71,6 +76,10 @@ func (s *State) UnmarshalJSON(data []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *State) AgentID() datastore.AgentID {
|
||||||
|
return s.agentID
|
||||||
|
}
|
||||||
|
|
||||||
func (s *State) Specs() Specs {
|
func (s *State) Specs() Specs {
|
||||||
return s.specs
|
return s.specs
|
||||||
}
|
}
|
||||||
|
92
internal/auth/agent/authenticator.go
Normal file
92
internal/auth/agent/authenticator.go
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/auth"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/datastore"
|
||||||
|
"github.com/lestrrat-go/jwx/v2/jws"
|
||||||
|
"github.com/lestrrat-go/jwx/v2/jwt"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"gitlab.com/wpetit/goweb/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Authenticator struct {
|
||||||
|
repo datastore.AgentRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 := jwt.Parse([]byte(rawToken), jwt.WithVerify(false))
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rawThumbprint, exists := token.Get(keyThumbprint)
|
||||||
|
if !exists {
|
||||||
|
return nil, errors.Errorf("could not find '%s' claim", keyThumbprint)
|
||||||
|
}
|
||||||
|
|
||||||
|
thumbrint, ok := rawThumbprint.(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.Errorf("unexpected '%s' claim value: '%v'", keyThumbprint, rawThumbprint)
|
||||||
|
}
|
||||||
|
|
||||||
|
agents, _, err := a.repo.Query(
|
||||||
|
ctx,
|
||||||
|
datastore.WithAgentQueryThumbprints(thumbrint),
|
||||||
|
datastore.WithAgentQueryLimit(1),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(agents) != 1 {
|
||||||
|
return nil, errors.Errorf("unexpected number of found agents: '%d'", len(agents))
|
||||||
|
}
|
||||||
|
|
||||||
|
agent, err := a.repo.Get(
|
||||||
|
ctx,
|
||||||
|
agents[0].ID,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = jwt.Parse(
|
||||||
|
[]byte(rawToken),
|
||||||
|
jwt.WithKeySet(agent.KeySet.Set, jws.WithRequireKid(false)),
|
||||||
|
jwt.WithValidate(true),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
user := &User{
|
||||||
|
agent: agent,
|
||||||
|
}
|
||||||
|
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAuthenticator(repo datastore.AgentRepository) *Authenticator {
|
||||||
|
return &Authenticator{
|
||||||
|
repo: repo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ auth.Authenticator = &Authenticator{}
|
37
internal/auth/agent/jwt.go
Normal file
37
internal/auth/agent/jwt.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/jwk"
|
||||||
|
"github.com/lestrrat-go/jwx/v2/jwa"
|
||||||
|
"github.com/lestrrat-go/jwx/v2/jwt"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
const keyThumbprint = "thumbprint"
|
||||||
|
|
||||||
|
func GenerateToken(key jwk.Key, thumbprint string) (string, error) {
|
||||||
|
token := jwt.New()
|
||||||
|
|
||||||
|
if err := token.Set(keyThumbprint, thumbprint); err != nil {
|
||||||
|
return "", errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
23
internal/auth/agent/user.go
Normal file
23
internal/auth/agent/user.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/auth"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/datastore"
|
||||||
|
)
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
agent *datastore.Agent
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subject implements auth.User
|
||||||
|
func (u *User) Subject() string {
|
||||||
|
return fmt.Sprintf("agent-%d", u.agent.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) Agent() *datastore.Agent {
|
||||||
|
return u.agent
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
67
internal/auth/thirdparty/authenticator.go
vendored
Normal file
67
internal/auth/thirdparty/authenticator.go
vendored
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
package thirdparty
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/auth"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/jwk"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"gitlab.com/wpetit/goweb/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Authenticator struct {
|
||||||
|
keys jwk.Set
|
||||||
|
issuer string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
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) *Authenticator {
|
||||||
|
return &Authenticator{
|
||||||
|
keys: keys,
|
||||||
|
issuer: issuer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ auth.Authenticator = &Authenticator{}
|
61
internal/auth/thirdparty/jwt.go
vendored
Normal file
61
internal/auth/thirdparty/jwt.go
vendored
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package thirdparty
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/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) (jwt.Token, error) {
|
||||||
|
token, err := jwt.Parse(
|
||||||
|
[]byte(rawToken),
|
||||||
|
jwt.WithKeySet(keys, jws.WithRequireKid(false)),
|
||||||
|
jwt.WithIssuer(issuer),
|
||||||
|
jwt.WithValidate(true),
|
||||||
|
)
|
||||||
|
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()
|
||||||
|
|
||||||
|
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/thirdparty/user.go
vendored
Normal file
32
internal/auth/thirdparty/user.go
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package thirdparty
|
||||||
|
|
||||||
|
import "forge.cadoles.com/Cadoles/emissary/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{}
|
@ -13,27 +13,49 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
http *http.Client
|
http *http.Client
|
||||||
serverURL string
|
defaultOpts Options
|
||||||
|
serverURL string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) apiGet(ctx context.Context, path string, result any) error {
|
func (c *Client) apiGet(ctx context.Context, path string, result any, funcs ...OptionFunc) error {
|
||||||
if err := c.apiDo(ctx, http.MethodGet, path, nil, result); err != nil {
|
if err := c.apiDo(ctx, http.MethodGet, path, nil, result, funcs...); err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) apiPost(ctx context.Context, path string, payload any, result any) error {
|
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); err != nil {
|
if err := c.apiDo(ctx, http.MethodPost, path, payload, result, funcs...); err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) apiDo(ctx context.Context, method string, path string, payload any, response any) error {
|
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
|
url := c.serverURL + path
|
||||||
|
|
||||||
logger.Debug(
|
logger.Debug(
|
||||||
@ -56,6 +78,12 @@ func (c *Client) apiDo(ctx context.Context, method string, path string, payload
|
|||||||
return errors.WithStack(err)
|
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)
|
res, err := c.http.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
@ -72,6 +100,12 @@ func (c *Client) apiDo(ctx context.Context, method string, path string, payload
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) defaultOptions() *Options {
|
||||||
|
return &Options{
|
||||||
|
Headers: c.defaultOpts.Headers,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func withResponse[T any]() struct {
|
func withResponse[T any]() struct {
|
||||||
Data T
|
Data T
|
||||||
Error *api.Error
|
Error *api.Error
|
||||||
@ -96,9 +130,15 @@ func joinSlice[T any](items []T) string {
|
|||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(serverURL string) *Client {
|
func New(serverURL string, funcs ...OptionFunc) *Client {
|
||||||
|
opts := Options{}
|
||||||
|
for _, fn := range funcs {
|
||||||
|
fn(&opts)
|
||||||
|
}
|
||||||
|
|
||||||
return &Client{
|
return &Client{
|
||||||
serverURL: serverURL,
|
serverURL: serverURL,
|
||||||
http: &http.Client{},
|
http: &http.Client{},
|
||||||
|
defaultOpts: opts,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
27
internal/client/delete_agent.go
Normal file
27
internal/client/delete_agent.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/datastore"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Client) DeleteAgent(ctx context.Context, agentID datastore.AgentID, funcs ...OptionFunc) (datastore.AgentID, error) {
|
||||||
|
response := withResponse[struct {
|
||||||
|
AgentID int64 `json:"agentId"`
|
||||||
|
}]()
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/api/v1/agents/%d", agentID)
|
||||||
|
|
||||||
|
if err := c.apiDelete(ctx, path, nil, &response, funcs...); err != nil {
|
||||||
|
return 0, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.Error != nil {
|
||||||
|
return 0, errors.WithStack(response.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return datastore.AgentID(response.Data.AgentID), nil
|
||||||
|
}
|
34
internal/client/delete_agent_spec.go
Normal file
34
internal/client/delete_agent_spec.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/datastore"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/spec"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Client) DeleteAgentSpec(ctx context.Context, agentID datastore.AgentID, name spec.Name, funcs ...OptionFunc) (spec.Name, error) {
|
||||||
|
payload := struct {
|
||||||
|
Name spec.Name `json:"name"`
|
||||||
|
}{
|
||||||
|
Name: name,
|
||||||
|
}
|
||||||
|
|
||||||
|
response := withResponse[struct {
|
||||||
|
Name spec.Name `json:"name"`
|
||||||
|
}]()
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/api/v1/agents/%d/specs", agentID)
|
||||||
|
|
||||||
|
if err := c.apiDelete(ctx, path, payload, &response, funcs...); err != nil {
|
||||||
|
return "", errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.Error != nil {
|
||||||
|
return "", errors.WithStack(response.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.Data.Name, nil
|
||||||
|
}
|
@ -8,14 +8,14 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *Client) GetAgent(ctx context.Context, agentID datastore.AgentID) (*datastore.Agent, error) {
|
func (c *Client) GetAgent(ctx context.Context, agentID datastore.AgentID, funcs ...OptionFunc) (*datastore.Agent, error) {
|
||||||
response := withResponse[struct {
|
response := withResponse[struct {
|
||||||
Agent *datastore.Agent `json:"agent"`
|
Agent *datastore.Agent `json:"agent"`
|
||||||
}]()
|
}]()
|
||||||
|
|
||||||
path := fmt.Sprintf("/api/v1/agents/%d", agentID)
|
path := fmt.Sprintf("/api/v1/agents/%d", agentID)
|
||||||
|
|
||||||
if err := c.apiGet(ctx, path, &response); err != nil {
|
if err := c.apiGet(ctx, path, &response, funcs...); err != nil {
|
||||||
return nil, errors.WithStack(err)
|
return nil, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,14 +9,14 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *Client) GetAgentSpecs(ctx context.Context, agentID datastore.AgentID) ([]spec.Spec, error) {
|
func (c *Client) GetAgentSpecs(ctx context.Context, agentID datastore.AgentID, funcs ...OptionFunc) ([]spec.Spec, error) {
|
||||||
response := withResponse[struct {
|
response := withResponse[struct {
|
||||||
Specs []*spec.RawSpec `json:"specs"`
|
Specs []*spec.RawSpec `json:"specs"`
|
||||||
}]()
|
}]()
|
||||||
|
|
||||||
path := fmt.Sprintf("/api/v1/agents/%d/specs", agentID)
|
path := fmt.Sprintf("/api/v1/agents/%d/specs", agentID)
|
||||||
|
|
||||||
if err := c.apiGet(ctx, path, &response); err != nil {
|
if err := c.apiGet(ctx, path, &response, funcs...); err != nil {
|
||||||
return nil, errors.WithStack(err)
|
return nil, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
@ -12,11 +12,18 @@ import (
|
|||||||
type QueryAgentsOptionFunc func(*QueryAgentsOptions)
|
type QueryAgentsOptionFunc func(*QueryAgentsOptions)
|
||||||
|
|
||||||
type QueryAgentsOptions struct {
|
type QueryAgentsOptions struct {
|
||||||
Limit *int
|
Options []OptionFunc
|
||||||
Offset *int
|
Limit *int
|
||||||
RemoteIDs []string
|
Offset *int
|
||||||
IDs []datastore.AgentID
|
Thumbprints []string
|
||||||
Statuses []datastore.AgentStatus
|
IDs []datastore.AgentID
|
||||||
|
Statuses []datastore.AgentStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithQueryAgentsOptions(funcs ...OptionFunc) QueryAgentsOptionFunc {
|
||||||
|
return func(opts *QueryAgentsOptions) {
|
||||||
|
opts.Options = funcs
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithQueryAgentsLimit(limit int) QueryAgentsOptionFunc {
|
func WithQueryAgentsLimit(limit int) QueryAgentsOptionFunc {
|
||||||
@ -31,9 +38,9 @@ func WithQueryAgentsOffset(offset int) QueryAgentsOptionFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithQueryAgentsRemoteID(remoteIDs ...string) QueryAgentsOptionFunc {
|
func WithQueryAgentsThumbprints(thumbprints ...string) QueryAgentsOptionFunc {
|
||||||
return func(opts *QueryAgentsOptions) {
|
return func(opts *QueryAgentsOptions) {
|
||||||
opts.RemoteIDs = remoteIDs
|
opts.Thumbprints = thumbprints
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,11 +68,11 @@ func (c *Client) QueryAgents(ctx context.Context, funcs ...QueryAgentsOptionFunc
|
|||||||
query.Set("ids", joinSlice(options.IDs))
|
query.Set("ids", joinSlice(options.IDs))
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.RemoteIDs != nil && len(options.RemoteIDs) > 0 {
|
if options.Thumbprints != nil && len(options.Thumbprints) > 0 {
|
||||||
query.Set("remoteIds", joinSlice(options.RemoteIDs))
|
query.Set("thumbprints", joinSlice(options.Thumbprints))
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.Statuses != nil && len(options.RemoteIDs) > 0 {
|
if options.Statuses != nil && len(options.Statuses) > 0 {
|
||||||
query.Set("statuses", joinSlice(options.Statuses))
|
query.Set("statuses", joinSlice(options.Statuses))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,7 +83,11 @@ func (c *Client) QueryAgents(ctx context.Context, funcs ...QueryAgentsOptionFunc
|
|||||||
Total int `json:"total"`
|
Total int `json:"total"`
|
||||||
}]()
|
}]()
|
||||||
|
|
||||||
if err := c.apiGet(ctx, path, &response); err != nil {
|
if options.Options == nil {
|
||||||
|
options.Options = make([]OptionFunc, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.apiGet(ctx, path, &response, options.Options...); err != nil {
|
||||||
return nil, 0, errors.WithStack(err)
|
return nil, 0, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,22 +3,40 @@ package client
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/agent/metadata"
|
||||||
"forge.cadoles.com/Cadoles/emissary/internal/datastore"
|
"forge.cadoles.com/Cadoles/emissary/internal/datastore"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/jwk"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *Client) RegisterAgent(ctx context.Context, remoteID string) (*datastore.Agent, error) {
|
func (c *Client) RegisterAgent(ctx context.Context, key jwk.Key, thumbprint string, meta []metadata.Tuple, funcs ...OptionFunc) (*datastore.Agent, error) {
|
||||||
|
keySet, err := jwk.PublicKeySet(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
signature, err := jwk.Sign(key, thumbprint, meta)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
payload := struct {
|
payload := struct {
|
||||||
RemoteID string `json:"remoteId"`
|
KeySet jwk.Set `json:"keySet"`
|
||||||
|
Thumbprint string `json:"thumbprint"`
|
||||||
|
Metadata []metadata.Tuple `json:"metadata"`
|
||||||
|
Signature string `json:"signature"`
|
||||||
}{
|
}{
|
||||||
RemoteID: remoteID,
|
Thumbprint: thumbprint,
|
||||||
|
Metadata: meta,
|
||||||
|
Signature: signature,
|
||||||
|
KeySet: keySet,
|
||||||
}
|
}
|
||||||
|
|
||||||
response := withResponse[struct {
|
response := withResponse[struct {
|
||||||
Agent *datastore.Agent `json:"agent"`
|
Agent *datastore.Agent `json:"agent"`
|
||||||
}]()
|
}]()
|
||||||
|
|
||||||
if err := c.apiPost(ctx, "/api/v1/register", payload, &response); err != nil {
|
if err := c.apiPost(ctx, "/api/v1/register", payload, &response, funcs...); err != nil {
|
||||||
return nil, errors.WithStack(err)
|
return nil, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
61
internal/client/update_agent.go
Normal file
61
internal/client/update_agent.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/datastore"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UpdateAgentOptions struct {
|
||||||
|
Status *int
|
||||||
|
Options []OptionFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateAgentOptionFunc func(*UpdateAgentOptions)
|
||||||
|
|
||||||
|
func WithAgentStatus(status int) UpdateAgentOptionFunc {
|
||||||
|
return func(opts *UpdateAgentOptions) {
|
||||||
|
opts.Status = &status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithUpdateAgentsOptions(funcs ...OptionFunc) UpdateAgentOptionFunc {
|
||||||
|
return func(opts *UpdateAgentOptions) {
|
||||||
|
opts.Options = funcs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) UpdateAgent(ctx context.Context, agentID datastore.AgentID, funcs ...UpdateAgentOptionFunc) (*datastore.Agent, error) {
|
||||||
|
opts := &UpdateAgentOptions{}
|
||||||
|
for _, fn := range funcs {
|
||||||
|
fn(opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
payload := map[string]any{}
|
||||||
|
|
||||||
|
if opts.Status != nil {
|
||||||
|
payload["status"] = *opts.Status
|
||||||
|
}
|
||||||
|
|
||||||
|
response := withResponse[struct {
|
||||||
|
Agent *datastore.Agent `json:"agent"`
|
||||||
|
}]()
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/api/v1/agents/%d", agentID)
|
||||||
|
|
||||||
|
if opts.Options == nil {
|
||||||
|
opts.Options = make([]OptionFunc, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.apiPut(ctx, path, payload, &response, opts.Options...); err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.Error != nil {
|
||||||
|
return nil, errors.WithStack(response.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.Data.Agent, nil
|
||||||
|
}
|
39
internal/client/update_agent_spec.go
Normal file
39
internal/client/update_agent_spec.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/agent/metadata"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/datastore"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/spec"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Client) UpdateAgentSpec(ctx context.Context, agentID datastore.AgentID, spc spec.Spec, funcs ...OptionFunc) (*datastore.Spec, error) {
|
||||||
|
payload := struct {
|
||||||
|
Name spec.Name `json:"name"`
|
||||||
|
Revision int `json:"revision"`
|
||||||
|
Data metadata.Metadata `json:"data"`
|
||||||
|
}{
|
||||||
|
Name: spc.SpecName(),
|
||||||
|
Revision: spc.SpecRevision(),
|
||||||
|
Data: spc.SpecData(),
|
||||||
|
}
|
||||||
|
|
||||||
|
response := withResponse[struct {
|
||||||
|
Spec *datastore.Spec `json:"spec"`
|
||||||
|
}]()
|
||||||
|
|
||||||
|
path := fmt.Sprintf("/api/v1/agents/%d/specs", agentID)
|
||||||
|
|
||||||
|
if err := c.apiPost(ctx, path, payload, &response, funcs...); err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.Error != nil {
|
||||||
|
return nil, errors.WithStack(response.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.Data.Spec, nil
|
||||||
|
}
|
@ -10,7 +10,6 @@ import (
|
|||||||
"forge.cadoles.com/Cadoles/emissary/internal/command/common"
|
"forge.cadoles.com/Cadoles/emissary/internal/command/common"
|
||||||
"forge.cadoles.com/Cadoles/emissary/internal/openwrt/uci"
|
"forge.cadoles.com/Cadoles/emissary/internal/openwrt/uci"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
_ "github.com/santhosh-tekuri/jsonschema/v5/httploader"
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ package agent
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"forge.cadoles.com/Cadoles/emissary/internal/command/agent/openwrt"
|
"forge.cadoles.com/Cadoles/emissary/internal/command/agent/openwrt"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/command/config"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -11,7 +12,9 @@ func Root() *cli.Command {
|
|||||||
Usage: "Agent related commands",
|
Usage: "Agent related commands",
|
||||||
Subcommands: []*cli.Command{
|
Subcommands: []*cli.Command{
|
||||||
openwrt.Root(),
|
openwrt.Root(),
|
||||||
|
config.Root(),
|
||||||
RunCommand(),
|
RunCommand(),
|
||||||
|
ShowThumbprintCommand(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,13 +4,19 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"forge.cadoles.com/Cadoles/emissary/internal/agent"
|
"forge.cadoles.com/Cadoles/emissary/internal/agent"
|
||||||
"forge.cadoles.com/Cadoles/emissary/internal/agent/controller/gateway"
|
"forge.cadoles.com/Cadoles/emissary/internal/agent/controller/app"
|
||||||
"forge.cadoles.com/Cadoles/emissary/internal/agent/controller/openwrt"
|
"forge.cadoles.com/Cadoles/emissary/internal/agent/controller/openwrt"
|
||||||
"forge.cadoles.com/Cadoles/emissary/internal/agent/controller/persistence"
|
"forge.cadoles.com/Cadoles/emissary/internal/agent/controller/persistence"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/agent/controller/proxy"
|
||||||
"forge.cadoles.com/Cadoles/emissary/internal/agent/controller/spec"
|
"forge.cadoles.com/Cadoles/emissary/internal/agent/controller/spec"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/agent/metadata"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/agent/metadata/collector/buildinfo"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/agent/metadata/collector/shell"
|
||||||
"forge.cadoles.com/Cadoles/emissary/internal/command/common"
|
"forge.cadoles.com/Cadoles/emissary/internal/command/common"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/config"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/jwk"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/machineid"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
_ "github.com/santhosh-tekuri/jsonschema/v5/httploader"
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"gitlab.com/wpetit/goweb/logger"
|
"gitlab.com/wpetit/goweb/logger"
|
||||||
)
|
)
|
||||||
@ -40,11 +46,11 @@ func RunCommand() *cli.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ctrlConf.Spec.Enabled {
|
if ctrlConf.Spec.Enabled {
|
||||||
controllers = append(controllers, spec.NewController(string(ctrlConf.Spec.ServerURL)))
|
controllers = append(controllers, spec.NewController())
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctrlConf.Gateway.Enabled {
|
if ctrlConf.Proxy.Enabled {
|
||||||
controllers = append(controllers, gateway.NewController())
|
controllers = append(controllers, proxy.NewController())
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctrlConf.UCI.Enabled {
|
if ctrlConf.UCI.Enabled {
|
||||||
@ -53,9 +59,56 @@ func RunCommand() *cli.Command {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ctrlConf.App.Enabled {
|
||||||
|
controllers = append(controllers, app.NewController(
|
||||||
|
app.WithDataDir(string(ctrlConf.App.DataDir)),
|
||||||
|
app.WithDownloadDir(string(ctrlConf.App.DownloadDir)),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctrlConf.SysUpgrade.Enabled {
|
||||||
|
sysUpgradeArgs := make([]string, 0)
|
||||||
|
if len(ctrlConf.SysUpgrade.SysUpgradeCommand) > 1 {
|
||||||
|
sysUpgradeArgs = ctrlConf.SysUpgrade.SysUpgradeCommand[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
firmwareVersionArgs := make([]string, 0)
|
||||||
|
if len(ctrlConf.SysUpgrade.FirmwareVersionCommand) > 1 {
|
||||||
|
firmwareVersionArgs = ctrlConf.SysUpgrade.FirmwareVersionCommand[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
controllers = append(controllers, openwrt.NewSysUpgradeController(
|
||||||
|
openwrt.WithSysUpgradeCommand(
|
||||||
|
ctrlConf.SysUpgrade.SysUpgradeCommand[0],
|
||||||
|
sysUpgradeArgs...,
|
||||||
|
),
|
||||||
|
openwrt.WithSysUpgradeShellFirmwareVersion(
|
||||||
|
ctrlConf.SysUpgrade.FirmwareVersionCommand[0],
|
||||||
|
firmwareVersionArgs...,
|
||||||
|
),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
key, err := jwk.LoadOrGenerate(string(conf.Agent.PrivateKeyPath), jwk.DefaultKeySize)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
thumbprint, err := machineid.Get()
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
collectors := createShellCollectors(&conf.Agent)
|
||||||
|
collectors = append(collectors, buildinfo.NewCollector())
|
||||||
|
|
||||||
agent := agent.New(
|
agent := agent.New(
|
||||||
|
string(conf.Agent.ServerURL),
|
||||||
|
key,
|
||||||
|
thumbprint,
|
||||||
agent.WithInterval(time.Duration(conf.Agent.ReconciliationInterval)*time.Second),
|
agent.WithInterval(time.Duration(conf.Agent.ReconciliationInterval)*time.Second),
|
||||||
agent.WithControllers(controllers...),
|
agent.WithControllers(controllers...),
|
||||||
|
agent.WithCollectors(collectors...),
|
||||||
)
|
)
|
||||||
|
|
||||||
if err := agent.Run(ctx.Context); err != nil {
|
if err := agent.Run(ctx.Context); err != nil {
|
||||||
@ -66,3 +119,15 @@ func RunCommand() *cli.Command {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createShellCollectors(conf *config.AgentConfig) []metadata.Collector {
|
||||||
|
collectors := make([]metadata.Collector, 0)
|
||||||
|
|
||||||
|
for _, c := range conf.Collectors {
|
||||||
|
collector := shell.NewCollector(string(c.Name), string(c.Command), c.Args...)
|
||||||
|
|
||||||
|
collectors = append(collectors, collector)
|
||||||
|
}
|
||||||
|
|
||||||
|
return collectors
|
||||||
|
}
|
||||||
|
30
internal/command/agent/show_thumbprint.go
Normal file
30
internal/command/agent/show_thumbprint.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/command/common"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/machineid"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ShowThumbprintCommand() *cli.Command {
|
||||||
|
flags := common.Flags()
|
||||||
|
|
||||||
|
return &cli.Command{
|
||||||
|
Name: "show-thumbprint",
|
||||||
|
Usage: "Show the current agent thumbprint",
|
||||||
|
Flags: flags,
|
||||||
|
Action: func(ctx *cli.Context) error {
|
||||||
|
thumbprint, err := machineid.Get()
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(thumbprint)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
53
internal/command/api/agent/count.go
Normal file
53
internal/command/api/agent/count.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/client"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/command/api/apierr"
|
||||||
|
clientFlag "forge.cadoles.com/Cadoles/emissary/internal/command/api/flag"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/format"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CountCommand() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
|
Name: "count",
|
||||||
|
Usage: "Count agents",
|
||||||
|
Flags: clientFlag.ComposeFlags(),
|
||||||
|
Action: func(ctx *cli.Context) error {
|
||||||
|
baseFlags := clientFlag.GetBaseFlags(ctx)
|
||||||
|
|
||||||
|
token, err := clientFlag.GetToken(baseFlags)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(apierr.Wrap(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
client := client.New(baseFlags.ServerURL, client.WithToken(token))
|
||||||
|
|
||||||
|
_, total, err := client.QueryAgents(ctx.Context)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(apierr.Wrap(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
hints := format.Hints{
|
||||||
|
OutputMode: baseFlags.OutputMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
results := []struct {
|
||||||
|
Total int `json:"total"`
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Total: total,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := format.Write(baseFlags.Format, os.Stdout, hints, clientFlag.AsAnySlice(results)...); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
56
internal/command/api/agent/delete.go
Normal file
56
internal/command/api/agent/delete.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/client"
|
||||||
|
agentFlag "forge.cadoles.com/Cadoles/emissary/internal/command/api/agent/flag"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/command/api/apierr"
|
||||||
|
clientFlag "forge.cadoles.com/Cadoles/emissary/internal/command/api/flag"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/datastore"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/format"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DeleteCommand() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
|
Name: "delete",
|
||||||
|
Usage: "Delete agent",
|
||||||
|
Flags: agentFlag.WithAgentFlags(),
|
||||||
|
Action: func(ctx *cli.Context) error {
|
||||||
|
baseFlags := clientFlag.GetBaseFlags(ctx)
|
||||||
|
|
||||||
|
token, err := clientFlag.GetToken(baseFlags)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(apierr.Wrap(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
agentID, err := agentFlag.AssertAgentID(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := client.New(baseFlags.ServerURL, client.WithToken(token))
|
||||||
|
|
||||||
|
agentID, err = client.DeleteAgent(ctx.Context, agentID)
|
||||||
|
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 {
|
||||||
|
ID datastore.AgentID `json:"id"`
|
||||||
|
}{
|
||||||
|
ID: agentID,
|
||||||
|
}); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
34
internal/command/api/agent/flag/flag.go
Normal file
34
internal/command/api/agent/flag/flag.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package flag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
clientFlag "forge.cadoles.com/Cadoles/emissary/internal/command/api/flag"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/datastore"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func WithAgentFlags(flags ...cli.Flag) []cli.Flag {
|
||||||
|
baseFlags := clientFlag.ComposeFlags(
|
||||||
|
&cli.Int64Flag{
|
||||||
|
Name: "agent-id",
|
||||||
|
Aliases: []string{"a"},
|
||||||
|
Usage: "use `AGENT_ID` as selected agent",
|
||||||
|
Value: -1,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
flags = append(flags, baseFlags...)
|
||||||
|
|
||||||
|
return flags
|
||||||
|
}
|
||||||
|
|
||||||
|
func AssertAgentID(ctx *cli.Context) (datastore.AgentID, error) {
|
||||||
|
rawAgentID := ctx.Int64("agent-id")
|
||||||
|
|
||||||
|
if rawAgentID == -1 {
|
||||||
|
return -1, errors.New("flag 'agent-id' is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
return datastore.AgentID(rawAgentID), nil
|
||||||
|
}
|
49
internal/command/api/agent/get.go
Normal file
49
internal/command/api/agent/get.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/client"
|
||||||
|
agentFlag "forge.cadoles.com/Cadoles/emissary/internal/command/api/agent/flag"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/command/api/apierr"
|
||||||
|
clientFlag "forge.cadoles.com/Cadoles/emissary/internal/command/api/flag"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/format"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetCommand() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
|
Name: "get",
|
||||||
|
Usage: "Get agent",
|
||||||
|
Flags: agentFlag.WithAgentFlags(),
|
||||||
|
Action: func(ctx *cli.Context) error {
|
||||||
|
baseFlags := clientFlag.GetBaseFlags(ctx)
|
||||||
|
|
||||||
|
token, err := clientFlag.GetToken(baseFlags)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(apierr.Wrap(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
agentID, err := agentFlag.AssertAgentID(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := client.New(baseFlags.ServerURL, client.WithToken(token))
|
||||||
|
|
||||||
|
agent, err := client.GetAgent(ctx.Context, agentID)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(apierr.Wrap(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
hints := agentHints(baseFlags.OutputMode)
|
||||||
|
|
||||||
|
if err := format.Write(baseFlags.Format, os.Stdout, hints, agent); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
43
internal/command/api/agent/query.go
Normal file
43
internal/command/api/agent/query.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/client"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/command/api/apierr"
|
||||||
|
clientFlag "forge.cadoles.com/Cadoles/emissary/internal/command/api/flag"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/format"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func QueryCommand() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
|
Name: "query",
|
||||||
|
Usage: "Query agents",
|
||||||
|
Flags: clientFlag.ComposeFlags(),
|
||||||
|
Action: func(ctx *cli.Context) error {
|
||||||
|
baseFlags := clientFlag.GetBaseFlags(ctx)
|
||||||
|
|
||||||
|
token, err := clientFlag.GetToken(baseFlags)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(apierr.Wrap(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
client := client.New(baseFlags.ServerURL, client.WithToken(token))
|
||||||
|
|
||||||
|
agents, _, err := client.QueryAgents(ctx.Context)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(apierr.Wrap(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
hints := agentHints(baseFlags.OutputMode)
|
||||||
|
|
||||||
|
if err := format.Write(baseFlags.Format, os.Stdout, hints, clientFlag.AsAnySlice(agents)...); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
21
internal/command/api/agent/root.go
Normal file
21
internal/command/api/agent/root.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/command/api/agent/spec"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Root() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
|
Name: "agent",
|
||||||
|
Usage: "Agents related commands",
|
||||||
|
Subcommands: []*cli.Command{
|
||||||
|
QueryCommand(),
|
||||||
|
CountCommand(),
|
||||||
|
UpdateCommand(),
|
||||||
|
GetCommand(),
|
||||||
|
DeleteCommand(),
|
||||||
|
spec.Root(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
67
internal/command/api/agent/spec/delete.go
Normal file
67
internal/command/api/agent/spec/delete.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
package spec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/client"
|
||||||
|
agentFlag "forge.cadoles.com/Cadoles/emissary/internal/command/api/agent/flag"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/command/api/apierr"
|
||||||
|
clientFlag "forge.cadoles.com/Cadoles/emissary/internal/command/api/flag"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/format"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/spec"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DeleteCommand() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
|
Name: "delete",
|
||||||
|
Usage: "Delete spec",
|
||||||
|
|
||||||
|
Flags: agentFlag.WithAgentFlags(
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "spec-name",
|
||||||
|
Usage: "use `NAME` as specification's name",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Action: func(ctx *cli.Context) error {
|
||||||
|
baseFlags := clientFlag.GetBaseFlags(ctx)
|
||||||
|
|
||||||
|
token, err := clientFlag.GetToken(baseFlags)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(apierr.Wrap(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
agentID, err := agentFlag.AssertAgentID(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
specName, err := assertSpecName(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := client.New(baseFlags.ServerURL, client.WithToken(token))
|
||||||
|
|
||||||
|
specName, err = client.DeleteAgentSpec(ctx.Context, agentID, specName)
|
||||||
|
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 spec.Name `json:"name"`
|
||||||
|
}{
|
||||||
|
Name: specName,
|
||||||
|
}); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
50
internal/command/api/agent/spec/get.go
Normal file
50
internal/command/api/agent/spec/get.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package spec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/client"
|
||||||
|
agentFlag "forge.cadoles.com/Cadoles/emissary/internal/command/api/agent/flag"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/command/api/apierr"
|
||||||
|
clientFlag "forge.cadoles.com/Cadoles/emissary/internal/command/api/flag"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/format"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetCommand() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
|
Name: "get",
|
||||||
|
Usage: "Get agent specifications",
|
||||||
|
Flags: agentFlag.WithAgentFlags(),
|
||||||
|
Action: func(ctx *cli.Context) error {
|
||||||
|
baseFlags := clientFlag.GetBaseFlags(ctx)
|
||||||
|
agentID, err := agentFlag.AssertAgentID(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := clientFlag.GetToken(baseFlags)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(apierr.Wrap(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
client := client.New(baseFlags.ServerURL, client.WithToken(token))
|
||||||
|
|
||||||
|
specs, err := client.GetAgentSpecs(ctx.Context, agentID)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(apierr.Wrap(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
hints := format.Hints{
|
||||||
|
OutputMode: baseFlags.OutputMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := format.Write(baseFlags.Format, os.Stdout, hints, clientFlag.AsAnySlice(specs)...); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
17
internal/command/api/agent/spec/root.go
Normal file
17
internal/command/api/agent/spec/root.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package spec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Root() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
|
Name: "spec",
|
||||||
|
Usage: "Specifications related commands",
|
||||||
|
Subcommands: []*cli.Command{
|
||||||
|
GetCommand(),
|
||||||
|
UpdateCommand(),
|
||||||
|
DeleteCommand(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
185
internal/command/api/agent/spec/update.go
Normal file
185
internal/command/api/agent/spec/update.go
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
package spec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/client"
|
||||||
|
agentFlag "forge.cadoles.com/Cadoles/emissary/internal/command/api/agent/flag"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/command/api/apierr"
|
||||||
|
clientFlag "forge.cadoles.com/Cadoles/emissary/internal/command/api/flag"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/format"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/spec"
|
||||||
|
jsonpatch "github.com/evanphx/json-patch/v5"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func UpdateCommand() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
|
Name: "update",
|
||||||
|
Usage: "Update agent specification",
|
||||||
|
Flags: agentFlag.WithAgentFlags(
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "spec-name",
|
||||||
|
Usage: "use `NAME` as specification's name",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "spec-data",
|
||||||
|
Usage: "use `DATA` as specification's data, '-' to read from STDIN",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "no-patch",
|
||||||
|
Usage: "Dont use spec-data as a patch to existing specification",
|
||||||
|
},
|
||||||
|
&cli.IntFlag{
|
||||||
|
Name: "revision",
|
||||||
|
Usage: "Use `REVISION` as specification revision number",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Action: func(ctx *cli.Context) error {
|
||||||
|
baseFlags := clientFlag.GetBaseFlags(ctx)
|
||||||
|
agentID, err := agentFlag.AssertAgentID(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
specName, err := assertSpecName(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
specData, err := assertSpecData(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
noPatch := ctx.Bool("no-patch")
|
||||||
|
|
||||||
|
token, err := clientFlag.GetToken(baseFlags)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(apierr.Wrap(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
client := client.New(baseFlags.ServerURL, client.WithToken(token))
|
||||||
|
|
||||||
|
specs, err := client.GetAgentSpecs(ctx.Context, agentID)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(apierr.Wrap(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
var existingSpec spec.Spec
|
||||||
|
|
||||||
|
for _, s := range specs {
|
||||||
|
if s.SpecName() != specName {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
existingSpec = s
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
revision := 0
|
||||||
|
|
||||||
|
if existingSpec != nil {
|
||||||
|
originSpecData := existingSpec.SpecData()
|
||||||
|
|
||||||
|
if !noPatch {
|
||||||
|
specData, err = applyPatch(originSpecData, specData)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
revision = existingSpec.SpecRevision()
|
||||||
|
}
|
||||||
|
|
||||||
|
if specificRevision := ctx.Int("revision"); specificRevision != 0 {
|
||||||
|
revision = specificRevision
|
||||||
|
}
|
||||||
|
|
||||||
|
rawSpec := &spec.RawSpec{
|
||||||
|
Name: specName,
|
||||||
|
Revision: revision,
|
||||||
|
Data: specData,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := spec.Validate(ctx.Context, rawSpec); err != nil {
|
||||||
|
return errors.WithStack(apierr.Wrap(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
spec, err := client.UpdateAgentSpec(ctx.Context, agentID, rawSpec)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(apierr.Wrap(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
hints := format.Hints{
|
||||||
|
OutputMode: baseFlags.OutputMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := format.Write(baseFlags.Format, os.Stdout, hints, spec); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertSpecName(ctx *cli.Context) (spec.Name, error) {
|
||||||
|
specName := ctx.String("spec-name")
|
||||||
|
|
||||||
|
if specName == "" {
|
||||||
|
return "", errors.New("flag 'spec-name' is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
return spec.Name(specName), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertSpecData(ctx *cli.Context) (map[string]any, error) {
|
||||||
|
rawSpecData := ctx.String("spec-data")
|
||||||
|
|
||||||
|
if rawSpecData == "" {
|
||||||
|
return nil, errors.New("flag 'spec-data' is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
var specData map[string]any
|
||||||
|
|
||||||
|
if rawSpecData == "-" {
|
||||||
|
decoder := json.NewDecoder(os.Stdin)
|
||||||
|
if err := decoder.Decode(&specData); err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := json.Unmarshal([]byte(rawSpecData), &specData); err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return specData, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyPatch(origin any, patch any) (map[string]any, error) {
|
||||||
|
originJSON, err := json.Marshal(origin)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
patchJSON, err := json.Marshal(patch)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := jsonpatch.MergePatch(originJSON, patchJSON)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var specData map[string]any
|
||||||
|
|
||||||
|
if err := json.Unmarshal(result, &specData); err != nil {
|
||||||
|
}
|
||||||
|
|
||||||
|
return specData, nil
|
||||||
|
}
|
62
internal/command/api/agent/update.go
Normal file
62
internal/command/api/agent/update.go
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/client"
|
||||||
|
agentFlag "forge.cadoles.com/Cadoles/emissary/internal/command/api/agent/flag"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/command/api/apierr"
|
||||||
|
clientFlag "forge.cadoles.com/Cadoles/emissary/internal/command/api/flag"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/format"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func UpdateCommand() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
|
Name: "update",
|
||||||
|
Usage: "Updata agent",
|
||||||
|
Flags: agentFlag.WithAgentFlags(
|
||||||
|
&cli.IntFlag{
|
||||||
|
Name: "status",
|
||||||
|
Usage: "Set `STATUS` to selected agent",
|
||||||
|
Value: -1,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Action: func(ctx *cli.Context) error {
|
||||||
|
baseFlags := clientFlag.GetBaseFlags(ctx)
|
||||||
|
|
||||||
|
token, err := clientFlag.GetToken(baseFlags)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(apierr.Wrap(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
agentID, err := agentFlag.AssertAgentID(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
options := make([]client.UpdateAgentOptionFunc, 0)
|
||||||
|
|
||||||
|
status := ctx.Int("status")
|
||||||
|
if status != -1 {
|
||||||
|
options = append(options, client.WithAgentStatus(status))
|
||||||
|
}
|
||||||
|
|
||||||
|
client := client.New(baseFlags.ServerURL, client.WithToken(token))
|
||||||
|
|
||||||
|
agent, err := client.UpdateAgent(ctx.Context, agentID, options...)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(apierr.Wrap(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
hints := agentHints(baseFlags.OutputMode)
|
||||||
|
|
||||||
|
if err := format.Write(baseFlags.Format, os.Stdout, hints, agent); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
16
internal/command/api/agent/util.go
Normal file
16
internal/command/api/agent/util.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package agent
|
||||||
|
|
||||||
|
import "forge.cadoles.com/Cadoles/emissary/internal/format"
|
||||||
|
|
||||||
|
func agentHints(outputMode format.OutputMode) format.Hints {
|
||||||
|
return format.Hints{
|
||||||
|
OutputMode: outputMode,
|
||||||
|
Props: []format.Prop{
|
||||||
|
format.NewProp("ID", "ID"),
|
||||||
|
format.NewProp("Thumbprint", "Thumbprint"),
|
||||||
|
format.NewProp("Status", "Status"),
|
||||||
|
format.NewProp("CreatedAt", "CreatedAt"),
|
||||||
|
format.NewProp("UpdatedAt", "UpdatedAt"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
91
internal/command/api/apierr/wrap.go
Normal file
91
internal/command/api/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)
|
||||||
|
}
|
96
internal/command/api/flag/flag.go
Normal file
96
internal/command/api/flag/flag.go
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
package flag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/format"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/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:3000",
|
||||||
|
},
|
||||||
|
&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"},
|
||||||
|
Usage: "use `TOKEN` as authentication token",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "token-file",
|
||||||
|
Usage: "use `TOKEN_FILE` as file containing the authentication token",
|
||||||
|
Value: ".emissary-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/api/flag/util.go
Normal file
11
internal/command/api/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
|
||||||
|
}
|
16
internal/command/api/root.go
Normal file
16
internal/command/api/root.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/command/api/agent"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Root() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
|
Name: "api",
|
||||||
|
Usage: "API related commands",
|
||||||
|
Subcommands: []*cli.Command{
|
||||||
|
agent.Root(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
@ -6,7 +6,6 @@ import (
|
|||||||
"forge.cadoles.com/Cadoles/emissary/internal/command/common"
|
"forge.cadoles.com/Cadoles/emissary/internal/command/common"
|
||||||
"forge.cadoles.com/Cadoles/emissary/internal/config"
|
"forge.cadoles.com/Cadoles/emissary/internal/config"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
_ "github.com/santhosh-tekuri/jsonschema/v5/httploader"
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"gitlab.com/wpetit/goweb/logger"
|
"gitlab.com/wpetit/goweb/logger"
|
||||||
)
|
)
|
||||||
|
54
internal/command/server/auth/create_token.go
Normal file
54
internal/command/server/auth/create_token.go
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/auth/thirdparty"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/command/common"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/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)", []thirdparty.Role{thirdparty.RoleReader, thirdparty.RoleWriter}),
|
||||||
|
Value: string(thirdparty.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.Server.PrivateKeyPath), jwk.DefaultKeySize)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := thirdparty.GenerateToken(ctx.Context, key, string(conf.Server.Issuer), subject, thirdparty.Role(role))
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(token)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
15
internal/command/server/auth/root.go
Normal file
15
internal/command/server/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(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
@ -36,8 +36,8 @@ func MigrateCommand() *cli.Command {
|
|||||||
return errors.Wrap(err, "Could not load configuration")
|
return errors.Wrap(err, "Could not load configuration")
|
||||||
}
|
}
|
||||||
|
|
||||||
driver := string(conf.Database.Driver)
|
driver := string(conf.Server.Database.Driver)
|
||||||
dsn := string(conf.Database.DSN)
|
dsn := string(conf.Server.Database.DSN)
|
||||||
|
|
||||||
migr, err := migrate.New("migrations", driver, dsn)
|
migr, err := migrate.New("migrations", driver, dsn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -20,10 +20,10 @@ func PingCommand() *cli.Command {
|
|||||||
return errors.Wrap(err, "Could not load configuration")
|
return errors.Wrap(err, "Could not load configuration")
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Info(ctx.Context, "connecting to database", logger.F("dsn", conf.Database.DSN))
|
logger.Info(ctx.Context, "connecting to database", logger.F("dsn", conf.Server.Database.DSN))
|
||||||
|
|
||||||
driver := string(conf.Database.Driver)
|
driver := string(conf.Server.Database.Driver)
|
||||||
dsn := string(conf.Database.DSN)
|
dsn := string(conf.Server.Database.DSN)
|
||||||
|
|
||||||
db, err := sql.Open(driver, dsn)
|
db, err := sql.Open(driver, dsn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -40,7 +40,7 @@ func PingCommand() *cli.Command {
|
|||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Info(ctx.Context, "connection succeeded", logger.F("dsn", conf.Database.DSN))
|
logger.Info(ctx.Context, "connection succeeded", logger.F("dsn", conf.Server.Database.DSN))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
@ -18,8 +18,8 @@ func ResetCommand() *cli.Command {
|
|||||||
return errors.Wrap(err, "Could not load configuration")
|
return errors.Wrap(err, "Could not load configuration")
|
||||||
}
|
}
|
||||||
|
|
||||||
driver := string(conf.Database.Driver)
|
driver := string(conf.Server.Database.Driver)
|
||||||
dsn := string(conf.Database.DSN)
|
dsn := string(conf.Server.Database.DSN)
|
||||||
|
|
||||||
migr, err := migrate.New("migrations", driver, dsn)
|
migr, err := migrate.New("migrations", driver, dsn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -2,6 +2,7 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"forge.cadoles.com/Cadoles/emissary/internal/command/config"
|
"forge.cadoles.com/Cadoles/emissary/internal/command/config"
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/command/server/auth"
|
||||||
"forge.cadoles.com/Cadoles/emissary/internal/command/server/database"
|
"forge.cadoles.com/Cadoles/emissary/internal/command/server/database"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
@ -14,6 +15,7 @@ func Root() *cli.Command {
|
|||||||
RunCommand(),
|
RunCommand(),
|
||||||
database.Root(),
|
database.Root(),
|
||||||
config.Root(),
|
config.Root(),
|
||||||
|
auth.Root(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ import (
|
|||||||
"forge.cadoles.com/Cadoles/emissary/internal/command/common"
|
"forge.cadoles.com/Cadoles/emissary/internal/command/common"
|
||||||
"forge.cadoles.com/Cadoles/emissary/internal/server"
|
"forge.cadoles.com/Cadoles/emissary/internal/server"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
_ "github.com/santhosh-tekuri/jsonschema/v5/httploader"
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"gitlab.com/wpetit/goweb/logger"
|
"gitlab.com/wpetit/goweb/logger"
|
||||||
)
|
)
|
||||||
@ -29,7 +28,7 @@ func RunCommand() *cli.Command {
|
|||||||
logger.SetLevel(logger.Level(conf.Logger.Level))
|
logger.SetLevel(logger.Level(conf.Logger.Level))
|
||||||
|
|
||||||
srv := server.New(
|
srv := server.New(
|
||||||
server.WithConfig(conf),
|
server.WithConfig(conf.Server),
|
||||||
)
|
)
|
||||||
|
|
||||||
addrs, srvErrs := srv.Start(ctx.Context)
|
addrs, srvErrs := srv.Start(ctx.Context)
|
||||||
|
@ -1,15 +1,28 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
|
import "forge.cadoles.com/Cadoles/emissary/internal/agent/controller/openwrt"
|
||||||
|
|
||||||
type AgentConfig struct {
|
type AgentConfig struct {
|
||||||
ReconciliationInterval InterpolatedInt `yaml:"reconciliationInterval"`
|
ServerURL InterpolatedString `yaml:"serverUrl"`
|
||||||
Controllers ControllersConfig `yaml:"controllers"`
|
PrivateKeyPath InterpolatedString `yaml:"privateKeyPath"`
|
||||||
|
ReconciliationInterval InterpolatedInt `yaml:"reconciliationInterval"`
|
||||||
|
Controllers ControllersConfig `yaml:"controllers"`
|
||||||
|
Collectors []ShellCollectorConfig `yaml:"collectors"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ShellCollectorConfig struct {
|
||||||
|
Name InterpolatedString `yaml:"name"`
|
||||||
|
Command InterpolatedString `yaml:"command"`
|
||||||
|
Args InterpolatedStringSlice `yaml:"args"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ControllersConfig struct {
|
type ControllersConfig struct {
|
||||||
Persistence PersistenceControllerConfig `yaml:"persistence"`
|
Persistence PersistenceControllerConfig `yaml:"persistence"`
|
||||||
Spec SpecControllerConfig `yaml:"spec"`
|
Spec SpecControllerConfig `yaml:"spec"`
|
||||||
Gateway GatewayControllerConfig `yaml:"gateway"`
|
Proxy ProxyControllerConfig `yaml:"proxy"`
|
||||||
UCI UCIControllerConfig `yaml:"uci"`
|
UCI UCIControllerConfig `yaml:"uci"`
|
||||||
|
App AppControllerConfig `yaml:"app"`
|
||||||
|
SysUpgrade SysUpgradeControllerConfig `yaml:"sysupgrade"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PersistenceControllerConfig struct {
|
type PersistenceControllerConfig struct {
|
||||||
@ -18,11 +31,9 @@ type PersistenceControllerConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SpecControllerConfig struct {
|
type SpecControllerConfig struct {
|
||||||
Enabled InterpolatedBool `yaml:"enabled"`
|
Enabled InterpolatedBool `yaml:"enabled"`
|
||||||
ServerURL InterpolatedString `yaml:"serverUrl"`
|
|
||||||
}
|
}
|
||||||
|
type ProxyControllerConfig struct {
|
||||||
type GatewayControllerConfig struct {
|
|
||||||
Enabled InterpolatedBool `yaml:"enabled"`
|
Enabled InterpolatedBool `yaml:"enabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,19 +43,32 @@ type UCIControllerConfig struct {
|
|||||||
ConfigBackupFile InterpolatedString `yaml:"configBackupFile"`
|
ConfigBackupFile InterpolatedString `yaml:"configBackupFile"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AppControllerConfig struct {
|
||||||
|
Enabled InterpolatedBool `yaml:"enabled"`
|
||||||
|
DataDir InterpolatedString `yaml:"dataDir"`
|
||||||
|
DownloadDir InterpolatedString `yaml:"downloadDir"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SysUpgradeControllerConfig struct {
|
||||||
|
Enabled InterpolatedBool `yaml:"enabled"`
|
||||||
|
SysUpgradeCommand InterpolatedStringSlice `yaml:"sysupgradeCommand"`
|
||||||
|
FirmwareVersionCommand InterpolatedStringSlice `yaml:"firmwareVersionCommand"`
|
||||||
|
}
|
||||||
|
|
||||||
func NewDefaultAgentConfig() AgentConfig {
|
func NewDefaultAgentConfig() AgentConfig {
|
||||||
return AgentConfig{
|
return AgentConfig{
|
||||||
|
ServerURL: "http://127.0.0.1:3000",
|
||||||
|
PrivateKeyPath: "agent-key.json",
|
||||||
ReconciliationInterval: 5,
|
ReconciliationInterval: 5,
|
||||||
Controllers: ControllersConfig{
|
Controllers: ControllersConfig{
|
||||||
Spec: SpecControllerConfig{
|
Spec: SpecControllerConfig{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
ServerURL: "http://127.0.0.1:3000",
|
|
||||||
},
|
},
|
||||||
Persistence: PersistenceControllerConfig{
|
Persistence: PersistenceControllerConfig{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
StateFile: "state.json",
|
StateFile: "state.json",
|
||||||
},
|
},
|
||||||
Gateway: GatewayControllerConfig{
|
Proxy: ProxyControllerConfig{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
},
|
},
|
||||||
UCI: UCIControllerConfig{
|
UCI: UCIControllerConfig{
|
||||||
@ -52,6 +76,23 @@ func NewDefaultAgentConfig() AgentConfig {
|
|||||||
ConfigBackupFile: "uci-backup.conf",
|
ConfigBackupFile: "uci-backup.conf",
|
||||||
BinPath: "uci",
|
BinPath: "uci",
|
||||||
},
|
},
|
||||||
|
App: AppControllerConfig{
|
||||||
|
Enabled: true,
|
||||||
|
DataDir: "apps/data",
|
||||||
|
DownloadDir: "apps/bundles",
|
||||||
|
},
|
||||||
|
SysUpgrade: SysUpgradeControllerConfig{
|
||||||
|
Enabled: true,
|
||||||
|
SysUpgradeCommand: InterpolatedStringSlice{"sysupgrade", "--force", "-u", "-v", openwrt.FirmwareFileTemplate},
|
||||||
|
FirmwareVersionCommand: InterpolatedStringSlice{"sh", "-c", `source /etc/openwrt_release && echo "$DISTRIB_ID-$DISTRIB_RELEASE-$DISTRIB_REVISION"`},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Collectors: []ShellCollectorConfig{
|
||||||
|
{
|
||||||
|
Name: "uname",
|
||||||
|
Command: "uname",
|
||||||
|
Args: []string{"-a"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,11 +10,9 @@ import (
|
|||||||
|
|
||||||
// Config definition
|
// Config definition
|
||||||
type Config struct {
|
type Config struct {
|
||||||
HTTP HTTPConfig `yaml:"http"`
|
Logger LoggerConfig `yaml:"logger"`
|
||||||
Logger LoggerConfig `yaml:"logger"`
|
Server ServerConfig `yaml:"server"`
|
||||||
Database DatabaseConfig `yaml:"database"`
|
Agent AgentConfig `yaml:"agent"`
|
||||||
CORS CORSConfig `yaml:"cors"`
|
|
||||||
Agent AgentConfig `yaml:"agent"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFromFile retrieves the configuration from the given file
|
// NewFromFile retrieves the configuration from the given file
|
||||||
@ -43,11 +41,9 @@ func NewDumpDefault() *Config {
|
|||||||
// NewDefault return new default configuration
|
// NewDefault return new default configuration
|
||||||
func NewDefault() *Config {
|
func NewDefault() *Config {
|
||||||
return &Config{
|
return &Config{
|
||||||
HTTP: NewDefaultHTTPConfig(),
|
Logger: NewDefaultLoggerConfig(),
|
||||||
Logger: NewDefaultLoggerConfig(),
|
Agent: NewDefaultAgentConfig(),
|
||||||
Database: NewDefaultDatabaseConfig(),
|
Server: NewDefaultServerConfig(),
|
||||||
CORS: NewDefaultCORSConfig(),
|
|
||||||
Agent: NewDefaultAgentConfig(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
19
internal/config/server.go
Normal file
19
internal/config/server.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
type ServerConfig struct {
|
||||||
|
PrivateKeyPath InterpolatedString `yaml:"privateKeyPath"`
|
||||||
|
Issuer InterpolatedString `yaml:"issuer"`
|
||||||
|
HTTP HTTPConfig `yaml:"http"`
|
||||||
|
Database DatabaseConfig `yaml:"database"`
|
||||||
|
CORS CORSConfig `yaml:"cors"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDefaultServerConfig() ServerConfig {
|
||||||
|
return ServerConfig{
|
||||||
|
PrivateKeyPath: "server-key.json",
|
||||||
|
Issuer: "http://127.0.0.1:3000",
|
||||||
|
HTTP: NewDefaultHTTPConfig(),
|
||||||
|
Database: NewDefaultDatabaseConfig(),
|
||||||
|
CORS: NewDefaultCORSConfig(),
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,11 @@
|
|||||||
package datastore
|
package datastore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/lestrrat-go/jwx/v2/jwk"
|
||||||
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AgentID int64
|
type AgentID int64
|
||||||
@ -16,9 +20,36 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Agent struct {
|
type Agent struct {
|
||||||
ID AgentID `json:"id"`
|
ID AgentID `json:"id"`
|
||||||
RemoteID string `json:"remoteId"`
|
Thumbprint string `json:"thumbprint"`
|
||||||
Status AgentStatus `json:"status"`
|
KeySet *SerializableKeySet `json:"keyset,omitempty"`
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
Metadata map[string]any `json:"metadata,omitempty"`
|
||||||
UpdatedAt time.Time `json:"updatedAt"`
|
Status AgentStatus `json:"status"`
|
||||||
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
UpdatedAt time.Time `json:"updatedAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SerializableKeySet struct {
|
||||||
|
jwk.Set
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SerializableKeySet) UnmarshalJSON(data []byte) error {
|
||||||
|
keySet := jwk.NewSet()
|
||||||
|
|
||||||
|
if err := json.Unmarshal(data, &keySet); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Set = keySet
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SerializableKeySet) MarshalJSON() ([]byte, error) {
|
||||||
|
data, err := json.Marshal(s.Set)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, nil
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
package datastore
|
package datastore
|
||||||
|
|
||||||
import "context"
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/lestrrat-go/jwx/v2/jwk"
|
||||||
|
)
|
||||||
|
|
||||||
type AgentRepository interface {
|
type AgentRepository interface {
|
||||||
Create(ctx context.Context, remoteID string, state AgentStatus) (*Agent, error)
|
Create(ctx context.Context, thumbprint string, keySet jwk.Set, metadata map[string]any) (*Agent, error)
|
||||||
Get(ctx context.Context, id AgentID) (*Agent, error)
|
Get(ctx context.Context, id AgentID) (*Agent, error)
|
||||||
Update(ctx context.Context, id AgentID, updates ...AgentUpdateOptionFunc) (*Agent, error)
|
Update(ctx context.Context, id AgentID, updates ...AgentUpdateOptionFunc) (*Agent, error)
|
||||||
Query(ctx context.Context, opts ...AgentQueryOptionFunc) ([]*Agent, int, error)
|
Query(ctx context.Context, opts ...AgentQueryOptionFunc) ([]*Agent, int, error)
|
||||||
@ -17,11 +21,12 @@ type AgentRepository interface {
|
|||||||
type AgentQueryOptionFunc func(*AgentQueryOptions)
|
type AgentQueryOptionFunc func(*AgentQueryOptions)
|
||||||
|
|
||||||
type AgentQueryOptions struct {
|
type AgentQueryOptions struct {
|
||||||
Limit *int
|
Limit *int
|
||||||
Offset *int
|
Offset *int
|
||||||
RemoteIDs []string
|
IDs []AgentID
|
||||||
IDs []AgentID
|
Thumbprints []string
|
||||||
Statuses []AgentStatus
|
Metadata *map[string]any
|
||||||
|
Statuses []AgentStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithAgentQueryLimit(limit int) AgentQueryOptionFunc {
|
func WithAgentQueryLimit(limit int) AgentQueryOptionFunc {
|
||||||
@ -36,9 +41,9 @@ func WithAgentQueryOffset(offset int) AgentQueryOptionFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithAgentQueryRemoteID(remoteIDs ...string) AgentQueryOptionFunc {
|
func WithAgentQueryMetadata(metadata map[string]any) AgentQueryOptionFunc {
|
||||||
return func(opts *AgentQueryOptions) {
|
return func(opts *AgentQueryOptions) {
|
||||||
opts.RemoteIDs = remoteIDs
|
opts.Metadata = &metadata
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,10 +59,19 @@ func WithAgentQueryStatus(statuses ...AgentStatus) AgentQueryOptionFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WithAgentQueryThumbprints(thumbprints ...string) AgentQueryOptionFunc {
|
||||||
|
return func(opts *AgentQueryOptions) {
|
||||||
|
opts.Thumbprints = thumbprints
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type AgentUpdateOptionFunc func(*AgentUpdateOptions)
|
type AgentUpdateOptionFunc func(*AgentUpdateOptions)
|
||||||
|
|
||||||
type AgentUpdateOptions struct {
|
type AgentUpdateOptions struct {
|
||||||
Status *AgentStatus
|
Status *AgentStatus
|
||||||
|
Metadata *map[string]any
|
||||||
|
KeySet *jwk.Set
|
||||||
|
Thumbprint *string
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithAgentUpdateStatus(status AgentStatus) AgentUpdateOptionFunc {
|
func WithAgentUpdateStatus(status AgentStatus) AgentUpdateOptionFunc {
|
||||||
@ -65,3 +79,21 @@ func WithAgentUpdateStatus(status AgentStatus) AgentUpdateOptionFunc {
|
|||||||
opts.Status = &status
|
opts.Status = &status
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WithAgentUpdateMetadata(metadata map[string]any) AgentUpdateOptionFunc {
|
||||||
|
return func(opts *AgentUpdateOptions) {
|
||||||
|
opts.Metadata = &metadata
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithAgentUpdateKeySet(keySet jwk.Set) AgentUpdateOptionFunc {
|
||||||
|
return func(opts *AgentUpdateOptions) {
|
||||||
|
opts.KeySet = &keySet
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithAgentUpdateThumbprint(thumbprint string) AgentUpdateOptionFunc {
|
||||||
|
return func(opts *AgentUpdateOptions) {
|
||||||
|
opts.Thumbprint = &thumbprint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -4,7 +4,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"forge.cadoles.com/Cadoles/emissary/internal/spec"
|
"forge.cadoles.com/Cadoles/emissary/internal/spec"
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type SpecID int64
|
type SpecID int64
|
||||||
@ -26,10 +25,6 @@ func (s *Spec) SpecRevision() int {
|
|||||||
return s.Revision
|
return s.Revision
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Spec) SpecData() any {
|
func (s *Spec) SpecData() map[string]any {
|
||||||
return s.Data
|
return s.Data
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Spec) SpecValid() (bool, error) {
|
|
||||||
return false, errors.WithStack(spec.ErrSchemaUnknown)
|
|
||||||
}
|
|
||||||
|
@ -3,10 +3,13 @@ package sqlite
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"forge.cadoles.com/Cadoles/emissary/internal/datastore"
|
"forge.cadoles.com/Cadoles/emissary/internal/datastore"
|
||||||
|
|
||||||
|
"github.com/lestrrat-go/jwx/v2/jwk"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gitlab.com/wpetit/goweb/logger"
|
"gitlab.com/wpetit/goweb/logger"
|
||||||
)
|
)
|
||||||
@ -116,7 +119,7 @@ func (r *AgentRepository) Query(ctx context.Context, opts ...datastore.AgentQuer
|
|||||||
count := 0
|
count := 0
|
||||||
|
|
||||||
err := r.withTx(ctx, func(tx *sql.Tx) error {
|
err := r.withTx(ctx, func(tx *sql.Tx) error {
|
||||||
query := `SELECT id, remote_id, status, created_at, updated_at FROM agents`
|
query := `SELECT id, thumbprint, status, created_at, updated_at FROM agents`
|
||||||
|
|
||||||
limit := 10
|
limit := 10
|
||||||
if options.Limit != nil {
|
if options.Limit != nil {
|
||||||
@ -133,20 +136,18 @@ func (r *AgentRepository) Query(ctx context.Context, opts ...datastore.AgentQuer
|
|||||||
args := []any{offset, limit}
|
args := []any{offset, limit}
|
||||||
|
|
||||||
if options.IDs != nil && len(options.IDs) > 0 {
|
if options.IDs != nil && len(options.IDs) > 0 {
|
||||||
filters += "id in ("
|
filter, newArgs, newParamIndex := inFilter("id", paramIndex, options.IDs)
|
||||||
|
|
||||||
filter, newArgs, newParamIndex := inFilter("id", paramIndex, options.RemoteIDs)
|
|
||||||
filters += filter
|
filters += filter
|
||||||
paramIndex = newParamIndex
|
paramIndex = newParamIndex
|
||||||
args = append(args, newArgs...)
|
args = append(args, newArgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.RemoteIDs != nil && len(options.RemoteIDs) > 0 {
|
if options.Thumbprints != nil && len(options.Thumbprints) > 0 {
|
||||||
if filters != "" {
|
if filters != "" {
|
||||||
filters += " AND "
|
filters += " AND "
|
||||||
}
|
}
|
||||||
|
|
||||||
filter, newArgs, newParamIndex := inFilter("remote_id", paramIndex, options.RemoteIDs)
|
filter, newArgs, newParamIndex := inFilter("thumbprint", paramIndex, options.Thumbprints)
|
||||||
filters += filter
|
filters += filter
|
||||||
paramIndex = newParamIndex
|
paramIndex = newParamIndex
|
||||||
args = append(args, newArgs...)
|
args = append(args, newArgs...)
|
||||||
@ -180,10 +181,14 @@ func (r *AgentRepository) Query(ctx context.Context, opts ...datastore.AgentQuer
|
|||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
agent := &datastore.Agent{}
|
agent := &datastore.Agent{}
|
||||||
|
|
||||||
if err := rows.Scan(&agent.ID, &agent.RemoteID, &agent.Status, &agent.CreatedAt, &agent.UpdatedAt); err != nil {
|
metadata := JSONMap{}
|
||||||
|
|
||||||
|
if err := rows.Scan(&agent.ID, &agent.Thumbprint, &agent.Status, &agent.CreatedAt, &agent.UpdatedAt); err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
agent.Metadata = metadata
|
||||||
|
|
||||||
agents = append(agents, agent)
|
agents = append(agents, agent)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,12 +207,12 @@ func (r *AgentRepository) Query(ctx context.Context, opts ...datastore.AgentQuer
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create implements datastore.AgentRepository
|
// Create implements datastore.AgentRepository
|
||||||
func (r *AgentRepository) Create(ctx context.Context, remoteID string, status datastore.AgentStatus) (*datastore.Agent, error) {
|
func (r *AgentRepository) Create(ctx context.Context, thumbprint string, keySet jwk.Set, metadata map[string]any) (*datastore.Agent, error) {
|
||||||
agent := &datastore.Agent{}
|
agent := &datastore.Agent{}
|
||||||
|
|
||||||
err := r.withTx(ctx, func(tx *sql.Tx) error {
|
err := r.withTx(ctx, func(tx *sql.Tx) error {
|
||||||
query := `SELECT count(id) FROM agents WHERE remote_id = $1`
|
query := `SELECT count(id) FROM agents WHERE thumbprint = $1`
|
||||||
row := tx.QueryRowContext(ctx, query, remoteID)
|
row := tx.QueryRowContext(ctx, query, thumbprint)
|
||||||
|
|
||||||
var count int
|
var count int
|
||||||
|
|
||||||
@ -222,21 +227,37 @@ func (r *AgentRepository) Create(ctx context.Context, remoteID string, status da
|
|||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
|
|
||||||
query = `
|
query = `
|
||||||
INSERT INTO agents (remote_id, status, created_at, updated_at)
|
INSERT INTO agents (thumbprint, keyset, metadata, status, created_at, updated_at)
|
||||||
VALUES($1, $2, $3, $3)
|
VALUES($1, $2, $3, $4, $5, $5)
|
||||||
RETURNING "id", "remote_id", "status", "created_at", "updated_at"
|
RETURNING "id", "thumbprint", "keyset", "metadata", "status", "created_at", "updated_at"
|
||||||
`
|
`
|
||||||
|
|
||||||
row = tx.QueryRowContext(
|
rawKeySet, err := json.Marshal(keySet)
|
||||||
ctx, query,
|
|
||||||
remoteID, status, now,
|
|
||||||
)
|
|
||||||
|
|
||||||
err := row.Scan(&agent.ID, &agent.RemoteID, &agent.Status, &agent.CreatedAt, &agent.UpdatedAt)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
row = tx.QueryRowContext(
|
||||||
|
ctx, query,
|
||||||
|
thumbprint, rawKeySet, JSONMap(metadata), datastore.AgentStatusPending, now,
|
||||||
|
)
|
||||||
|
|
||||||
|
metadata := JSONMap{}
|
||||||
|
|
||||||
|
err = row.Scan(&agent.ID, &agent.Thumbprint, &rawKeySet, &metadata, &agent.Status, &agent.CreatedAt, &agent.UpdatedAt)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
agent.Metadata = metadata
|
||||||
|
|
||||||
|
keySet, err = jwk.Parse(rawKeySet)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
agent.KeySet = &datastore.SerializableKeySet{keySet}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -248,9 +269,21 @@ func (r *AgentRepository) Create(ctx context.Context, remoteID string, status da
|
|||||||
|
|
||||||
// Delete implements datastore.AgentRepository
|
// Delete implements datastore.AgentRepository
|
||||||
func (r *AgentRepository) Delete(ctx context.Context, id datastore.AgentID) error {
|
func (r *AgentRepository) Delete(ctx context.Context, id datastore.AgentID) error {
|
||||||
query := `DELETE FROM agents WHERE id = $1`
|
err := r.withTx(ctx, func(tx *sql.Tx) error {
|
||||||
|
query := `DELETE FROM agents WHERE id = $1`
|
||||||
|
_, err := r.db.ExecContext(ctx, query, id)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
_, err := r.db.ExecContext(ctx, query, id)
|
query = `DELETE FROM specs WHERE agent_id = $1`
|
||||||
|
_, err = r.db.ExecContext(ctx, query, id)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
@ -266,14 +299,17 @@ func (r *AgentRepository) Get(ctx context.Context, id datastore.AgentID) (*datas
|
|||||||
|
|
||||||
err := r.withTx(ctx, func(tx *sql.Tx) error {
|
err := r.withTx(ctx, func(tx *sql.Tx) error {
|
||||||
query := `
|
query := `
|
||||||
SELECT "remote_id", "status", "created_at", "updated_at"
|
SELECT "id", "thumbprint", "keyset", "metadata", "status", "created_at", "updated_at"
|
||||||
FROM agents
|
FROM agents
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
`
|
`
|
||||||
|
|
||||||
row := r.db.QueryRowContext(ctx, query, id)
|
row := r.db.QueryRowContext(ctx, query, id)
|
||||||
|
|
||||||
if err := row.Scan(&agent.RemoteID, &agent.Status, &agent.CreatedAt, &agent.UpdatedAt); err != nil {
|
metadata := JSONMap{}
|
||||||
|
var rawKeySet []byte
|
||||||
|
|
||||||
|
if err := row.Scan(&agent.ID, &agent.Thumbprint, &rawKeySet, &metadata, &agent.Status, &agent.CreatedAt, &agent.UpdatedAt); err != nil {
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
return datastore.ErrNotFound
|
return datastore.ErrNotFound
|
||||||
}
|
}
|
||||||
@ -281,6 +317,15 @@ func (r *AgentRepository) Get(ctx context.Context, id datastore.AgentID) (*datas
|
|||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
agent.Metadata = metadata
|
||||||
|
|
||||||
|
keySet := jwk.NewSet()
|
||||||
|
if err := json.Unmarshal(rawKeySet, &keySet); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
agent.KeySet = &datastore.SerializableKeySet{keySet}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -313,23 +358,60 @@ func (r *AgentRepository) Update(ctx context.Context, id datastore.AgentID, opts
|
|||||||
|
|
||||||
if options.Status != nil {
|
if options.Status != nil {
|
||||||
query += fmt.Sprintf(`, status = $%d`, index)
|
query += fmt.Sprintf(`, status = $%d`, index)
|
||||||
|
|
||||||
args = append(args, *options.Status)
|
args = append(args, *options.Status)
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.KeySet != nil {
|
||||||
|
rawKeySet, err := json.Marshal(*options.KeySet)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
query += fmt.Sprintf(`, keyset = $%d`, index)
|
||||||
|
args = append(args, rawKeySet)
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.Thumbprint != nil {
|
||||||
|
query += fmt.Sprintf(`, thumbprint = $%d`, index)
|
||||||
|
args = append(args, *options.Thumbprint)
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.Metadata != nil {
|
||||||
|
query += fmt.Sprintf(`, metadata = $%d`, index)
|
||||||
|
args = append(args, JSONMap(*options.Metadata))
|
||||||
index++
|
index++
|
||||||
}
|
}
|
||||||
|
|
||||||
query += `
|
query += `
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
RETURNING "id","remote_id","status","updated_at","created_at"
|
RETURNING "id", "thumbprint", "keyset", "metadata", "status", "created_at", "updated_at"
|
||||||
`
|
`
|
||||||
|
|
||||||
row := tx.QueryRowContext(ctx, query, args...)
|
row := tx.QueryRowContext(ctx, query, args...)
|
||||||
|
|
||||||
if err := row.Scan(&agent.ID, &agent.RemoteID, &agent.Status, &agent.CreatedAt, &agent.UpdatedAt); err != nil {
|
metadata := JSONMap{}
|
||||||
|
var rawKeySet []byte
|
||||||
|
|
||||||
|
if err := row.Scan(&agent.ID, &agent.Thumbprint, &rawKeySet, &metadata, &agent.Status, &agent.CreatedAt, &agent.UpdatedAt); err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return datastore.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
agent.Metadata = metadata
|
||||||
|
|
||||||
|
keySet := jwk.NewSet()
|
||||||
|
if err := json.Unmarshal(rawKeySet, &keySet); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
agent.KeySet = &datastore.SerializableKeySet{keySet}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
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/emissary/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{}
|
18
internal/format/prop.go
Normal file
18
internal/format/prop.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package format
|
||||||
|
|
||||||
|
type Prop struct {
|
||||||
|
name string
|
||||||
|
label string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Prop) Name() string {
|
||||||
|
return p.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Prop) Label() string {
|
||||||
|
return p.label
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProp(name, label string) Prop {
|
||||||
|
return Prop{name, label}
|
||||||
|
}
|
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
|
||||||
|
}
|
49
internal/format/table/prop.go
Normal file
49
internal/format/table/prop.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package table
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/internal/format"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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.Interface())
|
||||||
|
}
|
||||||
|
}
|
75
internal/format/table/writer.go
Normal file
75
internal/format/table/writer.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package table
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"forge.cadoles.com/Cadoles/emissary/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())
|
||||||
|
|
||||||
|
if isCompactMode && len(value) > w.compactModeMaxColumnWidth {
|
||||||
|
value = value[:w.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/emissary/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/emissary/internal/format/json"
|
||||||
|
_ "forge.cadoles.com/Cadoles/emissary/internal/format/table"
|
||||||
|
)
|
6
internal/imports/passwd/passwd_import.go
Normal file
6
internal/imports/passwd/passwd_import.go
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
package passwd
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "forge.cadoles.com/arcad/edge/pkg/module/auth/http/passwd/argon2id"
|
||||||
|
_ "forge.cadoles.com/arcad/edge/pkg/module/auth/http/passwd/plain"
|
||||||
|
)
|
8
internal/imports/spec/spec_import.go
Normal file
8
internal/imports/spec/spec_import.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package spec
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "forge.cadoles.com/Cadoles/emissary/internal/agent/controller/openwrt/spec/sysupgrade"
|
||||||
|
_ "forge.cadoles.com/Cadoles/emissary/internal/spec/app"
|
||||||
|
_ "forge.cadoles.com/Cadoles/emissary/internal/spec/proxy"
|
||||||
|
_ "forge.cadoles.com/Cadoles/emissary/internal/spec/uci"
|
||||||
|
)
|
6
internal/imports/sql/sql_import.go
Normal file
6
internal/imports/sql/sql_import.go
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
package sql
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "github.com/jackc/pgx/v5/stdlib"
|
||||||
|
_ "modernc.org/sqlite"
|
||||||
|
)
|
133
internal/jwk/jwk.go
Normal file
133
internal/jwk/jwk.go
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user