Compare commits
118 Commits
auth-modul
...
master
Author | SHA1 | Date |
---|---|---|
wpetit | 1029cc8f0f | |
wpetit | 0f673671b8 | |
wpetit | 6aec6da078 | |
wpetit | 2fbc7186c0 | |
wpetit | 7633ae0419 | |
wpetit | 335b34625b | |
wpetit | 776dbba5b0 | |
wpetit | 8f9428b3f3 | |
wpetit | a268759d33 | |
wpetit | a276b92a03 | |
wpetit | b9c08f647c | |
wpetit | 59f023a7d9 | |
wpetit | 753a6c9708 | |
wpetit | b120e590b6 | |
wpetit | 242bf379a8 | |
wpetit | 065a9002a0 | |
wpetit | 83a1e89665 | |
wpetit | d9e8aac458 | |
wpetit | 32f04af138 | |
wpetit | 870db072e0 | |
wpetit | ad49c1718c | |
wpetit | f4a7366aad | |
wpetit | 02c74b6f8d | |
wpetit | 8889694125 | |
wpetit | 6a99409a15 | |
wpetit | 2fc590d708 | |
wpetit | 6e4bf2f025 | |
wpetit | 22a3326be9 | |
wpetit | 0cfb132b65 | |
wpetit | de4ab0d02c | |
wpetit | d1458bab4a | |
wpetit | a5c67c29d0 | |
wpetit | 1544212ab5 | |
wpetit | efb8ba8b99 | |
wpetit | 4d064de164 | |
wpetit | 8a5a1cd482 | |
wpetit | 3fd25988cf | |
wpetit | ebe3e77879 | |
wpetit | 3078ea7d21 | |
wpetit | 4c6e979bb6 | |
wpetit | 0fded0170a | |
wpetit | 6ddd831025 | |
wpetit | 4fe68e335a | |
wpetit | 599ff749d3 | |
wpetit | 9f89c89fb9 | |
wpetit | d2472623f2 | |
wpetit | c63af872ea | |
wpetit | 8e574c299b | |
wpetit | c3535a4a9b | |
wpetit | 7e58551f6a | |
wpetit | 41d5db6321 | |
wpetit | 8eb441daee | |
wpetit | 17808d14c9 | |
wpetit | ba9ae6e391 | |
wpetit | abc60b9ae3 | |
wpetit | f99b1ac6ac | |
wpetit | 1606ff5937 | |
wpetit | 90020d6ea6 | |
wpetit | 7b6e39088d | |
wpetit | 9944a37670 | |
wpetit | 78307b6850 | |
wpetit | 2543386e5c | |
wpetit | 20c4189599 | |
wpetit | c7b639b643 | |
wpetit | b5b4042cc7 | |
wpetit | 9e3fc427bb | |
wpetit | d0b57ab15f | |
wpetit | dc93c585eb | |
wpetit | de330c0042 | |
wpetit | 310dac296f | |
wpetit | 4db7576b12 | |
wpetit | f5283b86ed | |
wpetit | 98ebd7a168 | |
wpetit | 8ca31d05c0 | |
wpetit | 34c6a089b5 | |
wpetit | da73b842e1 | |
wpetit | 55d7241d95 | |
wpetit | 240b07af66 | |
wpetit | 68e35bf5a6 | |
wpetit | 4bc2d864ad | |
wpetit | dc18381dea | |
wpetit | 1dde96043a | |
wpetit | f758acb4e5 | |
wpetit | 054e80bbfb | |
wpetit | 32c6f0a77e | |
wpetit | 050e529f0a | |
wpetit | 006f13bc7b | |
wpetit | 84c8fd51f6 | |
wpetit | f08f645432 | |
wpetit | fbb27d6ea4 | |
wpetit | d8ce2901d2 | |
wpetit | 1996f4dc56 | |
wpetit | e09de0b0a4 | |
wpetit | 72765de20b | |
wpetit | ed535b6f5d | |
wpetit | 07452ad8ab | |
wpetit | 0f0fdfb02b | |
wpetit | 9eefce9b41 | |
wpetit | 0577762be9 | |
wpetit | cf8a3f8ac0 | |
wpetit | 1f4f795d43 | |
wpetit | fd12d2ba42 | |
wpetit | 6399196fe5 | |
wpetit | fef0321475 | |
wpetit | a9d2c282f2 | |
wpetit | 0bb7f2cd85 | |
wpetit | b61bf52df9 | |
wpetit | f1dd467c95 | |
wpetit | 3136d71032 | |
wpetit | 8680e139e7 | |
wpetit | 8789b85d92 | |
wpetit | fefcba5901 | |
wpetit | 5ad4ab2e23 | |
wpetit | 4eb1f8fc90 | |
wpetit | 640f429580 | |
wpetit | 7f07e52ae0 | |
wpetit | c4865c149f | |
wpetit | b9f985ab0c |
|
@ -0,0 +1,4 @@
|
||||||
|
RUN_APP_ARGS=""
|
||||||
|
#EDGE_DOCUMENTSTORE_DSN="rpc://localhost:3001/documentstore?tenant=local&appId=%APPID%"
|
||||||
|
#EDGE_BLOBSTORE_DSN="cache://localhost:3001/blobstore?driver=rpc&tenant=local&appId=%APPID%&blobCacheStoreType=fs&blobCacheStoreBaseDir=data/cache/%APPID%&blobCacheSize=64MB"
|
||||||
|
#EDGE_SHARESTORE_DSN="rpc://localhost:3001/sharestore?tenant=local"
|
|
@ -1,7 +1,13 @@
|
||||||
/node_modules
|
/node_modules
|
||||||
/bin
|
/bin
|
||||||
/pkg/sdk/client/dist
|
|
||||||
/.env
|
/.env
|
||||||
/tools
|
/tools
|
||||||
*.sqlite
|
*.sqlite*
|
||||||
/.gitea-release
|
/.gitea-release
|
||||||
|
/.edge
|
||||||
|
/data
|
||||||
|
.mktools/
|
||||||
|
/dist
|
||||||
|
/.chglog
|
||||||
|
/CHANGELOG.md
|
||||||
|
/storage-server.key
|
|
@ -0,0 +1,124 @@
|
||||||
|
project_name: edge
|
||||||
|
before:
|
||||||
|
hooks:
|
||||||
|
- go mod tidy
|
||||||
|
- go generate ./...
|
||||||
|
builds:
|
||||||
|
- id: edge-cli
|
||||||
|
binary: edge-cli
|
||||||
|
env:
|
||||||
|
- CGO_ENABLED=0
|
||||||
|
ldflags:
|
||||||
|
- -s
|
||||||
|
- -w
|
||||||
|
gcflags:
|
||||||
|
- -trimpath="${PWD}"
|
||||||
|
asmflags:
|
||||||
|
- -trimpath="${PWD}"
|
||||||
|
goos:
|
||||||
|
- linux
|
||||||
|
goarch:
|
||||||
|
- amd64
|
||||||
|
main: ./cmd/cli
|
||||||
|
- id: storage-server
|
||||||
|
binary: storage-server
|
||||||
|
env:
|
||||||
|
- CGO_ENABLED=0
|
||||||
|
ldflags:
|
||||||
|
- -s
|
||||||
|
- -w
|
||||||
|
gcflags:
|
||||||
|
- -trimpath="${PWD}"
|
||||||
|
asmflags:
|
||||||
|
- -trimpath="${PWD}"
|
||||||
|
goos:
|
||||||
|
- linux
|
||||||
|
goarch:
|
||||||
|
- amd64
|
||||||
|
main: ./cmd/storage-server
|
||||||
|
archives:
|
||||||
|
- id: edge-cli
|
||||||
|
builds: ["edge-cli"]
|
||||||
|
name_template: 'edge-cli_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
||||||
|
files:
|
||||||
|
- README.md
|
||||||
|
- CHANGELOG.md
|
||||||
|
- id: storage-server
|
||||||
|
builds: ["storage-server"]
|
||||||
|
name_template: 'storage-server_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
||||||
|
files:
|
||||||
|
- README.md
|
||||||
|
- CHANGELOG.md
|
||||||
|
checksum:
|
||||||
|
name_template: 'checksums.txt'
|
||||||
|
snapshot:
|
||||||
|
name_template: "{{ .Version }}"
|
||||||
|
changelog:
|
||||||
|
sort: asc
|
||||||
|
filters:
|
||||||
|
exclude:
|
||||||
|
- '^docs:'
|
||||||
|
- '^test:'
|
||||||
|
nfpms:
|
||||||
|
- id: edge-cli
|
||||||
|
builds:
|
||||||
|
- "edge-cli"
|
||||||
|
package_name: edge-cli
|
||||||
|
homepage: https://forge.cadoles.com/arcad/edge
|
||||||
|
maintainer: William Petit <wpetit@cadoles.com>
|
||||||
|
description: |-
|
||||||
|
|
||||||
|
license: AGPL-3.0
|
||||||
|
formats:
|
||||||
|
- apk
|
||||||
|
- deb
|
||||||
|
- id: storage-server
|
||||||
|
builds:
|
||||||
|
- "storage-server"
|
||||||
|
package_name: storage-server
|
||||||
|
homepage: https://forge.cadoles.com/arcad/edge
|
||||||
|
maintainer: William Petit <wpetit@cadoles.com>
|
||||||
|
description: |-
|
||||||
|
|
||||||
|
license: AGPL-3.0
|
||||||
|
formats:
|
||||||
|
- apk
|
||||||
|
- deb
|
||||||
|
contents:
|
||||||
|
# Deb
|
||||||
|
- src: misc/packaging/systemd/storage-server.systemd.service
|
||||||
|
dst: /usr/lib/systemd/system/storage-server.service
|
||||||
|
packager: deb
|
||||||
|
- src: misc/packaging/systemd/storage-server.env
|
||||||
|
dst: /etc/storage-server/environ
|
||||||
|
type: config|noreplace
|
||||||
|
file_info:
|
||||||
|
mode: 0640
|
||||||
|
packager: deb
|
||||||
|
|
||||||
|
# APK
|
||||||
|
- src: misc/packaging/openrc/storage-server.openrc.sh
|
||||||
|
dst: /etc/init.d/storage-server
|
||||||
|
file_info:
|
||||||
|
mode: 0755
|
||||||
|
packager: apk
|
||||||
|
- src: misc/packaging/openrc/storage-server.conf
|
||||||
|
type: config|noreplace
|
||||||
|
dst: /etc/conf.d/storage-server
|
||||||
|
file_info:
|
||||||
|
mode: 0640
|
||||||
|
packager: apk
|
||||||
|
- src: misc/packaging/openrc/storage-server.logrotate.conf
|
||||||
|
dst: /etc/logrotate.d/storage-server
|
||||||
|
packager: apk
|
||||||
|
- dst: /var/lib/storage-server
|
||||||
|
type: dir
|
||||||
|
file_info:
|
||||||
|
mode: 0700
|
||||||
|
packager: apk
|
||||||
|
- dst: /var/log/storage-server
|
||||||
|
type: dir
|
||||||
|
file_info:
|
||||||
|
mode: 0700
|
||||||
|
scripts:
|
||||||
|
postinstall: "misc/packaging/common/postinstall-storage-server.sh"
|
|
@ -0,0 +1,50 @@
|
||||||
|
@Library('cadoles') _
|
||||||
|
|
||||||
|
pipeline {
|
||||||
|
agent {
|
||||||
|
dockerfile {
|
||||||
|
label 'docker'
|
||||||
|
filename 'Dockerfile'
|
||||||
|
dir 'misc/jenkins'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stages {
|
||||||
|
stage('Run unit tests') {
|
||||||
|
steps {
|
||||||
|
script {
|
||||||
|
sh 'make GOTEST_ARGS="-timeout 10m -count=1 -v" test'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Release') {
|
||||||
|
when {
|
||||||
|
anyOf {
|
||||||
|
branch 'master'
|
||||||
|
branch 'develop'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
script {
|
||||||
|
withCredentials([
|
||||||
|
usernamePassword([
|
||||||
|
credentialsId: 'forge-jenkins',
|
||||||
|
usernameVariable: 'GITEA_RELEASE_USERNAME',
|
||||||
|
passwordVariable: 'GITEA_RELEASE_PASSWORD'
|
||||||
|
])
|
||||||
|
]) {
|
||||||
|
sh 'make .mktools'
|
||||||
|
sh "export MKT_PROJECT_VERSION_BRANCH_NAME=${env.BRANCH_NAME}; make gitea-release"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
always {
|
||||||
|
cleanWs()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
93
Makefile
93
Makefile
|
@ -2,36 +2,52 @@ LINT_ARGS ?= --timeout 5m
|
||||||
GITCHLOG_ARGS ?=
|
GITCHLOG_ARGS ?=
|
||||||
SHELL := /bin/bash
|
SHELL := /bin/bash
|
||||||
|
|
||||||
GOTEST_ARGS ?= -short
|
GOTEST_ARGS ?= -short -timeout 60s
|
||||||
|
|
||||||
ESBUILD_VERSION ?= v0.17.5
|
ESBUILD_VERSION ?= v0.17.5
|
||||||
|
|
||||||
GIT_VERSION := $(shell git describe --always)
|
APP_PATH ?= misc/client-sdk-testsuite/dist
|
||||||
|
RUN_APP_ARGS ?=
|
||||||
|
RUN_STORAGE_SERVER_ARGS ?=
|
||||||
|
|
||||||
build: build-edge-cli
|
GORELEASER_VERSION ?= v1.21.2
|
||||||
|
GORELEASER_ARGS ?= release --snapshot --clean
|
||||||
|
|
||||||
watch:
|
SHELL := bash
|
||||||
go run -mod=readonly github.com/cortesi/modd/cmd/modd@latest
|
|
||||||
|
|
||||||
|
build: build-cli build-storage-server build-client-sdk-test-app
|
||||||
|
|
||||||
|
watch: tools/modd/bin/modd
|
||||||
|
tools/modd/bin/modd
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test: test-go
|
test: test-go
|
||||||
|
|
||||||
test-go:
|
test-go:
|
||||||
go test -v -count=1 $(GOTEST_ARGS) ./...
|
go test -count=1 $(GOTEST_ARGS) ./...
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
golangci-lint run --enable-all $(LINT_ARGS)
|
golangci-lint run --enable-all $(LINT_ARGS)
|
||||||
|
|
||||||
build-edge-cli: build-sdk
|
build-cli: build-sdk
|
||||||
CGO_ENABLED=0 go build \
|
CGO_ENABLED=0 go build \
|
||||||
-v \
|
-v \
|
||||||
-o ./bin/cli \
|
-o ./bin/cli \
|
||||||
./cmd/cli
|
./cmd/cli
|
||||||
|
|
||||||
|
build-storage-server: build-sdk
|
||||||
|
CGO_ENABLED=0 go build \
|
||||||
|
-v \
|
||||||
|
-o ./bin/storage-server \
|
||||||
|
./cmd/storage-server
|
||||||
|
|
||||||
|
build-client-sdk-test-app:
|
||||||
|
cd misc/client-sdk-testsuite && $(MAKE) dist
|
||||||
|
|
||||||
install-git-hooks:
|
install-git-hooks:
|
||||||
git config core.hooksPath .githooks
|
git config core.hooksPath .githooks
|
||||||
|
|
||||||
|
|
||||||
tools/esbuild/bin/esbuild:
|
tools/esbuild/bin/esbuild:
|
||||||
mkdir -p tools/esbuild/bin
|
mkdir -p tools/esbuild/bin
|
||||||
curl -fsSL https://esbuild.github.io/dl/$(ESBUILD_VERSION) | sh
|
curl -fsSL https://esbuild.github.io/dl/$(ESBUILD_VERSION) | sh
|
||||||
|
@ -44,41 +60,82 @@ pkg/sdk/client/dist/client.js: tools/esbuild/bin/esbuild node_modules
|
||||||
mkdir -p pkg/sdk/client/dist
|
mkdir -p pkg/sdk/client/dist
|
||||||
tools/esbuild/bin/esbuild \
|
tools/esbuild/bin/esbuild \
|
||||||
pkg/sdk/client/src/index.ts \
|
pkg/sdk/client/src/index.ts \
|
||||||
|
--minify \
|
||||||
--bundle \
|
--bundle \
|
||||||
--sourcemap \
|
--sourcemap \
|
||||||
--target=es2020 \
|
--target=es2015 \
|
||||||
--format=iife \
|
--format=iife \
|
||||||
--global-name=Edge \
|
--global-name=Edge \
|
||||||
--define:global=window \
|
--define:global=window \
|
||||||
--platform=browser \
|
--platform=browser \
|
||||||
--footer:js="Edge=Edge.default;" \
|
--loader:.svg=dataurl \
|
||||||
--outfile=pkg/sdk/client/dist/client.js
|
--outfile=pkg/sdk/client/dist/client.js
|
||||||
|
|
||||||
node_modules:
|
node_modules:
|
||||||
npm ci
|
npm ci
|
||||||
|
|
||||||
gitea-release: tools/gitea-release/bin/gitea-release.sh build
|
run-app: .env
|
||||||
|
( set -o allexport && source .env && set +o allexport && bin/cli app run -p $(APP_PATH) $$RUN_APP_ARGS )
|
||||||
|
|
||||||
|
run-storage-server: .env
|
||||||
|
( set -o allexport && source .env && set +o allexport && bin/storage-server run $$RUN_STORAGE_SERVER_ARGS )
|
||||||
|
|
||||||
|
.env:
|
||||||
|
cp .env.dist .env
|
||||||
|
|
||||||
|
gitea-release: .mktools tools/yq/bin/yq tools/gitea-release/bin/gitea-release.sh goreleaser build
|
||||||
mkdir -p .gitea-release
|
mkdir -p .gitea-release
|
||||||
rm -rf .gitea-release/*
|
rm -rf .gitea-release/*
|
||||||
|
|
||||||
cp bin/cli .gitea-release/edge_cli_amd64
|
cp dist/*.deb .gitea-release/
|
||||||
|
cp dist/*.tar.gz .gitea-release/
|
||||||
|
cp dist/*.apk .gitea-release/
|
||||||
|
cp CHANGELOG.md .gitea-release/
|
||||||
|
|
||||||
# Create client-sdk-testsuite package
|
# Create client-sdk-testsuite package
|
||||||
.gitea-release/edge_cli_amd64 app package -d ./misc/client-sdk-testsuite/dist -o .gitea-release
|
tools/yq/bin/yq -i '.version = "$(MKT_PROJECT_VERSION)"' ./misc/client-sdk-testsuite/dist/manifest.yml
|
||||||
|
bin/cli app package -d ./misc/client-sdk-testsuite/dist -o .gitea-release
|
||||||
|
|
||||||
GITEA_RELEASE_PROJECT="edge" \
|
GITEA_RELEASE_PROJECT="edge" \
|
||||||
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="$(MKT_PROJECT_VERSION)" \
|
||||||
GITEA_RELEASE_NAME="$(GIT_VERSION)" \
|
GITEA_RELEASE_NAME="$(MKT_PROJECT_VERSION)" \
|
||||||
GITEA_RELEASE_COMMITISH_TARGET="$(GIT_VERSION)" \
|
GITEA_RELEASE_COMMITISH_TARGET="$$(git rev-parse HEAD)" \
|
||||||
GITEA_RELEASE_IS_DRAFT="false" \
|
GITEA_RELEASE_IS_DRAFT="false" \
|
||||||
GITEA_RELEASE_IS_PRERELEASE="true" \
|
GITEA_RELEASE_IS_PRERELEASE="true" \
|
||||||
GITEA_RELEASE_BODY="" \
|
GITEA_RELEASE_BODY="" \
|
||||||
GITEA_RELEASE_ATTACHMENTS="$(shell find .gitea-release/* -type f)" \
|
GITEA_RELEASE_ATTACHMENTS="$$(find .gitea-release/* -type f)" \
|
||||||
tools/gitea-release/bin/gitea-release.sh
|
tools/gitea-release/bin/gitea-release.sh
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
tools/yq/bin/yq:
|
||||||
|
mkdir -p tools/yq/bin
|
||||||
|
curl -L --output tools/yq/bin/yq https://github.com/mikefarah/yq/releases/download/v4.31.1/yq_linux_amd64
|
||||||
|
chmod +x tools/yq/bin/yq
|
||||||
|
|
||||||
|
tools/modd/bin/modd:
|
||||||
|
mkdir -p tools/modd/bin
|
||||||
|
GOBIN=$(PWD)/tools/modd/bin go install -mod=readonly github.com/cortesi/modd/cmd/modd@latest
|
||||||
|
|
||||||
|
.PHONY: goreleaser
|
||||||
|
goreleaser: .env .mktools changelog
|
||||||
|
( set -o allexport && source .env && set +o allexport && curl -sfL https://goreleaser.com/static/run | VERSION="$(GORELEASER_VERSION)" GORELEASER_CURRENT_TAG="$$MKT_PROJECT_VERSION" bash /dev/stdin $(GORELEASER_ARGS) )
|
||||||
|
|
||||||
|
.PHONY: changelog
|
||||||
|
changelog: .mktools
|
||||||
|
$(MAKE) MKT_GIT_CHGLOG_ARGS='--next-tag "$(MKT_PROJECT_VERSION)" --tag-filter-pattern "$(MKT_PROJECT_VERSION_CHANNEL)" --output CHANGELOG.md' mkt-changelog
|
||||||
|
|
||||||
|
.PHONY: mktools
|
||||||
|
mktools:
|
||||||
|
rm -rf .mktools
|
||||||
|
curl -q https://forge.cadoles.com/Cadoles/mktools/raw/branch/master/install.sh | $(SHELL)
|
||||||
|
|
||||||
|
.mktools:
|
||||||
|
$(MAKE) mktools
|
||||||
|
|
||||||
|
-include .mktools/*.mk
|
|
@ -0,0 +1,146 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
"flag"
|
||||||
|
"io"
|
||||||
|
mrand "math/rand"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/storage"
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/storage/driver"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"gitlab.com/wpetit/goweb/logger"
|
||||||
|
|
||||||
|
_ "forge.cadoles.com/arcad/edge/pkg/storage/driver/cache"
|
||||||
|
_ "forge.cadoles.com/arcad/edge/pkg/storage/driver/rpc"
|
||||||
|
_ "forge.cadoles.com/arcad/edge/pkg/storage/driver/sqlite"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
dsn string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.StringVar(&dsn, "dsn", "cache://./test-cache.sqlite?driver=sqlite&_pragma=foreign_keys(1)&_pragma=journal_mode=wal&bigCacheShards=32&bigCacheHardMaxCacheSize=128&bigCacheMaxEntrySize=125&bigCacheMaxEntriesInWindow=200000", "blobstore dsn")
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
logger.SetLevel(logger.LevelDebug)
|
||||||
|
|
||||||
|
blobStore, err := driver.NewBlobStore(dsn)
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatal(ctx, "could not create blobstore", logger.CapturedE(errors.WithStack(err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
bucket, err := blobStore.OpenBucket(ctx, "default")
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatal(ctx, "could not open bucket", logger.CapturedE(errors.WithStack(err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := bucket.Close(); err != nil {
|
||||||
|
logger.Fatal(ctx, "could not close bucket", logger.CapturedE(errors.WithStack(err)))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go readRandomBlobs(ctx, bucket)
|
||||||
|
|
||||||
|
for {
|
||||||
|
writeRandomBlob(ctx, bucket)
|
||||||
|
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
|
||||||
|
size, err := bucket.Size(ctx)
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatal(ctx, "could not retrieve bucket size", logger.CapturedE(errors.WithStack(err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Debug(ctx, "bucket stats", logger.F("size", size))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func readRandomBlobs(ctx context.Context, bucket storage.BlobBucket) {
|
||||||
|
for {
|
||||||
|
infos, err := bucket.List(ctx)
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatal(ctx, "could not list blobs", logger.CapturedE(errors.WithStack(err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
total := len(infos)
|
||||||
|
if total == 0 {
|
||||||
|
logger.Debug(ctx, "no blob yet")
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
blob := infos[mrand.Intn(total)]
|
||||||
|
|
||||||
|
readBlob(ctx, bucket, blob.ID())
|
||||||
|
|
||||||
|
time.Sleep(250 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func readBlob(ctx context.Context, bucket storage.BlobBucket, blobID storage.BlobID) {
|
||||||
|
ctx = logger.With(ctx, logger.F("blobID", blobID))
|
||||||
|
|
||||||
|
reader, err := bucket.NewReader(ctx, blobID)
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatal(ctx, "could not create reader", logger.CapturedE(errors.WithStack(err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := reader.Close(); err != nil {
|
||||||
|
logger.Fatal(ctx, "could not close reader", logger.CapturedE(errors.WithStack(err)))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if _, err := io.ReadAll(reader); err != nil {
|
||||||
|
logger.Fatal(ctx, "could not read blob", logger.CapturedE(errors.WithStack(err)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeRandomBlob(ctx context.Context, bucket storage.BlobBucket) {
|
||||||
|
blobID := storage.NewBlobID()
|
||||||
|
buff := make([]byte, 10*1024)
|
||||||
|
|
||||||
|
writer, err := bucket.NewWriter(ctx, blobID)
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatal(ctx, "could not create writer", logger.CapturedE(errors.WithStack(err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := writer.Close(); err != nil {
|
||||||
|
logger.Fatal(ctx, "could not close writer", logger.CapturedE(errors.WithStack(err)))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if _, err := rand.Read(buff); err != nil {
|
||||||
|
logger.Fatal(ctx, "could not read random data", logger.CapturedE(errors.WithStack(err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := writer.Write(buff); err != nil {
|
||||||
|
logger.Fatal(ctx, "could not write blob", logger.CapturedE(errors.WithStack(err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
printMemUsage(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func printMemUsage(ctx context.Context) {
|
||||||
|
var m runtime.MemStats
|
||||||
|
runtime.ReadMemStats(&m)
|
||||||
|
logger.Debug(
|
||||||
|
ctx, "memory usage",
|
||||||
|
logger.F("alloc", m.Alloc/1024/1024),
|
||||||
|
logger.F("totalAlloc", m.TotalAlloc/1024/1024),
|
||||||
|
logger.F("sys", m.Sys/1024/1024),
|
||||||
|
logger.F("numGC", m.NumGC),
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/app"
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/app/metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
var manifestMetadataValidators = []app.MetadataValidator{
|
||||||
|
metadata.WithMinimumRoleValidator("visitor", "user", "superuser", "admin", "superadmin"),
|
||||||
|
metadata.WithNamedPathsValidator(metadata.NamedPathAdmin, metadata.NamedPathIcon),
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"username": "superadmin",
|
||||||
|
"algo": "argon2id",
|
||||||
|
"password": "$argon2id$v=19$m=65536,t=3,p=2$cWOxfEyBy4EyKZR5usB2Pw$xG+Z/E2DUJP9kF0s1fhZjIuP03gFQ65dP7pHRJz7eR8",
|
||||||
|
"claims": {
|
||||||
|
"edge_entrypoint": "edge",
|
||||||
|
"edge_role": "superadmin",
|
||||||
|
"edge_tenant": "dev.cli",
|
||||||
|
"preferred_username": "SuperAdmin",
|
||||||
|
"sub": "superadmin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": "admin",
|
||||||
|
"algo": "argon2id",
|
||||||
|
"password": "$argon2id$v=19$m=65536,t=3,p=2$WXXc4ECnkej6WO7f0Xya6Q$UG2wcGltJcuW0cNTR85mAl65tI1kFWMMw7ADS2FMOvY",
|
||||||
|
"claims": {
|
||||||
|
"edge_entrypoint": "edge",
|
||||||
|
"edge_role": "admin",
|
||||||
|
"edge_tenant": "dev.cli",
|
||||||
|
"preferred_username": "Admin",
|
||||||
|
"sub": "admin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": "superuser",
|
||||||
|
"algo": "argon2id",
|
||||||
|
"password": "$argon2id$v=19$m=65536,t=3,p=2$gkDAWCzfU23+un3x0ny+YA$L/NSPrd5iKPK/UnSCKfSz4EO+v94N3LTLky4QGJOfpI",
|
||||||
|
"claims": {
|
||||||
|
"edge_entrypoint": "edge",
|
||||||
|
"edge_role": "superuser",
|
||||||
|
"edge_tenant": "dev.cli",
|
||||||
|
"preferred_username": "SuperUser",
|
||||||
|
"sub": "superuser"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": "user",
|
||||||
|
"algo": "argon2id",
|
||||||
|
"password": "$argon2id$v=19$m=65536,t=3,p=2$DhUm9qXUKP35Lzp5M37eZA$2+h6yDxSTHZqFZIuI7JZfFWozwrObna8a8yCgEEPlPE",
|
||||||
|
"claims": {
|
||||||
|
"edge_entrypoint": "edge",
|
||||||
|
"edge_role": "user",
|
||||||
|
"edge_tenant": "dev.cli",
|
||||||
|
"preferred_username": "User",
|
||||||
|
"sub": "user"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,47 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/module/auth/http/passwd"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/module/auth/http/passwd/argon2id"
|
||||||
|
_ "forge.cadoles.com/arcad/edge/pkg/module/auth/http/passwd/plain"
|
||||||
|
)
|
||||||
|
|
||||||
|
func HashPasswordCommand() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
|
Name: "hash-password",
|
||||||
|
Usage: "Hash the provided password with the specified algorithm",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "password",
|
||||||
|
Usage: "hash `PASSWORD`",
|
||||||
|
Aliases: []string{"p"},
|
||||||
|
Value: "",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "algorithm",
|
||||||
|
Usage: fmt.Sprintf("use `ALGORITHM` to hash password (available: %v)", passwd.Algorithms()),
|
||||||
|
Aliases: []string{"a"},
|
||||||
|
Value: string(argon2id.Algo),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.Context) error {
|
||||||
|
algo := ctx.String("algorithm")
|
||||||
|
password := ctx.String("password")
|
||||||
|
|
||||||
|
hash, err := passwd.Hash(passwd.Algo(algo), password)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(hash)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/app"
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/bundle"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func InfoCommand() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
|
Name: "info",
|
||||||
|
Usage: "Print app manifest informations",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "path",
|
||||||
|
Usage: "use `PATH` as app bundle (zip, zim or directory bundle)",
|
||||||
|
Aliases: []string{"p"},
|
||||||
|
Value: "",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.Context) error {
|
||||||
|
appPath := ctx.String("path")
|
||||||
|
|
||||||
|
bundle, err := bundle.FromPath(appPath)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not load app bundle")
|
||||||
|
}
|
||||||
|
|
||||||
|
manifest, err := app.LoadManifest(bundle)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not load app manifest")
|
||||||
|
}
|
||||||
|
|
||||||
|
if valid, err := manifest.Validate(manifestMetadataValidators...); !valid {
|
||||||
|
return errors.Wrap(err, "invalid app manifest")
|
||||||
|
}
|
||||||
|
|
||||||
|
encoder := yaml.NewEncoder(os.Stdout)
|
||||||
|
|
||||||
|
if err := encoder.Encode(manifest); err != nil {
|
||||||
|
return errors.Wrap(err, "could not encode manifest")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := encoder.Close(); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -47,11 +47,15 @@ func PackageCommand() *cli.Command {
|
||||||
|
|
||||||
bundle := bundle.NewDirectoryBundle(appDir)
|
bundle := bundle.NewDirectoryBundle(appDir)
|
||||||
|
|
||||||
manifest, err := app.LoadAppManifest(bundle)
|
manifest, err := app.LoadManifest(bundle)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "could not load app manifest")
|
return errors.Wrap(err, "could not load app manifest")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if valid, err := manifest.Validate(manifestMetadataValidators...); !valid {
|
||||||
|
return errors.Wrap(err, "invalid app manifest")
|
||||||
|
}
|
||||||
|
|
||||||
if err := os.MkdirAll(outputDir, 0o755); err != nil {
|
if err := os.MkdirAll(outputDir, 0o755); err != nil {
|
||||||
return errors.Wrapf(err, "could not create directory ''%s'", outputDir)
|
return errors.Wrapf(err, "could not create directory ''%s'", outputDir)
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,8 @@ func Root() *cli.Command {
|
||||||
Subcommands: []*cli.Command{
|
Subcommands: []*cli.Command{
|
||||||
RunCommand(),
|
RunCommand(),
|
||||||
PackageCommand(),
|
PackageCommand(),
|
||||||
|
HashPasswordCommand(),
|
||||||
|
InfoCommand(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,40 +1,81 @@
|
||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/app"
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/bus"
|
||||||
"forge.cadoles.com/arcad/edge/pkg/bus/memory"
|
"forge.cadoles.com/arcad/edge/pkg/bus/memory"
|
||||||
appHTTP "forge.cadoles.com/arcad/edge/pkg/http"
|
appHTTP "forge.cadoles.com/arcad/edge/pkg/http"
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/jwtutil"
|
||||||
"forge.cadoles.com/arcad/edge/pkg/module"
|
"forge.cadoles.com/arcad/edge/pkg/module"
|
||||||
"forge.cadoles.com/arcad/edge/pkg/module/cast"
|
appModule "forge.cadoles.com/arcad/edge/pkg/module/app"
|
||||||
"forge.cadoles.com/arcad/edge/pkg/storage/sqlite"
|
appModuleMemory "forge.cadoles.com/arcad/edge/pkg/module/app/memory"
|
||||||
|
authModule "forge.cadoles.com/arcad/edge/pkg/module/auth"
|
||||||
|
authHTTP "forge.cadoles.com/arcad/edge/pkg/module/auth/http"
|
||||||
|
authModuleMiddleware "forge.cadoles.com/arcad/edge/pkg/module/auth/middleware"
|
||||||
|
blobModule "forge.cadoles.com/arcad/edge/pkg/module/blob"
|
||||||
|
castModule "forge.cadoles.com/arcad/edge/pkg/module/cast"
|
||||||
|
fetchModule "forge.cadoles.com/arcad/edge/pkg/module/fetch"
|
||||||
|
netModule "forge.cadoles.com/arcad/edge/pkg/module/net"
|
||||||
|
rpcModule "forge.cadoles.com/arcad/edge/pkg/module/rpc"
|
||||||
|
shareModule "forge.cadoles.com/arcad/edge/pkg/module/share"
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/storage"
|
||||||
"gitlab.com/wpetit/goweb/logger"
|
"gitlab.com/wpetit/goweb/logger"
|
||||||
|
|
||||||
"forge.cadoles.com/arcad/edge/pkg/bundle"
|
"forge.cadoles.com/arcad/edge/pkg/bundle"
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/chi/v5/middleware"
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
|
"github.com/lestrrat-go/jwx/v2/jwa"
|
||||||
|
"github.com/lestrrat-go/jwx/v2/jwk"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
"github.com/wlynxg/anet"
|
||||||
|
|
||||||
_ "modernc.org/sqlite"
|
_ "embed"
|
||||||
|
|
||||||
|
_ "forge.cadoles.com/arcad/edge/pkg/module/auth/http/passwd/argon2id"
|
||||||
|
_ "forge.cadoles.com/arcad/edge/pkg/module/auth/http/passwd/plain"
|
||||||
|
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/storage/driver"
|
||||||
|
|
||||||
|
// Register storage drivers
|
||||||
|
_ "forge.cadoles.com/arcad/edge/pkg/storage/driver/cache"
|
||||||
|
_ "forge.cadoles.com/arcad/edge/pkg/storage/driver/rpc"
|
||||||
|
_ "forge.cadoles.com/arcad/edge/pkg/storage/driver/sqlite"
|
||||||
|
|
||||||
|
// Register casting device supported types
|
||||||
|
_ "forge.cadoles.com/arcad/edge/pkg/module/cast/arcast"
|
||||||
|
_ "forge.cadoles.com/arcad/edge/pkg/module/cast/chromecast"
|
||||||
|
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/storage/share"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var dummySecret = []byte("not_so_secret")
|
||||||
|
|
||||||
func RunCommand() *cli.Command {
|
func RunCommand() *cli.Command {
|
||||||
return &cli.Command{
|
return &cli.Command{
|
||||||
Name: "run",
|
Name: "run",
|
||||||
Usage: "Run the specified app bundle",
|
Usage: "Run the specified app bundle",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.StringFlag{
|
&cli.StringSliceFlag{
|
||||||
Name: "path",
|
Name: "path",
|
||||||
Usage: "use `PATH` as app bundle (zipped bundle or directory)",
|
Usage: "use `PATH` as app bundle (zipped bundle or directory)",
|
||||||
Aliases: []string{"p"},
|
Aliases: []string{"p"},
|
||||||
Value: ".",
|
Value: cli.NewStringSlice("."),
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "address",
|
Name: "address",
|
||||||
Usage: "use `ADDRESS` as http server listening address",
|
Usage: "use `ADDRESS` as http server base listening address",
|
||||||
Aliases: []string{"a"},
|
Aliases: []string{"a"},
|
||||||
Value: ":8080",
|
Value: ":8080",
|
||||||
},
|
},
|
||||||
|
@ -49,75 +90,407 @@ func RunCommand() *cli.Command {
|
||||||
Value: 0,
|
Value: 0,
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "storage-file",
|
Name: "blobstore-dsn",
|
||||||
Usage: "use `FILE` for SQLite storage database",
|
Usage: "use `DSN` for blob storage",
|
||||||
Value: "data.sqlite",
|
EnvVars: []string{"EDGE_BLOBSTORE_DSN"},
|
||||||
|
Value: "sqlite://.edge/%APPID%/data.sqlite?_pragma=foreign_keys(1)&_pragma=busy_timeout=60000",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "documentstore-dsn",
|
||||||
|
Usage: "use `DSN` for document storage",
|
||||||
|
EnvVars: []string{"EDGE_DOCUMENTSTORE_DSN"},
|
||||||
|
Value: "sqlite://.edge/%APPID%/data.sqlite?_pragma=foreign_keys(1)&_pragma=busy_timeout=60000",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "sharestore-dsn",
|
||||||
|
Usage: "use `DSN` for share storage",
|
||||||
|
EnvVars: []string{"EDGE_SHARESTORE_DSN"},
|
||||||
|
Value: "sqlite://.edge/share.sqlite?_pragma=foreign_keys(1)&_pragma=busy_timeout=60000",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "accounts-file",
|
||||||
|
Usage: "use `FILE` as local accounts",
|
||||||
|
Value: ".edge/%APPID%/accounts.json",
|
||||||
|
},
|
||||||
|
&cli.Int64Flag{
|
||||||
|
Name: "max-upload-size",
|
||||||
|
Usage: "use `MAX-UPLOAD-SIZE` as blob max upload size",
|
||||||
|
Value: 128 << (10 * 2), // 128Mb
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: func(ctx *cli.Context) error {
|
Action: func(ctx *cli.Context) error {
|
||||||
address := ctx.String("address")
|
address := ctx.String("address")
|
||||||
path := ctx.String("path")
|
paths := ctx.StringSlice("path")
|
||||||
|
|
||||||
logFormat := ctx.String("log-format")
|
logFormat := ctx.String("log-format")
|
||||||
logLevel := ctx.Int("log-level")
|
logLevel := ctx.Int("log-level")
|
||||||
storageFile := ctx.String("storage-file")
|
blobstoreDSN := ctx.String("blobstore-dsn")
|
||||||
|
documentstoreDSN := ctx.String("documentstore-dsn")
|
||||||
|
shareStoreDSN := ctx.String("sharestore-dsn")
|
||||||
|
accountsFile := ctx.String("accounts-file")
|
||||||
|
maxUploadSize := ctx.Int64("max-upload-size")
|
||||||
|
|
||||||
logger.SetFormat(logger.Format(logFormat))
|
logger.SetFormat(logger.Format(logFormat))
|
||||||
logger.SetLevel(logger.Level(logLevel))
|
logger.SetLevel(logger.Level(logLevel))
|
||||||
|
|
||||||
cmdCtx := ctx.Context
|
cmdCtx := ctx.Context
|
||||||
|
|
||||||
absPath, err := filepath.Abs(path)
|
host, portStr, err := net.SplitHostPort(address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "could not resolve path '%s'", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Info(cmdCtx, "opening app bundle", logger.F("path", absPath))
|
|
||||||
|
|
||||||
bundle, err := bundle.FromPath(path)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "could not open path '%s' as an app bundle", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
mux := chi.NewMux()
|
|
||||||
|
|
||||||
mux.Use(middleware.Logger)
|
|
||||||
|
|
||||||
bus := memory.NewBus()
|
|
||||||
|
|
||||||
db, err := sql.Open("sqlite", storageFile)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "could not open database with path '%s'", storageFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
documentStore := sqlite.NewDocumentStoreWithDB(db)
|
|
||||||
blobStore := sqlite.NewBlobStoreWithDB(db)
|
|
||||||
|
|
||||||
handler := appHTTP.NewHandler(
|
|
||||||
appHTTP.WithBus(bus),
|
|
||||||
appHTTP.WithServerModules(
|
|
||||||
module.ContextModuleFactory(),
|
|
||||||
module.ConsoleModuleFactory(),
|
|
||||||
cast.CastModuleFactory(),
|
|
||||||
module.LifecycleModuleFactory(bus),
|
|
||||||
module.NetModuleFactory(bus),
|
|
||||||
module.RPCModuleFactory(bus),
|
|
||||||
module.StoreModuleFactory(documentStore),
|
|
||||||
module.BlobModuleFactory(bus, blobStore),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
if err := handler.Load(bundle); err != nil {
|
|
||||||
return errors.Wrap(err, "could not load app bundle")
|
|
||||||
}
|
|
||||||
|
|
||||||
mux.Handle("/*", handler)
|
|
||||||
|
|
||||||
logger.Info(cmdCtx, "listening", logger.F("address", address))
|
|
||||||
|
|
||||||
if err := http.ListenAndServe(address, mux); err != nil {
|
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
port, err := strconv.ParseUint(portStr, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
manifests := make([]*app.Manifest, len(paths))
|
||||||
|
for idx, pth := range paths {
|
||||||
|
bdl, err := bundle.FromPath(pth)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
manifest, err := app.LoadManifest(bdl)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
manifests[idx] = manifest
|
||||||
|
}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
for idx, p := range paths {
|
||||||
|
wg.Add(1)
|
||||||
|
|
||||||
|
go func(path string, basePort uint64, appIndex int) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
port := basePort + uint64(appIndex)
|
||||||
|
address := fmt.Sprintf("%s:%d", host, port)
|
||||||
|
appsRepository := newAppRepository(host, basePort, manifests...)
|
||||||
|
|
||||||
|
appCtx := logger.With(cmdCtx, logger.F("address", address))
|
||||||
|
|
||||||
|
if err := runApp(appCtx, path, address, documentstoreDSN, blobstoreDSN, shareStoreDSN, accountsFile, appsRepository, maxUploadSize); err != nil {
|
||||||
|
logger.Error(appCtx, "could not run app", logger.CapturedE(errors.WithStack(err)))
|
||||||
|
}
|
||||||
|
}(p, port, idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func runApp(ctx context.Context, path, address, documentStoreDSN, blobStoreDSN, shareStoreDSN, accountsFile string, appRepository appModule.Repository, maxUploadSize int64) error {
|
||||||
|
absPath, err := filepath.Abs(path)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "could not resolve path '%s'", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info(ctx, "opening app bundle", logger.F("path", absPath))
|
||||||
|
|
||||||
|
bundle, err := bundle.FromPath(path)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "could not open path '%s' as an app bundle", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
manifest, err := app.LoadManifest(bundle)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not load manifest from app bundle")
|
||||||
|
}
|
||||||
|
|
||||||
|
if valid, err := manifest.Validate(manifestMetadataValidators...); !valid {
|
||||||
|
return errors.Wrap(err, "invalid app manifest")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = logger.With(ctx, logger.F("appID", manifest.ID))
|
||||||
|
|
||||||
|
// Add auth handler
|
||||||
|
key, err := jwtutil.NewSymmetricKey(dummySecret)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
deps := &moduleDeps{}
|
||||||
|
funcs := []ModuleDepFunc{
|
||||||
|
initAppID(manifest),
|
||||||
|
initMemoryBus,
|
||||||
|
initDatastores(documentStoreDSN, blobStoreDSN, shareStoreDSN, manifest.ID),
|
||||||
|
initAccounts(accountsFile, manifest.ID),
|
||||||
|
initAppRepository(appRepository),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fn := range funcs {
|
||||||
|
if err := fn(deps); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handler := appHTTP.NewHandler(
|
||||||
|
appHTTP.WithBus(deps.Bus),
|
||||||
|
appHTTP.WithServerModules(getServerModules(deps)...),
|
||||||
|
appHTTP.WithHTTPMounts(
|
||||||
|
appModule.Mount(appRepository),
|
||||||
|
authModule.Mount(
|
||||||
|
authHTTP.NewLocalHandler(
|
||||||
|
key,
|
||||||
|
jwa.HS256,
|
||||||
|
authHTTP.WithRoutePrefix("/auth"),
|
||||||
|
authHTTP.WithAccounts(deps.Accounts...),
|
||||||
|
),
|
||||||
|
authModule.WithJWT(func() (jwk.Set, error) {
|
||||||
|
return jwtutil.NewSymmetricKeySet(dummySecret)
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
blobModule.Mount(maxUploadSize), // 10Mb,
|
||||||
|
fetchModule.Mount(),
|
||||||
|
),
|
||||||
|
appHTTP.WithHTTPMiddlewares(
|
||||||
|
authModuleMiddleware.DefaultUser(key, jwa.HS256, authModuleMiddleware.WithAnonymousUser()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if err := handler.Load(ctx, bundle); err != nil {
|
||||||
|
return errors.Wrap(err, "could not load app bundle")
|
||||||
|
}
|
||||||
|
|
||||||
|
router := chi.NewRouter()
|
||||||
|
router.Use(middleware.Logger)
|
||||||
|
router.Use(middleware.Compress(5))
|
||||||
|
|
||||||
|
// Add app handler
|
||||||
|
router.Handle("/*", handler)
|
||||||
|
|
||||||
|
logger.Info(ctx, "listening", logger.F("address", address))
|
||||||
|
|
||||||
|
if err := http.ListenAndServe(address, router); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type moduleDeps struct {
|
||||||
|
AppID app.ID
|
||||||
|
Bus bus.Bus
|
||||||
|
DocumentStore storage.DocumentStore
|
||||||
|
BlobStore storage.BlobStore
|
||||||
|
AppRepository appModule.Repository
|
||||||
|
ShareStore share.Store
|
||||||
|
Accounts []authHTTP.LocalAccount
|
||||||
|
}
|
||||||
|
|
||||||
|
type ModuleDepFunc func(*moduleDeps) error
|
||||||
|
|
||||||
|
func getServerModules(deps *moduleDeps) []app.ServerModuleFactory {
|
||||||
|
return []app.ServerModuleFactory{
|
||||||
|
module.LifecycleModuleFactory(),
|
||||||
|
module.ContextModuleFactory(),
|
||||||
|
module.ConsoleModuleFactory(),
|
||||||
|
castModule.CastModuleFactory(),
|
||||||
|
netModule.ModuleFactory(deps.Bus),
|
||||||
|
rpcModule.ModuleFactory(deps.Bus),
|
||||||
|
module.StoreModuleFactory(deps.DocumentStore),
|
||||||
|
blobModule.ModuleFactory(deps.Bus, deps.BlobStore),
|
||||||
|
authModule.ModuleFactory(
|
||||||
|
authModule.WithJWT(func() (jwk.Set, error) {
|
||||||
|
return jwtutil.NewSymmetricKeySet(dummySecret)
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
appModule.ModuleFactory(deps.AppRepository),
|
||||||
|
fetchModule.ModuleFactory(deps.Bus),
|
||||||
|
shareModule.ModuleFactory(deps.AppID, deps.ShareStore),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ensureDir(path string) error {
|
||||||
|
if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func injectAppID(str string, appID app.ID) string {
|
||||||
|
return strings.ReplaceAll(str, "%APPID%", string(appID))
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:embed default-accounts.json
|
||||||
|
var defaultAccounts []byte
|
||||||
|
|
||||||
|
func loadLocalAccounts(path string) ([]authHTTP.LocalAccount, error) {
|
||||||
|
if err := ensureDir(path); err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
if err := os.WriteFile(path, defaultAccounts, 0o640); err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data = defaultAccounts
|
||||||
|
} else {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var accounts []authHTTP.LocalAccount
|
||||||
|
|
||||||
|
if err := json.Unmarshal(data, &accounts); err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return accounts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func findMatchingDeviceAddress(ctx context.Context, from string, defaultAddr string) (string, error) {
|
||||||
|
if from == "" {
|
||||||
|
return defaultAddr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fromIP := net.ParseIP(from)
|
||||||
|
|
||||||
|
if fromIP == nil {
|
||||||
|
return defaultAddr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ifaces, err := anet.Interfaces()
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ifa := range ifaces {
|
||||||
|
addrs, err := anet.InterfaceAddrsByInterface(&ifa)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(
|
||||||
|
ctx, "could not retrieve iface adresses",
|
||||||
|
logger.CapturedE(errors.WithStack(err)), logger.F("iface", ifa.Name),
|
||||||
|
)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, addr := range addrs {
|
||||||
|
ip, network, err := net.ParseCIDR(addr.String())
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(
|
||||||
|
ctx, "could not parse address",
|
||||||
|
logger.CapturedE(errors.WithStack(err)), logger.F("address", addr.String()),
|
||||||
|
)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !network.Contains(fromIP) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if ip.To4() == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return ip.To4().String(), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultAddr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAppRepository(host string, basePort uint64, manifests ...*app.Manifest) *appModuleMemory.Repository {
|
||||||
|
if host == "" {
|
||||||
|
host = "127.0.0.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
return appModuleMemory.NewRepository(
|
||||||
|
func(ctx context.Context, id app.ID, from string) (string, error) {
|
||||||
|
appIndex := 0
|
||||||
|
for i := 0; i < len(manifests); i++ {
|
||||||
|
if manifests[i].ID == id {
|
||||||
|
appIndex = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addr, err := findMatchingDeviceAddress(ctx, from, host)
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("http://%s:%d", addr, int(basePort)+appIndex), nil
|
||||||
|
},
|
||||||
|
manifests...,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func initAppID(manifest *app.Manifest) ModuleDepFunc {
|
||||||
|
return func(deps *moduleDeps) error {
|
||||||
|
deps.AppID = manifest.ID
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func initAppRepository(repo appModule.Repository) ModuleDepFunc {
|
||||||
|
return func(deps *moduleDeps) error {
|
||||||
|
deps.AppRepository = repo
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func initMemoryBus(deps *moduleDeps) error {
|
||||||
|
deps.Bus = memory.NewBus()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func initDatastores(documentStoreDSN, blobStoreDSN, shareStoreDSN string, appID app.ID) ModuleDepFunc {
|
||||||
|
return func(deps *moduleDeps) error {
|
||||||
|
documentStoreDSN = injectAppID(documentStoreDSN, appID)
|
||||||
|
|
||||||
|
documentStore, err := driver.NewDocumentStore(documentStoreDSN)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
deps.DocumentStore = documentStore
|
||||||
|
|
||||||
|
blobStoreDSN = injectAppID(blobStoreDSN, appID)
|
||||||
|
|
||||||
|
blobStore, err := driver.NewBlobStore(blobStoreDSN)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
deps.BlobStore = blobStore
|
||||||
|
|
||||||
|
shareStore, err := driver.NewShareStore(shareStoreDSN)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
deps.ShareStore = shareStore
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func initAccounts(accountsFile string, appID app.ID) ModuleDepFunc {
|
||||||
|
return func(deps *moduleDeps) error {
|
||||||
|
accountsFile = injectAppID(accountsFile, appID)
|
||||||
|
|
||||||
|
accounts, err := loadLocalAccounts(accountsFile)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not load local accounts")
|
||||||
|
}
|
||||||
|
|
||||||
|
deps.Accounts = accounts
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
package cast
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/module/cast"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func LoadURLCommand() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
|
Name: "load-url",
|
||||||
|
Usage: "Load `URL` in casting device",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "device",
|
||||||
|
Aliases: []string{"d"},
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "url",
|
||||||
|
Aliases: []string{"u"},
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
&cli.DurationFlag{
|
||||||
|
Name: "timeout",
|
||||||
|
Aliases: []string{"t"},
|
||||||
|
Value: 10 * time.Second,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.Context) error {
|
||||||
|
device := ctx.String("device")
|
||||||
|
url := ctx.String("url")
|
||||||
|
timeout := ctx.Duration("timeout")
|
||||||
|
|
||||||
|
timeoutCtx, cancel := context.WithTimeout(ctx.Context, timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := cast.StopCast(timeoutCtx, device); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cast.LoadURL(timeoutCtx, device, url); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package cast
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
|
||||||
|
// Register casting device supported types
|
||||||
|
_ "forge.cadoles.com/arcad/edge/pkg/module/cast/arcast"
|
||||||
|
_ "forge.cadoles.com/arcad/edge/pkg/module/cast/chromecast"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Root() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
|
Name: "cast",
|
||||||
|
Usage: "Cast related commands",
|
||||||
|
Subcommands: []*cli.Command{
|
||||||
|
ScanCommand(),
|
||||||
|
LoadURLCommand(),
|
||||||
|
StatusCommand(),
|
||||||
|
StopCastCommand(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package cast
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/module/cast"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ScanCommand() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
|
Name: "scan",
|
||||||
|
Usage: "Scan network for casting devices",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.DurationFlag{
|
||||||
|
Name: "timeout",
|
||||||
|
Aliases: []string{"t"},
|
||||||
|
Value: 10 * time.Second,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.Context) error {
|
||||||
|
timeout := ctx.Duration("timeout")
|
||||||
|
|
||||||
|
searchCtx, cancel := context.WithTimeout(ctx.Context, timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
devices, err := cast.SearchDevices(searchCtx)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("%+v", errors.WithStack(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, dev := range devices {
|
||||||
|
log.Printf("[DEVICE] %s %s %s %s:%d", dev.DeviceID(), dev.DeviceType(), dev.DeviceName(), dev.DeviceHost().String(), dev.DevicePort())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package cast
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/module/cast"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func StatusCommand() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
|
Name: "status",
|
||||||
|
Usage: "Retrieve casting device status",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "device",
|
||||||
|
Aliases: []string{"d"},
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
&cli.DurationFlag{
|
||||||
|
Name: "timeout",
|
||||||
|
Aliases: []string{"t"},
|
||||||
|
Value: 10 * time.Second,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.Context) error {
|
||||||
|
device := ctx.String("device")
|
||||||
|
timeout := ctx.Duration("timeout")
|
||||||
|
|
||||||
|
getStatusCtx, cancel := context.WithTimeout(ctx.Context, timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
status, err := cast.GetStatus(getStatusCtx, device)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[STATUS] %s %s", status.Title(), status.State())
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package cast
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/module/cast"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func StopCastCommand() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
|
Name: "stop-cast",
|
||||||
|
Usage: "Stop casting process on device",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "device",
|
||||||
|
Aliases: []string{"d"},
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
&cli.DurationFlag{
|
||||||
|
Name: "timeout",
|
||||||
|
Aliases: []string{"t"},
|
||||||
|
Value: 10 * time.Second,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.Context) error {
|
||||||
|
device := ctx.String("device")
|
||||||
|
timeout := ctx.Duration("timeout")
|
||||||
|
|
||||||
|
timeoutCtx, cancel := context.WithTimeout(ctx.Context, timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := cast.StopCast(timeoutCtx, device); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,8 +3,9 @@ package main
|
||||||
import (
|
import (
|
||||||
"forge.cadoles.com/arcad/edge/cmd/cli/command"
|
"forge.cadoles.com/arcad/edge/cmd/cli/command"
|
||||||
"forge.cadoles.com/arcad/edge/cmd/cli/command/app"
|
"forge.cadoles.com/arcad/edge/cmd/cli/command/app"
|
||||||
|
"forge.cadoles.com/arcad/edge/cmd/cli/command/cast"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
command.Main(app.Root())
|
command.Main(app.Root(), cast.Root())
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"forge.cadoles.com/arcad/edge/cmd/storage-server/command/flag"
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/jwtutil"
|
||||||
|
"github.com/lestrrat-go/jwx/v2/jwa"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CheckToken() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
|
Name: "check-token",
|
||||||
|
Usage: "Validate and print the given token with the private key",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "token",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
flag.PrivateKey,
|
||||||
|
flag.PrivateKeySigningAlgorithm,
|
||||||
|
flag.PrivateKeyDefaultSize,
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.Context) error {
|
||||||
|
privateKeyFile := flag.GetPrivateKey(ctx)
|
||||||
|
signingAlgorithm := flag.GetSigningAlgorithm(ctx)
|
||||||
|
privateKeyDefaultSize := flag.GetPrivateKeyDefaultSize(ctx)
|
||||||
|
rawToken := ctx.String("token")
|
||||||
|
|
||||||
|
if rawToken == "" {
|
||||||
|
return errors.New("you must provide a value for --token flag")
|
||||||
|
}
|
||||||
|
|
||||||
|
privateKey, err := jwtutil.LoadOrGenerateKey(
|
||||||
|
privateKeyFile,
|
||||||
|
privateKeyDefaultSize,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
keySet, err := jwtutil.NewKeySet()
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = jwtutil.AddKeyWithSigningAlgo(keySet, privateKey, jwa.SignatureAlgorithm(signingAlgorithm))
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := jwtutil.Parse([]byte(rawToken), keySet)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
claims, err := token.AsMap(ctx.Context)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
json, err := json.MarshalIndent(claims, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(string(json))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"forge.cadoles.com/arcad/edge/cmd/storage-server/command/flag"
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/jwtutil"
|
||||||
|
"github.com/lestrrat-go/jwx/v2/jwa"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewToken() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
|
Name: "new-token",
|
||||||
|
Usage: "Generate new authentication token",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "tenant",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
flag.PrivateKey,
|
||||||
|
flag.PrivateKeySigningAlgorithm,
|
||||||
|
flag.PrivateKeyDefaultSize,
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.Context) error {
|
||||||
|
privateKeyFile := flag.GetPrivateKey(ctx)
|
||||||
|
signingAlgorithm := flag.GetSigningAlgorithm(ctx)
|
||||||
|
privateKeyDefaultSize := flag.GetPrivateKeyDefaultSize(ctx)
|
||||||
|
tenant := ctx.String("tenant")
|
||||||
|
|
||||||
|
if tenant == "" {
|
||||||
|
return errors.New("you must provide a value for --tenant flag")
|
||||||
|
}
|
||||||
|
|
||||||
|
privateKey, err := jwtutil.LoadOrGenerateKey(
|
||||||
|
privateKeyFile,
|
||||||
|
privateKeyDefaultSize,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
claims := map[string]any{
|
||||||
|
"tenant": tenant,
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := jwtutil.SignedToken(privateKey, jwa.SignatureAlgorithm(signingAlgorithm), claims)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not generate signed token")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(string(token))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Root() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
|
Name: "auth",
|
||||||
|
Usage: "Auth related command",
|
||||||
|
Subcommands: []*cli.Command{
|
||||||
|
NewToken(),
|
||||||
|
CheckToken(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
package flag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/lestrrat-go/jwx/v2/jwa"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
const PrivateKeyFlagName = "private-key"
|
||||||
|
|
||||||
|
var PrivateKey = &cli.StringFlag{
|
||||||
|
Name: PrivateKeyFlagName,
|
||||||
|
EnvVars: []string{"STORAGE_SERVER_PRIVATE_KEY"},
|
||||||
|
Value: "storage-server.key",
|
||||||
|
TakesFile: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPrivateKey(ctx *cli.Context) string {
|
||||||
|
return ctx.String(PrivateKeyFlagName)
|
||||||
|
}
|
||||||
|
|
||||||
|
const SigningAlgorithmFlagName = "signing-algorithm"
|
||||||
|
|
||||||
|
var PrivateKeySigningAlgorithm = &cli.StringFlag{
|
||||||
|
Name: SigningAlgorithmFlagName,
|
||||||
|
EnvVars: []string{"STORAGE_SERVER_SIGNING_ALGORITHM"},
|
||||||
|
Value: jwa.RS256.String(),
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSigningAlgorithm(ctx *cli.Context) string {
|
||||||
|
return ctx.String(SigningAlgorithmFlagName)
|
||||||
|
}
|
||||||
|
|
||||||
|
const PrivateKeyDefaultSizeFlagName = "private-key-default-size"
|
||||||
|
|
||||||
|
var PrivateKeyDefaultSize = &cli.IntFlag{
|
||||||
|
Name: PrivateKeyDefaultSizeFlagName,
|
||||||
|
EnvVars: []string{"STORAGE_SERVER_PRIVATE_KEY_DEFAULT_SIZE"},
|
||||||
|
Value: 2048,
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPrivateKeyDefaultSize(ctx *cli.Context) int {
|
||||||
|
return ctx.Int(PrivateKeyDefaultSizeFlagName)
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Main(commands ...*cli.Command) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
app := &cli.App{
|
||||||
|
Name: "storage-server",
|
||||||
|
Usage: "Edge storage server",
|
||||||
|
Commands: commands,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "debug",
|
||||||
|
EnvVars: []string{"DEBUG"},
|
||||||
|
Value: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
app.ExitErrHandler = func(ctx *cli.Context, err error) {
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
debug := ctx.Bool("debug")
|
||||||
|
|
||||||
|
if !debug {
|
||||||
|
fmt.Printf("[ERROR] %v\n", err)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("%+v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(cli.FlagsByName(app.Flags))
|
||||||
|
sort.Sort(cli.CommandsByName(app.Commands))
|
||||||
|
|
||||||
|
if err := app.RunContext(ctx, os.Args); err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,331 @@
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/getsentry/sentry-go"
|
||||||
|
"github.com/hashicorp/golang-lru/v2/expirable"
|
||||||
|
"github.com/keegancsmith/rpc"
|
||||||
|
"github.com/lestrrat-go/jwx/v2/jwa"
|
||||||
|
"github.com/lestrrat-go/jwx/v2/jwk"
|
||||||
|
"gitlab.com/wpetit/goweb/logger"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
|
||||||
|
// Register storage drivers
|
||||||
|
_ "forge.cadoles.com/arcad/edge/pkg/storage/driver/cache"
|
||||||
|
_ "forge.cadoles.com/arcad/edge/pkg/storage/driver/rpc"
|
||||||
|
_ "forge.cadoles.com/arcad/edge/pkg/storage/driver/sqlite"
|
||||||
|
|
||||||
|
"forge.cadoles.com/arcad/edge/cmd/storage-server/command/flag"
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/jwtutil"
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/storage"
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/storage/driver"
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/storage/driver/rpc/server"
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/storage/share"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Run() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
|
Name: "run",
|
||||||
|
Usage: "Run server",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "address",
|
||||||
|
EnvVars: []string{"STORAGE_SERVER_ADDRESS"},
|
||||||
|
Aliases: []string{"addr"},
|
||||||
|
Value: ":3001",
|
||||||
|
},
|
||||||
|
&cli.IntFlag{
|
||||||
|
Name: "log-level",
|
||||||
|
EnvVars: []string{"STORAGE_SERVER_LOG_LEVEL"},
|
||||||
|
Value: int(logger.LevelError),
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "blobstore-dsn-pattern",
|
||||||
|
EnvVars: []string{"STORAGE_SERVER_BLOBSTORE_DSN_PATTERN"},
|
||||||
|
Value: fmt.Sprintf("sqlite://data/%%TENANT%%/%%APPID%%/blobstore.sqlite?_pragma=foreign_keys(1)&_pragma=busy_timeout=%d&_pragma=journal_mode=wal", (60 * time.Second).Milliseconds()),
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "documentstore-dsn-pattern",
|
||||||
|
EnvVars: []string{"STORAGE_SERVER_DOCUMENTSTORE_DSN_PATTERN"},
|
||||||
|
Value: fmt.Sprintf("sqlite://data/%%TENANT%%/%%APPID%%/documentstore.sqlite?_pragma=foreign_keys(1)&_pragma=busy_timeout=%d&_pragma=journal_mode=wal", (60 * time.Second).Milliseconds()),
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "sharestore-dsn-pattern",
|
||||||
|
EnvVars: []string{"STORAGE_SERVER_SHARESTORE_DSN_PATTERN"},
|
||||||
|
Value: fmt.Sprintf("sqlite://data/%%TENANT%%/sharestore.sqlite?_pragma=foreign_keys(1)&_pragma=busy_timeout=%d&_pragma=journal_mode=wal", (60 * time.Second).Milliseconds()),
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "sentry-dsn",
|
||||||
|
EnvVars: []string{"STORAGE_SERVER_SENTRY_DSN"},
|
||||||
|
Value: "",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "sentry-environment",
|
||||||
|
EnvVars: []string{"STORAGE_SERVER_SENTRY_ENVIRONMENT"},
|
||||||
|
Value: "",
|
||||||
|
},
|
||||||
|
flag.PrivateKey,
|
||||||
|
flag.PrivateKeySigningAlgorithm,
|
||||||
|
flag.PrivateKeyDefaultSize,
|
||||||
|
&cli.DurationFlag{
|
||||||
|
Name: "cache-ttl",
|
||||||
|
EnvVars: []string{"STORAGE_SERVER_CACHE_TTL"},
|
||||||
|
Value: time.Hour,
|
||||||
|
},
|
||||||
|
&cli.IntFlag{
|
||||||
|
Name: "cache-size",
|
||||||
|
EnvVars: []string{"STORAGE_SERVER_CACHE_SIZE"},
|
||||||
|
Value: 32,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.Context) error {
|
||||||
|
addr := ctx.String("address")
|
||||||
|
blobStoreDSNPattern := ctx.String("blobstore-dsn-pattern")
|
||||||
|
documentStoreDSNPattern := ctx.String("documentstore-dsn-pattern")
|
||||||
|
shareStoreDSNPattern := ctx.String("sharestore-dsn-pattern")
|
||||||
|
cacheSize := ctx.Int("cache-size")
|
||||||
|
cacheTTL := ctx.Duration("cache-ttl")
|
||||||
|
privateKeyFile := flag.GetPrivateKey(ctx)
|
||||||
|
signingAlgorithm := flag.GetSigningAlgorithm(ctx)
|
||||||
|
privateKeyDefaultSize := flag.GetPrivateKeyDefaultSize(ctx)
|
||||||
|
logLevel := ctx.Int("log-level")
|
||||||
|
|
||||||
|
logger.SetLevel(logger.Level(logLevel))
|
||||||
|
|
||||||
|
sentryDSN := ctx.String("sentry-dsn")
|
||||||
|
sentryEnvironment := ctx.String("sentry-environment")
|
||||||
|
if sentryDSN != "" {
|
||||||
|
if sentryEnvironment == "" {
|
||||||
|
sentryEnvironment, _ = os.Hostname()
|
||||||
|
}
|
||||||
|
|
||||||
|
err := sentry.Init(sentry.ClientOptions{
|
||||||
|
Dsn: sentryDSN,
|
||||||
|
Debug: logLevel == int(logger.LevelDebug),
|
||||||
|
AttachStacktrace: true,
|
||||||
|
Environment: sentryEnvironment,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(ctx.Context, "could not initialize sentry", logger.CapturedE(errors.WithStack(err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.SetCaptureFunc(func(err error) {
|
||||||
|
sentry.CaptureException(err)
|
||||||
|
})
|
||||||
|
|
||||||
|
defer sentry.Flush(2 * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
router := chi.NewRouter()
|
||||||
|
|
||||||
|
privateKey, err := jwtutil.LoadOrGenerateKey(
|
||||||
|
privateKeyFile,
|
||||||
|
privateKeyDefaultSize,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
getBlobStoreServer := createGetCachedStoreServer(
|
||||||
|
func(dsn string) (storage.BlobStore, error) {
|
||||||
|
return driver.NewBlobStore(dsn)
|
||||||
|
},
|
||||||
|
func(store storage.BlobStore) *rpc.Server {
|
||||||
|
return server.NewBlobStoreServer(store)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
getShareStoreServer := createGetCachedStoreServer(
|
||||||
|
func(dsn string) (share.Store, error) {
|
||||||
|
return driver.NewShareStore(dsn)
|
||||||
|
},
|
||||||
|
func(store share.Store) *rpc.Server {
|
||||||
|
return server.NewShareStoreServer(store)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
getDocumentStoreServer := createGetCachedStoreServer(
|
||||||
|
func(dsn string) (storage.DocumentStore, error) {
|
||||||
|
return driver.NewDocumentStore(dsn)
|
||||||
|
},
|
||||||
|
func(store storage.DocumentStore) *rpc.Server {
|
||||||
|
return server.NewDocumentStoreServer(store)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
router.Use(middleware.RealIP)
|
||||||
|
router.Use(middleware.Logger)
|
||||||
|
|
||||||
|
logger.Debug(ctx.Context, "using authentication", logger.F("privateKey", privateKeyFile), logger.F("signingAlgorithm", signingAlgorithm))
|
||||||
|
|
||||||
|
router.Use(authenticate(privateKey, jwa.SignatureAlgorithm(signingAlgorithm)))
|
||||||
|
|
||||||
|
router.Handle("/blobstore", createStoreHandler(getBlobStoreServer, blobStoreDSNPattern, true, cacheSize, cacheTTL))
|
||||||
|
router.Handle("/documentstore", createStoreHandler(getDocumentStoreServer, documentStoreDSNPattern, true, cacheSize, cacheTTL))
|
||||||
|
router.Handle("/sharestore", createStoreHandler(getShareStoreServer, shareStoreDSNPattern, false, cacheSize, cacheTTL))
|
||||||
|
|
||||||
|
logger.Info(ctx.Context, "listening", logger.F("addr", addr))
|
||||||
|
|
||||||
|
if err := http.ListenAndServe(addr, router); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type getRPCServerFunc func(cacheSize int, cacheTTL time.Duration, tenant, appID, dsnPattern string) (*rpc.Server, error)
|
||||||
|
|
||||||
|
func createGetCachedStoreServer[T any](storeFactory func(dsn string) (T, error), serverFactory func(store T) *rpc.Server) getRPCServerFunc {
|
||||||
|
var (
|
||||||
|
cache *expirable.LRU[string, *rpc.Server]
|
||||||
|
initCache sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
|
return func(cacheSize int, cacheTTL time.Duration, tenant, appID, dsnPattern string) (*rpc.Server, error) {
|
||||||
|
initCache.Do(func() {
|
||||||
|
cache = expirable.NewLRU[string, *rpc.Server](cacheSize, nil, cacheTTL)
|
||||||
|
})
|
||||||
|
|
||||||
|
key := fmt.Sprintf("%s:%s", tenant, appID)
|
||||||
|
|
||||||
|
storeServer, _ := cache.Get(key)
|
||||||
|
if storeServer != nil {
|
||||||
|
return storeServer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
dsn := strings.ReplaceAll(dsnPattern, "%TENANT%", tenant)
|
||||||
|
dsn = strings.ReplaceAll(dsn, "%APPID%", appID)
|
||||||
|
|
||||||
|
store, err := storeFactory(dsn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
storeServer = serverFactory(store)
|
||||||
|
|
||||||
|
cache.Add(key, storeServer)
|
||||||
|
|
||||||
|
return storeServer, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createStoreHandler(getStoreServer getRPCServerFunc, dsnPattern string, appIDRequired bool, cacheSize int, cacheTTL time.Duration) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
tenant, ok := ctx.Value("tenant").(string)
|
||||||
|
if !ok || tenant == "" {
|
||||||
|
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
appID := r.URL.Query().Get("appId")
|
||||||
|
if appIDRequired && appID == "" {
|
||||||
|
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
server, err := getStoreServer(cacheSize, cacheTTL, tenant, appID, dsnPattern)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(r.Context(), "could not retrieve store server", logger.CapturedE(errors.WithStack(err)), logger.F("tenant", tenant))
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
server.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func authenticate(privateKey jwk.Key, signingAlgorithm jwa.SignatureAlgorithm) func(http.Handler) http.Handler {
|
||||||
|
var (
|
||||||
|
createKeySet sync.Once
|
||||||
|
err error
|
||||||
|
getKeySet jwtutil.GetKeySetFunc
|
||||||
|
)
|
||||||
|
|
||||||
|
return func(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
createKeySet.Do(func() {
|
||||||
|
var keySet jwk.Set
|
||||||
|
|
||||||
|
keySet, err = jwtutil.NewKeySet()
|
||||||
|
if err != nil {
|
||||||
|
err = errors.WithStack(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = jwtutil.AddKeyWithSigningAlgo(keySet, privateKey, signingAlgorithm)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.WithStack(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
getKeySet = func() (jwk.Set, error) {
|
||||||
|
return keySet, nil
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(ctx, "could not create keyset accessor", logger.CapturedE(errors.WithStack(err)))
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := jwtutil.FindToken(r, getKeySet, jwtutil.WithFinders(
|
||||||
|
jwtutil.FindTokenFromQueryString("token"),
|
||||||
|
))
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(ctx, "could not find jwt token", logger.CapturedE(errors.WithStack(err)))
|
||||||
|
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenMap, err := token.AsMap(ctx)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(ctx, "could not transform token to map", logger.CapturedE(errors.WithStack(err)))
|
||||||
|
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rawTenant, exists := tokenMap["tenant"]
|
||||||
|
if !exists {
|
||||||
|
logger.Warn(ctx, "could not find tenant claim", logger.F("token", token))
|
||||||
|
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tenant, ok := rawTenant.(string)
|
||||||
|
if !ok {
|
||||||
|
logger.Warn(ctx, "unexpected tenant claim value", logger.F("token", token))
|
||||||
|
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
r = r.WithContext(context.WithValue(ctx, "tenant", tenant))
|
||||||
|
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"forge.cadoles.com/arcad/edge/cmd/storage-server/command"
|
||||||
|
"forge.cadoles.com/arcad/edge/cmd/storage-server/command/auth"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
command.Main(
|
||||||
|
command.Run(),
|
||||||
|
auth.Root(),
|
||||||
|
)
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ Une **Edge App** est une application capable de s'exécuter dans un environnemen
|
||||||
|
|
||||||
### Référence
|
### Référence
|
||||||
|
|
||||||
|
- [Fichier `manifest.yml`](./apps/manifest.md)
|
||||||
- [API Client](./apps/client-api/README.md)
|
- [API Client](./apps/client-api/README.md)
|
||||||
- [API Serveur](./apps/server-api/README.md)
|
- [API Serveur](./apps/server-api/README.md)
|
||||||
|
|
||||||
|
|
|
@ -1,64 +1,15 @@
|
||||||
# API Client
|
# API Client
|
||||||
|
|
||||||
## Méthodes
|
## Usage
|
||||||
|
|
||||||
### `Edge.connect(): Promise`
|
Afin de pouvoir utiliser le SDK "client", vous devez inclure dans la page HTML de votre application la balise `<script>` suivante:
|
||||||
|
|
||||||
> `TODO`
|
```html
|
||||||
|
<script src="/edge/sdk/client.js"></script>
|
||||||
### `Edge.disconnect(): void`
|
|
||||||
|
|
||||||
> `TODO`
|
|
||||||
|
|
||||||
|
|
||||||
### `Edge.send(message: Object): void`
|
|
||||||
|
|
||||||
> `TODO`
|
|
||||||
|
|
||||||
|
|
||||||
### `Edge.rpc(method: string, params: Object): Promise`
|
|
||||||
|
|
||||||
> `TODO`
|
|
||||||
#### Exemple
|
|
||||||
|
|
||||||
**Côté serveur**
|
|
||||||
|
|
||||||
```js
|
|
||||||
function onInit() {
|
|
||||||
rpc.register(echo);
|
|
||||||
}
|
|
||||||
|
|
||||||
function echo(ctx, params) {
|
|
||||||
return params;
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Côté client**
|
Vous pourrez ensuite accéder aux variables globales suivantes:
|
||||||
|
|
||||||
```js
|
- [`Edge.Client`](./edge-client.md) - Client principal d'échange avec le serveur
|
||||||
Edge.connect().then(() => {
|
- [`Edge.Frame`](./edge-frame.md) - Utilitaire de communication avec une frame parente
|
||||||
Edge.rpc("echo", { hello: "world!" })
|
- [`Edge.Menu`](./edge-menu.md) - Gestionnaire de menu
|
||||||
.then(result => console.log(result))
|
|
||||||
.catch(err => console.error(err));
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### `Edge.upload(blob: Blob, metadata: Object): Promise`
|
|
||||||
|
|
||||||
> `TODO`
|
|
||||||
|
|
||||||
### `Edge.blobUrl(bucketName: string, blobId: string): string`
|
|
||||||
|
|
||||||
> `TODO`
|
|
||||||
|
|
||||||
## Événements
|
|
||||||
|
|
||||||
### `"message"`
|
|
||||||
|
|
||||||
> `TODO`
|
|
||||||
|
|
||||||
#### Exemple
|
|
||||||
|
|
||||||
```js
|
|
||||||
Edge.addEventListener("message", evt => console.log(evt.detail));
|
|
||||||
```
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
# `Edge.Client`
|
||||||
|
|
||||||
|
## Méthodes
|
||||||
|
|
||||||
|
### `Edge.Client.connect(): Promise`
|
||||||
|
|
||||||
|
> `TODO`
|
||||||
|
|
||||||
|
### `Edge.Client.disconnect(): void`
|
||||||
|
|
||||||
|
> `TODO`
|
||||||
|
|
||||||
|
|
||||||
|
### `Edge.Client.send(message: Object): void`
|
||||||
|
|
||||||
|
> `TODO`
|
||||||
|
|
||||||
|
|
||||||
|
### `Edge.Client.rpc(method: string, params: Object): Promise`
|
||||||
|
|
||||||
|
> `TODO`
|
||||||
|
#### Exemple
|
||||||
|
|
||||||
|
**Côté serveur**
|
||||||
|
|
||||||
|
```js
|
||||||
|
function onInit() {
|
||||||
|
rpc.register(echo);
|
||||||
|
}
|
||||||
|
|
||||||
|
function echo(ctx, params) {
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Côté client**
|
||||||
|
|
||||||
|
```js
|
||||||
|
Edge.Client.connect().then(() => {
|
||||||
|
Edge.Client.rpc("echo", { hello: "world!" })
|
||||||
|
.then(result => console.log(result))
|
||||||
|
.catch(err => console.error(err));
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### `Edge.Client.upload(blob: Blob, metadata: Object): Promise`
|
||||||
|
|
||||||
|
> `TODO`
|
||||||
|
|
||||||
|
### `Edge.Client.blobUrl(bucketName: string, blobId: string): string`
|
||||||
|
|
||||||
|
> `TODO`
|
||||||
|
|
||||||
|
### `Edge.Client.externalUrl(url: string): string`
|
||||||
|
|
||||||
|
Retourne une URL "locale" permettant d'accéder à une ressource externe, en fonction de règles propres à l'application. Voir module [`fetch`](../server-api/fetch.md).
|
||||||
|
|
||||||
|
## Événements
|
||||||
|
|
||||||
|
### `"message"`
|
||||||
|
|
||||||
|
> `TODO`
|
||||||
|
|
||||||
|
#### Exemple
|
||||||
|
|
||||||
|
```js
|
||||||
|
Edge.Client.addEventListener("message", evt => console.log(evt.detail));
|
||||||
|
```
|
|
@ -0,0 +1,30 @@
|
||||||
|
# `Edge.Frame`
|
||||||
|
|
||||||
|
## Méthodes
|
||||||
|
|
||||||
|
### `Edge.Frame.addEventListener(name: string, listener: (event) => void)`
|
||||||
|
|
||||||
|
> `TODO`
|
||||||
|
|
||||||
|
## Événements
|
||||||
|
|
||||||
|
### `"title_changed"`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface TitleChangedEvent {
|
||||||
|
detail: {
|
||||||
|
title: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `"size_changed"`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface SizeChangedEvent {
|
||||||
|
detail: {
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
|
@ -0,0 +1,27 @@
|
||||||
|
# `Edge.Menu`
|
||||||
|
|
||||||
|
## Méthodes
|
||||||
|
|
||||||
|
### `Edge.Menu.show()`
|
||||||
|
|
||||||
|
Afficher le menu.
|
||||||
|
|
||||||
|
### `Edge.Menu.hide()`
|
||||||
|
|
||||||
|
Cacher le menu.
|
||||||
|
|
||||||
|
### `Edge.Menu.setItem(name: string, label:string, options?: { iconUrl?: string, linkUrl?: string, order?: number })`
|
||||||
|
|
||||||
|
Créer/mettre à jour l'item nommé de la section du menu associée à l'application.
|
||||||
|
|
||||||
|
### `Edge.Menu.removeItem(name: string)`
|
||||||
|
|
||||||
|
Supprimer l'item de la section du menu associée à l'application.
|
||||||
|
|
||||||
|
### `Edge.Menu.setAppIconUrl(url: string)`
|
||||||
|
|
||||||
|
Mettre à jour l'URL de l'icône de la section du menu associée à l'application.
|
||||||
|
|
||||||
|
### `Edge.Menu.setAppTitle(title: string)`
|
||||||
|
|
||||||
|
Mettre à jour le titre de la section du menu associée à l'application.
|
|
@ -0,0 +1,36 @@
|
||||||
|
# Le fichier `manifest.yml`
|
||||||
|
|
||||||
|
Le fichier `manifest.yml` à la racine du bundle de votre application contient des informations décrivant celles ci. Vous trouverez ci dessous un exemple commenté.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# REQUIS - L'identifiant de votre application. Il doit être globalement unique.
|
||||||
|
# Un identifiant du type nom de domaine inversé est en général conseillé (ex: tld.mycompany.myapp)
|
||||||
|
id: tld.mycompany.myapp
|
||||||
|
|
||||||
|
# REQUIS - Le numéro de version de votre application
|
||||||
|
# Celui ci devrait respecter le format "semver 2" (voir https://semver.org/)
|
||||||
|
version: 0.0.0
|
||||||
|
|
||||||
|
# REQUIS - Le titre de votre application.
|
||||||
|
title: My App
|
||||||
|
|
||||||
|
# OPTIONNEL - Les mots-clés associés à votre applications.
|
||||||
|
tags: ["chat"]
|
||||||
|
|
||||||
|
# OPTIONNEL - La description de votre application.
|
||||||
|
# Vous pouvez utiliser la syntaxe Markdown pour la mettre en forme.
|
||||||
|
description: |>
|
||||||
|
A simple demo application
|
||||||
|
|
||||||
|
# OPTIONNEL - Métadonnées associées à l'application
|
||||||
|
metadata:
|
||||||
|
# OPTIONNEL - Liste des chemins permettant d'accéder à certains URLs identifiées (page d'administration, icône si existante, etc)
|
||||||
|
paths:
|
||||||
|
# Si défini, chemin vers la page d'administration de l'application
|
||||||
|
admin: /admin
|
||||||
|
# Si défini, chemin vers l'icône associée à l'application
|
||||||
|
icon: /my-app-icon.png
|
||||||
|
|
||||||
|
# OPTIONNEL - Role minimum requis pour pouvoir accéder à l'application
|
||||||
|
minimumRole: visitor
|
||||||
|
```
|
|
@ -22,23 +22,7 @@ my-app
|
||||||
|
|
||||||
Ce fichier est le manifeste de votre application. Il permet au serveur d'identifier celle ci et de récupérer des informations la concernant.
|
Ce fichier est le manifeste de votre application. Il permet au serveur d'identifier celle ci et de récupérer des informations la concernant.
|
||||||
|
|
||||||
```yaml
|
[Voir le fichier `manifest.yml` d'exemple](./manifest.md)
|
||||||
---
|
|
||||||
# L'identifiant de votre application. Il doit être globalement unique.
|
|
||||||
# Un identifiant du type nom de domaine inversé est en général conseillé (ex: tld.mycompany.myapp)
|
|
||||||
id: tld.mycompany.myapp
|
|
||||||
|
|
||||||
# Le titre de votre application.
|
|
||||||
title: My App
|
|
||||||
|
|
||||||
# Les mots-clés associés à votre applications.
|
|
||||||
tags: ["chat"]
|
|
||||||
|
|
||||||
# La description de votre application.
|
|
||||||
# Vous pouvez utiliser la syntaxe Markdown pour la mettre en forme.
|
|
||||||
description: |>
|
|
||||||
A simple demo application
|
|
||||||
```
|
|
||||||
|
|
||||||
## 4. Créer la page d'accueil
|
## 4. Créer la page d'accueil
|
||||||
|
|
||||||
|
@ -56,13 +40,13 @@ Créer le fichier `my-app/public/index.html`:
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
// On utilise le SDK via la variable globale "Edge"
|
// On utilise le SDK via la variable globale "Edge"
|
||||||
// pour se connecter au serveur de notre application.
|
// pour se connecter au serveur de notre application.
|
||||||
Edge.connect().then(() => {
|
Edge.Client.connect().then(() => {
|
||||||
// Une fois connecté, on envoie un message au serveur.
|
// Une fois connecté, on envoie un message au serveur.
|
||||||
Edge.send({ "hello": "world" });
|
Edge.Client.send({ "hello": "world" });
|
||||||
});
|
});
|
||||||
|
|
||||||
// On écoute les messages en provenance du serveur.
|
// On écoute les messages en provenance du serveur.
|
||||||
Edge.addEventListener("message", (evt) => {
|
Edge.Client.addEventListener("message", (evt) => {
|
||||||
console.log("New server message", evt.detail)
|
console.log("New server message", evt.detail)
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -20,10 +20,14 @@ function onInit() {
|
||||||
|
|
||||||
Listes des modules disponibles côté serveur.
|
Listes des modules disponibles côté serveur.
|
||||||
|
|
||||||
|
- [`app`](./app.md)
|
||||||
|
- [`auth`](./auth.md)
|
||||||
|
- [`blob`](./blob.md)
|
||||||
|
- [`cast`](./cast.md)
|
||||||
- [`console`](./console.md)
|
- [`console`](./console.md)
|
||||||
- [`context`](./context.md)
|
- [`context`](./context.md)
|
||||||
|
- [`fetch`](./fetch.md)
|
||||||
- [`net`](./net.md)
|
- [`net`](./net.md)
|
||||||
- [`rpc`](./rpc.md)
|
- [`rpc`](./rpc.md)
|
||||||
|
- [`share`](./share.md)
|
||||||
- [`store`](./store.md)
|
- [`store`](./store.md)
|
||||||
- [`blob`](./blob.md)
|
|
||||||
- [`cast`](./cast.md)
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
# Module `app`
|
||||||
|
|
||||||
|
Ce module permet de récupérer des informations sur les applications actives dans l'environnement Edge courant.
|
||||||
|
|
||||||
|
## Méthodes
|
||||||
|
|
||||||
|
### `app.list(ctx: Context): []Manifest`
|
||||||
|
|
||||||
|
Récupère la liste des applications actives.
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
|
||||||
|
- `ctx` **Context** Le contexte d'exécution. Voir la documentation du module [`context`](./context.md)
|
||||||
|
|
||||||
|
#### Valeur de retour
|
||||||
|
|
||||||
|
Liste des objets `Manifest` décrivant chaque application active.
|
||||||
|
|
||||||
|
### `app.get(ctx: Context, appId: string): Manifest`
|
||||||
|
|
||||||
|
Récupère les informations de l'application identifiée par `appId`.
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
|
||||||
|
- `ctx` **Context** Le contexte d'exécution. Voir la documentation du module [`context`](./context.md)
|
||||||
|
- `appId` **string** Identifiant de l'application
|
||||||
|
|
||||||
|
#### Valeur de retour
|
||||||
|
|
||||||
|
Objet `Manifest` associé à l'application, ou `null` si aucune application n'a été trouvée correspondant à l'identifiant.
|
||||||
|
|
||||||
|
### `app.getUrl(ctx: Context, appId: string, from: string = ''): Manifest`
|
||||||
|
|
||||||
|
Retourne l'URL permettant d'accéder à l'application identifiée par `appId`.
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
|
||||||
|
- `ctx` **Context** Le contexte d'exécution. Voir la documentation du module [`context`](./context.md)
|
||||||
|
- `appId` **string** Identifiant de l'application
|
||||||
|
- `from` **string** Adresse IP qui accédera à l'application (permet de générer la bonne URL vis à vis du réseau d'origine)
|
||||||
|
|
||||||
|
#### Valeur de retour
|
||||||
|
|
||||||
|
URL associée à l'application, ou `null` si aucune application n'a été trouvée correspondant à l'identifiant.
|
||||||
|
|
||||||
|
## Objets
|
||||||
|
|
||||||
|
### `Manifest`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface Manifest {
|
||||||
|
id: string // Identifiant de l'application
|
||||||
|
version: string // Version de l'application
|
||||||
|
title: string // Titre associé à l'application
|
||||||
|
description: string // Description associée à l'application
|
||||||
|
tags: string[] // Mots clés associés à l'application
|
||||||
|
metadata: { [key: string]: any } // Métadonnées associées à l'application. Voir ../manifest.md
|
||||||
|
}
|
||||||
|
```
|
|
@ -0,0 +1,49 @@
|
||||||
|
# Module `auth`
|
||||||
|
|
||||||
|
Ce module permet de récupérer des informations concernant l'utilisateur connecté et ses attributs.
|
||||||
|
|
||||||
|
### Utilisateurs anonymes
|
||||||
|
|
||||||
|
Edge génère automatiquement une session pour les utilisateurs anonymes. Ainsi, qu'un utilisateur soit identifié ou non les `claims` suivants seront toujours valués:
|
||||||
|
|
||||||
|
- `auth.CLAIM_SUBJECT`
|
||||||
|
- `auth.CLAIM_PREFERRED_USERNAME`
|
||||||
|
- `auth.CLAIM_ISSUER` (prendra la valeur `anon` dans le cas d'un utilisateur anonyme)
|
||||||
|
|
||||||
|
## Méthodes
|
||||||
|
|
||||||
|
### `auth.getClaim(ctx: Context, name: string): string`
|
||||||
|
|
||||||
|
Récupère un attribut associé à l'utilisateur.
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
|
||||||
|
- `ctx` **Context** Le contexte d'exécution. Voir la documentation du module [`context`](./context.md)
|
||||||
|
- `name` **string** Nom de l'attribut à retrouver
|
||||||
|
|
||||||
|
#### Valeur de retour
|
||||||
|
|
||||||
|
Valeur de l'attribut associé ou vide si la requête est non authentifiée ou que l'attribut n'a pas été trouvé.
|
||||||
|
|
||||||
|
#### Usage
|
||||||
|
|
||||||
|
```js
|
||||||
|
function onClientMessage(ctx, message) {
|
||||||
|
var subject = auth.getClaim(ctx, auth.CLAIM_SUBJECT);
|
||||||
|
console.log("Connected user is", subject);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Propriétés
|
||||||
|
|
||||||
|
### `auth.CLAIM_SUBJECT`
|
||||||
|
|
||||||
|
Cette propriété identifie l'utilisateur connecté. Si la valeur retournée par la méthode `getClaim()` est vide, alors l'utilisateur n'est pas connecté.
|
||||||
|
|
||||||
|
### `auth.CLAIM_ROLE`
|
||||||
|
|
||||||
|
Cette propriété retourne le rôle de l'utilisateur connecté au sein du "tenant" courant. Si la valeur retournée par la méthode `getClaim()` est vide, alors l'utilisateur n'est pas connecté.
|
||||||
|
|
||||||
|
### `auth.CLAIM_PREFERRED_USERNAME`
|
||||||
|
|
||||||
|
Cette propriété retourne le nom "préféré pour l'affichage" de l'utilisateur connecté. Si la valeur retournée par la méthode `getClaim()` est vide, alors l'utilisateur n'est pas connecté ou l'utilisateur n'a pas défini de nom d'utilisateur particulier.
|
|
@ -38,11 +38,15 @@ function onBlobDownload(ctx, bucketName, blobId) {
|
||||||
|
|
||||||
> `TODO`
|
> `TODO`
|
||||||
|
|
||||||
### `blob.writeBlob(ctx: Context, bucketName: string, blobId: string)`
|
### `blob.getBlobInfo(ctx: Context, bucketName: string, blobId: string): BlobInfo`
|
||||||
|
|
||||||
> `TODO`
|
> `TODO`
|
||||||
|
|
||||||
### `blob.readBlob(ctx: Context, bucketName: string, blobId: string)`
|
### `blob.writeBlob(ctx: Context, bucketName: string, blobId: string, data: any)`
|
||||||
|
|
||||||
|
> `TODO`
|
||||||
|
|
||||||
|
### `blob.readBlob(ctx: Context, bucketName: string, blobId: string): ArrayBuffer`
|
||||||
|
|
||||||
> `TODO`
|
> `TODO`
|
||||||
|
|
||||||
|
@ -58,7 +62,7 @@ function onBlobDownload(ctx, bucketName, blobId) {
|
||||||
|
|
||||||
> `TODO`
|
> `TODO`
|
||||||
|
|
||||||
### `blob.getBlobInfo(ctx: Context, bucketName: string, blobId: string): BlobInfo`
|
### `blob.getBucketSize(ctx: Context, bucketName: string): number`
|
||||||
|
|
||||||
> `TODO`
|
> `TODO`
|
||||||
|
|
||||||
|
@ -70,4 +74,16 @@ Voir la documentation de l'objet [`Context`](./context.md#Context).
|
||||||
|
|
||||||
### `BlobInfo`
|
### `BlobInfo`
|
||||||
|
|
||||||
### `Metadata`
|
```typescript
|
||||||
|
interface BlobInfo {
|
||||||
|
id: string // Identifiant du blob
|
||||||
|
bucket: string // Nom du bucket contenant le blob
|
||||||
|
size: number // Taille du blob
|
||||||
|
modTime: number // Timestamp Unix de dernière modification du blob
|
||||||
|
contentType: string // Type MIME du contenu du blob
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `Metadata`
|
||||||
|
|
||||||
|
L'objet `Metadata` est un objet clé/valeur arbitraire transmis avec la requête de téléversement. Voir la méthode [`Edge.Client.upload(blob, metadata)`](../client-api/README.md#edge-upload-blob-blob-metadata-object-promise) du SDK client.
|
|
@ -43,12 +43,6 @@ function onClientMessage(ctx, message) {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Propriétés
|
|
||||||
|
|
||||||
### `context.SESSION_ID`
|
|
||||||
|
|
||||||
Clé permettant de récupérer la clé de session associé au client émetteur du message courant.
|
|
||||||
|
|
||||||
#### Usage
|
#### Usage
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
# Module `fetch`
|
||||||
|
|
||||||
|
Ce module permet l'accès à des ressources distantes (sur Internet) depuis votre application.
|
||||||
|
|
||||||
|
## Fonctions de rappel
|
||||||
|
|
||||||
|
Pour permettre aux utilisateurs d'accéder à des ressources distantes, vous devez déclarer la fonction `onClientFetch(ctx: Context, url: string, remoteAddr: string)` dans le fichier `server/main.js` de votre application.
|
||||||
|
|
||||||
|
### `onClientFetch(ctx: Context, url: string, remoteAddr: string)`
|
||||||
|
|
||||||
|
#### Usage
|
||||||
|
|
||||||
|
**Côté client**
|
||||||
|
```js
|
||||||
|
// Création d'une URL "locale" permettant d'accéder à la ressource distante
|
||||||
|
var url = Edge.Client.externalUrl("http://example.com")
|
||||||
|
|
||||||
|
// Vous pouvez utiliser l'URL comme attribut `src` d'une balise <img> par exemple
|
||||||
|
// ou effectuer une requête fetch() avec celle ci.
|
||||||
|
fetch(url).then(res => res.text()).then(content => console.log(content));
|
||||||
|
```
|
||||||
|
|
||||||
|
**Côté serveur**
|
||||||
|
```js
|
||||||
|
function onClientFetch(ctx, url, remoteAddr) {
|
||||||
|
// Autoriser la récupération de l'URL demandée ou non
|
||||||
|
// Dans cet exemple, seule l'URL externe 'http://example.com' est autorisée
|
||||||
|
// Les autres URLs recevront une erreur HTTP 403 - Forbidden
|
||||||
|
var authorized = url === "http://example.com"
|
||||||
|
|
||||||
|
return { allow: authorized };
|
||||||
|
}
|
||||||
|
```
|
|
@ -32,9 +32,9 @@ Aucune
|
||||||
```js
|
```js
|
||||||
// Les données envoyées par le serveur sont accessibles
|
// Les données envoyées par le serveur sont accessibles
|
||||||
// via la propriété evt.detail.
|
// via la propriété evt.detail.
|
||||||
Edge.on('message', evt => console.log(evt.detail));
|
Edge.Client.on('message', evt => console.log(evt.detail));
|
||||||
|
|
||||||
Edge.connect();
|
Edge.Client.connect();
|
||||||
```
|
```
|
||||||
|
|
||||||
**Côté serveur**
|
**Côté serveur**
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Module `rpc`
|
# Module `rpc`
|
||||||
|
|
||||||
Ce module permet de déclarer des méthodes côté serveur qui seront "invoquable" côté client via la méthode [`Edge.rpc(method: string, params: Object): Promise`](../client-api/README.md#edgerpcmethod-string-params-object-promise).
|
Ce module permet de déclarer des méthodes côté serveur qui seront "invoquable" côté client via la méthode [`Edge.Client.rpc(method: string, params: Object): Promise`](../client-api/README.md#edgerpcmethod-string-params-object-promise).
|
||||||
|
|
||||||
## Méthodes
|
## Méthodes
|
||||||
|
|
||||||
|
@ -31,8 +31,8 @@ function echo(ctx, params) {
|
||||||
**Côté client**
|
**Côté client**
|
||||||
|
|
||||||
```js
|
```js
|
||||||
Edge.connect().then(() => {
|
Edge.Client.connect().then(() => {
|
||||||
Edge.rpc("echo", { hello: "world!" })
|
Edge.Client.rpc("echo", { hello: "world!" })
|
||||||
.then(result => console.log(result))
|
.then(result => console.log(result))
|
||||||
.catch(err => console.error(err));
|
.catch(err => console.error(err));
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,143 @@
|
||||||
|
# Module `share`
|
||||||
|
|
||||||
|
Ce module permet partager des ressources à destination des autres applications exécutées dans l'environnement Edge.
|
||||||
|
|
||||||
|
## Propriétés
|
||||||
|
|
||||||
|
### `share.ANY_TYPE`, `share.ANY_NAME`
|
||||||
|
|
||||||
|
Les propriétés `share.ANY_TYPE` et `share.ANY_NAME` sont utilisées dans la méthode `share.findResources()` pour récupérer ne pas appliquer de filtre spécifique au type ou au nom des attributs respectivement.
|
||||||
|
|
||||||
|
### `share.TYPE_TEXT`, `share.TYPE_NUMBER`, `share.TYPE_PATH`, `share.TYPE_BOOL`
|
||||||
|
|
||||||
|
Ces propriétés correspondant aux types d'attributs possibles dans une ressource.
|
||||||
|
|
||||||
|
Le type `share.TYPE_PATH` décrit un "chemin" destiné à être transformé en URL par l'application consommatrice de la ressource sous la forme `${APP_URL}${PATH}`. Ce type d'attribut peut être utilisé pour partager des URLs (médias, pages, etc) entre applications.
|
||||||
|
|
||||||
|
## Méthodes
|
||||||
|
|
||||||
|
### `share.upsertResource(ctx: Context, resourceId: string, ...attributes: Attribute[]): Resource`
|
||||||
|
|
||||||
|
Cette méthode permet de créer une ressource ou de la mettre à jour si elle existe déjà. Elle prend en paramètre le contexte d'exécution, l'identifiant de la ressource et une liste d'attributs.
|
||||||
|
|
||||||
|
Si la ressource n'existe pas, elle sera créée avec les attributs fournis. Si elle existe, les attributs existants seront mis à jour avec les valeurs fournies.
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
|
||||||
|
- `ctx: Context`: Le contexte d'exécution.
|
||||||
|
- `resourceId: string`: L'identifiant de la ressource.
|
||||||
|
- `...attributes: Attribute[]`: Une liste d'attributs. Chaque attribut est représenté par un objet de type `Attribute`.
|
||||||
|
|
||||||
|
#### Valeur de retour
|
||||||
|
|
||||||
|
La méthode retourne un objet de type `Resource` qui représente la ressource créée ou mise à jour.
|
||||||
|
|
||||||
|
#### Usage
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const resource = share.upsertResource(ctx, "my-resource", { name: "color", type: share.TYPE_TEXT, value: "red" });
|
||||||
|
console.log(resource);
|
||||||
|
// Output: { id: "my-resource", origin: "my.app", attributes: [{ name: "color", type: "text", value: "red", createdAt: "2023-04-21T14:30:00Z", updatedAt: "2023-04-21T14:30:00Z" }] }
|
||||||
|
```
|
||||||
|
|
||||||
|
### `share.findResources(ctx: Context, withAttribute?: string, withType?: string): []Resource`
|
||||||
|
|
||||||
|
Cette méthode permet de rechercher des ressources en fonction de leurs attributs. Elle prend en paramètre le contexte d'exécution et deux paramètres optionnels qui permettent de filtrer les ressources.
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
|
||||||
|
- `ctx: Context`: Le contexte d'exécution.
|
||||||
|
- `withAttribute?: string`: (optionnel) Le nom de l'attribut à rechercher (`share.ANY_NAME` par défaut)
|
||||||
|
- `withType?: string`: (optionnel) Le type de l'attribut à rechercher (`share.ANY_TYPE` par défaut)
|
||||||
|
|
||||||
|
#### Valeur de retour
|
||||||
|
|
||||||
|
La méthode retourne un tableau d'objets de type `Resource` qui représentent les ressources correspondant aux critères de recherche.
|
||||||
|
|
||||||
|
#### Usage
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const resources = share.findResources(ctx, "color", share.TYPE_TEXT);
|
||||||
|
console.log(resources);
|
||||||
|
// Output: [{ id: "my-resource", origin: "my/app", attributes: [{ name: "color", type: "text", value: "red", createdAt: "2023-04-21T14:30:00Z", updatedAt: "2023-04-21T14:30:00Z" }] }]
|
||||||
|
```
|
||||||
|
|
||||||
|
### `share.deleteAttributes(ctx: Context, resourceId: string, ...names: string[]): Resource`
|
||||||
|
|
||||||
|
Cette méthode supprime un ou plusieurs attributs de la ressource spécifiée.
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
|
||||||
|
- `ctx: Context`: contexte d'exécution
|
||||||
|
- `resourceId: string`: identifiant unique de la ressource à modifier
|
||||||
|
- `...names: string[]`: tableau de noms d'attributs à supprimer
|
||||||
|
|
||||||
|
#### Valeur de retour
|
||||||
|
|
||||||
|
La méthode retourne un objet de type `Resource` qui représente la ressource modifiée.
|
||||||
|
|
||||||
|
#### Usage
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const resource = share.upsertResource(ctx, "my-resource", { name: "color", type: share.TYPE_TEXT, value: "red" });
|
||||||
|
console.log(resource);
|
||||||
|
// Output: { id: "my-resource", origin: "my.app", attributes: [{ name: "color", type: "text", value: "red", createdAt: "2023-04-21T14:30:00Z", updatedAt: "2023-04-21T14:30:00Z" }] }
|
||||||
|
```
|
||||||
|
|
||||||
|
### `share.deleteResource(ctx: Context, resourceId: string)`
|
||||||
|
|
||||||
|
Cette méthode supprime la ressource spécifiée.
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
|
||||||
|
- `ctx: Context`: contexte d'exécution
|
||||||
|
- `resourceId: string`: identifiant unique de la ressource à supprimer
|
||||||
|
|
||||||
|
#### Valeur de retour
|
||||||
|
|
||||||
|
La méthode ne retourne pas de valeur.
|
||||||
|
|
||||||
|
#### Usage
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const resource = share.deleteResource(ctx, "my-resource");
|
||||||
|
```
|
||||||
|
|
||||||
|
## Objets
|
||||||
|
|
||||||
|
### `Context`
|
||||||
|
|
||||||
|
Voir la documentation du module [`context`](./context.md)
|
||||||
|
|
||||||
|
|
||||||
|
### `Resource`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface Resource {
|
||||||
|
id: string
|
||||||
|
origin: string
|
||||||
|
attributes: Attribute[]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `Attribute`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface Attribute {
|
||||||
|
name: string
|
||||||
|
type: ValueType
|
||||||
|
createdAt: string
|
||||||
|
updatedAt: string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `ValueType`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
enum ValueType {
|
||||||
|
TYPE_TEXT = "text",
|
||||||
|
TYPE_PATH = "path",
|
||||||
|
TYPE_NUMBER = "number",
|
||||||
|
TYPE_BOOL = "bool"
|
||||||
|
}
|
||||||
|
```
|
|
@ -178,6 +178,6 @@ var results = store.query(ctx, "myCollection", {
|
||||||
limit: 10,
|
limit: 10,
|
||||||
offset: 5,
|
offset: 5,
|
||||||
orderBy: "foo",
|
orderBy: "foo",
|
||||||
orderDirection: store.ASC,
|
orderDirection: store.DIRECTION_ASC,
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
109
go.mod
109
go.mod
|
@ -1,59 +1,90 @@
|
||||||
module forge.cadoles.com/arcad/edge
|
module forge.cadoles.com/arcad/edge
|
||||||
|
|
||||||
go 1.19
|
go 1.21.4
|
||||||
|
|
||||||
require modernc.org/sqlite v1.20.4
|
toolchain go1.21.5
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gogo/protobuf v0.0.0-20161014173244-50d1bd39ce4e // indirect
|
forge.cadoles.com/arcad/arcast v0.0.0-20231220090835-5d0311b7315d
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
github.com/getsentry/sentry-go v0.25.0
|
||||||
github.com/hashicorp/go.net v0.0.0-20151006203346-104dcad90073 // indirect
|
github.com/hashicorp/golang-lru/v2 v2.0.7
|
||||||
github.com/hashicorp/mdns v0.0.0-20151206042412-9d85cf22f9f8 // indirect
|
github.com/hashicorp/mdns v1.0.5
|
||||||
github.com/miekg/dns v0.0.0-20161006100029-fc4e1e2843d8 // indirect
|
github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf
|
||||||
|
github.com/jackc/puddle/v2 v2.2.1
|
||||||
|
github.com/keegancsmith/rpc v1.3.0
|
||||||
|
github.com/klauspost/compress v1.16.6
|
||||||
|
github.com/lestrrat-go/jwx/v2 v2.0.8
|
||||||
|
github.com/mitchellh/hashstructure/v2 v2.0.2
|
||||||
|
github.com/ulikunitz/xz v0.5.11
|
||||||
|
github.com/wlynxg/anet v0.0.1
|
||||||
|
go.uber.org/goleak v1.3.0
|
||||||
|
modernc.org/sqlite v1.20.4
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cdr.dev/slog v1.4.0 // indirect
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||||
github.com/alecthomas/chroma v0.7.0 // indirect
|
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
|
||||||
|
github.com/charmbracelet/lipgloss v0.7.1 // indirect
|
||||||
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.0 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.0 // indirect
|
||||||
|
github.com/goccy/go-json v0.9.11 // indirect
|
||||||
|
github.com/gogo/protobuf v0.0.0-20161014173244-50d1bd39ce4e // indirect
|
||||||
|
github.com/grandcat/zeroconf v1.0.1-0.20230119201135-e4f60f8407b1 // indirect
|
||||||
|
github.com/jaevor/go-nanoid v1.3.0 // indirect
|
||||||
|
github.com/leodido/go-urn v1.2.1 // 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.0 // indirect
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||||
|
github.com/miekg/dns v1.1.57 // indirect
|
||||||
|
github.com/muesli/reflow v0.3.0 // indirect
|
||||||
|
github.com/muesli/termenv v0.15.2 // indirect
|
||||||
|
github.com/rivo/uniseg v0.4.4 // indirect
|
||||||
|
go.opentelemetry.io/otel v1.21.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/trace v1.21.0 // indirect
|
||||||
|
golang.org/x/sync v0.5.0 // indirect
|
||||||
|
gopkg.in/go-playground/validator.v9 v9.29.1 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
cdr.dev/slog v1.6.1
|
||||||
github.com/barnybug/go-cast v0.0.0-20201201064555-a87ccbc26692
|
github.com/barnybug/go-cast v0.0.0-20201201064555-a87ccbc26692
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 // indirect
|
github.com/davecgh/go-spew v1.1.1
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
|
||||||
github.com/dlclark/regexp2 v1.7.0 // indirect
|
github.com/dlclark/regexp2 v1.7.0 // indirect
|
||||||
github.com/dop251/goja v0.0.0-20230203172422-5460598cfa32 // indirect
|
github.com/dop251/goja v0.0.0-20230203172422-5460598cfa32
|
||||||
github.com/dop251/goja_nodejs v0.0.0-20230207183254-2229640ea097 // indirect
|
github.com/dop251/goja_nodejs v0.0.0-20230207183254-2229640ea097
|
||||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
github.com/dustin/go-humanize v1.0.0 // indirect
|
||||||
github.com/fatih/color v1.7.0 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.1
|
||||||
github.com/gabriel-vasile/mimetype v1.4.1 // indirect
|
github.com/go-chi/chi/v5 v5.0.10
|
||||||
github.com/go-chi/chi/v5 v5.0.8 // indirect
|
|
||||||
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
|
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
|
github.com/google/uuid v1.3.0
|
||||||
github.com/google/uuid v1.3.0 // indirect
|
|
||||||
github.com/gorilla/websocket v1.4.2 // indirect
|
github.com/gorilla/websocket v1.4.2 // indirect
|
||||||
github.com/igm/sockjs-go/v3 v3.0.2 // indirect
|
github.com/igm/sockjs-go/v3 v3.0.2
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.4 // indirect
|
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
github.com/mitchellh/mapstructure v1.5.0
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/oklog/ulid/v2 v2.1.0
|
||||||
github.com/oklog/ulid/v2 v2.1.0 // indirect
|
github.com/orcaman/concurrent-map v1.0.0
|
||||||
github.com/orcaman/concurrent-map v1.0.0 // indirect
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/spf13/afero v1.9.3 // indirect
|
github.com/urfave/cli/v2 v2.26.0
|
||||||
github.com/urfave/cli/v2 v2.24.3 // indirect
|
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||||
gitlab.com/wpetit/goweb v0.0.0-20230206085656-dec695f0e2e9 // indirect
|
gitlab.com/wpetit/goweb v0.0.0-20231215190137-4a8add1d3d07
|
||||||
go.opencensus.io v0.22.5 // indirect
|
golang.org/x/crypto v0.16.0
|
||||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa // indirect
|
golang.org/x/mod v0.14.0
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
golang.org/x/net v0.19.0
|
||||||
golang.org/x/net v0.4.0 // indirect
|
golang.org/x/sys v0.15.0 // indirect
|
||||||
golang.org/x/sys v0.3.0 // indirect
|
golang.org/x/term v0.15.0 // indirect
|
||||||
golang.org/x/term v0.3.0 // indirect
|
golang.org/x/text v0.14.0 // indirect
|
||||||
golang.org/x/text v0.5.0 // indirect
|
golang.org/x/tools v0.16.1 // indirect
|
||||||
golang.org/x/tools v0.1.12 // indirect
|
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
gopkg.in/yaml.v2 v2.4.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
|
||||||
|
@ -64,3 +95,5 @@ require (
|
||||||
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.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
replace github.com/allegro/bigcache/v3 v3.1.0 => github.com/Bornholm/bigcache v0.0.0-20231201111725-1ddf51584cad
|
||||||
|
|
670
go.sum
670
go.sum
|
@ -1,80 +1,33 @@
|
||||||
cdr.dev/slog v1.4.0 h1:tLXQJ/hZ5Q051h0MBHSd2Ha8xzdXj7CjtzmG/8dUvUk=
|
cdr.dev/slog v1.6.1 h1:IQjWZD0x6//sfv5n+qEhbu3wBkmtBQY5DILXNvMaIv4=
|
||||||
cdr.dev/slog v1.4.0/go.mod h1:C5OL99WyuOK8YHZdYY57dAPN1jK2WJlCdq2VP6xeQns=
|
cdr.dev/slog v1.6.1/go.mod h1:eHEYQLaZvxnIAXC+XdTSNLb/kgA/X2RVSF72v5wsxEI=
|
||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY=
|
||||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM=
|
||||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
|
||||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
||||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
cloud.google.com/go/logging v1.7.0 h1:CJYxlNNNNAMkHp9em/YEXcfJg+rPDg7YfwoRpMU+t5I=
|
||||||
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M=
|
||||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
cloud.google.com/go/longrunning v0.5.1 h1:Fr7TXftcqTudoyRJa113hyaqlGdiBQkp0Gq7tErFDWI=
|
||||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
cloud.google.com/go/longrunning v0.5.1/go.mod h1:spvimkwdz6SPWKEt/XBij79E9fiTkHSQl/fRUUQJYJc=
|
||||||
cloud.google.com/go v0.49.0/go.mod h1:hGvAdzcWNbyuxS3nWhD7H2cIJxjRRTRLQVB0bdputVY=
|
forge.cadoles.com/arcad/arcast v0.0.0-20231220090835-5d0311b7315d h1:SPDaDDF5StoprDqop8j8zozs8xK32EEWnUHLccWplKM=
|
||||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
forge.cadoles.com/arcad/arcast v0.0.0-20231220090835-5d0311b7315d/go.mod h1:QR8p4kUScWBcTQ0dE/gR+2ndpKOx77mIDSYGqSF1gms=
|
||||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
|
||||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
|
||||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
|
||||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
|
||||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
|
||||||
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
|
||||||
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
|
|
||||||
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
|
|
||||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
|
||||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
|
||||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
|
||||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
|
||||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
|
||||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
|
||||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
|
||||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
|
||||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
|
||||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
|
||||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
|
||||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
|
||||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
|
||||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
|
||||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
|
||||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
|
||||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
|
||||||
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
|
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
|
||||||
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
|
|
||||||
github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0=
|
|
||||||
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
|
||||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
|
|
||||||
github.com/alecthomas/chroma v0.7.0 h1:z+0HgTUmkpRDRz0SRSdMaqOLfJV4F+N1FPDZUZIDUzw=
|
|
||||||
github.com/alecthomas/chroma v0.7.0/go.mod h1:1U/PfCsTALWWYHDnsIQkxEBM0+6LLe0v8+RSVMOwxeY=
|
|
||||||
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
|
|
||||||
github.com/alecthomas/kong v0.1.17-0.20190424132513-439c674f7ae0/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI=
|
|
||||||
github.com/alecthomas/kong v0.2.1-0.20190708041108-0548c6b1afae/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI=
|
|
||||||
github.com/alecthomas/kong-hcl v0.1.8-0.20190615233001-b21fea9723c8/go.mod h1:MRgZdU3vrFd05IQ89AxUZ0aYdF39BYoNFa324SodPCA=
|
|
||||||
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
|
|
||||||
github.com/barnybug/go-cast v0.0.0-20201201064555-a87ccbc26692 h1:JW4WZlqyaNWUUahfr7MigeDW6jmtam5cTzzo1lwsFhE=
|
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/barnybug/go-cast v0.0.0-20201201064555-a87ccbc26692/go.mod h1:Au0ipPuCBA7zsOC61SnyrYetm8VT3vo1UJtwHeYke44=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
github.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E=
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
|
||||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
|
||||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
|
||||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E=
|
|
||||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ=
|
|
||||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk=
|
|
||||||
github.com/davecgh/go-spew v1.0.1-0.20160907170601-6d212800a42e/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/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
|
||||||
github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk=
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4=
|
||||||
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc=
|
||||||
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||||
github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
|
github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
|
||||||
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
|
@ -88,518 +41,250 @@ github.com/dop251/goja_nodejs v0.0.0-20230207183254-2229640ea097 h1:WsLyDk8yHsVT
|
||||||
github.com/dop251/goja_nodejs v0.0.0-20230207183254-2229640ea097/go.mod h1:0tlktQL7yHfYEtjcRGi/eiOkbDR5XF7gyFFvbC5//E0=
|
github.com/dop251/goja_nodejs v0.0.0-20230207183254-2229640ea097/go.mod h1:0tlktQL7yHfYEtjcRGi/eiOkbDR5XF7gyFFvbC5//E0=
|
||||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
|
||||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
|
||||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
|
||||||
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.1 h1:TRWk7se+TOjCYgRth7+1/OYLNiRNIotknkFtf/dnN7Q=
|
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/gabriel-vasile/mimetype v1.4.1/go.mod h1:05Vi0w3Y9c/lNvJOdmIwvrrAhX3rYhfQQCaf9VJcv7M=
|
||||||
github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
github.com/getsentry/sentry-go v0.25.0 h1:q6Eo+hS+yoJlTO3uu/azhQadsD8V+jQn2D8VvX1eOyI=
|
||||||
github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
|
github.com/getsentry/sentry-go v0.25.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
|
||||||
github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||||
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
|
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
|
||||||
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
|
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
|
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
|
||||||
|
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
||||||
|
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
|
||||||
|
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
||||||
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
|
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-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
|
||||||
|
github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
|
||||||
|
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
github.com/gogo/protobuf v0.0.0-20161014173244-50d1bd39ce4e h1:eeyMpoxANuWNQ9O2auv4wXxJsrXzLUhdHaOmNWEGkRY=
|
github.com/gogo/protobuf v0.0.0-20161014173244-50d1bd39ce4e h1:eeyMpoxANuWNQ9O2auv4wXxJsrXzLUhdHaOmNWEGkRY=
|
||||||
github.com/gogo/protobuf v0.0.0-20161014173244-50d1bd39ce4e/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v0.0.0-20161014173244-50d1bd39ce4e/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
||||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
||||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
|
|
||||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
|
||||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
|
||||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
|
||||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
|
||||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
|
||||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
|
||||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
|
||||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
|
||||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
|
||||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
|
||||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
|
||||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
|
||||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
|
||||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/go-cmp v0.3.2-0.20191216170541-340f1ebe299e/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
|
||||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
|
||||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
|
||||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
|
||||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
|
||||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
|
||||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
|
||||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
|
||||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
|
||||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
|
||||||
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
|
||||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
|
||||||
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
|
||||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
|
||||||
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
|
||||||
github.com/gorilla/csrf v1.6.0/go.mod h1:7tSf8kmjNYr7IWDCYhd3U8Ck34iQ/Yw5CJu7bAkHEGI=
|
|
||||||
github.com/gorilla/handlers v1.4.1/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
|
|
||||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
|
||||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
|
||||||
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
|
||||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||||
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/hashicorp/go.net v0.0.0-20151006203346-104dcad90073 h1:9dodOMuH6u7LvPEkVydBv6KTHdm+SqsHOxHTzRW+1+w=
|
github.com/grandcat/zeroconf v1.0.1-0.20230119201135-e4f60f8407b1 h1:cNb52t5fkWv8ZiicKWnc2eZnhsCCoH7WmRBMIbMp04Q=
|
||||||
|
github.com/grandcat/zeroconf v1.0.1-0.20230119201135-e4f60f8407b1/go.mod h1:I6CSXU4zCGL08JOk9NbcT0ofAgnIkS/fVXbYzfSoDic=
|
||||||
github.com/hashicorp/go.net v0.0.0-20151006203346-104dcad90073/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
github.com/hashicorp/go.net v0.0.0-20151006203346-104dcad90073/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/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
|
||||||
github.com/hashicorp/mdns v0.0.0-20151206042412-9d85cf22f9f8 h1:yupxZNIxm5U8Tfb8g65irIuHkgF8c4koHC7daPSyMTE=
|
|
||||||
github.com/hashicorp/mdns v0.0.0-20151206042412-9d85cf22f9f8/go.mod h1:aa76Av3qgPeIQp9Y3qIkTBPieQYNkQ13Kxe7pze9Wb0=
|
github.com/hashicorp/mdns v0.0.0-20151206042412-9d85cf22f9f8/go.mod h1:aa76Av3qgPeIQp9Y3qIkTBPieQYNkQ13Kxe7pze9Wb0=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/hashicorp/mdns v1.0.5 h1:1M5hW1cunYeoXOqHwEb/GBDDHAFo0Yqb/uz/beC6LbE=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/hashicorp/mdns v1.0.5/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
|
||||||
github.com/igm/sockjs-go/v3 v3.0.2 h1:2m0k53w0DBiGozeQUIEPR6snZFmpFpYvVsGnfLPNXbE=
|
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/igm/sockjs-go/v3 v3.0.2/go.mod h1:UqchsOjeagIBFHvd+RZpLaVRbCwGilEC08EDHsD1jYE=
|
||||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf h1:FtEj8sfIcaaBfAKrE1Cwb61YDtYq9JxChK1c7AKce7s=
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf/go.mod h1:yrqSXGoD/4EKfF26AOGzscPOgTTJcyAwM2rpixWT+t4=
|
||||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
|
github.com/jaevor/go-nanoid v1.3.0 h1:nD+iepesZS6pr3uOVf20vR9GdGgJW1HPaR46gtrxzkg=
|
||||||
|
github.com/jaevor/go-nanoid v1.3.0/go.mod h1:SI+jFaPuddYkqkVQoNGHs81navCtH388TcrH0RqFKgY=
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/keegancsmith/rpc v1.3.0 h1:wGWOpjcNrZaY8GDYZJfvyxmlLljm3YQWF+p918DXtDk=
|
||||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
github.com/keegancsmith/rpc v1.3.0/go.mod h1:6O2xnOGjPyvIPbvp0MdrOe5r6cu1GZ4JoTzpzDhWeo0=
|
||||||
|
github.com/klauspost/compress v1.16.6 h1:91SKEy4K37vkp255cJ8QesJhjyRO0hn9i9G0GoUwLsk=
|
||||||
|
github.com/klauspost/compress v1.16.6/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
github.com/kr/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/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
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/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||||
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
|
github.com/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80=
|
||||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
|
||||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
|
||||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
|
||||||
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
|
github.com/lestrrat-go/httprc v1.0.4 h1:bAZymwoZQb+Oq8MEbyipag7iSq6YIga8Wj6GOiJGdI8=
|
||||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
github.com/lestrrat-go/httprc v1.0.4/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo=
|
||||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI=
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4=
|
||||||
github.com/miekg/dns v0.0.0-20161006100029-fc4e1e2843d8 h1:ALvJ9V8nNf04PFHMR2sot56N/pjrx5LzZGvUlnhdiCE=
|
github.com/lestrrat-go/jwx/v2 v2.0.8 h1:jCFT8oc0hEDVjgUgsBy1F9cbjsjAVZSXNi7JaU9HR/Q=
|
||||||
|
github.com/lestrrat-go/jwx/v2 v2.0.8/go.mod h1:zLxnyv9rTlEvOUHbc48FAfIL8iYu2hHvIRaTFGc8mT0=
|
||||||
|
github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4=
|
||||||
|
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
|
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||||
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||||
|
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||||
|
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
github.com/miekg/dns v0.0.0-20161006100029-fc4e1e2843d8/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
github.com/miekg/dns v0.0.0-20161006100029-fc4e1e2843d8/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
||||||
|
github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM=
|
||||||
|
github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk=
|
||||||
|
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/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E=
|
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
||||||
|
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||||
|
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
||||||
|
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
||||||
github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU=
|
github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU=
|
||||||
github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
|
github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
|
||||||
github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
|
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/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/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
|
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
|
||||||
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/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
|
|
||||||
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
|
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/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
|
||||||
|
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
|
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
||||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
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/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
|
||||||
github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk=
|
|
||||||
github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/testify v1.1.5-0.20160925220609-976c720a22c8/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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
github.com/stretchr/testify v1.7.0/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.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
|
||||||
|
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||||
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/v2 v2.24.3 h1:7Q1w8VN8yE0MJEHP06bv89PjYsN4IHWED2s1v/Zlfm0=
|
github.com/urfave/cli/v2 v2.26.0 h1:3f3AMg3HpThFNT4I++TKOejZO8yU55t3JnnSr4S4QEI=
|
||||||
github.com/urfave/cli/v2 v2.24.3/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
|
github.com/urfave/cli/v2 v2.26.0/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
github.com/wlynxg/anet v0.0.1 h1:VbkEEgHxPSrRQSiyRd0pmrbcEQAEU2TTb8fb4DmSYoQ=
|
||||||
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
github.com/wlynxg/anet v0.0.1/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
||||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
||||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
gitlab.com/wpetit/goweb v0.0.0-20230206085656-dec695f0e2e9 h1:6JlkcdjYVQglPWYuemK2MoZAtRE4vFx85zLXflGIyI8=
|
gitlab.com/wpetit/goweb v0.0.0-20231215190137-4a8add1d3d07 h1:0V95X1cBpdj5zyOe6oGtn/BQHlRpV8WlL3eTs3jaxiA=
|
||||||
gitlab.com/wpetit/goweb v0.0.0-20230206085656-dec695f0e2e9/go.mod h1:3sus4zjoUv1GB7eDLL60QaPkUnXJCWBpjvbe0jWifeY=
|
gitlab.com/wpetit/goweb v0.0.0-20231215190137-4a8add1d3d07/go.mod h1:Nfr7aZPiSN6biFumhiHbh9k8A3rKQRzR+o0bVtv78UY=
|
||||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc=
|
||||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo=
|
||||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4=
|
||||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM=
|
||||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
|
||||||
go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0=
|
go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
|
||||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc=
|
||||||
|
go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ=
|
||||||
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
|
||||||
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.0.0-20211108221036-ceb1ce70b4fa h1:idItI2DDfCokpg0N51B2VtiLdJ4vAuXC9fnCb2gACo4=
|
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
|
||||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
|
||||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
|
||||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
|
||||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
|
||||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
|
||||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
|
||||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
|
||||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
|
||||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
|
||||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
|
||||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
|
||||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
|
||||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
|
||||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
|
||||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
|
||||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 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.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
||||||
|
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/net v0.0.0-20161013035702-8b4af36cd21a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20161013035702-8b4af36cd21a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
|
||||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
|
||||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
|
||||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
|
||||||
|
golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||||
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
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.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
|
|
||||||
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||||
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210426080607-c94f62235c83/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
|
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/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-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.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
|
|
||||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||||
|
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
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-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.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI=
|
|
||||||
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
|
||||||
|
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||||
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.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
|
|
||||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
|
||||||
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.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
|
|
||||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
|
||||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
|
||||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
|
||||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
|
||||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
|
||||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
|
||||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
|
||||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
|
||||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
|
||||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
|
||||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
|
||||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
|
||||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
|
||||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
|
||||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
|
||||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
|
||||||
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
|
||||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
|
||||||
golang.org/x/tools v0.1.12 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.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA=
|
||||||
|
golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
google.golang.org/genproto v0.0.0-20230726155614-23370e0ffb3e h1:xIXmWJ303kJCuogpj0bHq+dcjcZHU+XFyc1I0Yl9cRg=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
google.golang.org/genproto v0.0.0-20230726155614-23370e0ffb3e/go.mod h1:0ggbjUrZYpy1q+ANUS30SEoGZ53cdfwtbuG7Ptgy108=
|
||||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
google.golang.org/genproto/googleapis/api v0.0.0-20230706204954-ccb25ca9f130 h1:XVeBY8d/FaK4848myy41HBqnDwvxeV3zMZhwN1TvAMU=
|
||||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
google.golang.org/genproto/googleapis/api v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:mPBs5jNgx2GuQGvFwUvVKqtn6HsUw9nP64BedgvqEsQ=
|
||||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230706204954-ccb25ca9f130 h1:2FZP5XuJY9zQyGM5N0rtovnoXjiMUEIUMvw0m9wlpLc=
|
||||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:8mL13HKkDa+IuJ8yruA3ci0q+0vsUz4m//+ottjwS5o=
|
||||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw=
|
||||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo=
|
||||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
|
||||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
|
||||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
|
||||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
|
||||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
|
||||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
|
||||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
|
||||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
|
||||||
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
|
||||||
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
|
||||||
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
|
||||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
|
||||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
|
||||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
|
||||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
|
||||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
|
||||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
|
||||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
|
||||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
|
||||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
|
||||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
|
||||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
|
||||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
|
||||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
|
||||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
|
||||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
|
||||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
|
||||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
|
||||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
|
||||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
|
||||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
|
||||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
|
||||||
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
|
||||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
|
||||||
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
|
||||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
|
||||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
|
||||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
|
||||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
|
||||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
|
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
|
||||||
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
||||||
|
gopkg.in/go-playground/validator.v9 v9.29.1 h1:SvGtYmN60a5CVKTOzMSyfzWDeZRxRuGvRQyEAKbw1xc=
|
||||||
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
|
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
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=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
|
||||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
|
||||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
|
||||||
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
|
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
|
||||||
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||||
modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
|
modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
|
||||||
modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
|
modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
|
||||||
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
|
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
|
||||||
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
|
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
|
||||||
|
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
|
||||||
|
modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
|
||||||
|
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
|
||||||
|
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
|
||||||
modernc.org/libc v1.22.2 h1:4U7v51GyhlWqQmwCHj28Rdq2Yzwk55ovjFrdPjs8Hb0=
|
modernc.org/libc v1.22.2 h1:4U7v51GyhlWqQmwCHj28Rdq2Yzwk55ovjFrdPjs8Hb0=
|
||||||
modernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug=
|
modernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug=
|
||||||
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
||||||
|
@ -612,8 +297,9 @@ modernc.org/sqlite v1.20.4 h1:J8+m2trkN+KKoE7jglyHYYYiaq5xmz2HoHJIiBlRzbE=
|
||||||
modernc.org/sqlite v1.20.4/go.mod h1:zKcGyrICaxNTMEHSr1HQ2GUraP0j+845GYw37+EyT6A=
|
modernc.org/sqlite v1.20.4/go.mod h1:zKcGyrICaxNTMEHSr1HQ2GUraP0j+845GYw37+EyT6A=
|
||||||
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.15.0 h1:oY+JeD11qVVSgVvodMJsu7Edf8tr5E/7tuhF5cNYz34=
|
||||||
|
modernc.org/tcl v1.15.0/go.mod h1:xRoGotBZ6dU+Zo2tca+2EqVEeMmOUBzHnhIwq4YrVnE=
|
||||||
modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
|
modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
|
||||||
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE=
|
||||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
modernc.org/z v1.7.0/go.mod h1:hVdgNMh8ggTuRG1rGU8x+xGRFfiQUIAw0ZqlPy8+HyQ=
|
||||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
|
||||||
|
|
|
@ -4,4 +4,9 @@ title: SDK Test
|
||||||
version: 0.0.0
|
version: 0.0.0
|
||||||
description: |
|
description: |
|
||||||
Suite de tests pour le SDK client
|
Suite de tests pour le SDK client
|
||||||
tags: ["test"]
|
tags: ["test"]
|
||||||
|
|
||||||
|
metadata:
|
||||||
|
paths:
|
||||||
|
icon: /icon.png
|
||||||
|
minimumRole: superadmin
|
Binary file not shown.
After Width: | Height: | Size: 77 KiB |
|
@ -4,25 +4,49 @@
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<title>Client SDK Test suite</title>
|
<title>Client SDK Test suite</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<link rel="stylesheet" href="vendor/mocha.css" />
|
<link rel="icon" type="image/png" href="/icon.png">
|
||||||
|
<link rel="stylesheet" href="/vendor/mocha.css" />
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
background-color: white;
|
background-color: #f7f7f7;
|
||||||
|
}
|
||||||
|
body:not([edge-auto-padding="false"]) #mocha-stats {
|
||||||
|
top: 75px !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="mocha"></div>
|
<div id="mocha"></div>
|
||||||
<script src="vendor/chai.js"></script>
|
<script src="/vendor/chai.js"></script>
|
||||||
<script src="vendor/mocha.js"></script>
|
<script src="/vendor/mocha.js"></script>
|
||||||
<script class="mocha-init">
|
<script class="mocha-init">
|
||||||
mocha.setup('bdd');
|
mocha.setup('bdd');
|
||||||
mocha.checkLeaks();
|
mocha.checkLeaks();
|
||||||
</script>
|
</script>
|
||||||
<script src="/edge/sdk/client.js"></script>
|
<script src="/edge/sdk/client.js"></script>
|
||||||
<script src="test/client-sdk.js"></script>
|
<script src="/test/util.js"></script>
|
||||||
|
<script src="/test/client-sdk.js"></script>
|
||||||
|
<script src="/test/auth-module.js"></script>
|
||||||
|
<script src="/test/net-module.js"></script>
|
||||||
|
<script src="/test/rpc-module.js"></script>
|
||||||
|
<script src="/test/file-module.js"></script>
|
||||||
|
<script src="/test/app-module.js"></script>
|
||||||
|
<script src="/test/fetch-module.js"></script>
|
||||||
|
<script src="/test/share-module.js"></script>
|
||||||
<script class="mocha-exec">
|
<script class="mocha-exec">
|
||||||
mocha.run();
|
mocha.run();
|
||||||
|
|
||||||
|
Edge.Menu
|
||||||
|
.setAppIconUrl('/icon.png')
|
||||||
|
.setAppTitle('SDK Tests')
|
||||||
|
.setItem('client', 'Client', { linkUrl: '/?grep=Edge', order: 1 })
|
||||||
|
.setItem('auth-module', 'Auth Module', { linkUrl: '/?grep=Auth%20Module' , order: 4})
|
||||||
|
.setItem('net-module', 'Net Module', { linkUrl: '/?grep=Net%20Module' , order: 3})
|
||||||
|
.setItem('rpc', 'Remote Procedure Call', { linkUrl: '/?grep=Remote%20Procedure%20Call' , order: 5})
|
||||||
|
.setItem('file-module', 'File Module', { linkUrl: '/?grep=File%20Module', order: 6})
|
||||||
|
.setItem('app-module', 'App Module', { linkUrl: '/?grep=App%20Module' , order: 7})
|
||||||
|
.setItem('fetch-module', 'Fetch Module', { linkUrl: '/?grep=Fetch%20Module' , order: 8})
|
||||||
|
.setItem('share-module', 'Share Module', { linkUrl: '/?grep=Share%20Module' , order: 9})
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
|
@ -0,0 +1,45 @@
|
||||||
|
describe('App Module', function() {
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
return Edge.Client.connect();
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
Edge.Client.disconnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should list apps', function() {
|
||||||
|
return Edge.Client.rpc("listApps")
|
||||||
|
.then(apps => {
|
||||||
|
console.log("listApps result:", apps);
|
||||||
|
chai.assert.isNotNull(apps);
|
||||||
|
chai.assert.isAtLeast(apps.length, 1);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should retrieve requested app', function() {
|
||||||
|
return Edge.Client.rpc("getApp", { appId: "edge.sdk.client.test" })
|
||||||
|
.then(app => {
|
||||||
|
console.log("getApp result:", app);
|
||||||
|
chai.assert.isNotNull(app);
|
||||||
|
chai.assert.equal(app.id, "edge.sdk.client.test");
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should retrieve requested app url without from address', function() {
|
||||||
|
return Edge.Client.rpc("getAppUrl", { appId: "edge.sdk.client.test" })
|
||||||
|
.then(url => {
|
||||||
|
console.log("getAppUrl result:", url);
|
||||||
|
chai.assert.isNotEmpty(url);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should retrieve requested app url with from address', function() {
|
||||||
|
return Edge.Client.rpc("getAppUrl", { appId: "edge.sdk.client.test", from: "127.0.0.2" })
|
||||||
|
.then(url => {
|
||||||
|
console.log("getAppUrl result:", url);
|
||||||
|
chai.assert.isNotEmpty(url);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -0,0 +1,21 @@
|
||||||
|
describe('Auth Module', function() {
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
return Edge.Client.connect();
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
Edge.Client.disconnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should retrieve user informations', function() {
|
||||||
|
return Edge.Client.rpc("getUserInfo")
|
||||||
|
.then(userInfo => {
|
||||||
|
console.log("getUserInfo result:", userInfo);
|
||||||
|
chai.assert.property(userInfo, 'subject');
|
||||||
|
chai.assert.property(userInfo, 'role');
|
||||||
|
chai.assert.property(userInfo, 'preferredUsername');
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -1,148 +1,26 @@
|
||||||
Edge.debug = true;
|
Edge.Client.debug = true;
|
||||||
|
Edge.Frame.debug = true;
|
||||||
|
|
||||||
describe('Edge', function() {
|
describe('Edge', function() {
|
||||||
|
|
||||||
describe('#connect()', function() {
|
describe('#connect()', function() {
|
||||||
after(() => {
|
after(() => {
|
||||||
Edge.disconnect();
|
Edge.Client.disconnect();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should open the connection', function() {
|
it('should open the connection', function() {
|
||||||
return Edge.connect()
|
return Edge.Client.connect()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
chai.assert.isNotNull(Edge._conn);
|
chai.assert.isNotNull(Edge.Client._conn);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#disconnect()', function() {
|
describe('#disconnect()', function() {
|
||||||
it('should close the connection', function() {
|
it('should close the connection', function() {
|
||||||
Edge.disconnect();
|
Edge.Client.disconnect();
|
||||||
chai.assert.isNull(Edge._conn);
|
chai.assert.isNull(Edge.Client._conn);
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('#send()', function() {
|
|
||||||
this.timeout(5000);
|
|
||||||
|
|
||||||
before(() => {
|
|
||||||
return Edge.connect();
|
|
||||||
});
|
|
||||||
|
|
||||||
after(() => {
|
|
||||||
Edge.disconnect();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should send a message to the server and echo back', function(done) {
|
|
||||||
const now = new Date();
|
|
||||||
const handler = evt => {
|
|
||||||
chai.assert.equal(evt.detail.now, now.toJSON());
|
|
||||||
Edge.removeEventListener('message', handler);
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Server should echo back message
|
|
||||||
Edge.addEventListener('message', handler);
|
|
||||||
|
|
||||||
// Send message to server
|
|
||||||
Edge.send({ now });
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Remote Procedure Call', function() {
|
|
||||||
|
|
||||||
before(() => {
|
|
||||||
return Edge.connect();
|
|
||||||
});
|
|
||||||
|
|
||||||
after(() => {
|
|
||||||
Edge.disconnect();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call the remote echo() method and resolve the returned value', function() {
|
|
||||||
const foo = "bar";
|
|
||||||
|
|
||||||
return Edge.rpc('echo', { foo })
|
|
||||||
.then(result => {
|
|
||||||
chai.assert.equal(result.foo, foo);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call the remote throwErrorFromClient() method and reject with an error', function() {
|
|
||||||
return Edge.rpc('throwErrorFromClient')
|
|
||||||
.catch(err => {
|
|
||||||
// Assert that it's an "internal" error
|
|
||||||
// See https://www.jsonrpc.org/specification#error_object
|
|
||||||
chai.assert.equal(err.code, -32603);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call an unregistered method and reject with an error', function() {
|
|
||||||
return Edge.rpc('unregisteredMethod')
|
|
||||||
.catch(err => {
|
|
||||||
// Assert that it's an "method not found" error
|
|
||||||
// See https://www.jsonrpc.org/specification#error_object
|
|
||||||
chai.assert.equal(err.code, -32601);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should call the add() method repetitively and keep count of the sent values', function() {
|
|
||||||
this.timeout(10000);
|
|
||||||
|
|
||||||
const values = [];
|
|
||||||
for(let i = 0; i <= 1000; i++) {
|
|
||||||
values.push((Math.random() * 1000 | 0));
|
|
||||||
}
|
|
||||||
return Edge.rpc('reset')
|
|
||||||
.then(() => {
|
|
||||||
return Promise.all(values.map(v => Edge.rpc("add", {value: v})));
|
|
||||||
})
|
|
||||||
.then(() => Edge.rpc('total'))
|
|
||||||
.then(remoteTotal => {
|
|
||||||
const localTotal = values.reduce((t, v) => t+v);
|
|
||||||
console.log("Remote total:", remoteTotal, "Local total:", localTotal);
|
|
||||||
chai.assert.equal(remoteTotal, localTotal)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('File Module', function() {
|
|
||||||
|
|
||||||
before(() => {
|
|
||||||
return Edge.connect();
|
|
||||||
});
|
|
||||||
|
|
||||||
after(() => {
|
|
||||||
Edge.disconnect();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should upload then download a blob', function() {
|
|
||||||
const content = JSON.stringify({"date": new Date()});
|
|
||||||
const blob = new Blob([content], {type: "application/json"});
|
|
||||||
|
|
||||||
return Edge.upload(blob)
|
|
||||||
.then(upload => upload.result())
|
|
||||||
.then(result => {
|
|
||||||
|
|
||||||
chai.assert.isNotEmpty(result.blobId);
|
|
||||||
chai.assert.isNotEmpty(result.bucket);
|
|
||||||
|
|
||||||
const blobUrl = Edge.blobUrl(result.bucket, result.blobId);
|
|
||||||
chai.assert.isNotEmpty(blobUrl);
|
|
||||||
|
|
||||||
return fetch(blobUrl)
|
|
||||||
.then(res => res.text())
|
|
||||||
.then(blobContent => {
|
|
||||||
chai.assert.equal(content, blobContent);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
chai.assert.fail(err);
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
describe('Fetch Module', function () {
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
return Edge.Client.connect();
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
Edge.Client.disconnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fetch an authorized external url', function () {
|
||||||
|
var externalUrl = Edge.Client.externalUrl("http://example.com");
|
||||||
|
|
||||||
|
return fetch(externalUrl)
|
||||||
|
.then(res => {
|
||||||
|
chai.assert.equal(res.status, 200)
|
||||||
|
return res.text()
|
||||||
|
})
|
||||||
|
.then(content => {
|
||||||
|
chai.assert.include(content, '<h1>Example Domain</h1>')
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not fetch an unauthorized external url', function () {
|
||||||
|
var externalUrl = Edge.Client.externalUrl("https://google.com");
|
||||||
|
|
||||||
|
return fetch(externalUrl)
|
||||||
|
.then(res => {
|
||||||
|
chai.assert.equal(res.status, 403)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -0,0 +1,36 @@
|
||||||
|
describe('File Module', function () {
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
return Edge.Client.connect();
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
Edge.Client.disconnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should upload then download a blob', function () {
|
||||||
|
const content = JSON.stringify({ "date": new Date() });
|
||||||
|
const blob = new Blob([content], { type: "application/json" });
|
||||||
|
|
||||||
|
return Edge.Client.upload(blob)
|
||||||
|
.then(upload => upload.result())
|
||||||
|
.then(result => {
|
||||||
|
|
||||||
|
chai.assert.isNotEmpty(result.blobId);
|
||||||
|
chai.assert.isNotEmpty(result.bucket);
|
||||||
|
|
||||||
|
const blobUrl = Edge.Client.blobUrl(result.bucket, result.blobId);
|
||||||
|
chai.assert.isNotEmpty(blobUrl);
|
||||||
|
|
||||||
|
return fetch(blobUrl)
|
||||||
|
.then(res => res.text())
|
||||||
|
.then(blobContent => {
|
||||||
|
chai.assert.equal(content, blobContent);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
chai.assert.fail(err);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -0,0 +1,49 @@
|
||||||
|
describe('Net Module', function () {
|
||||||
|
this.timeout(5000);
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
return Edge.Client.connect();
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
Edge.Client.disconnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should broadcast a message from server', function (done) {
|
||||||
|
const message = { test: 'broadcast', now: Date.now() };
|
||||||
|
|
||||||
|
const handler = (evt) => {
|
||||||
|
const receivedMessage = evt.detail;
|
||||||
|
if (receivedMessage.test !== 'broadcast') return;
|
||||||
|
|
||||||
|
chai.assert.deepEqual(message, evt.detail);
|
||||||
|
|
||||||
|
Edge.Client.removeEventListener('message', handler);
|
||||||
|
done();
|
||||||
|
};
|
||||||
|
|
||||||
|
Edge.Client.addEventListener("message", handler);
|
||||||
|
Edge.Client.send(message);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should send a message to the server and echo back', function(done) {
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
const handler = evt => {
|
||||||
|
const receivedMessage = evt.detail;
|
||||||
|
if (receivedMessage.test !== 'echo') return;
|
||||||
|
|
||||||
|
chai.assert.equal(receivedMessage.now, now.toJSON());
|
||||||
|
|
||||||
|
Edge.Client.removeEventListener('message', handler);
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server should echo back message
|
||||||
|
Edge.Client.addEventListener('message', handler);
|
||||||
|
|
||||||
|
// Send message to server
|
||||||
|
Edge.Client.send({ test: 'echo', now });
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -0,0 +1,59 @@
|
||||||
|
describe('Remote Procedure Call', function () {
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
return Edge.Client.connect();
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
Edge.Client.disconnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call the remote echo() method and resolve the returned value', function () {
|
||||||
|
const foo = "bar";
|
||||||
|
|
||||||
|
return Edge.Client.rpc('echo', { foo })
|
||||||
|
.then(result => {
|
||||||
|
console.log(result);
|
||||||
|
chai.assert.equal(result.foo, foo);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call the remote throwErrorFromClient() method and reject with an error', function () {
|
||||||
|
return Edge.Client.rpc('throwErrorFromClient')
|
||||||
|
.catch(err => {
|
||||||
|
// Assert that it's an "internal" error
|
||||||
|
// See https://www.jsonrpc.org/specification#error_object
|
||||||
|
chai.assert.equal(err.code, -32603);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call an unregistered method and reject with an error', function () {
|
||||||
|
return Edge.Client.rpc('unregisteredMethod')
|
||||||
|
.catch(err => {
|
||||||
|
// Assert that it's an "method not found" error
|
||||||
|
// See https://www.jsonrpc.org/specification#error_object
|
||||||
|
chai.assert.equal(err.code, -32601);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should call the add() method repetitively and keep count of the sent values', function () {
|
||||||
|
this.timeout(30000);
|
||||||
|
|
||||||
|
const values = [];
|
||||||
|
for (let i = 0; i <= 1000; i++) {
|
||||||
|
values.push((Math.random() * 1000 | 0));
|
||||||
|
}
|
||||||
|
return Edge.Client.rpc('reset')
|
||||||
|
.then(() => {
|
||||||
|
return Promise.all(values.map(v => Edge.Client.rpc("add", { value: v })));
|
||||||
|
})
|
||||||
|
.then(() => Edge.Client.rpc('total'))
|
||||||
|
.then(remoteTotal => {
|
||||||
|
const localTotal = values.reduce((t, v) => t + v);
|
||||||
|
console.log("Remote total:", remoteTotal, "Local total:", localTotal);
|
||||||
|
chai.assert.equal(remoteTotal, localTotal)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -0,0 +1,29 @@
|
||||||
|
describe('Share Module', function() {
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
return Edge.Client.connect();
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
Edge.Client.disconnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a new resource and find it', async () => {
|
||||||
|
const resource = await TestUtil.serverSideCall('share', 'upsertResource', 'my-resource', { name: "color", type: "text", value: "red" });
|
||||||
|
chai.assert.isNotNull(resource);
|
||||||
|
chai.assert.equal(resource.origin, 'edge.sdk.client.test')
|
||||||
|
|
||||||
|
|
||||||
|
const results = await TestUtil.serverSideCall('share', 'findResources', 'color', 'text');
|
||||||
|
chai.assert.isAbove(results.length, 0);
|
||||||
|
|
||||||
|
const createdResource = results.find(res => {
|
||||||
|
return res.origin === 'edge.sdk.client.test' &&
|
||||||
|
res.attributes.find(attr => attr.name === 'color' && attr.type === 'text')
|
||||||
|
})
|
||||||
|
|
||||||
|
chai.assert.isNotNull(createdResource)
|
||||||
|
|
||||||
|
console.log(createdResource)
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,7 @@
|
||||||
|
(function(TestUtil) {
|
||||||
|
TestUtil.serverSideCall = (module, func, ...args) => {
|
||||||
|
return Edge.Client.rpc('serverSideCall', { module, func, args })
|
||||||
|
}
|
||||||
|
console.log(TestUtil)
|
||||||
|
|
||||||
|
}(globalThis.TestUtil = globalThis.TestUtil || {}));
|
|
@ -10,13 +10,26 @@ function onInit() {
|
||||||
rpc.register("add", add);
|
rpc.register("add", add);
|
||||||
rpc.register("reset", reset);
|
rpc.register("reset", reset);
|
||||||
rpc.register("total", total);
|
rpc.register("total", total);
|
||||||
|
rpc.register("getUserInfo", getUserInfo);
|
||||||
|
|
||||||
|
rpc.register("listApps");
|
||||||
|
rpc.register("getApp");
|
||||||
|
rpc.register("getAppUrl");
|
||||||
|
|
||||||
|
rpc.register("serverSideCall", serverSideCall)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called for each client message
|
// Called for each client message
|
||||||
function onClientMessage(ctx, data) {
|
function onClientMessage(ctx, message) {
|
||||||
var sessionId = context.get(ctx, context.SESSION_ID);
|
console.log("onClientMessage", message);
|
||||||
console.log("onClientMessage", sessionId, data.now);
|
|
||||||
net.send(ctx, { now: data.now });
|
switch (message.test) {
|
||||||
|
case "broadcast":
|
||||||
|
net.broadcast(message);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
net.send(ctx, message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called for each blob upload request
|
// Called for each blob upload request
|
||||||
|
@ -60,4 +73,41 @@ function reset(ctx, params) {
|
||||||
|
|
||||||
function total(ctx, params) {
|
function total(ctx, params) {
|
||||||
return count;
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getUserInfo(ctx, params) {
|
||||||
|
var subject = auth.getClaim(ctx, auth.CLAIM_SUBJECT);
|
||||||
|
var role = auth.getClaim(ctx, auth.CLAIM_ROLE);
|
||||||
|
var preferredUsername = auth.getClaim(ctx, auth.CLAIM_PREFERRED_USERNAME);
|
||||||
|
|
||||||
|
return {
|
||||||
|
subject: subject,
|
||||||
|
role: role,
|
||||||
|
preferredUsername: preferredUsername,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function listApps(ctx) {
|
||||||
|
return app.list(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getApp(ctx, params) {
|
||||||
|
var appId = params.appId;
|
||||||
|
return app.get(ctx, appId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAppUrl(ctx, params) {
|
||||||
|
var appId = params.appId;
|
||||||
|
var from = params.from;
|
||||||
|
|
||||||
|
return app.getUrl(ctx, appId, from ? from : '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function onClientFetch(ctx, url, remoteAddr) {
|
||||||
|
return { allow: url === 'http://example.com' };
|
||||||
|
}
|
||||||
|
|
||||||
|
function serverSideCall(ctx, params) {
|
||||||
|
console.log("Calling %s.%s(args...)", params.module, params.func)
|
||||||
|
return globalThis[params.module][params.func].call(null, ctx, ...params.args);
|
||||||
}
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
FROM reg.cadoles.com/proxy_cache/library/ubuntu:22.04
|
||||||
|
|
||||||
|
ARG HTTP_PROXY=
|
||||||
|
ARG HTTPS_PROXY=
|
||||||
|
ARG http_proxy=
|
||||||
|
ARG https_proxy=
|
||||||
|
ARG GO_VERSION=1.21.5
|
||||||
|
|
||||||
|
# Install dev environment dependencies
|
||||||
|
RUN export DEBIAN_FRONTEND=noninteractive &&\
|
||||||
|
apt-get update -y &&\
|
||||||
|
apt-get install -y --no-install-recommends curl ca-certificates build-essential wget unzip tar git jq
|
||||||
|
|
||||||
|
# Install Go
|
||||||
|
RUN mkdir -p /tmp \
|
||||||
|
&& wget -O /tmp/go${GO_VERSION}.linux-amd64.tar.gz https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz \
|
||||||
|
&& rm -rf /usr/local/go \
|
||||||
|
&& mkdir -p /usr/local \
|
||||||
|
&& tar -C /usr/local -xzf /tmp/go${GO_VERSION}.linux-amd64.tar.gz
|
||||||
|
|
||||||
|
ENV PATH="${PATH}:/usr/local/go/bin"
|
||||||
|
|
||||||
|
# Add LetsEncrypt certificates
|
||||||
|
RUN curl -k https://forge.cadoles.com/Cadoles/Jenkins/raw/branch/master/resources/com/cadoles/common/add-letsencrypt-ca.sh | bash
|
||||||
|
|
||||||
|
# Install NodeJS
|
||||||
|
RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
|
||||||
|
&& apt-get install -y nodejs
|
|
@ -0,0 +1,75 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
use_systemctl="True"
|
||||||
|
systemd_version=0
|
||||||
|
if ! command -V systemctl >/dev/null 2>&1; then
|
||||||
|
use_systemctl="False"
|
||||||
|
else
|
||||||
|
systemd_version=$(systemctl --version | head -1 | cut -d ' ' -f 2)
|
||||||
|
fi
|
||||||
|
|
||||||
|
service_name=storage-server
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
if [ "${use_systemctl}" = "False" ]; then
|
||||||
|
rm -f /usr/lib/systemd/system/${service_name}.service
|
||||||
|
else
|
||||||
|
rm -f /etc/chkconfig/${service_name}
|
||||||
|
rm -f /etc/init.d/${service_name}
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanInstall() {
|
||||||
|
printf "\033[32m Post Install of an clean install\033[0m\n"
|
||||||
|
if [ "${use_systemctl}" = "False" ]; then
|
||||||
|
if command -V chkconfig >/dev/null 2>&1; then
|
||||||
|
chkconfig --add ${service_name}
|
||||||
|
fi
|
||||||
|
|
||||||
|
service ${service_name} restart || :
|
||||||
|
else
|
||||||
|
if [[ "${systemd_version}" -lt 231 ]]; then
|
||||||
|
printf "\033[31m systemd version %s is less then 231, fixing the service file \033[0m\n" "${systemd_version}"
|
||||||
|
sed -i "s/=+/=/g" /usr/lib/systemd/system/${service_name}.service
|
||||||
|
fi
|
||||||
|
printf "\033[32m Reload the service unit from disk\033[0m\n"
|
||||||
|
systemctl daemon-reload || :
|
||||||
|
printf "\033[32m Unmask the service\033[0m\n"
|
||||||
|
systemctl unmask ${service_name} || :
|
||||||
|
printf "\033[32m Set the preset flag for the service unit\033[0m\n"
|
||||||
|
systemctl preset ${service_name} || :
|
||||||
|
printf "\033[32m Set the enabled flag for the service unit\033[0m\n"
|
||||||
|
systemctl enable ${service_name} || :
|
||||||
|
systemctl restart ${service_name} || :
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
upgrade() {
|
||||||
|
printf "\033[32m Post Install of an upgrade\033[0m\n"
|
||||||
|
systemctl daemon-reload || :
|
||||||
|
systemctl restart ${service_name} || :
|
||||||
|
}
|
||||||
|
|
||||||
|
# Step 2, check if this is a clean install or an upgrade
|
||||||
|
action="$1"
|
||||||
|
if [ "$1" = "configure" ] && [ -z "$2" ]; then
|
||||||
|
action="install"
|
||||||
|
elif [ "$1" = "configure" ] && [ -n "$2" ]; then
|
||||||
|
action="upgrade"
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$action" in
|
||||||
|
"1" | "install")
|
||||||
|
cleanInstall
|
||||||
|
;;
|
||||||
|
"2" | "upgrade")
|
||||||
|
printf "\033[32m Post Install of an upgrade\033[0m\n"
|
||||||
|
upgrade
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
printf "\033[32m Alpine\033[0m"
|
||||||
|
cleanInstall
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
cleanup
|
|
@ -0,0 +1,9 @@
|
||||||
|
export STORAGE_SERVER_ADDRESS=:3001
|
||||||
|
export STORAGE_SERVER_BLOBSTORE_DSN_PATTERN="sqlite:///var/lib/storage-server/data/%TENANT%/%APPID%/blobstore.sqlite?_pragma=foreign_keys(1)&_pragma=busy_timeout=60000"
|
||||||
|
export STORAGE_SERVER_DOCUMENTSTORE_DSN_PATTERN="sqlite:///var/lib/storage-server/data/%TENANT%/%APPID%/documentstore.sqlite?_pragma=foreign_keys(1)&_pragma=busy_timeout=60000"
|
||||||
|
export STORAGE_SERVER_SHARESTORE_DSN_PATTERN="sqlite:///var/lib/storage-server/data/%TENANT%/sharestore.sqlite?_pragma=foreign_keys(1)&_pragma=busy_timeout=60000"
|
||||||
|
export STORAGE_SERVER_PRIVATE_KEY="/var/lib/storage-server/storage-server.key"
|
||||||
|
export STORAGE_SERVER_PRIVATE_KEY_DEFAULT_SIZE="2048"
|
||||||
|
export STORAGE_SERVER_SIGNING_ALGORITHM="RS256"
|
||||||
|
export STORAGE_SERVER_CACHE_TTL=1h
|
||||||
|
export STORAGE_SERVER_CACHE_SIZE=32
|
|
@ -0,0 +1,9 @@
|
||||||
|
/var/log/storage-server/storage-server.log {
|
||||||
|
missingok
|
||||||
|
sharedscripts
|
||||||
|
compress
|
||||||
|
rotate 7
|
||||||
|
postrotate
|
||||||
|
/etc/init.d/storage-server restart
|
||||||
|
endscript
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
#!/sbin/openrc-run
|
||||||
|
|
||||||
|
command="/usr/bin/storage-server"
|
||||||
|
command_args="run"
|
||||||
|
supervisor=supervise-daemon
|
||||||
|
output_log="/var/log/storage-server/storage-server.log"
|
||||||
|
error_log="$output_log"
|
||||||
|
|
||||||
|
depend() {
|
||||||
|
need net
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
STORAGE_SERVER_ADDRESS=:3001
|
||||||
|
STORAGE_SERVER_BLOBSTORE_DSN_PATTERN="sqlite:///var/lib/storage-server/data/%TENANT%/%APPID%/blobstore.sqlite?_pragma=foreign_keys(1)&_pragma=busy_timeout=60000"
|
||||||
|
STORAGE_SERVER_DOCUMENTSTORE_DSN_PATTERN="sqlite:///var/lib/storage-server/data/%TENANT%/%APPID%/documentstore.sqlite?_pragma=foreign_keys(1)&_pragma=busy_timeout=60000"
|
||||||
|
STORAGE_SERVER_SHARESTORE_DSN_PATTERN="sqlite:///var/lib/storage-server/data/%TENANT%/sharestore.sqlite?_pragma=foreign_keys(1)&_pragma=busy_timeout=60000"
|
||||||
|
STORAGE_SERVER_PRIVATE_KEY="/var/lib/storage-server/storage-server.key"
|
||||||
|
STORAGE_SERVER_PRIVATE_KEY_DEFAULT_SIZE="2048"
|
||||||
|
STORAGE_SERVER_SIGNING_ALGORITHM="RS256"
|
||||||
|
STORAGE_SERVER_CACHE_TTL=1h
|
||||||
|
STORAGE_SERVER_CACHE_SIZE=32
|
|
@ -0,0 +1,35 @@
|
||||||
|
[Unit]
|
||||||
|
Description=storage-server service
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
Restart=on-failure
|
||||||
|
EnvironmentFile=/etc/storage-server/environ
|
||||||
|
ExecStart=/usr/bin/storage-server run
|
||||||
|
EnvironmentFile=/etc/storage-server/environ
|
||||||
|
NoNewPrivileges=yes
|
||||||
|
PrivateTmp=yes
|
||||||
|
PrivateDevices=yes
|
||||||
|
PrivateUsers=yes
|
||||||
|
DynamicUser=yes
|
||||||
|
StateDirectory=storage-server
|
||||||
|
DevicePolicy=closed
|
||||||
|
ProtectSystem=true
|
||||||
|
ProtectHome=read-only
|
||||||
|
ProtectKernelLogs=yes
|
||||||
|
ProtectProc=invisible
|
||||||
|
ProtectClock=yes
|
||||||
|
ProtectControlGroups=yes
|
||||||
|
ProtectKernelModules=yes
|
||||||
|
ProtectKernelTunables=yes
|
||||||
|
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK
|
||||||
|
RestrictNamespaces=yes
|
||||||
|
RestrictRealtime=yes
|
||||||
|
RestrictSUIDSGID=yes
|
||||||
|
MemoryDenyWriteExecute=yes
|
||||||
|
LockPersonality=yes
|
||||||
|
CapabilityBoundingSet=~CAP_SETUID CAP_SETGID CAP_SETPCAP CAP_SYS_ADMIN CAP_SYS_PTRACE CAP_CHOWN CAP_FSETID CAP_SETFCAP CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_FOWNER CAP_IPC_OWNER CAP_NET_ADMIN CAP_WAKE_ALARM CAP_SYS_TTY_CONFIG
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
25
modd.conf
25
modd.conf
|
@ -1,12 +1,21 @@
|
||||||
**/*.go
|
**/*.go
|
||||||
pkg/app/sdk/client/src/**/*.js
|
**/*.tmpl
|
||||||
pkg/app/sdk/client/src/**/*.ts
|
pkg/sdk/client/src/**/*.js
|
||||||
misc/client-sdk-testsuite/src/**/*
|
pkg/sdk/client/src/**/*.ts
|
||||||
|
misc/client-sdk-testsuite/dist/server/*.js
|
||||||
modd.conf
|
modd.conf
|
||||||
|
.env
|
||||||
{
|
{
|
||||||
prep: make build-sdk
|
prep: make build-sdk build-cli build-storage-server
|
||||||
prep: cd misc/client-sdk-testsuite && make dist
|
daemon: make run-app
|
||||||
prep: make GOTEST_ARGS="-short" test
|
daemon: make run-storage-server
|
||||||
prep: make build
|
|
||||||
daemon: bin/cli app run -p misc/client-sdk-testsuite/dist --storage-file ./sdk-testsuite.sqlite
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
misc/client-sdk-testsuite/src/**/*
|
||||||
|
{
|
||||||
|
prep: make build-client-sdk-test-app
|
||||||
|
}
|
||||||
|
|
||||||
|
**/*.go {
|
||||||
|
# prep: make GOTEST_ARGS="-short" test
|
||||||
|
}
|
|
@ -10,14 +10,50 @@
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/sockjs-client": "^1.5.1",
|
"@types/sockjs-client": "^1.5.1",
|
||||||
|
"@webcomponents/webcomponentsjs": "^2.8.0",
|
||||||
|
"core-js": "^3.30.1",
|
||||||
|
"lit": "^2.7.2",
|
||||||
"sockjs-client": "^1.6.1"
|
"sockjs-client": "^1.6.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@lit-labs/ssr-dom-shim": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-92uQ5ARf7UXYrzaFcAX3T2rTvaS9Z1//ukV+DqjACM4c8s0ZBQd7ayJU5Dh2AFLD/Ayuyz4uMmxQec8q3U4Ong=="
|
||||||
|
},
|
||||||
|
"node_modules/@lit/reactive-element": {
|
||||||
|
"version": "1.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.6.1.tgz",
|
||||||
|
"integrity": "sha512-va15kYZr7KZNNPZdxONGQzpUr+4sxVu7V/VG7a8mRfPPXUyhEYj5RzXCQmGrlP3tAh0L3HHm5AjBMFYRqlM9SA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@lit-labs/ssr-dom-shim": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/sockjs-client": {
|
"node_modules/@types/sockjs-client": {
|
||||||
"version": "1.5.1",
|
"version": "1.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/sockjs-client/-/sockjs-client-1.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/sockjs-client/-/sockjs-client-1.5.1.tgz",
|
||||||
"integrity": "sha512-bmZM6A1GPdjF0bcuIUC+50hZEMGkzMsiG9by6X9U+7IZFOiPtz7MJ9h05FSpPVxlj4i+TzzoG3ESo1FJlbLb6A=="
|
"integrity": "sha512-bmZM6A1GPdjF0bcuIUC+50hZEMGkzMsiG9by6X9U+7IZFOiPtz7MJ9h05FSpPVxlj4i+TzzoG3ESo1FJlbLb6A=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/trusted-types": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g=="
|
||||||
|
},
|
||||||
|
"node_modules/@webcomponents/webcomponentsjs": {
|
||||||
|
"version": "2.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@webcomponents/webcomponentsjs/-/webcomponentsjs-2.8.0.tgz",
|
||||||
|
"integrity": "sha512-loGD63sacRzOzSJgQnB9ZAhaQGkN7wl2Zuw7tsphI5Isa0irijrRo6EnJii/GgjGefIFO8AIO7UivzRhFaEk9w=="
|
||||||
|
},
|
||||||
|
"node_modules/core-js": {
|
||||||
|
"version": "3.30.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.30.1.tgz",
|
||||||
|
"integrity": "sha512-ZNS5nbiSwDTq4hFosEDqm65izl2CWmLz0hARJMyNQBgkUZMIF51cQiMvIQKA6hvuaeWxQDP3hEedM1JZIgTldQ==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/core-js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "3.2.7",
|
"version": "3.2.7",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
|
||||||
|
@ -55,6 +91,34 @@
|
||||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/lit": {
|
||||||
|
"version": "2.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/lit/-/lit-2.7.2.tgz",
|
||||||
|
"integrity": "sha512-9QnZmG5mIKPRja96cpndMclLSi0Qrz2BXD6EbqNqCKMMjOWVm/BwAeXufFk2jqFsNmY07HOzU8X+8aTSVt3yrA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@lit/reactive-element": "^1.6.0",
|
||||||
|
"lit-element": "^3.3.0",
|
||||||
|
"lit-html": "^2.7.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/lit-element": {
|
||||||
|
"version": "3.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.3.1.tgz",
|
||||||
|
"integrity": "sha512-Gl+2409uXWbf7n6cCl7Kzasm7zjT9xmdwi2BhLNi70sRKAgRkqueDu5mSIH3hPYMM0/vqBCdPXod3NbGkRA2ww==",
|
||||||
|
"dependencies": {
|
||||||
|
"@lit-labs/ssr-dom-shim": "^1.1.0",
|
||||||
|
"@lit/reactive-element": "^1.3.0",
|
||||||
|
"lit-html": "^2.7.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/lit-html": {
|
||||||
|
"version": "2.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.7.2.tgz",
|
||||||
|
"integrity": "sha512-ZJCfKlA2XELu5tn7XuzOziGFGvf1SeQm+ngLWoJ8bXtSkRrrR3ms6SWy+gsdxeYwySLij5xAhdd2C3EX0ftxdQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/trusted-types": "^2.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
|
@ -139,11 +203,39 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@lit-labs/ssr-dom-shim": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-92uQ5ARf7UXYrzaFcAX3T2rTvaS9Z1//ukV+DqjACM4c8s0ZBQd7ayJU5Dh2AFLD/Ayuyz4uMmxQec8q3U4Ong=="
|
||||||
|
},
|
||||||
|
"@lit/reactive-element": {
|
||||||
|
"version": "1.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.6.1.tgz",
|
||||||
|
"integrity": "sha512-va15kYZr7KZNNPZdxONGQzpUr+4sxVu7V/VG7a8mRfPPXUyhEYj5RzXCQmGrlP3tAh0L3HHm5AjBMFYRqlM9SA==",
|
||||||
|
"requires": {
|
||||||
|
"@lit-labs/ssr-dom-shim": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/sockjs-client": {
|
"@types/sockjs-client": {
|
||||||
"version": "1.5.1",
|
"version": "1.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/sockjs-client/-/sockjs-client-1.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/sockjs-client/-/sockjs-client-1.5.1.tgz",
|
||||||
"integrity": "sha512-bmZM6A1GPdjF0bcuIUC+50hZEMGkzMsiG9by6X9U+7IZFOiPtz7MJ9h05FSpPVxlj4i+TzzoG3ESo1FJlbLb6A=="
|
"integrity": "sha512-bmZM6A1GPdjF0bcuIUC+50hZEMGkzMsiG9by6X9U+7IZFOiPtz7MJ9h05FSpPVxlj4i+TzzoG3ESo1FJlbLb6A=="
|
||||||
},
|
},
|
||||||
|
"@types/trusted-types": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g=="
|
||||||
|
},
|
||||||
|
"@webcomponents/webcomponentsjs": {
|
||||||
|
"version": "2.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@webcomponents/webcomponentsjs/-/webcomponentsjs-2.8.0.tgz",
|
||||||
|
"integrity": "sha512-loGD63sacRzOzSJgQnB9ZAhaQGkN7wl2Zuw7tsphI5Isa0irijrRo6EnJii/GgjGefIFO8AIO7UivzRhFaEk9w=="
|
||||||
|
},
|
||||||
|
"core-js": {
|
||||||
|
"version": "3.30.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.30.1.tgz",
|
||||||
|
"integrity": "sha512-ZNS5nbiSwDTq4hFosEDqm65izl2CWmLz0hARJMyNQBgkUZMIF51cQiMvIQKA6hvuaeWxQDP3hEedM1JZIgTldQ=="
|
||||||
|
},
|
||||||
"debug": {
|
"debug": {
|
||||||
"version": "3.2.7",
|
"version": "3.2.7",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
|
||||||
|
@ -175,6 +267,34 @@
|
||||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||||
},
|
},
|
||||||
|
"lit": {
|
||||||
|
"version": "2.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/lit/-/lit-2.7.2.tgz",
|
||||||
|
"integrity": "sha512-9QnZmG5mIKPRja96cpndMclLSi0Qrz2BXD6EbqNqCKMMjOWVm/BwAeXufFk2jqFsNmY07HOzU8X+8aTSVt3yrA==",
|
||||||
|
"requires": {
|
||||||
|
"@lit/reactive-element": "^1.6.0",
|
||||||
|
"lit-element": "^3.3.0",
|
||||||
|
"lit-html": "^2.7.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lit-element": {
|
||||||
|
"version": "3.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.3.1.tgz",
|
||||||
|
"integrity": "sha512-Gl+2409uXWbf7n6cCl7Kzasm7zjT9xmdwi2BhLNi70sRKAgRkqueDu5mSIH3hPYMM0/vqBCdPXod3NbGkRA2ww==",
|
||||||
|
"requires": {
|
||||||
|
"@lit-labs/ssr-dom-shim": "^1.1.0",
|
||||||
|
"@lit/reactive-element": "^1.3.0",
|
||||||
|
"lit-html": "^2.7.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lit-html": {
|
||||||
|
"version": "2.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.7.2.tgz",
|
||||||
|
"integrity": "sha512-ZJCfKlA2XELu5tn7XuzOziGFGvf1SeQm+ngLWoJ8bXtSkRrrR3ms6SWy+gsdxeYwySLij5xAhdd2C3EX0ftxdQ==",
|
||||||
|
"requires": {
|
||||||
|
"@types/trusted-types": "^2.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"ms": {
|
"ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
|
|
|
@ -10,6 +10,9 @@
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/sockjs-client": "^1.5.1",
|
"@types/sockjs-client": "^1.5.1",
|
||||||
|
"@webcomponents/webcomponentsjs": "^2.8.0",
|
||||||
|
"core-js": "^3.30.1",
|
||||||
|
"lit": "^2.7.2",
|
||||||
"sockjs-client": "^1.6.1"
|
"sockjs-client": "^1.6.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
package app
|
|
||||||
|
|
||||||
type ID string
|
|
||||||
|
|
||||||
type Manifest struct {
|
|
||||||
ID ID `yaml:"id"`
|
|
||||||
Version string `yaml:"version"`
|
|
||||||
Title string `yaml:"title"`
|
|
||||||
Description string `yaml:"description"`
|
|
||||||
Tags []string `yaml:"tags"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type App struct {
|
|
||||||
ID ID
|
|
||||||
Manifest *Manifest
|
|
||||||
}
|
|
|
@ -1,101 +0,0 @@
|
||||||
package app
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"gitlab.com/wpetit/goweb/logger"
|
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
|
|
||||||
"forge.cadoles.com/arcad/edge/pkg/bundle"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
type FilesystemLoader struct {
|
|
||||||
searchPatterns []string
|
|
||||||
}
|
|
||||||
|
|
||||||
type LoadedApp struct {
|
|
||||||
App *App
|
|
||||||
Bundle bundle.Bundle
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *FilesystemLoader) Load(ctx context.Context) ([]*LoadedApp, error) {
|
|
||||||
apps := make([]*LoadedApp, 0)
|
|
||||||
|
|
||||||
for _, seachPattern := range l.searchPatterns {
|
|
||||||
absSearchPattern, err := filepath.Abs(seachPattern)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "could not generate absolute path for '%s'", seachPattern)
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Debug(ctx, "searching apps in filesystem", logger.F("searchPattern", absSearchPattern))
|
|
||||||
|
|
||||||
files, err := filepath.Glob(absSearchPattern)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "could not search files with pattern '%s'", absSearchPattern)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, f := range files {
|
|
||||||
loopCtx := logger.With(ctx, logger.F("file", f))
|
|
||||||
|
|
||||||
logger.Debug(loopCtx, "found app bundle")
|
|
||||||
|
|
||||||
b, err := bundle.FromPath(f)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error(loopCtx, "could not load bundle", logger.E(errors.WithStack(err)))
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Debug(loopCtx, "loading app manifest")
|
|
||||||
|
|
||||||
appManifest, err := LoadAppManifest(b)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error(loopCtx, "could not load app manifest", logger.E(errors.WithStack(err)))
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
g := &App{
|
|
||||||
ID: appManifest.ID,
|
|
||||||
Manifest: appManifest,
|
|
||||||
}
|
|
||||||
|
|
||||||
apps = append(apps, &LoadedApp{
|
|
||||||
App: g,
|
|
||||||
Bundle: b,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return apps, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewFilesystemLoader(searchPatterns ...string) *FilesystemLoader {
|
|
||||||
return &FilesystemLoader{
|
|
||||||
searchPatterns: searchPatterns,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func LoadAppManifest(b bundle.Bundle) (*Manifest, error) {
|
|
||||||
reader, _, err := b.File("manifest.yml")
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "could not read manifest.yml")
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if err := reader.Close(); err != nil {
|
|
||||||
panic(errors.WithStack(err))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
manifest := &Manifest{}
|
|
||||||
|
|
||||||
decoder := yaml.NewDecoder(reader)
|
|
||||||
if err := decoder.Decode(manifest); err != nil {
|
|
||||||
return nil, errors.Wrap(err, "could not decode manifest.yml")
|
|
||||||
}
|
|
||||||
|
|
||||||
return manifest, nil
|
|
||||||
}
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/bundle"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/mod/semver"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ID string
|
||||||
|
|
||||||
|
type Manifest struct {
|
||||||
|
ID ID `yaml:"id" json:"id"`
|
||||||
|
Version string `yaml:"version" json:"version"`
|
||||||
|
Title string `yaml:"title" json:"title"`
|
||||||
|
Description string `yaml:"description" json:"description"`
|
||||||
|
Tags []string `yaml:"tags" json:"tags"`
|
||||||
|
Metadata MapStr `yaml:"metadata" json:"metadata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MetadataValidator func(map[string]any) (bool, error)
|
||||||
|
|
||||||
|
func (m *Manifest) Validate(validators ...MetadataValidator) (bool, error) {
|
||||||
|
if m.ID == "" {
|
||||||
|
return false, errors.New("'id' property should not be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.Version == "" {
|
||||||
|
return false, errors.New("'version' property should not be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
version := m.Version
|
||||||
|
if !strings.HasPrefix(version, "v") {
|
||||||
|
version = "v" + version
|
||||||
|
}
|
||||||
|
|
||||||
|
if !semver.IsValid(version) {
|
||||||
|
return false, errors.Errorf("version '%s' does not respect semver format", m.Version)
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.Title == "" {
|
||||||
|
return false, errors.New("'title' property should not be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.Tags != nil {
|
||||||
|
for _, t := range m.Tags {
|
||||||
|
if strings.ContainsAny(t, " \t\n\r") {
|
||||||
|
return false, errors.Errorf("tag '%s' should not contain any space or new line", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range validators {
|
||||||
|
valid, err := v(m.Metadata)
|
||||||
|
if !valid || err != nil {
|
||||||
|
return valid, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadManifest(b bundle.Bundle) (*Manifest, error) {
|
||||||
|
reader, _, err := b.File("manifest.yml")
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "could not read manifest.yml")
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := reader.Close(); err != nil {
|
||||||
|
panic(errors.WithStack(err))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
manifest := &Manifest{}
|
||||||
|
|
||||||
|
decoder := yaml.NewDecoder(reader)
|
||||||
|
if err := decoder.Decode(manifest); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "could not decode manifest.yml")
|
||||||
|
}
|
||||||
|
|
||||||
|
return manifest, nil
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MapStr map[string]interface{}
|
||||||
|
|
||||||
|
func MapStrUnion(dict1 MapStr, dict2 MapStr) MapStr {
|
||||||
|
dict := MapStr{}
|
||||||
|
|
||||||
|
for k, v := range dict1 {
|
||||||
|
dict[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range dict2 {
|
||||||
|
dict[k] = v
|
||||||
|
}
|
||||||
|
return dict
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms *MapStr) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||||
|
var result map[interface{}]interface{}
|
||||||
|
err := unmarshal(&result)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
*ms = cleanUpInterfaceMap(result)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanUpInterfaceArray(in []interface{}) []interface{} {
|
||||||
|
result := make([]interface{}, len(in))
|
||||||
|
for i, v := range in {
|
||||||
|
result[i] = cleanUpMapValue(v)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanUpInterfaceMap(in map[interface{}]interface{}) MapStr {
|
||||||
|
result := make(MapStr)
|
||||||
|
for k, v := range in {
|
||||||
|
result[fmt.Sprintf("%v", k)] = cleanUpMapValue(v)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanUpMapValue(v interface{}) interface{} {
|
||||||
|
switch v := v.(type) {
|
||||||
|
case []interface{}:
|
||||||
|
return cleanUpInterfaceArray(v)
|
||||||
|
case map[interface{}]interface{}:
|
||||||
|
return cleanUpInterfaceMap(v)
|
||||||
|
case string:
|
||||||
|
return v
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("%v", v)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package metadata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/app"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func WithMinimumRoleValidator(roles ...string) app.MetadataValidator {
|
||||||
|
return func(metadata map[string]any) (bool, error) {
|
||||||
|
rawMinimumRole, exists := metadata["minimumRole"]
|
||||||
|
if !exists {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
minimumRole, ok := rawMinimumRole.(string)
|
||||||
|
if !ok {
|
||||||
|
return false, errors.Errorf("metadata['minimumRole']: unexpected value type '%T'", rawMinimumRole)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, r := range roles {
|
||||||
|
if minimumRole == r {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, errors.Errorf("metadata['minimumRole']: unexpected role '%s'", minimumRole)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
package metadata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/app"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NamedPath string
|
||||||
|
|
||||||
|
const (
|
||||||
|
NamedPathAdmin NamedPath = "admin"
|
||||||
|
NamedPathIcon NamedPath = "icon"
|
||||||
|
)
|
||||||
|
|
||||||
|
func WithNamedPathsValidator(names ...NamedPath) app.MetadataValidator {
|
||||||
|
set := map[NamedPath]struct{}{}
|
||||||
|
for _, n := range names {
|
||||||
|
set[n] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(metadata map[string]any) (bool, error) {
|
||||||
|
rawPaths, exists := metadata["paths"]
|
||||||
|
if !exists {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
paths, ok := rawPaths.(app.MapStr)
|
||||||
|
if !ok {
|
||||||
|
return false, errors.Errorf("metadata['paths']: unexpected named path value type '%T'", rawPaths)
|
||||||
|
}
|
||||||
|
|
||||||
|
for n, p := range paths {
|
||||||
|
if _, exists := set[NamedPath(n)]; !exists {
|
||||||
|
return false, errors.Errorf("metadata['paths']: unexpected named path '%s'", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
path, ok := p.(string)
|
||||||
|
if !ok {
|
||||||
|
return false, errors.Errorf("metadata['paths']['%s']: unexpected named path value type '%T'", n, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(path, "/") {
|
||||||
|
return false, errors.Errorf("metadata['paths']['%s']: named path value should start with a '/'", n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
id: foo.arcad.app
|
||||||
|
version: v0.0.0
|
||||||
|
title: Foo
|
||||||
|
description: A test app
|
||||||
|
tags: ["test"]
|
||||||
|
metadata:
|
||||||
|
minimumRole: foo
|
|
@ -0,0 +1,10 @@
|
||||||
|
id: foo.arcad.app
|
||||||
|
version: v0.0.0
|
||||||
|
title: Foo
|
||||||
|
description: A test app
|
||||||
|
tags: ["test"]
|
||||||
|
metadata:
|
||||||
|
paths:
|
||||||
|
invalid: /admin
|
||||||
|
icon: /my-app-icon.png
|
||||||
|
minimumRole: visitor
|
|
@ -0,0 +1,10 @@
|
||||||
|
id: foo.arcad.app
|
||||||
|
version: v0.0.0
|
||||||
|
title: Foo
|
||||||
|
description: A test app
|
||||||
|
tags: ["test"]
|
||||||
|
metadata:
|
||||||
|
paths:
|
||||||
|
admin: /admin
|
||||||
|
icon: /my-app-icon.png
|
||||||
|
minimumRole: visitor
|
|
@ -0,0 +1,74 @@
|
||||||
|
package metadata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"forge.cadoles.com/arcad/edge/pkg/app"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type validatorTestCase struct {
|
||||||
|
File string
|
||||||
|
ExpectValid bool
|
||||||
|
ExpectError bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var validatorTestCases = []validatorTestCase{
|
||||||
|
{
|
||||||
|
File: "valid.yml",
|
||||||
|
ExpectValid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
File: "invalid-paths.yml",
|
||||||
|
ExpectValid: false,
|
||||||
|
ExpectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
File: "invalid-minimum-role.yml",
|
||||||
|
ExpectValid: false,
|
||||||
|
ExpectError: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var validators = []app.MetadataValidator{
|
||||||
|
WithMinimumRoleValidator("visitor", "user", "superuser", "admin", "superadmin"),
|
||||||
|
WithNamedPathsValidator(NamedPathAdmin, NamedPathIcon),
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManifestValidate(t *testing.T) {
|
||||||
|
for _, tc := range validatorTestCases {
|
||||||
|
func(tc *validatorTestCase) {
|
||||||
|
t.Run(tc.File, func(t *testing.T) {
|
||||||
|
data, err := ioutil.ReadFile(filepath.Join("testdata/manifests", tc.File))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%+v", errors.WithStack(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
var manifest app.Manifest
|
||||||
|
|
||||||
|
if err := yaml.Unmarshal(data, &manifest); err != nil {
|
||||||
|
t.Fatalf("%+v", errors.WithStack(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
valid, err := manifest.Validate(validators...)
|
||||||
|
|
||||||
|
t.Logf("[RESULT] valid:%v, err:%v", valid, err)
|
||||||
|
|
||||||
|
if e, g := tc.ExpectValid, valid; e != g {
|
||||||
|
t.Errorf("valid: expected '%v', got '%v'", e, g)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.ExpectError && err == nil {
|
||||||
|
t.Error("err should not be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tc.ExpectError && err != nil {
|
||||||
|
t.Errorf("err: expected nil, got '%+v'", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}(&tc)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"gitlab.com/wpetit/goweb/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
ModuleFactories []ServerModuleFactory
|
||||||
|
ErrorHandler func(ctx context.Context, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type OptionFunc func(opts *Options)
|
||||||
|
|
||||||
|
func NewOptions(funcs ...OptionFunc) *Options {
|
||||||
|
opts := &Options{
|
||||||
|
ModuleFactories: make([]ServerModuleFactory, 0),
|
||||||
|
ErrorHandler: func(ctx context.Context, err error) {
|
||||||
|
logger.Error(ctx, err.Error(), logger.E(errors.WithStack(err)))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fn := range funcs {
|
||||||
|
fn(opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithModulesFactories(factories ...ServerModuleFactory) OptionFunc {
|
||||||
|
return func(opts *Options) {
|
||||||
|
opts.ModuleFactories = factories
|
||||||
|
}
|
||||||
|
}
|
|
@ -39,3 +39,18 @@ func NewPromiseProxy(promise *goja.Promise, resolve func(result interface{}), re
|
||||||
|
|
||||||
return proxy
|
return proxy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewPromiseProxyFrom(rt *goja.Runtime) *PromiseProxy {
|
||||||
|
promise, resolve, reject := rt.NewPromise()
|
||||||
|
|
||||||
|
return NewPromiseProxy(promise, resolve, reject)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isPromise(v any) (*goja.Promise, bool) {
|
||||||
|
if v == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
promise, ok := v.(*goja.Promise)
|
||||||
|
return promise, ok
|
||||||
|
}
|
||||||
|
|
|
@ -1,77 +1,146 @@
|
||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/dop251/goja"
|
"github.com/dop251/goja"
|
||||||
"github.com/dop251/goja_nodejs/eventloop"
|
"github.com/dop251/goja_nodejs/eventloop"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"gitlab.com/wpetit/goweb/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrFuncDoesNotExist = errors.New("function does not exist")
|
var (
|
||||||
|
ErrFuncDoesNotExist = errors.New("function does not exist")
|
||||||
|
ErrUnknownError = errors.New("unknown error")
|
||||||
|
)
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
runtime *goja.Runtime
|
loop *eventloop.EventLoop
|
||||||
loop *eventloop.EventLoop
|
factories []ServerModuleFactory
|
||||||
modules []ServerModule
|
modules []ServerModule
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) Load(name string, src string) error {
|
func (s *Server) ExecFuncByName(ctx context.Context, funcName string, args ...any) (any, error) {
|
||||||
_, err := s.runtime.RunScript(name, src)
|
ctx = logger.With(ctx, logger.F("function", funcName), logger.F("args", args))
|
||||||
|
|
||||||
|
ret, err := s.Exec(ctx, funcName, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "could not run js script")
|
return nil, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) ExecFuncByName(funcName string, args ...interface{}) (goja.Value, error) {
|
func (s *Server) Exec(ctx context.Context, callableOrFuncname any, args ...any) (any, error) {
|
||||||
callable, ok := goja.AssertFunction(s.runtime.Get(funcName))
|
type result struct {
|
||||||
if !ok {
|
value any
|
||||||
return nil, errors.WithStack(ErrFuncDoesNotExist)
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.Exec(callable, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) Exec(callable goja.Callable, args ...interface{}) (goja.Value, error) {
|
|
||||||
var (
|
|
||||||
wg sync.WaitGroup
|
|
||||||
value goja.Value
|
|
||||||
err error
|
err error
|
||||||
)
|
}
|
||||||
|
|
||||||
wg.Add(1)
|
done := make(chan result)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
// Drain done channel
|
||||||
|
for range done {
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
s.loop.RunOnLoop(func(rt *goja.Runtime) {
|
||||||
|
defer close(done)
|
||||||
|
|
||||||
|
var callable goja.Callable
|
||||||
|
switch typ := callableOrFuncname.(type) {
|
||||||
|
case goja.Callable:
|
||||||
|
callable = typ
|
||||||
|
|
||||||
|
case string:
|
||||||
|
call, ok := goja.AssertFunction(rt.Get(typ))
|
||||||
|
if !ok {
|
||||||
|
done <- result{
|
||||||
|
err: errors.WithStack(ErrFuncDoesNotExist),
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
callable = call
|
||||||
|
|
||||||
|
default:
|
||||||
|
done <- result{
|
||||||
|
err: errors.Errorf("callableOrFuncname: expected callable or function name, got '%T'", callableOrFuncname),
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
recovered := recover()
|
||||||
|
if recovered == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
recoveredErr, ok := recovered.(error)
|
||||||
|
if !ok {
|
||||||
|
panic(recovered)
|
||||||
|
}
|
||||||
|
|
||||||
|
done <- result{
|
||||||
|
err: recoveredErr,
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
s.loop.RunOnLoop(func(vm *goja.Runtime) {
|
|
||||||
jsArgs := make([]goja.Value, 0, len(args))
|
jsArgs := make([]goja.Value, 0, len(args))
|
||||||
for _, a := range args {
|
for _, a := range args {
|
||||||
jsArgs = append(jsArgs, vm.ToValue(a))
|
jsArgs = append(jsArgs, rt.ToValue(a))
|
||||||
}
|
}
|
||||||
|
|
||||||
value, err = callable(nil, jsArgs...)
|
logger.Debug(ctx, "executing callable", logger.F("callable", callableOrFuncname))
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
value, err := callable(nil, jsArgs...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.WithStack(err)
|
done <- result{
|
||||||
|
err: errors.WithStack(err),
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Done()
|
done <- result{
|
||||||
|
value: value.Export(),
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Debug(ctx, "executed callable", logger.F("callable", callableOrFuncname), logger.F("duration", time.Since(start).String()))
|
||||||
})
|
})
|
||||||
|
|
||||||
wg.Wait()
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
return value, err
|
return nil, nil
|
||||||
|
|
||||||
|
case result := <-done:
|
||||||
|
if result.err != nil {
|
||||||
|
return nil, errors.WithStack(result.err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if promise, ok := isPromise(result.value); ok {
|
||||||
|
return s.waitForPromise(promise), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.value, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) IsPromise(v goja.Value) (*goja.Promise, bool) {
|
func (s *Server) waitForPromise(promise *goja.Promise) any {
|
||||||
promise, ok := v.Export().(*goja.Promise)
|
|
||||||
return promise, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) WaitForPromise(promise *goja.Promise) goja.Value {
|
|
||||||
var (
|
var (
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
value goja.Value
|
value any
|
||||||
)
|
)
|
||||||
|
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
|
@ -91,7 +160,7 @@ func (s *Server) WaitForPromise(promise *goja.Promise) goja.Value {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
value = promise.Result()
|
value = promise.Result().Export()
|
||||||
|
|
||||||
breakLoop = true
|
breakLoop = true
|
||||||
})
|
})
|
||||||
|
@ -111,28 +180,41 @@ func (s *Server) WaitForPromise(promise *goja.Promise) goja.Value {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) NewPromise() *PromiseProxy {
|
func (s *Server) Start(ctx context.Context, name string, src string) error {
|
||||||
promise, resolve, reject := s.runtime.NewPromise()
|
|
||||||
|
|
||||||
return NewPromiseProxy(promise, resolve, reject)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) ToValue(v interface{}) goja.Value {
|
|
||||||
return s.runtime.ToValue(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) Start() error {
|
|
||||||
s.loop.Start()
|
s.loop.Start()
|
||||||
|
|
||||||
for _, mod := range s.modules {
|
done := make(chan error)
|
||||||
initMod, ok := mod.(InitializableModule)
|
|
||||||
if !ok {
|
s.loop.RunOnLoop(func(rt *goja.Runtime) {
|
||||||
continue
|
defer close(done)
|
||||||
|
|
||||||
|
rt.SetFieldNameMapper(goja.TagFieldNameMapper("goja", true))
|
||||||
|
rt.SetRandSource(createRandomSource())
|
||||||
|
|
||||||
|
if err := s.loadModules(ctx, rt); err != nil {
|
||||||
|
err = errors.WithStack(err)
|
||||||
|
done <- err
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := initMod.OnInit(); err != nil {
|
if _, err := rt.RunScript(name, src); err != nil {
|
||||||
return errors.WithStack(err)
|
done <- errors.Wrap(err, "could not run js script")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := s.initModules(ctx, rt); err != nil {
|
||||||
|
err = errors.WithStack(err)
|
||||||
|
done <- err
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
done <- nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := <-done; err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -142,36 +224,50 @@ func (s *Server) Stop() {
|
||||||
s.loop.Stop()
|
s.loop.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) initModules(factories ...ServerModuleFactory) {
|
func (s *Server) loadModules(ctx context.Context, rt *goja.Runtime) error {
|
||||||
runtime := goja.New()
|
modules := make([]ServerModule, 0, len(s.factories))
|
||||||
|
|
||||||
runtime.SetFieldNameMapper(goja.TagFieldNameMapper("goja", true))
|
for _, moduleFactory := range s.factories {
|
||||||
runtime.SetRandSource(createRandomSource())
|
|
||||||
|
|
||||||
modules := make([]ServerModule, 0, len(factories))
|
|
||||||
|
|
||||||
for _, moduleFactory := range factories {
|
|
||||||
mod := moduleFactory(s)
|
mod := moduleFactory(s)
|
||||||
export := runtime.NewObject()
|
|
||||||
|
export := rt.NewObject()
|
||||||
mod.Export(export)
|
mod.Export(export)
|
||||||
runtime.Set(mod.Name(), export)
|
|
||||||
|
rt.Set(mod.Name(), export)
|
||||||
|
|
||||||
modules = append(modules, mod)
|
modules = append(modules, mod)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.runtime = runtime
|
|
||||||
s.modules = modules
|
s.modules = modules
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) initModules(ctx context.Context, rt *goja.Runtime) error {
|
||||||
|
for _, mod := range s.modules {
|
||||||
|
initMod, ok := mod.(InitializableModule)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Debug(ctx, "initializing module", logger.F("module", initMod.Name()))
|
||||||
|
|
||||||
|
if err := initMod.OnInit(ctx, rt); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(factories ...ServerModuleFactory) *Server {
|
func NewServer(factories ...ServerModuleFactory) *Server {
|
||||||
server := &Server{
|
server := &Server{
|
||||||
|
factories: factories,
|
||||||
loop: eventloop.NewEventLoop(
|
loop: eventloop.NewEventLoop(
|
||||||
eventloop.EnableConsole(false),
|
eventloop.EnableConsole(false),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
server.initModules(factories...)
|
|
||||||
|
|
||||||
return server
|
return server
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
"github.com/dop251/goja"
|
"github.com/dop251/goja"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -13,5 +15,5 @@ type ServerModule interface {
|
||||||
|
|
||||||
type InitializableModule interface {
|
type InitializableModule interface {
|
||||||
ServerModule
|
ServerModule
|
||||||
OnInit() error
|
OnInit(ctx context.Context, rt *goja.Runtime) error
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ func TestBundle(t *testing.T) {
|
||||||
bundles := []Bundle{
|
bundles := []Bundle{
|
||||||
NewDirectoryBundle("testdata/bundle"),
|
NewDirectoryBundle("testdata/bundle"),
|
||||||
NewTarBundle("testdata/bundle.tar.gz"),
|
NewTarBundle("testdata/bundle.tar.gz"),
|
||||||
NewZipBundle("testdata/bundle.zip"),
|
Must(NewZipBundleFromPath("testdata/bundle.zip")),
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, b := range bundles {
|
for _, b := range bundles {
|
||||||
|
|
|
@ -3,7 +3,7 @@ package bundle
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
@ -40,8 +40,6 @@ func (fs *FileSystem) Open(name string) (http.File, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Error(ctx, "could not open bundle file", logger.E(err))
|
|
||||||
|
|
||||||
return nil, errors.Wrapf(err, "could not open bundle file '%s'", p)
|
return nil, errors.Wrapf(err, "could not open bundle file '%s'", p)
|
||||||
}
|
}
|
||||||
defer readCloser.Close()
|
defer readCloser.Close()
|
||||||
|
@ -53,16 +51,14 @@ func (fs *FileSystem) Open(name string) (http.File, error) {
|
||||||
if fileInfo.IsDir() {
|
if fileInfo.IsDir() {
|
||||||
files, err := fs.bundle.Dir(p)
|
files, err := fs.bundle.Dir(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(ctx, "could not read bundle directory", logger.E(err))
|
|
||||||
|
|
||||||
return nil, errors.Wrapf(err, "could not read bundle directory '%s'", p)
|
return nil, errors.Wrapf(err, "could not read bundle directory '%s'", p)
|
||||||
}
|
}
|
||||||
|
|
||||||
file.files = files
|
file.files = files
|
||||||
} else {
|
} else {
|
||||||
data, err := ioutil.ReadAll(readCloser)
|
data, err := io.ReadAll(readCloser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(ctx, "could not read bundle file", logger.E(err))
|
logger.Error(ctx, "could not read bundle file", logger.CapturedE(errors.WithStack(err)))
|
||||||
|
|
||||||
return nil, errors.Wrapf(err, "could not read bundle file '%s'", p)
|
return nil, errors.Wrapf(err, "could not read bundle file '%s'", p)
|
||||||
}
|
}
|
||||||
|
@ -99,3 +95,5 @@ func (f *File) Readdir(count int) ([]os.FileInfo, error) {
|
||||||
func (f *File) Stat() (os.FileInfo, error) {
|
func (f *File) Stat() (os.FileInfo, error) {
|
||||||
return f.fi, nil
|
return f.fi, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ http.FileSystem = &FileSystem{}
|
||||||
|
|
|
@ -13,6 +13,7 @@ type ArchiveExt string
|
||||||
const (
|
const (
|
||||||
ExtZip ArchiveExt = "zip"
|
ExtZip ArchiveExt = "zip"
|
||||||
ExtTarGz ArchiveExt = "tar.gz"
|
ExtTarGz ArchiveExt = "tar.gz"
|
||||||
|
ExtZim ArchiveExt = "zim"
|
||||||
)
|
)
|
||||||
|
|
||||||
func FromPath(path string) (Bundle, error) {
|
func FromPath(path string) (Bundle, error) {
|
||||||
|
@ -53,7 +54,16 @@ func matchArchivePattern(archivePath string) (Bundle, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if matches {
|
if matches {
|
||||||
return NewZipBundle(archivePath), nil
|
return NewZipBundleFromPath(archivePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
matches, err = filepath.Match(fmt.Sprintf("*.%s", ExtZim), base)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "could not match file archive '%s'", archivePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches {
|
||||||
|
return NewZimBundle(archivePath), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, errors.WithStack(ErrUnknownBundleArchiveExt)
|
return nil, errors.WithStack(ErrUnknownBundleArchiveExt)
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
package zim
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
type BlobReader interface {
|
||||||
|
io.ReadCloser
|
||||||
|
Size() (int64, error)
|
||||||
|
}
|
|
@ -0,0 +1,163 @@
|
||||||
|
package zim
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CompressedBlobReader struct {
|
||||||
|
reader *Reader
|
||||||
|
decoderFactory BlobDecoderFactory
|
||||||
|
|
||||||
|
clusterStartOffset uint64
|
||||||
|
clusterEndOffset uint64
|
||||||
|
blobIndex uint32
|
||||||
|
blobSize int
|
||||||
|
readOffset uint64
|
||||||
|
|
||||||
|
loadCluster sync.Once
|
||||||
|
loadClusterErr error
|
||||||
|
|
||||||
|
data []byte
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size implements BlobReader.
|
||||||
|
func (r *CompressedBlobReader) Size() (int64, error) {
|
||||||
|
if err := r.loadClusterData(); err != nil {
|
||||||
|
return 0, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int64(len(r.data)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements io.ReadCloser.
|
||||||
|
func (r *CompressedBlobReader) Close() error {
|
||||||
|
clear(r.data)
|
||||||
|
r.closed = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read implements io.ReadCloser.
|
||||||
|
func (r *CompressedBlobReader) Read(p []byte) (int, error) {
|
||||||
|
if err := r.loadClusterData(); err != nil {
|
||||||
|
return 0, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
length := len(p)
|
||||||
|
remaining := len(r.data) - int(r.readOffset)
|
||||||
|
if length > remaining {
|
||||||
|
length = remaining
|
||||||
|
}
|
||||||
|
|
||||||
|
chunk := make([]byte, length)
|
||||||
|
|
||||||
|
copy(chunk, r.data[r.readOffset:int(r.readOffset)+length])
|
||||||
|
copy(p, chunk)
|
||||||
|
|
||||||
|
if length == remaining {
|
||||||
|
return length, io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
r.readOffset += uint64(length)
|
||||||
|
|
||||||
|
return length, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *CompressedBlobReader) loadClusterData() error {
|
||||||
|
if r.closed {
|
||||||
|
return errors.WithStack(os.ErrClosed)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.loadCluster.Do(func() {
|
||||||
|
compressedData := make([]byte, r.clusterEndOffset-r.clusterStartOffset)
|
||||||
|
if err := r.reader.readRange(int64(r.clusterStartOffset+1), compressedData); err != nil {
|
||||||
|
r.loadClusterErr = errors.WithStack(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
blobBuffer := bytes.NewBuffer(compressedData)
|
||||||
|
|
||||||
|
decoder, err := r.decoderFactory(blobBuffer)
|
||||||
|
if err != nil {
|
||||||
|
r.loadClusterErr = errors.WithStack(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer decoder.Close()
|
||||||
|
|
||||||
|
uncompressedData, err := io.ReadAll(decoder)
|
||||||
|
if err != nil {
|
||||||
|
r.loadClusterErr = errors.WithStack(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
blobStart uint64
|
||||||
|
blobEnd uint64
|
||||||
|
)
|
||||||
|
|
||||||
|
if r.blobSize == 8 {
|
||||||
|
blobStart64, err := readUint64(uncompressedData[r.blobIndex*uint32(r.blobSize):r.blobIndex*uint32(r.blobSize)+uint32(r.blobSize)], binary.LittleEndian)
|
||||||
|
if err != nil {
|
||||||
|
r.loadClusterErr = errors.WithStack(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
blobStart = blobStart64
|
||||||
|
|
||||||
|
blobEnd64, err := readUint64(uncompressedData[r.blobIndex*uint32(r.blobSize)+uint32(r.blobSize):r.blobIndex*uint32(r.blobSize)+uint32(r.blobSize)+uint32(r.blobSize)], binary.LittleEndian)
|
||||||
|
if err != nil {
|
||||||
|
r.loadClusterErr = errors.WithStack(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
blobEnd = blobEnd64
|
||||||
|
} else {
|
||||||
|
blobStart32, err := readUint32(uncompressedData[r.blobIndex*uint32(r.blobSize):r.blobIndex*uint32(r.blobSize)+uint32(r.blobSize)], binary.LittleEndian)
|
||||||
|
if err != nil {
|
||||||
|
r.loadClusterErr = errors.WithStack(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
blobStart = uint64(blobStart32)
|
||||||
|
|
||||||
|
blobEnd32, err := readUint32(uncompressedData[r.blobIndex*uint32(r.blobSize)+uint32(r.blobSize):r.blobIndex*uint32(r.blobSize)+uint32(r.blobSize)+uint32(r.blobSize)], binary.LittleEndian)
|
||||||
|
if err != nil {
|
||||||
|
r.loadClusterErr = errors.WithStack(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
blobEnd = uint64(blobEnd32)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.data = make([]byte, blobEnd-blobStart)
|
||||||
|
copy(r.data, uncompressedData[blobStart:blobEnd])
|
||||||
|
})
|
||||||
|
if r.loadClusterErr != nil {
|
||||||
|
return errors.WithStack(r.loadClusterErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type BlobDecoderFactory func(io.Reader) (io.ReadCloser, error)
|
||||||
|
|
||||||
|
func NewCompressedBlobReader(reader *Reader, decoderFactory BlobDecoderFactory, clusterStartOffset, clusterEndOffset uint64, blobIndex uint32, blobSize int) *CompressedBlobReader {
|
||||||
|
return &CompressedBlobReader{
|
||||||
|
reader: reader,
|
||||||
|
decoderFactory: decoderFactory,
|
||||||
|
clusterStartOffset: clusterStartOffset,
|
||||||
|
clusterEndOffset: clusterEndOffset,
|
||||||
|
blobIndex: blobIndex,
|
||||||
|
blobSize: blobSize,
|
||||||
|
readOffset: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ BlobReader = &UncompressedBlobReader{}
|
|
@ -0,0 +1,193 @@
|
||||||
|
package zim
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type zimCompression int
|
||||||
|
|
||||||
|
const (
|
||||||
|
zimCompressionNoneZeno zimCompression = 0
|
||||||
|
zimCompressionNone zimCompression = 1
|
||||||
|
zimCompressionNoneZLib zimCompression = 2
|
||||||
|
zimCompressionNoneBZip2 zimCompression = 3
|
||||||
|
zimCompressionNoneXZ zimCompression = 4
|
||||||
|
zimCompressionNoneZStandard zimCompression = 5
|
||||||
|
)
|
||||||
|
|
||||||
|
type ContentEntry struct {
|
||||||
|
*BaseEntry
|
||||||
|
mimeType string
|
||||||
|
clusterIndex uint32
|
||||||
|
blobIndex uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ContentEntry) Compression() (int, error) {
|
||||||
|
clusterHeader, _, _, err := e.readClusterInfo()
|
||||||
|
if err != nil {
|
||||||
|
return 0, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int((clusterHeader << 4) >> 4), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ContentEntry) MimeType() string {
|
||||||
|
return e.mimeType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ContentEntry) Reader() (BlobReader, error) {
|
||||||
|
clusterHeader, clusterStartOffset, clusterEndOffset, err := e.readClusterInfo()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
compression := (clusterHeader << 4) >> 4
|
||||||
|
extended := (clusterHeader<<3)>>7 == 1
|
||||||
|
|
||||||
|
blobSize := 4
|
||||||
|
if extended {
|
||||||
|
blobSize = 8
|
||||||
|
}
|
||||||
|
|
||||||
|
switch compression {
|
||||||
|
|
||||||
|
// Uncompressed blobs
|
||||||
|
case uint8(zimCompressionNoneZeno):
|
||||||
|
fallthrough
|
||||||
|
case uint8(zimCompressionNone):
|
||||||
|
startPos := clusterStartOffset + 1
|
||||||
|
blobOffset := uint64(e.blobIndex * uint32(blobSize))
|
||||||
|
|
||||||
|
data := make([]byte, 2*blobSize)
|
||||||
|
if err := e.reader.readRange(int64(startPos+blobOffset), data); err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
blobStart uint64
|
||||||
|
blobEnd uint64
|
||||||
|
)
|
||||||
|
|
||||||
|
if extended {
|
||||||
|
blobStart64, err := readUint64(data[0:blobSize], binary.LittleEndian)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
blobStart = blobStart64
|
||||||
|
|
||||||
|
blobEnd64, err := readUint64(data[blobSize:blobSize*2], binary.LittleEndian)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
blobEnd = uint64(blobEnd64)
|
||||||
|
} else {
|
||||||
|
blobStart32, err := readUint32(data[0:blobSize], binary.LittleEndian)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
blobStart = uint64(blobStart32)
|
||||||
|
|
||||||
|
blobEnd32, err := readUint32(data[blobSize:blobSize*2], binary.LittleEndian)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
blobEnd = uint64(blobEnd32)
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewUncompressedBlobReader(e.reader, startPos+blobStart, startPos+blobEnd, blobSize), nil
|
||||||
|
|
||||||
|
// Supported compression algorithms
|
||||||
|
case uint8(zimCompressionNoneXZ):
|
||||||
|
return NewXZBlobReader(e.reader, clusterStartOffset, clusterEndOffset, e.blobIndex, blobSize), nil
|
||||||
|
|
||||||
|
case uint8(zimCompressionNoneZStandard):
|
||||||
|
return NewZStdBlobReader(e.reader, clusterStartOffset, clusterEndOffset, e.blobIndex, blobSize), nil
|
||||||
|
|
||||||
|
// Unsupported compression algorithms
|
||||||
|
case uint8(zimCompressionNoneZLib):
|
||||||
|
fallthrough
|
||||||
|
case uint8(zimCompressionNoneBZip2):
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
return nil, errors.Wrapf(ErrCompressionAlgorithmNotSupported, "unexpected compression algorithm '%d'", compression)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ContentEntry) Redirect() (*ContentEntry, error) {
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ContentEntry) readClusterInfo() (uint8, uint64, uint64, error) {
|
||||||
|
startClusterOffset, clusterEndOffset, err := e.reader.getClusterOffsets(int(e.clusterIndex))
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, 0, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data := make([]byte, 1)
|
||||||
|
if err := e.reader.readRange(int64(startClusterOffset), data); err != nil {
|
||||||
|
return 0, 0, 0, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
clusterHeader := uint8(data[0])
|
||||||
|
|
||||||
|
return clusterHeader, startClusterOffset, clusterEndOffset, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) parseContentEntry(offset int64, base *BaseEntry) (*ContentEntry, error) {
|
||||||
|
entry := &ContentEntry{
|
||||||
|
BaseEntry: base,
|
||||||
|
}
|
||||||
|
|
||||||
|
data := make([]byte, 16)
|
||||||
|
if err := r.readRange(offset, data); err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mimeTypeIndex, err := readUint16(data[0:2], binary.LittleEndian)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if mimeTypeIndex >= uint16(len(r.mimeTypes)) {
|
||||||
|
return nil, errors.Errorf("mime type index '%d' greater than mime types length '%d'", mimeTypeIndex, len(r.mimeTypes))
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.mimeType = r.mimeTypes[mimeTypeIndex]
|
||||||
|
|
||||||
|
entry.namespace = Namespace(data[3:4])
|
||||||
|
|
||||||
|
clusterIndex, err := readUint32(data[8:12], binary.LittleEndian)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.clusterIndex = clusterIndex
|
||||||
|
|
||||||
|
blobIndex, err := readUint32(data[12:16], binary.LittleEndian)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.blobIndex = blobIndex
|
||||||
|
|
||||||
|
strs, _, err := r.readStringsAt(offset+16, 2, 1024)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(strs) > 0 {
|
||||||
|
entry.url = strs[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(strs) > 1 {
|
||||||
|
entry.title = strs[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry, nil
|
||||||
|
}
|
|
@ -0,0 +1,135 @@
|
||||||
|
package zim
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Entry interface {
|
||||||
|
Redirect() (*ContentEntry, error)
|
||||||
|
Namespace() Namespace
|
||||||
|
URL() string
|
||||||
|
FullURL() string
|
||||||
|
Title() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type BaseEntry struct {
|
||||||
|
mimeTypeIndex uint16
|
||||||
|
namespace Namespace
|
||||||
|
url string
|
||||||
|
title string
|
||||||
|
reader *Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *BaseEntry) Namespace() Namespace {
|
||||||
|
return e.namespace
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *BaseEntry) Title() string {
|
||||||
|
if e.title == "" {
|
||||||
|
return e.url
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.title
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *BaseEntry) URL() string {
|
||||||
|
return e.url
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *BaseEntry) FullURL() string {
|
||||||
|
return toFullURL(e.Namespace(), e.URL())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) parseBaseEntry(offset int64) (*BaseEntry, error) {
|
||||||
|
entry := &BaseEntry{
|
||||||
|
reader: r,
|
||||||
|
}
|
||||||
|
|
||||||
|
data := make([]byte, 3)
|
||||||
|
if err := r.readRange(offset, data); err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mimeTypeIndex, err := readUint16(data[0:2], binary.LittleEndian)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.mimeTypeIndex = mimeTypeIndex
|
||||||
|
entry.namespace = Namespace(data[2])
|
||||||
|
|
||||||
|
return entry, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type RedirectEntry struct {
|
||||||
|
*BaseEntry
|
||||||
|
redirectIndex uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *RedirectEntry) Redirect() (*ContentEntry, error) {
|
||||||
|
if e.redirectIndex >= uint32(len(e.reader.urlIndex)) {
|
||||||
|
return nil, errors.Wrapf(ErrInvalidIndex, "entry index '%d' out of bounds", e.redirectIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
entryPtr := e.reader.urlIndex[e.redirectIndex]
|
||||||
|
entry, err := e.reader.parseEntryAt(int64(entryPtr))
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
entry, err = entry.Redirect()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
contentEntry, ok := entry.(*ContentEntry)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.WithStack(ErrInvalidRedirect)
|
||||||
|
}
|
||||||
|
|
||||||
|
return contentEntry, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) parseRedirectEntry(offset int64, base *BaseEntry) (*RedirectEntry, error) {
|
||||||
|
entry := &RedirectEntry{
|
||||||
|
BaseEntry: base,
|
||||||
|
}
|
||||||
|
|
||||||
|
data := make([]byte, 4)
|
||||||
|
if err := r.readRange(offset+8, data); err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
redirectIndex, err := readUint32(data, binary.LittleEndian)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.redirectIndex = redirectIndex
|
||||||
|
|
||||||
|
strs, _, err := r.readStringsAt(offset+12, 2, 1024)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(strs) > 0 {
|
||||||
|
entry.url = strs[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(strs) > 1 {
|
||||||
|
entry.title = strs[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toFullURL(ns Namespace, url string) string {
|
||||||
|
if ns == "\x00" {
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s/%s", ns, url)
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package zim
|
||||||
|
|
||||||
|
import "github.com/pkg/errors"
|
||||||
|
|
||||||
|
type EntryIterator struct {
|
||||||
|
index int
|
||||||
|
entry Entry
|
||||||
|
err error
|
||||||
|
reader *Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *EntryIterator) Next() bool {
|
||||||
|
if it.err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
entryCount := it.reader.EntryCount()
|
||||||
|
|
||||||
|
if it.index >= int(entryCount-1) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
entry, err := it.reader.EntryAt(it.index)
|
||||||
|
if err != nil {
|
||||||
|
it.err = errors.WithStack(err)
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
it.entry = entry
|
||||||
|
it.index++
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *EntryIterator) Err() error {
|
||||||
|
return it.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *EntryIterator) Index() int {
|
||||||
|
return it.index - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *EntryIterator) Entry() Entry {
|
||||||
|
return it.entry
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package zim
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrInvalidIndex = errors.New("invalid index")
|
||||||
|
ErrNotFound = errors.New("not found")
|
||||||
|
ErrInvalidRedirect = errors.New("invalid redirect")
|
||||||
|
ErrCompressionAlgorithmNotSupported = errors.New("compression algorithm not supported")
|
||||||
|
)
|
|
@ -0,0 +1,66 @@
|
||||||
|
package zim
|
||||||
|
|
||||||
|
import "github.com/pkg/errors"
|
||||||
|
|
||||||
|
func (r *Reader) Favicon() (*ContentEntry, error) {
|
||||||
|
illustration, err := r.getMetadataIllustration()
|
||||||
|
if err != nil && !errors.Is(err, ErrNotFound) {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if illustration != nil {
|
||||||
|
return illustration, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
namespaces := []Namespace{V5NamespaceLayout, V5NamespaceImageFile}
|
||||||
|
urls := []string{"favicon", "favicon.png"}
|
||||||
|
|
||||||
|
for _, ns := range namespaces {
|
||||||
|
for _, url := range urls {
|
||||||
|
entry, err := r.EntryWithURL(ns, url)
|
||||||
|
if err != nil && !errors.Is(err, ErrNotFound) {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors.Is(err, ErrNotFound) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := entry.Redirect()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return content, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.WithStack(ErrNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) getMetadataIllustration() (*ContentEntry, error) {
|
||||||
|
keys := []MetadataKey{MetadataIllustration96x96at2, MetadataIllustration48x48at1}
|
||||||
|
|
||||||
|
metadata, err := r.Metadata(keys...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, k := range keys {
|
||||||
|
if _, exists := metadata[k]; exists {
|
||||||
|
entry, err := r.EntryWithURL(V5NamespaceMetadata, string(k))
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := entry.Redirect()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return content, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.WithStack(ErrNotFound)
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
package zim
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MetadataKey string
|
||||||
|
|
||||||
|
// See https://wiki.openzim.org/wiki/Metadata
|
||||||
|
const (
|
||||||
|
MetadataName MetadataKey = "Name"
|
||||||
|
MetadataTitle MetadataKey = "Title"
|
||||||
|
MetadataDescription MetadataKey = "Description"
|
||||||
|
MetadataLongDescription MetadataKey = "LongDescription"
|
||||||
|
MetadataCreator MetadataKey = "Creator"
|
||||||
|
MetadataTags MetadataKey = "Tags"
|
||||||
|
MetadataDate MetadataKey = "Date"
|
||||||
|
MetadataPublisher MetadataKey = "Publisher"
|
||||||
|
MetadataFlavour MetadataKey = "Flavour"
|
||||||
|
MetadataSource MetadataKey = "Source"
|
||||||
|
MetadataLanguage MetadataKey = "Language"
|
||||||
|
MetadataIllustration48x48at1 MetadataKey = "Illustration_48x48@1"
|
||||||
|
MetadataIllustration96x96at2 MetadataKey = "Illustration_96x96@2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var knownKeys = []MetadataKey{
|
||||||
|
MetadataName,
|
||||||
|
MetadataTitle,
|
||||||
|
MetadataDescription,
|
||||||
|
MetadataLongDescription,
|
||||||
|
MetadataCreator,
|
||||||
|
MetadataPublisher,
|
||||||
|
MetadataLanguage,
|
||||||
|
MetadataTags,
|
||||||
|
MetadataDate,
|
||||||
|
MetadataFlavour,
|
||||||
|
MetadataSource,
|
||||||
|
MetadataIllustration48x48at1,
|
||||||
|
MetadataIllustration96x96at2,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metadata returns a copy of the internal metadata map of the ZIM file.
|
||||||
|
func (r *Reader) Metadata(keys ...MetadataKey) (map[MetadataKey]string, error) {
|
||||||
|
if len(keys) == 0 {
|
||||||
|
keys = knownKeys
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata := make(map[MetadataKey]string)
|
||||||
|
|
||||||
|
for _, key := range keys {
|
||||||
|
entry, err := r.EntryWithURL(V5NamespaceMetadata, string(key))
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, ErrNotFound) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := entry.Redirect()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
reader, err := content.Reader()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := io.ReadAll(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata[key] = string(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return metadata, nil
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package zim
|
||||||
|
|
||||||
|
type Namespace string
|
||||||
|
|
||||||
|
const (
|
||||||
|
V6NamespaceContent Namespace = "C"
|
||||||
|
V6NamespaceMetadata Namespace = "M"
|
||||||
|
V6NamespaceWellKnown Namespace = "W"
|
||||||
|
V6NamespaceSearch Namespace = "X"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
V5NamespaceLayout Namespace = "-"
|
||||||
|
V5NamespaceArticle Namespace = "A"
|
||||||
|
V5NamespaceArticleMetadata Namespace = "B"
|
||||||
|
V5NamespaceImageFile Namespace = "I"
|
||||||
|
V5NamespaceImageText Namespace = "J"
|
||||||
|
V5NamespaceMetadata Namespace = "M"
|
||||||
|
V5NamespaceCategoryText Namespace = "U"
|
||||||
|
V5NamespaceCategoryArticleList Namespace = "V"
|
||||||
|
V5NamespaceCategoryPerArticle Namespace = "W"
|
||||||
|
V5NamespaceSearch Namespace = "X"
|
||||||
|
)
|
|
@ -0,0 +1,30 @@
|
||||||
|
package zim
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
URLCacheSize int
|
||||||
|
URLCacheTTL time.Duration
|
||||||
|
CacheSize int
|
||||||
|
}
|
||||||
|
|
||||||
|
type OptionFunc func(opts *Options)
|
||||||
|
|
||||||
|
func NewOptions(funcs ...OptionFunc) *Options {
|
||||||
|
funcs = append([]OptionFunc{
|
||||||
|
WithCacheSize(2048),
|
||||||
|
}, funcs...)
|
||||||
|
|
||||||
|
opts := &Options{}
|
||||||
|
for _, fn := range funcs {
|
||||||
|
fn(opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithCacheSize(size int) OptionFunc {
|
||||||
|
return func(opts *Options) {
|
||||||
|
opts.CacheSize = size
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,558 @@
|
||||||
|
package zim
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
lru "github.com/hashicorp/golang-lru/v2"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"gitlab.com/wpetit/goweb/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
const zimFormatMagicNumber uint32 = 0x44D495A
|
||||||
|
const nullByte = '\x00'
|
||||||
|
const zimRedirect = 0xffff
|
||||||
|
|
||||||
|
type Reader struct {
|
||||||
|
majorVersion uint16
|
||||||
|
minorVersion uint16
|
||||||
|
uuid string
|
||||||
|
entryCount uint32
|
||||||
|
clusterCount uint32
|
||||||
|
urlPtrPos uint64
|
||||||
|
titlePtrPos uint64
|
||||||
|
clusterPtrPos uint64
|
||||||
|
mimeListPos uint64
|
||||||
|
mainPage uint32
|
||||||
|
layoutPage uint32
|
||||||
|
checksumPos uint64
|
||||||
|
|
||||||
|
mimeTypes []string
|
||||||
|
urlIndex []uint64
|
||||||
|
clusterIndex []uint64
|
||||||
|
|
||||||
|
cache *lru.Cache[string, Entry]
|
||||||
|
urls map[string]int
|
||||||
|
|
||||||
|
rangeReader RangeReadCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) Version() (majorVersion, minorVersion uint16) {
|
||||||
|
return r.majorVersion, r.minorVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) EntryCount() uint32 {
|
||||||
|
return r.entryCount
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) ClusterCount() uint32 {
|
||||||
|
return r.clusterCount
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) UUID() string {
|
||||||
|
return r.uuid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) Close() error {
|
||||||
|
if err := r.rangeReader.Close(); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) MainPage() (Entry, error) {
|
||||||
|
if r.mainPage == 0xffffffff {
|
||||||
|
return nil, errors.WithStack(ErrNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
entry, err := r.EntryAt(int(r.mainPage))
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(ErrNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) Entries() *EntryIterator {
|
||||||
|
return &EntryIterator{
|
||||||
|
reader: r,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) EntryAt(idx int) (Entry, error) {
|
||||||
|
if idx >= len(r.urlIndex) || idx < 0 {
|
||||||
|
return nil, errors.Wrapf(ErrInvalidIndex, "index '%d' out of bounds", idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
entryPtr := r.urlIndex[idx]
|
||||||
|
|
||||||
|
entry, err := r.parseEntryAt(int64(entryPtr))
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.cacheEntry(entryPtr, entry)
|
||||||
|
|
||||||
|
return entry, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) EntryWithFullURL(url string) (Entry, error) {
|
||||||
|
urlNum, exists := r.urls[url]
|
||||||
|
if !exists {
|
||||||
|
return nil, errors.WithStack(ErrNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
entry, err := r.EntryAt(urlNum)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) EntryWithURL(ns Namespace, url string) (Entry, error) {
|
||||||
|
fullURL := toFullURL(ns, url)
|
||||||
|
|
||||||
|
entry, err := r.EntryWithFullURL(fullURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) EntryWithTitle(ns Namespace, title string) (Entry, error) {
|
||||||
|
entry, found := r.getEntryByTitleFromCache(ns, title)
|
||||||
|
if found {
|
||||||
|
logger.Debug(context.Background(), "found entry with title from cache", logger.F("entry", entry.FullURL()))
|
||||||
|
return entry, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator := r.Entries()
|
||||||
|
|
||||||
|
for iterator.Next() {
|
||||||
|
entry := iterator.Entry()
|
||||||
|
|
||||||
|
if entry.Title() == title && entry.Namespace() == ns {
|
||||||
|
return entry, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := iterator.Err(); err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.WithStack(ErrNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) getURLCacheKey(fullURL string) string {
|
||||||
|
return "url:" + fullURL
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) getTitleCacheKey(ns Namespace, title string) string {
|
||||||
|
return fmt.Sprintf("title:%s/%s", ns, title)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) cacheEntry(offset uint64, entry Entry) {
|
||||||
|
urlKey := r.getURLCacheKey(entry.FullURL())
|
||||||
|
titleKey := r.getTitleCacheKey(entry.Namespace(), entry.Title())
|
||||||
|
|
||||||
|
_, urlFound := r.cache.Peek(urlKey)
|
||||||
|
_, titleFound := r.cache.Peek(titleKey)
|
||||||
|
|
||||||
|
if urlFound && titleFound {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
r.cache.Add(urlKey, entry)
|
||||||
|
r.cache.Add(titleKey, entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) getEntryByTitleFromCache(namespace Namespace, title string) (Entry, bool) {
|
||||||
|
key := r.getTitleCacheKey(namespace, title)
|
||||||
|
return r.cache.Get(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) parse() error {
|
||||||
|
if err := r.parseHeader(); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.parseMimeTypes(); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.parseURLIndex(); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.parseClusterIndex(); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) parseHeader() error {
|
||||||
|
header := make([]byte, 80)
|
||||||
|
if err := r.readRange(0, header); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
magicNumber, err := readUint32(header[0:4], binary.LittleEndian)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if magicNumber != zimFormatMagicNumber {
|
||||||
|
return errors.Errorf("invalid zim magic number '%d'", magicNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
majorVersion, err := readUint16(header[4:6], binary.LittleEndian)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.majorVersion = majorVersion
|
||||||
|
|
||||||
|
minorVersion, err := readUint16(header[6:8], binary.LittleEndian)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.minorVersion = minorVersion
|
||||||
|
|
||||||
|
if err := r.parseUUID(header[8:16]); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
entryCount, err := readUint32(header[24:28], binary.LittleEndian)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.entryCount = entryCount
|
||||||
|
|
||||||
|
clusterCount, err := readUint32(header[28:32], binary.LittleEndian)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.clusterCount = clusterCount
|
||||||
|
|
||||||
|
urlPtrPos, err := readUint64(header[32:40], binary.LittleEndian)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.urlPtrPos = urlPtrPos
|
||||||
|
|
||||||
|
titlePtrPos, err := readUint64(header[40:48], binary.LittleEndian)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.titlePtrPos = titlePtrPos
|
||||||
|
|
||||||
|
clusterPtrPos, err := readUint64(header[48:56], binary.LittleEndian)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.clusterPtrPos = clusterPtrPos
|
||||||
|
|
||||||
|
mimeListPos, err := readUint64(header[56:64], binary.LittleEndian)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.mimeListPos = mimeListPos
|
||||||
|
|
||||||
|
mainPage, err := readUint32(header[64:68], binary.LittleEndian)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.mainPage = mainPage
|
||||||
|
|
||||||
|
layoutPage, err := readUint32(header[68:72], binary.LittleEndian)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.layoutPage = layoutPage
|
||||||
|
|
||||||
|
checksumPos, err := readUint64(header[72:80], binary.LittleEndian)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.checksumPos = checksumPos
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) parseUUID(data []byte) error {
|
||||||
|
parts := make([]string, 0, 5)
|
||||||
|
|
||||||
|
val32, err := readUint32(data[0:4], binary.BigEndian)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
parts = append(parts, fmt.Sprintf("%08x", val32))
|
||||||
|
|
||||||
|
val16, err := readUint16(data[4:6], binary.BigEndian)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
parts = append(parts, fmt.Sprintf("%04x", val16))
|
||||||
|
|
||||||
|
val16, err = readUint16(data[6:8], binary.BigEndian)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
parts = append(parts, fmt.Sprintf("%04x", val16))
|
||||||
|
|
||||||
|
val16, err = readUint16(data[8:10], binary.BigEndian)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
parts = append(parts, fmt.Sprintf("%04x", val16))
|
||||||
|
|
||||||
|
val32, err = readUint32(data[10:14], binary.BigEndian)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
val16, err = readUint16(data[14:16], binary.BigEndian)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
parts = append(parts, fmt.Sprintf("%x%x", val32, val16))
|
||||||
|
|
||||||
|
r.uuid = strings.Join(parts, "-")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) parseMimeTypes() error {
|
||||||
|
mimeTypes := make([]string, 0)
|
||||||
|
offset := int64(r.mimeListPos)
|
||||||
|
read := int64(0)
|
||||||
|
var err error
|
||||||
|
var found []string
|
||||||
|
for {
|
||||||
|
found, read, err = r.readStringsAt(offset+read, 64, 1024)
|
||||||
|
if err != nil && !errors.Is(err, io.EOF) {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(found) == 0 || found[0] == "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
mimeTypes = append(mimeTypes, found...)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.mimeTypes = mimeTypes
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) parseURLIndex() error {
|
||||||
|
urlIndex, err := r.parsePointerIndex(int64(r.urlPtrPos), int64(r.entryCount))
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.urlIndex = urlIndex
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) parseClusterIndex() error {
|
||||||
|
clusterIndex, err := r.parsePointerIndex(int64(r.clusterPtrPos), int64(r.clusterCount+1))
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.clusterIndex = clusterIndex
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) parseEntryAt(offset int64) (Entry, error) {
|
||||||
|
base, err := r.parseBaseEntry(offset)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var entry Entry
|
||||||
|
|
||||||
|
if base.mimeTypeIndex == zimRedirect {
|
||||||
|
entry, err = r.parseRedirectEntry(offset, base)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
entry, err = r.parseContentEntry(offset, base)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) parsePointerIndex(startAddr int64, count int64) ([]uint64, error) {
|
||||||
|
index := make([]uint64, count)
|
||||||
|
|
||||||
|
data := make([]byte, count*8)
|
||||||
|
if err := r.readRange(startAddr, data); err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := int64(0); i < count; i++ {
|
||||||
|
offset := i * 8
|
||||||
|
ptr, err := readUint64(data[offset:offset+8], binary.LittleEndian)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
index[i] = ptr
|
||||||
|
}
|
||||||
|
|
||||||
|
return index, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) getClusterOffsets(clusterNum int) (uint64, uint64, error) {
|
||||||
|
if clusterNum > len(r.clusterIndex)-1 || clusterNum < 0 {
|
||||||
|
return 0, 0, errors.Wrapf(ErrInvalidIndex, "index '%d' out of bounds", clusterNum)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.clusterIndex[clusterNum], r.clusterIndex[clusterNum+1] - 1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) preload() error {
|
||||||
|
r.urls = make(map[string]int, r.entryCount)
|
||||||
|
|
||||||
|
iterator := r.Entries()
|
||||||
|
for iterator.Next() {
|
||||||
|
entry := iterator.Entry()
|
||||||
|
r.urls[entry.FullURL()] = iterator.Index()
|
||||||
|
}
|
||||||
|
if err := iterator.Err(); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) readRange(offset int64, v []byte) error {
|
||||||
|
read, err := r.rangeReader.ReadAt(v, offset)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if read != len(v) {
|
||||||
|
return io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) readStringsAt(offset int64, count int, bufferSize int) ([]string, int64, error) {
|
||||||
|
var sb strings.Builder
|
||||||
|
read := int64(0)
|
||||||
|
|
||||||
|
values := make([]string, 0, count)
|
||||||
|
wasNullByte := false
|
||||||
|
|
||||||
|
for {
|
||||||
|
data := make([]byte, bufferSize)
|
||||||
|
err := r.readRange(offset+read, data)
|
||||||
|
if err != nil && !errors.Is(err, io.EOF) {
|
||||||
|
return nil, read, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx := 0; idx < len(data); idx++ {
|
||||||
|
d := data[idx]
|
||||||
|
if err := sb.WriteByte(d); err != nil {
|
||||||
|
return nil, read, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
read++
|
||||||
|
|
||||||
|
if d == nullByte {
|
||||||
|
if wasNullByte {
|
||||||
|
return values, read, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
wasNullByte = true
|
||||||
|
|
||||||
|
str := strings.TrimRight(sb.String(), "\x00")
|
||||||
|
values = append(values, str)
|
||||||
|
|
||||||
|
if len(values) == count || errors.Is(err, io.EOF) {
|
||||||
|
return values, read, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.Reset()
|
||||||
|
} else {
|
||||||
|
wasNullByte = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type RangeReadCloser interface {
|
||||||
|
io.Closer
|
||||||
|
ReadAt(data []byte, offset int64) (n int, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewReader(rangeReader RangeReadCloser, funcs ...OptionFunc) (*Reader, error) {
|
||||||
|
opts := NewOptions(funcs...)
|
||||||
|
|
||||||
|
cache, err := lru.New[string, Entry](opts.CacheSize)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
reader := &Reader{
|
||||||
|
rangeReader: rangeReader,
|
||||||
|
cache: cache,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := reader.parse(); err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := reader.preload(); err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return reader, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Open(path string, funcs ...OptionFunc) (*Reader, error) {
|
||||||
|
file, err := os.OpenFile(path, os.O_RDONLY, os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
reader, err := NewReader(file, funcs...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return reader, nil
|
||||||
|
}
|
|
@ -0,0 +1,133 @@
|
||||||
|
package zim
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"gitlab.com/wpetit/goweb/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type readerTestCase struct {
|
||||||
|
UUID string `json:"uuid"`
|
||||||
|
EntryCount uint32 `json:"entryCount"`
|
||||||
|
Entries []struct {
|
||||||
|
Namespace Namespace `json:"namespace"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
Compression int `json:"compression"`
|
||||||
|
MimeType string `json:"mimeType"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
} `json:"entries"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReader(t *testing.T) {
|
||||||
|
if testing.Verbose() {
|
||||||
|
logger.SetLevel(logger.LevelDebug)
|
||||||
|
logger.SetFormat(logger.FormatHuman)
|
||||||
|
}
|
||||||
|
|
||||||
|
files, err := filepath.Glob("testdata/*.zim")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%+v", errors.WithStack(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, zf := range files {
|
||||||
|
testName := filepath.Base(zf)
|
||||||
|
testCase, err := loadZimFileTestCase(zf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%+v", errors.WithStack(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run(testName, func(t *testing.T) {
|
||||||
|
reader, err := Open(zf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%+v", errors.WithStack(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := reader.Close(); err != nil {
|
||||||
|
t.Errorf("%+v", errors.WithStack(err))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if e, g := testCase.UUID, reader.UUID(); e != g {
|
||||||
|
t.Errorf("reader.UUID(): expected '%s', got '%s'", e, g)
|
||||||
|
}
|
||||||
|
|
||||||
|
if e, g := testCase.EntryCount, reader.EntryCount(); e != g {
|
||||||
|
t.Errorf("reader.EntryCount(): expected '%v', got '%v'", e, g)
|
||||||
|
}
|
||||||
|
|
||||||
|
if testCase.Entries == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, entryTestCase := range testCase.Entries {
|
||||||
|
testName := fmt.Sprintf("Entry/%s/%s", entryTestCase.Namespace, entryTestCase.URL)
|
||||||
|
t.Run(testName, func(t *testing.T) {
|
||||||
|
entry, err := reader.EntryWithURL(entryTestCase.Namespace, entryTestCase.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%+v", errors.WithStack(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := entry.Redirect()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%+v", errors.WithStack(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if e, g := entryTestCase.MimeType, content.MimeType(); e != g {
|
||||||
|
t.Errorf("content.MimeType(): expected '%v', got '%v'", e, g)
|
||||||
|
}
|
||||||
|
|
||||||
|
if e, g := entryTestCase.Title, content.Title(); e != g {
|
||||||
|
t.Errorf("content.Title(): expected '%v', got '%v'", e, g)
|
||||||
|
}
|
||||||
|
|
||||||
|
compression, err := content.Compression()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%+v", errors.WithStack(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if e, g := entryTestCase.Compression, compression; e != g {
|
||||||
|
t.Errorf("content.Compression(): expected '%v', got '%v'", e, g)
|
||||||
|
}
|
||||||
|
|
||||||
|
contentReader, err := content.Reader()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%+v", errors.WithStack(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
size, err := contentReader.Size()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%+v", errors.WithStack(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if e, g := entryTestCase.Size, size; e != g {
|
||||||
|
t.Errorf("content.Size(): expected '%v', got '%v'", e, g)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadZimFileTestCase(zimFile string) (*readerTestCase, error) {
|
||||||
|
testCaseFile, _ := strings.CutSuffix(zimFile, ".zim")
|
||||||
|
|
||||||
|
data, err := os.ReadFile(testCaseFile + ".json")
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
testCase := &readerTestCase{}
|
||||||
|
if err := json.Unmarshal(data, testCase); err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return testCase, nil
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"uuid": "8d141c3b-115d-bf73-294a-ee3c2e6b97b0",
|
||||||
|
"entryCount": 6223,
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"namespace": "C",
|
||||||
|
"url": "users_page=9",
|
||||||
|
"compression": 5,
|
||||||
|
"size": 58646,
|
||||||
|
"mimeType": "text/html",
|
||||||
|
"title": "users_page=9"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
Binary file not shown.
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"uuid": "cf81f094-d802-c790-b854-c74ad9701ddb",
|
||||||
|
"entryCount": 271,
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"namespace": "C",
|
||||||
|
"url": "blog/202206-ShowroomInnovation.jpg",
|
||||||
|
"compression": 1,
|
||||||
|
"size": 260260,
|
||||||
|
"mimeType": "image/jpeg",
|
||||||
|
"title": "blog/202206-ShowroomInnovation.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"namespace": "C",
|
||||||
|
"url": "team/index.html",
|
||||||
|
"compression": 5,
|
||||||
|
"size": 93185,
|
||||||
|
"mimeType": "text/html",
|
||||||
|
"title": "Cadoles - Notre équipe"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue