snapshot of https://github.com/cortezaproject/corteza.git (2024.9.x, 2026-09-18; blobs > 131072B omitted)
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
This commit is contained in:
15
extra/server-discovery/.editorconfig
Normal file
15
extra/server-discovery/.editorconfig
Normal file
@@ -0,0 +1,15 @@
|
||||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
[*.go]
|
||||
indent_size = 2
|
||||
|
||||
# Tab indentation (no size specified)
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
144
extra/server-discovery/.env.example
Normal file
144
extra/server-discovery/.env.example
Normal file
@@ -0,0 +1,144 @@
|
||||
###############################################################################
|
||||
###############################################################################
|
||||
# Environment
|
||||
#
|
||||
|
||||
###############################################################################
|
||||
# Type: string
|
||||
# ENVIRONMENT=production
|
||||
|
||||
###############################################################################
|
||||
###############################################################################
|
||||
# HTTP Server
|
||||
#
|
||||
|
||||
########################################################################################################################
|
||||
# IP and port for the HTTP server.
|
||||
# Type: string
|
||||
# Default: :80
|
||||
# HTTP_ADDR=:80
|
||||
|
||||
###############################################################################
|
||||
# Base URL (prefix) for all routes (<baseUrl>/api, ...)
|
||||
# Type: string
|
||||
# Default: /
|
||||
# HTTP_BASE_URL=/
|
||||
|
||||
###############################################################################
|
||||
# When webapps are enabled (HTTP_WEBAPP_ENABLED) this is moved to '/api' if not explicitly set otherwise.
|
||||
# API base URL is internally prefixed with baseUrl
|
||||
# Type: string
|
||||
# Default: /
|
||||
# HTTP_API_BASE_URL=/
|
||||
|
||||
###############################################################################
|
||||
# Delays API startup for the amount of time specified (10s, 2m...).
|
||||
# This delay happens before service (`WAIT_FOR_SERVICES`) probing.
|
||||
# Type: time.Duration
|
||||
# Default: <no value>
|
||||
# WAIT_FOR=<no value>
|
||||
|
||||
###############################################################################
|
||||
# Show temporary status web page.
|
||||
# Type: bool
|
||||
# Default: <no value>
|
||||
# WAIT_FOR_STATUS_PAGE=<no value>
|
||||
|
||||
###############################################################################
|
||||
###############################################################################
|
||||
# Open search configuration
|
||||
#
|
||||
|
||||
###############################################################################
|
||||
# IP and port for the open search.
|
||||
# Use space delimited string to set multiple addresses.
|
||||
# Type: []string
|
||||
# ES_ADDRESS=<no value>
|
||||
|
||||
###############################################################################
|
||||
# Username for the open search connection.
|
||||
# Type: string
|
||||
# ES_USERNAME=<no value>
|
||||
|
||||
###############################################################################
|
||||
# Password for the open search connection.
|
||||
# Type: string
|
||||
# ES_PASSWORD=<no value>
|
||||
|
||||
###############################################################################
|
||||
# Path to the CA certificate file for the open search connection.
|
||||
# Type: string
|
||||
# ES_CERT_FILE=<no value>
|
||||
|
||||
###############################################################################
|
||||
# How often are indexes incremented. set value in seconds.
|
||||
# Type: int
|
||||
# Default: 600
|
||||
# ES_INDEX_INTERVAL=600
|
||||
|
||||
###############################################################################
|
||||
# ES_ENABLE_RETRY_ON_TIMEOUT=<no value>
|
||||
|
||||
###############################################################################
|
||||
# ES_MAX_RETRIES=<no value>
|
||||
|
||||
###############################################################################
|
||||
###############################################################################
|
||||
# Corteza server configuration
|
||||
#
|
||||
|
||||
###############################################################################
|
||||
# IP and port for the corteza server.
|
||||
# Type: string
|
||||
# CORTEZA_SERVER_BASE_URL=<no value>
|
||||
|
||||
###############################################################################
|
||||
# corteza server auth url. It is set to '/api' if not explicitly set otherwise.
|
||||
# API base URL is internally prefixed with baseUrl
|
||||
# Type: string
|
||||
# CORTEZA_SERVER_AUTH_URL=/
|
||||
|
||||
###############################################################################
|
||||
# corteza server auth url for discovery api, it is set to '/api/discovery' if not explicitly set otherwise.
|
||||
# API base URL is internally prefixed with baseUrl
|
||||
# Type: string
|
||||
# CORTEZA_SERVER_DISCOVERY_URL=/
|
||||
|
||||
###############################################################################
|
||||
###############################################################################
|
||||
# Corteza discovery indexer configuration
|
||||
#
|
||||
|
||||
###############################################################################
|
||||
# Enables indexing for corteza resources.
|
||||
# Type: bool
|
||||
# DISCOVERY_INDEXER_ENABLED=<no value>
|
||||
|
||||
###############################################################################
|
||||
# Enables indexing for corteza resources based on client permissions.
|
||||
# prefix is one of 'public', 'protected' or 'private'".
|
||||
# Type: string
|
||||
# DISCOVERY_INDEXER_<PREFIX>_INDEX_CLIENT_KEY=<no value>
|
||||
# DISCOVERY_INDEXER_<PREFIX>_INDEX_CLIENT_SECRET=<no value>
|
||||
|
||||
|
||||
###############################################################################
|
||||
###############################################################################
|
||||
# Corteza discovery searcher configuration
|
||||
#
|
||||
|
||||
###############################################################################
|
||||
# Enables searching for corteza resources and also enables search endpoints.
|
||||
# Type: bool
|
||||
# DISCOVERY_SEARCHER_ENABLED=<no value>
|
||||
|
||||
###############################################################################
|
||||
# Used to restrict corteza resources based on client permissions.
|
||||
# Type: string
|
||||
# DISCOVERY_SEARCHER_CLIENT_KEY=<no value>
|
||||
# DISCOVERY_SEARCHER_CLIENT_SECRET=<no value>
|
||||
|
||||
###############################################################################
|
||||
# It enables access to private indexing.
|
||||
# Type: string
|
||||
# DISCOVERY_SEARCHER_JWT_SECRET=<no value>
|
||||
18
extra/server-discovery/.gitignore
vendored
Normal file
18
extra/server-discovery/.gitignore
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
/.idea
|
||||
/coverage.txt
|
||||
/*.iml
|
||||
/.env-*
|
||||
.env
|
||||
/.dev*
|
||||
/.*cover.out*
|
||||
/overalls*
|
||||
profile.coverprofile
|
||||
/public_html
|
||||
/build
|
||||
/var
|
||||
/TODO
|
||||
/gin-bin
|
||||
/.vscode
|
||||
/federation/etc/.env*
|
||||
/certs
|
||||
|
||||
37
extra/server-discovery/Dockerfile
Normal file
37
extra/server-discovery/Dockerfile
Normal file
@@ -0,0 +1,37 @@
|
||||
# deploy stage
|
||||
FROM ubuntu:22.04
|
||||
|
||||
RUN apt-get -y update \
|
||||
&& apt-get -y install \
|
||||
ca-certificates \
|
||||
curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ARG VERSION=2024.9.10-discovery
|
||||
ARG SERVER_VERSION=${VERSION}
|
||||
ARG CORTEZA_SERVER_PATH=https://releases.cortezaproject.org/files/corteza-server-discovery-${SERVER_VERSION}-linux-amd64.tar.gz
|
||||
RUN mkdir /tmp/server
|
||||
ADD $CORTEZA_SERVER_PATH /tmp/server
|
||||
|
||||
VOLUME /data
|
||||
|
||||
RUN tar -zxvf "/tmp/server/$(basename $CORTEZA_SERVER_PATH)" -C / && \
|
||||
rm -rf "/tmp/server" && \
|
||||
mv /corteza-server /corteza
|
||||
|
||||
WORKDIR /corteza
|
||||
|
||||
#HEALTHCHECK --interval=30s --start-period=1m --timeout=30s --retries=3 \
|
||||
# CMD curl --silent --fail --fail-early http://127.0.0.1:80/healthcheck || exit 1
|
||||
|
||||
ENV STORAGE_PATH "/data"
|
||||
ENV HTTP_ADDR "0.0.0.0:80"
|
||||
ENV CORREDOR_ENABLED "false"
|
||||
ENV HTTP_WEBAPP_ENABLED "false"
|
||||
ENV PATH "/corteza/bin:${PATH}"
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
ENTRYPOINT ["./bin/corteza-server"]
|
||||
|
||||
#CMD ["serve-api"]
|
||||
43
extra/server-discovery/Dockerfile.snapshot
Normal file
43
extra/server-discovery/Dockerfile.snapshot
Normal file
@@ -0,0 +1,43 @@
|
||||
# build stage
|
||||
FROM golang:1.24.1 as build-stage
|
||||
|
||||
ENV BUILD_OS=linux
|
||||
ENV BUILD_ARCH=amd64
|
||||
ENV BUILD_VERSION=latest
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
# Copy the full monorepo context (server + extra/server-discovery)
|
||||
# because go.mod has: replace github.com/cortezaproject/corteza/server => ../../server
|
||||
COPY server/ ./server/
|
||||
COPY extra/server-discovery/ ./extra/server-discovery/
|
||||
|
||||
WORKDIR /build/extra/server-discovery
|
||||
|
||||
RUN make release-clean release
|
||||
|
||||
|
||||
# deploy stage
|
||||
FROM ubuntu:24.04
|
||||
|
||||
RUN apt-get -y update \
|
||||
&& apt-get -y install \
|
||||
ca-certificates \
|
||||
curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV STORAGE_PATH "/data"
|
||||
ENV PATH "/corteza/bin:${PATH}"
|
||||
|
||||
WORKDIR /corteza
|
||||
|
||||
VOLUME /data
|
||||
|
||||
COPY --from=build-stage /build/extra/server-discovery/build/pkg/corteza-server ./
|
||||
|
||||
HEALTHCHECK --interval=30s --start-period=1m --timeout=30s --retries=3 \
|
||||
CMD curl --silent --fail --fail-early http://127.0.0.1:80/healthcheck || exit 1
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
ENTRYPOINT ["./bin/corteza-server"]
|
||||
56
extra/server-discovery/Makefile
Normal file
56
extra/server-discovery/Makefile
Normal file
@@ -0,0 +1,56 @@
|
||||
.PHONY: pack build help qa critic vet codegen provision docs build auth webapp
|
||||
|
||||
include Makefile.inc
|
||||
|
||||
BUILD_FLAVOUR ?= corteza
|
||||
BUILD_TIME ?= $(shell date +%FT%T%z)
|
||||
BUILD_VERSION ?= $(shell git describe --tags --abbrev=0)
|
||||
BUILD_ARCH ?= $(shell go env GOARCH)
|
||||
BUILD_OS ?= $(shell go env GOOS)
|
||||
BUILD_OS_is_windows = $(filter windows,$(BUILD_OS))
|
||||
BUILD_DEST_DIR ?= build
|
||||
BUILD_NAME = $(BUILD_FLAVOUR)-server-discovery-$(BUILD_VERSION)-$(BUILD_OS)-$(BUILD_ARCH)
|
||||
BUILD_BIN_NAME = $(BUILD_NAME)$(if $(BUILD_OS_is_windows),.exe,)
|
||||
|
||||
RELEASE_BASEDIR = $(BUILD_DEST_DIR)/pkg/$(BUILD_FLAVOUR)-server
|
||||
RELEASE_NAME = $(BUILD_NAME).tar.gz
|
||||
RELEASE_EXTRA_FILES ?= README.md LICENSE CONTRIBUTING.md DCO .env.example
|
||||
RELEASE_PKEY ?= .upload-rsa
|
||||
|
||||
LDFLAGS_VERSION = -X github.com/cortezaproject/corteza/server/pkg/version.Version=$(BUILD_VERSION)
|
||||
LDFLAGS_EXTRA ?=
|
||||
LDFLAGS = -ldflags "$(LDFLAGS_VERSION) $(LDFLAGS_EXTRA)"
|
||||
|
||||
########################################################################################################################
|
||||
# Building & packing
|
||||
|
||||
build: $(BUILD_DEST_DIR)/$(BUILD_BIN_NAME)
|
||||
|
||||
$(BUILD_DEST_DIR)/$(BUILD_BIN_NAME):
|
||||
GOOS=$(BUILD_OS) GOARCH=$(BUILD_ARCH) go build $(LDFLAGS) -o $@ *.go
|
||||
|
||||
release: build $(BUILD_DEST_DIR)/$(RELEASE_NAME)
|
||||
|
||||
$(BUILD_DEST_DIR)/$(RELEASE_NAME):
|
||||
@ mkdir -p $(RELEASE_BASEDIR) $(RELEASE_BASEDIR)/bin
|
||||
@ cp $(RELEASE_EXTRA_FILES) $(RELEASE_BASEDIR)/
|
||||
@ cp $(BUILD_DEST_DIR)/$(BUILD_BIN_NAME) $(RELEASE_BASEDIR)/bin/$(BUILD_FLAVOUR)-server
|
||||
tar -C $(dir $(RELEASE_BASEDIR)) -czf $(BUILD_DEST_DIR)/$(RELEASE_NAME) $(notdir $(RELEASE_BASEDIR))
|
||||
|
||||
release-clean:
|
||||
rm -rf $(BUILD_DEST_DIR)/$(BUILD_BIN_NAME)
|
||||
rm -rf $(BUILD_DEST_DIR)/$(RELEASE_NAME)
|
||||
|
||||
upload: $(RELEASE_PKEY)
|
||||
@ echo "put $(BUILD_DEST_DIR)/*.tar.gz" | sftp -q -o "StrictHostKeyChecking no" -i $(RELEASE_PKEY) $(RELEASE_SFTP_URI)
|
||||
@ rm -f $(RELEASE_PKEY)
|
||||
|
||||
$(RELEASE_PKEY):
|
||||
@ echo $(RELEASE_SFTP_KEY) | base64 -d > $@
|
||||
@ chmod 0400 $@
|
||||
|
||||
########################################################################################################################
|
||||
# Development
|
||||
|
||||
watch: $(GIN)
|
||||
$(GIN) $(GIN_ARGS) -- serve
|
||||
70
extra/server-discovery/Makefile.inc
Normal file
70
extra/server-discovery/Makefile.inc
Normal file
@@ -0,0 +1,70 @@
|
||||
GO = go
|
||||
GOGET = $(GO) get -u
|
||||
GOFLAGS ?= -trimpath
|
||||
#GOFLAGS ?= -mod=vendor
|
||||
GOPATH ?= $(HOME)/go
|
||||
|
||||
OAPI_CODEGEN = $(GOPATH)/bin/oapi-codegen
|
||||
CODEGEN_API = api/gen.go
|
||||
|
||||
GIN = $(GOPATH)/bin/gin
|
||||
GIN_ARG_PORT ?= 3200
|
||||
GIN_ARG_APORT ?= 3201
|
||||
GIN_ARG_LADDR ?= localhost
|
||||
GIN_ARGS ?= --laddr $(GIN_ARG_LADDR) --port $(GIN_ARG_PORT) --appPort $(GIN_ARG_APORT) --immediate --bin build/gin-bin
|
||||
|
||||
CGO_ENABLED = 1
|
||||
|
||||
export GOFLAGS
|
||||
export CGO_ENABLED
|
||||
|
||||
########################################################################################################################
|
||||
# Tool bins
|
||||
GOCRITIC = $(GOPATH)/bin/gocritic
|
||||
MOCKGEN = $(GOPATH)/bin/mockgen
|
||||
GOTEST = $(GOPATH)/bin/gotest
|
||||
STATICCHECK = $(GOPATH)/bin/staticcheck
|
||||
PROTOGEN = $(GOPATH)/bin/protoc-gen-go
|
||||
PROTOGEN_GRPC = $(GOPATH)/bin/protoc-gen-go-grpc
|
||||
#GIN = $(GOPATH)/bin/gin
|
||||
STATIK = $(GOPATH)/bin/statik
|
||||
CODEGEN = build/codegen
|
||||
|
||||
PROTOC = /usr/local/bin/protoc
|
||||
FSWATCH = /usr/local/bin/fswatch
|
||||
|
||||
# fswatch is intentionally left out...
|
||||
BINS = $(GOCRITIC) $(MOCKGEN) $(GOTEST) $(STATICCHECK) $(PROTOGEN) $(GIN) $(STATIK) $(CODEGEN)
|
||||
|
||||
|
||||
########################################################################################################################
|
||||
# Go Toolset
|
||||
$(GOCRITIC):
|
||||
$(GOGET) github.com/go-critic/go-critic/...
|
||||
|
||||
$(MOCKGEN):
|
||||
$(GOGET) github.com/golang/mock/mockgen
|
||||
|
||||
$(STATICCHECK):
|
||||
$(GOGET) honnef.co/go/tools/cmd/staticcheck
|
||||
|
||||
$(PROTOGEN):
|
||||
$(GOGET) github.com/golang/protobuf/protoc-gen-go
|
||||
|
||||
$(PROTOGEN_GRPC):
|
||||
$(GOGET) google.golang.org/grpc/cmd/protoc-gen-go-grpc
|
||||
|
||||
$(GIN):
|
||||
$(GOGET) github.com/codegangsta/gin
|
||||
|
||||
$(GOTEST):
|
||||
$(GOGET) github.com/rakyll/gotest
|
||||
|
||||
$(STATIK):
|
||||
$(GOGET) github.com/goware/statik
|
||||
|
||||
$(CODEGEN):
|
||||
$(GO) build -o $@ cmd/codegen/main.go
|
||||
|
||||
clean-tools:
|
||||
rm -f $(BINS)
|
||||
66
extra/server-discovery/app/app.go
Normal file
66
extra/server-discovery/app/app.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cortezaproject/corteza/extra/server-discovery/pkg/options"
|
||||
"github.com/cortezaproject/corteza/server/pkg/logger"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/elastic/go-elasticsearch/v7/esutil"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type (
|
||||
httpApiServer interface {
|
||||
//MountRoutes(mm ...func(chi.Router))
|
||||
//Serve(ctx context.Context)
|
||||
Serve(ctx context.Context)
|
||||
Activate(mm ...func(chi.Router))
|
||||
Shutdown()
|
||||
}
|
||||
|
||||
CortezaDiscoveryApp struct {
|
||||
Opt *options.Options
|
||||
lvl int
|
||||
Log *zap.Logger
|
||||
|
||||
// Servers
|
||||
HttpServer httpApiServer
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
_ *spew.ConfigState = nil
|
||||
_ esutil.BulkIndexer
|
||||
)
|
||||
|
||||
func New() (app *CortezaDiscoveryApp, err error) {
|
||||
app = &CortezaDiscoveryApp{
|
||||
Log: logger.MakeDebugLogger().WithOptions(zap.AddStacktrace(zap.PanicLevel)),
|
||||
}
|
||||
app.Opt, err = options.Init()
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (app CortezaDiscoveryApp) Serve(ctx context.Context) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
//func (app CortezaDiscoveryApp) InitService(ctx context.Context) (err error) {
|
||||
// // Initialize indexer service
|
||||
// err = indexer.Initialize(ctx, app.Log, indexer.Config{
|
||||
// ES: app.Opt.ES,
|
||||
// Indexer: app.Opt.Indexer,
|
||||
// })
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// return
|
||||
//}
|
||||
128
extra/server-discovery/app/boot_levels.go
Normal file
128
extra/server-discovery/app/boot_levels.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/cortezaproject/corteza/extra/server-discovery/indexer"
|
||||
"github.com/cortezaproject/corteza/extra/server-discovery/pkg/auth"
|
||||
"github.com/cortezaproject/corteza/extra/server-discovery/pkg/es"
|
||||
"github.com/cortezaproject/corteza/extra/server-discovery/pkg/healthcheck"
|
||||
"github.com/cortezaproject/corteza/extra/server-discovery/searcher"
|
||||
)
|
||||
|
||||
const (
|
||||
bootLevelWaiting = iota
|
||||
bootLevelSetup
|
||||
bootLevelStoreInitialized
|
||||
bootLevelProvisioned
|
||||
bootLevelServicesInitialized
|
||||
bootLevelActivated
|
||||
)
|
||||
|
||||
// Setup configures all required services
|
||||
func (app *CortezaDiscoveryApp) Setup() (err error) {
|
||||
app.lvl = bootLevelSetup
|
||||
|
||||
hcd := healthcheck.Defaults()
|
||||
if app.Opt.Searcher.Enabled {
|
||||
hcd.Add(searcher.Healthcheck, "OpenSearch")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// InitStore initializes open search store and runs upgrade procedures
|
||||
func (app *CortezaDiscoveryApp) InitStore(ctx context.Context) (err error) {
|
||||
if app.lvl >= bootLevelStoreInitialized {
|
||||
// Is store already initialised?
|
||||
return nil
|
||||
} else if err = app.Setup(); err != nil {
|
||||
// Initialize previous level
|
||||
return err
|
||||
}
|
||||
|
||||
app.lvl = bootLevelStoreInitialized
|
||||
return nil
|
||||
}
|
||||
|
||||
// Provision instance with configuration and settings
|
||||
// by importing preset configurations and running autodiscovery procedures
|
||||
func (app *CortezaDiscoveryApp) Provision(ctx context.Context) (err error) {
|
||||
if app.lvl >= bootLevelProvisioned {
|
||||
return
|
||||
}
|
||||
|
||||
if err = app.InitStore(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
app.lvl = bootLevelProvisioned
|
||||
return
|
||||
}
|
||||
|
||||
// InitServices initializes all services used
|
||||
func (app *CortezaDiscoveryApp) InitServices(ctx context.Context) (err error) {
|
||||
if app.lvl >= bootLevelServicesInitialized {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err = app.Provision(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if auth.HttpTokenVerifier, err = auth.TokenVerifierMiddlewareWithSecretSigner(string(app.Opt.Searcher.JwtSecret)); err != nil {
|
||||
return fmt.Errorf("could not set token verifier")
|
||||
}
|
||||
|
||||
//initialize Elastic search client
|
||||
esClient, err := es.Connect(app.Opt.ES)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to initialize Elasticsearch client: %s", err.Error())
|
||||
}
|
||||
|
||||
if app.Opt.Indexer.Enabled {
|
||||
err = indexer.Initialize(ctx, app.Log, indexer.Config{
|
||||
Corteza: app.Opt.Corteza,
|
||||
ES: app.Opt.ES,
|
||||
Indexer: app.Opt.Indexer,
|
||||
}, esClient)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if app.Opt.Searcher.Enabled {
|
||||
err = searcher.Initialize(ctx, app.Log, searcher.Config{
|
||||
Corteza: app.Opt.Corteza,
|
||||
ES: app.Opt.ES,
|
||||
HttpServer: app.Opt.HTTPServer,
|
||||
Searcher: app.Opt.Searcher,
|
||||
}, esClient)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
app.lvl = bootLevelServicesInitialized
|
||||
return
|
||||
}
|
||||
|
||||
// Activate start all internal services and watchers
|
||||
func (app *CortezaDiscoveryApp) Activate(ctx context.Context) (err error) {
|
||||
if app.lvl >= bootLevelActivated {
|
||||
return
|
||||
}
|
||||
|
||||
if err := app.InitServices(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if app.Opt.Indexer.Enabled {
|
||||
go indexer.Watchers(ctx)
|
||||
}
|
||||
|
||||
app.lvl = bootLevelActivated
|
||||
|
||||
return nil
|
||||
}
|
||||
35
extra/server-discovery/app/servers.go
Normal file
35
extra/server-discovery/app/servers.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
searcherRest "github.com/cortezaproject/corteza/extra/server-discovery/searcher/rest"
|
||||
"github.com/cortezaproject/corteza/server/pkg/options"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func (app *CortezaDiscoveryApp) MountHttpRoutes(r chi.Router) {
|
||||
var (
|
||||
ho = app.Opt.HTTPServer
|
||||
)
|
||||
|
||||
func() {
|
||||
if !app.Opt.Searcher.Enabled {
|
||||
app.Log.Info("JSON REST API disabled")
|
||||
return
|
||||
}
|
||||
|
||||
r.Route(options.CleanBase(ho.ApiBaseUrl), func(r chi.Router) {
|
||||
var fullPathAPI = "/" + strings.TrimPrefix(options.CleanBase(ho.BaseUrl, ho.ApiBaseUrl), "/")
|
||||
|
||||
app.Log.Info(
|
||||
"JSON REST API enabled",
|
||||
zap.String("baseUrl", fullPathAPI),
|
||||
)
|
||||
|
||||
r.Route("/", searcherRest.MountRoutes(app.Log))
|
||||
})
|
||||
}()
|
||||
|
||||
}
|
||||
36
extra/server-discovery/discovery.log
Normal file
36
extra/server-discovery/discovery.log
Normal file
@@ -0,0 +1,36 @@
|
||||
nohup: ignoring input
|
||||
: watch] Killed
|
||||
/home/fajfa/go/bin/gin --laddr localhost --port 3200 --appPort 3201 --immediate --bin build/gin-bin -- serve
|
||||
[gin] Listening at localhost:3200
|
||||
[gin] Building...
|
||||
[gin] [97;32;1mBuild finished[0m
|
||||
13:22:44.631 [35mDEBUG[0m http.waiting version route enabled: /version
|
||||
13:22:44.631 [35mDEBUG[0m http.waiting health check route enabled: /healthcheck
|
||||
13:22:44.631 [34mINFO[0m http starting HTTP server {"path-prefix": "/", "address": ":3201"}
|
||||
13:22:44.637 [34mINFO[0m Configuration index exist
|
||||
13:22:44.742 [34mINFO[0m index exists {"name": "corteza-private-system-users", "health": "yellow", "status": "open", "size": "6.7kb", "documents": "2"}
|
||||
13:22:44.742 [34mINFO[0m index exists {"name": "corteza-private-compose-namespaces", "health": "yellow", "status": "open", "size": "7.2kb", "documents": "6"}
|
||||
13:22:44.743 [34mINFO[0m index exists {"name": "corteza-private-compose-modules", "health": "yellow", "status": "open", "size": "9.5kb", "documents": "13"}
|
||||
13:22:44.743 [34mINFO[0m index exists {"name": "corteza-private-compose-records-480293021645275137-480293021646127105", "health": "yellow", "status": "open", "size": "277.2kb", "documents": "32"}
|
||||
13:22:44.743 [34mINFO[0m index exists {"name": "corteza-private-compose-records-480293021645275137-480300063720538113", "health": "yellow", "status": "open", "size": "61kb", "documents": "8"}
|
||||
13:22:44.743 [34mINFO[0m index exists {"name": "corteza-private-compose-records-480311255777869825-480311255779049473", "health": "yellow", "status": "open", "size": "60.9kb", "documents": "8"}
|
||||
returning
|
||||
13:22:44.789 [35mDEBUG[0m reindexing {"docs": 1, "index": "corteza-private-system-users"}
|
||||
13:22:44.823 [35mDEBUG[0m reindexing {"docs": 2, "index": "corteza-private-compose-namespaces"}
|
||||
13:22:44.851 [35mDEBUG[0m reindexing {"docs": 1, "index": "corteza-private-compose-modules"}
|
||||
13:22:44.881 [35mDEBUG[0m reindexing {"docs": 2, "index": "corteza-private-compose-modules"}
|
||||
13:22:44.925 [35mDEBUG[0m reindexing {"docs": 1, "index": "corteza-private-compose-records-480311255777869825-480311255779049473"}
|
||||
13:22:45.228 [35mDEBUG[0m reindexing {"docs": 4, "index": "corteza-private-compose-records-480293021645275137-480293021646127105"}
|
||||
13:22:45.928 [35mDEBUG[0m reindexing {"docs": 1, "index": "corteza-private-compose-records-480293021645275137-480300063720538113"}
|
||||
13:22:46.070 [35mDEBUG[0m batch indexing stats {"added": 12, "created": 0, "updated": 0, "deleted": 0, "flushed": 0, "failed": 0, "indexed": 0, "requests": 0, "queue length": 0}
|
||||
13:22:46.745 [35mDEBUG[0m batch indexing stats {"added": 12, "created": 0, "updated": 0, "deleted": 0, "flushed": 0, "failed": 0, "indexed": 0, "requests": 0, "queue length": 0}
|
||||
13:22:47.745 [35mDEBUG[0m batch indexing stats {"added": 12, "created": 0, "updated": 0, "deleted": 0, "flushed": 0, "failed": 0, "indexed": 0, "requests": 0, "queue length": 0}
|
||||
13:22:48.745 [34mINFO[0m idle
|
||||
13:22:48.797 [34mINFO[0m continuing reindexing from current time
|
||||
13:22:48.797 [34mINFO[0m JSON REST API enabled {"baseUrl": "/"}
|
||||
13:22:48.797 [35mDEBUG[0m http version route enabled: /version
|
||||
13:22:48.797 [35mDEBUG[0m http health check route enabled: /healthcheck
|
||||
13:22:48.797 [35mDEBUG[0m http entering active state
|
||||
13:23:44.891 [35mDEBUG[0m no feed changes since 2026-01-28 12:22:48.797151932 +0000 UTC; current time: 2026-01-28 12:23:44.891076917 +0000 UTC
|
||||
13:24:40.871 [35mDEBUG[0m no feed changes since 2026-01-28 12:23:44.858656232 +0000 UTC; current time: 2026-01-28 12:24:40.871870789 +0000 UTC
|
||||
make: *** [Makefile:56: watch] Killed
|
||||
71
extra/server-discovery/docker-compose.yaml
Normal file
71
extra/server-discovery/docker-compose.yaml
Normal file
@@ -0,0 +1,71 @@
|
||||
version: "3"
|
||||
services:
|
||||
opensearch-node:
|
||||
image: opensearchproject/opensearch:2.11.1
|
||||
container_name: opensearch-node
|
||||
restart: always
|
||||
environment:
|
||||
- cluster.name=opensearch-cluster
|
||||
- node.name=opensearch-node
|
||||
- node.roles=cluster_manager,data,ml
|
||||
- discovery.seed_hosts=opensearch-node
|
||||
- cluster.initial_cluster_manager_nodes=opensearch-node
|
||||
- bootstrap.memory_lock=true
|
||||
- OPENSEARCH_JAVA_OPTS=-Xms6g -Xmx6g
|
||||
- OPENSEARCH_INITIAL_ADMIN_PASSWORD=admin
|
||||
|
||||
# Enable security with demo certificates
|
||||
- plugins.security.ssl.transport.pemcert_filepath=esnode.pem
|
||||
- plugins.security.ssl.transport.pemkey_filepath=esnode-key.pem
|
||||
- plugins.security.ssl.transport.pemtrustedcas_filepath=root-ca.pem
|
||||
- plugins.security.ssl.transport.enforce_hostname_verification=false
|
||||
- plugins.security.ssl.http.enabled=true
|
||||
- plugins.security.ssl.http.pemcert_filepath=esnode.pem
|
||||
- plugins.security.ssl.http.pemkey_filepath=esnode-key.pem
|
||||
- plugins.security.ssl.http.pemtrustedcas_filepath=root-ca.pem
|
||||
- plugins.security.allow_unsafe_democertificates=true
|
||||
- plugins.security.allow_default_init_securityindex=true
|
||||
|
||||
# ML Commons settings
|
||||
- plugins.ml_commons.only_run_on_ml_node=false
|
||||
- plugins.ml_commons.model_access_control_enabled=false
|
||||
- plugins.ml_commons.native_memory_threshold=99
|
||||
- plugins.ml_commons.max_model_on_node=10
|
||||
mem_limit: 8g
|
||||
memswap_limit: 8g
|
||||
ulimits:
|
||||
memlock:
|
||||
soft: -1
|
||||
hard: -1
|
||||
nofile:
|
||||
soft: 65536
|
||||
hard: 65536
|
||||
volumes:
|
||||
- opensearch-data:/usr/share/opensearch/data
|
||||
ports:
|
||||
- 9200:9200
|
||||
- 9600:9600
|
||||
networks:
|
||||
- opensearch-net
|
||||
|
||||
opensearch-dashboards:
|
||||
image: opensearchproject/opensearch-dashboards:2.11.1
|
||||
container_name: opensearch-dashboards
|
||||
restart: always
|
||||
ports:
|
||||
- 5601:5601
|
||||
environment:
|
||||
- OPENSEARCH_HOSTS=https://opensearch-node:9200
|
||||
- OPENSEARCH_USERNAME=admin
|
||||
- OPENSEARCH_PASSWORD=admin
|
||||
- OPENSEARCH_SSL_VERIFICATIONMODE=none
|
||||
depends_on:
|
||||
- opensearch-node
|
||||
networks:
|
||||
- opensearch-net
|
||||
|
||||
volumes:
|
||||
opensearch-data:
|
||||
|
||||
networks:
|
||||
opensearch-net:
|
||||
68
extra/server-discovery/go.mod
Normal file
68
extra/server-discovery/go.mod
Normal file
@@ -0,0 +1,68 @@
|
||||
module github.com/cortezaproject/corteza/extra/server-discovery
|
||||
|
||||
go 1.24.1
|
||||
|
||||
require (
|
||||
github.com/cortezaproject/corteza/server v0.0.0-00010101000000-000000000000
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/elastic/go-elasticsearch/v7 v7.12.0
|
||||
github.com/go-chi/chi/v5 v5.0.7
|
||||
github.com/go-chi/cors v1.2.1
|
||||
github.com/go-chi/jwtauth v1.2.0
|
||||
github.com/go-oauth2/oauth2/v4 v4.4.3
|
||||
github.com/jmoiron/sqlx v1.3.5
|
||||
github.com/lestrrat-go/jwx v1.2.29
|
||||
github.com/microcosm-cc/bluemonday v1.0.26
|
||||
github.com/spf13/cast v1.4.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/tmc/langchaingo v0.1.13
|
||||
go.uber.org/atomic v1.9.0
|
||||
go.uber.org/zap v1.21.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557 // indirect
|
||||
github.com/PaesslerAG/gval v1.2.1 // indirect
|
||||
github.com/SentimensRG/ctx v0.0.0-20180729130232-0bfd988c655d // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/codegangsta/envy v0.0.0-20141216192214-4b78388c8ce4 // indirect
|
||||
github.com/codegangsta/gin v0.0.0-20230218063734-2c98d96c9244 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
|
||||
github.com/dlclark/regexp2 v1.10.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/css v1.0.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/joho/godotenv v1.4.0 // indirect
|
||||
github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect
|
||||
github.com/lestrrat-go/blackmagic v1.0.2 // indirect
|
||||
github.com/lestrrat-go/httpcc v1.0.1 // indirect
|
||||
github.com/lestrrat-go/iter v1.0.2 // indirect
|
||||
github.com/lestrrat-go/option v1.0.1 // indirect
|
||||
github.com/lestrrat-go/strftime v1.0.6 // indirect
|
||||
github.com/markbates/goth v1.71.1 // indirect
|
||||
github.com/mattn/go-shellwords v1.0.12 // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pkoukk/tiktoken-go v0.1.6 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/shopspring/decimal v1.3.1 // indirect
|
||||
github.com/sony/sonyflake v1.0.0 // indirect
|
||||
github.com/spf13/cobra v1.4.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/tidwall/btree v1.3.1 // indirect
|
||||
github.com/urfave/cli v1.22.17 // indirect
|
||||
go.uber.org/multierr v1.7.0 // indirect
|
||||
golang.org/x/crypto v0.31.0 // indirect
|
||||
golang.org/x/net v0.33.0 // indirect
|
||||
golang.org/x/oauth2 v0.21.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
moul.io/zapfilter v1.7.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/cortezaproject/corteza/server => ../../server
|
||||
689
extra/server-discovery/go.sum
Normal file
689
extra/server-discovery/go.sum
Normal file
@@ -0,0 +1,689 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go v0.67.0/go.mod h1:YNan/mUhNZFrYUor0vqrsQ0Ffl7Xtm/ACOy/vsTS858=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557 h1:l6surSnJ3RP4qA1qmKJ+hQn3UjytosdoG27WGjrDlVs=
|
||||
github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557/go.mod h1:sTrmvD/TxuypdOERsDOS7SndZg0rzzcCi1b6wQMXUYM=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/PaesslerAG/gval v1.2.1 h1:Ggwtej1xCyt1994VuDCSjycybIDo3duDCDghK/xc/A0=
|
||||
github.com/PaesslerAG/gval v1.2.1/go.mod h1:XRFLwvmkTEdYziLdaCeCa5ImcGVrfQbeNUbVR+C6xac=
|
||||
github.com/PaesslerAG/jsonpath v0.1.0/go.mod h1:4BzmtoM/PI8fPO4aQGIusjGxGir2BzcV0grWtFzq1Y8=
|
||||
github.com/PaesslerAG/jsonpath v0.1.1 h1:c1/AToHQMVsduPAa4Vh6xp2U0evy4t8SWp8imEsylIk=
|
||||
github.com/PaesslerAG/jsonpath v0.1.1/go.mod h1:lVboNxFGal/VwW6d9JzIy56bUsYAP6tH/x80vjnCseY=
|
||||
github.com/SentimensRG/ctx v0.0.0-20180729130232-0bfd988c655d h1:CbB/Ef3TyBvSSJx2HDSUiw49ONTpaX6BGiI0jJEX6b8=
|
||||
github.com/SentimensRG/ctx v0.0.0-20180729130232-0bfd988c655d/go.mod h1:cfn0Ycx1ASzCkl8+04zI4hrclf9YQ1QfncxzFiNtQLo=
|
||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||
github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/codegangsta/envy v0.0.0-20141216192214-4b78388c8ce4 h1:ihrIKrLQzm6Q6NJHBMemvaIGTFxgxQUEkn2AjN0Aulw=
|
||||
github.com/codegangsta/envy v0.0.0-20141216192214-4b78388c8ce4/go.mod h1:X7wHz0C25Lga6CnJ4WAQNbUQ9P/8eWSNv8qIO71YkSM=
|
||||
github.com/codegangsta/gin v0.0.0-20230218063734-2c98d96c9244 h1:p7q80MWx/lN0rvRiVPvX57b+z7IlQl+l4J31diJpwJw=
|
||||
github.com/codegangsta/gin v0.0.0-20230218063734-2c98d96c9244/go.mod h1:UBYuwaH3dMw91EZ7tGVaFF6GDj5j46S7zqB9lZPIe58=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/deckarep/golang-set v1.7.1 h1:SCQV0S6gTtp6itiFrTqI+pfmJ4LN85S1YzhDf9rTHJQ=
|
||||
github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
|
||||
github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
|
||||
github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/elastic/go-elasticsearch/v7 v7.12.0 h1:j4tvcMrZJLp39L2NYvBb7f+lHKPqPHSL3nvB8+/DV+s=
|
||||
github.com/elastic/go-elasticsearch/v7 v7.12.0/go.mod h1:OJ4wdbtDNk5g503kvlHLyErCgQwwzmDtaFC4XyOxXA4=
|
||||
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/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
|
||||
github.com/go-chi/chi v1.5.1/go.mod h1:REp24E+25iKvxgeTfHmdUoL5x15kBiDBlnIl5bCwe2k=
|
||||
github.com/go-chi/chi v3.3.4+incompatible h1:X+OApYAmoQS6jr1WoUgW+t5Ry5RYGXq2A//WAL5xdAU=
|
||||
github.com/go-chi/chi v3.3.4+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8=
|
||||
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
||||
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||
github.com/go-chi/jwtauth v1.2.0 h1:Z116SPpevIABBYsv8ih/AHYBHmd4EufKSKsLUnWdrTM=
|
||||
github.com/go-chi/jwtauth v1.2.0/go.mod h1:NTUpKoTQV6o25UwYE6w/VaLUu83hzrVKYTVo+lE6qDA=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-oauth2/oauth2/v4 v4.4.3 h1:IiU3iAK5NEeZ4mUf5HV9tN7iNlx4AGUkIJ5M1C4ARxY=
|
||||
github.com/go-oauth2/oauth2/v4 v4.4.3/go.mod h1:NR9Hugz5/Qe2OGxoPBhsTRNjnm/amC+z9+XTwt63rhs=
|
||||
github.com/go-session/session v3.1.2+incompatible/go.mod h1:8B3iivBQjrz/JtC68Np2T1yBBLxTan3mn/3OM0CyRt0=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/goccy/go-json v0.3.5/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.9.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
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/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-20200905233945-acf8798be1f7/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
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.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.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/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
||||
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/pat v0.0.0-20180118222023-199c85a7f6d1/go.mod h1:YeAe0gNeiNT5hoiZRI4yiOky6jVdNvfO2N6Kav/HmxY=
|
||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da/go.mod h1:ks+b9deReOc7jgqp+e7LuFiCBH6Rm5hL32cLcEAArb4=
|
||||
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
|
||||
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
|
||||
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
|
||||
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.10.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lestrrat-go/backoff/v2 v2.0.7/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y=
|
||||
github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A=
|
||||
github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y=
|
||||
github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ=
|
||||
github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k=
|
||||
github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
|
||||
github.com/lestrrat-go/codegen v1.0.0/go.mod h1:JhJw6OQAuPEfVKUCLItpaVLumDGWQznd1VaXrBk9TdM=
|
||||
github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2ttpEmkA4aQ3j4u9dStX2t4M8UM6qqNsG8=
|
||||
github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is=
|
||||
github.com/lestrrat-go/httpcc v1.0.0/go.mod h1:tGS/u00Vh5N6FHNkExqGGNId8e0Big+++0Gf8MBnAvE=
|
||||
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
|
||||
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
|
||||
github.com/lestrrat-go/iter v1.0.0/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc=
|
||||
github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc=
|
||||
github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI=
|
||||
github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4=
|
||||
github.com/lestrrat-go/jwx v1.1.0/go.mod h1:vn9FzD6gJtKkgYs7RTKV7CjWtEka8F/voUollhnn4QE=
|
||||
github.com/lestrrat-go/jwx v1.2.21/go.mod h1:9cfxnOH7G1gN75CaJP2hKGcxFEx5sPh1abRIA/ZJVh4=
|
||||
github.com/lestrrat-go/jwx v1.2.29 h1:QT0utmUJ4/12rmsVQrJ3u55bycPkKqGYuGT4tyRhxSQ=
|
||||
github.com/lestrrat-go/jwx v1.2.29/go.mod h1:hU8k2l6WF0ncx20uQdOmik/Gjg6E3/wIRtXSNFeZuB8=
|
||||
github.com/lestrrat-go/option v0.0.0-20210103042652-6f1ecfceda35/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
||||
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
||||
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
|
||||
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
||||
github.com/lestrrat-go/pdebug/v3 v3.0.1/go.mod h1:za+m+Ve24yCxTEhR59N7UlnJomWwCiIqbJRmKeiADU4=
|
||||
github.com/lestrrat-go/strftime v1.0.6 h1:CFGsDEt1pOpFNU+TJB0nhz9jl+K0hZSLE205AhTIGQQ=
|
||||
github.com/lestrrat-go/strftime v1.0.6/go.mod h1:f7jQKgV5nnJpYgdEasS+/y7EsTb8ykN2z68n3TtcTaw=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/markbates/going v1.0.0/go.mod h1:I6mnB4BPnEeqo85ynXIx1ZFLLbtiLHNXVgWeFO9OGOA=
|
||||
github.com/markbates/goth v1.71.1 h1:MYXrNZwtMsCqVouRv1nhz8APZaMGOsymTHupVvzJBUM=
|
||||
github.com/markbates/goth v1.71.1/go.mod h1:X6xdNgpapSENS0O35iTBBcMHoJDQDfI9bJl+APCkYMc=
|
||||
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
|
||||
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58=
|
||||
github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
|
||||
github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7/go.mod h1:zO8QMzTeZd5cpnIkz/Gn6iK0jDfGicM1nynOkkPIl28=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkoukk/tiktoken-go v0.1.6 h1:JF0TlJzhTbrI30wCvFuiw6FzP2+/bR+FIxUdgEAcUsw=
|
||||
github.com/pkoukk/tiktoken-go v0.1.6/go.mod h1:9NiV+i9mJKGj1rYOT+njbv+ZwA/zJxYdewGl6qVatpg=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
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/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
|
||||
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/sony/sonyflake v1.0.0 h1:MpU6Ro7tfXwgn2l5eluf9xQvQJDROTBImNCfRXn/YeM=
|
||||
github.com/sony/sonyflake v1.0.0/go.mod h1:Jv3cfhf/UFtolOTTRd3q4Nl6ENqM+KfyZ5PseKfZGF4=
|
||||
github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
|
||||
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q=
|
||||
github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8=
|
||||
github.com/tidwall/btree v0.0.0-20191029221954-400434d76274/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8=
|
||||
github.com/tidwall/btree v1.3.1 h1:636+tdVDs8Hjcf35Di260W2xCW4KuoXOKyk9QWOvCpA=
|
||||
github.com/tidwall/btree v1.3.1/go.mod h1:LGm8L/DZjPLmeWGjv5kFrY8dL4uVhMmzmmLYmsObdKE=
|
||||
github.com/tidwall/buntdb v1.1.2/go.mod h1:xAzi36Hir4FarpSHyfuZ6JzPJdjRZ8QlLZSntE2mqlI=
|
||||
github.com/tidwall/gjson v1.3.4/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
|
||||
github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb/go.mod h1:lKYYLFIr9OIgdgrtgkZ9zgRxRdvPYsExnYBsEAd8W5M=
|
||||
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/rtree v0.0.0-20180113144539-6cd427091e0e/go.mod h1:/h+UnNGt0IhNNJLkGikcdcJqm66zGD/uJGMRxK/9+Ao=
|
||||
github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563/go.mod h1:mLqSmt7Dv/CNneF2wfcChfN1rvapyQr01LGKnKex0DQ=
|
||||
github.com/tmc/langchaingo v0.1.13 h1:rcpMWBIi2y3B90XxfE4Ao8dhCQPVDMaNPnN5cGB1CaA=
|
||||
github.com/tmc/langchaingo v0.1.13/go.mod h1:vpQ5NOIhpzxDfTZK9B6tf2GM/MoaHewPWM5KXXGh7hg=
|
||||
github.com/urfave/cli v1.22.17 h1:SYzXoiPfQjHBbkYxbew5prZHS1TOLT3ierW8SYLqtVQ=
|
||||
github.com/urfave/cli v1.22.17/go.mod h1:b0ht0aqgH/6pBYzzxURyrM4xXNgsoT/n2ZzwQiEhNVo=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.14.0/go.mod h1:ol1PCaL0dX20wC0htZ7sYCsvCYmrouYra0zHzaclZhE=
|
||||
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
|
||||
github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
|
||||
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
|
||||
github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/atomic v1.8.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
|
||||
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/multierr v1.7.0 h1:zaiO/rmgFjbmCXdSYJWQcdvOCsthmdaHfr3Gm2Kx4Ec=
|
||||
go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
|
||||
go.uber.org/zap v1.20.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
|
||||
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
|
||||
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
|
||||
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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200927032502-5d4f70055728/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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
|
||||
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/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-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/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-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-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-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/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-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-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
||||
golang.org/x/tools v0.0.0-20200929161345-d7fc70abf50f/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
||||
golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/api v0.32.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
||||
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/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-20200929141702-51c3e5b607fe/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.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
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 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
moul.io/zapfilter v1.7.0 h1:7aFrG4N72bDH9a2BtYUuUaDS981Dxu3qybWfeqaeBDU=
|
||||
moul.io/zapfilter v1.7.0/go.mod h1:M+N2s+qZiA+bzRoyKMVRxyuERijS2ovi2pnMyiOGMvc=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
|
||||
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
|
||||
24
extra/server-discovery/indexer/internal.go
Normal file
24
extra/server-discovery/indexer/internal.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package indexer
|
||||
|
||||
var (
|
||||
status struct {
|
||||
Index string `json:"index"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
Full bool
|
||||
}
|
||||
)
|
||||
|
||||
//func pushStatus(ctx context.Context, esc *elasticsearch.Client) error {
|
||||
// var (
|
||||
// buf = &bytes.Buffer{}
|
||||
// st = struct {
|
||||
//
|
||||
// }
|
||||
// )
|
||||
//
|
||||
// json.NewEncoder(buf).Encode()
|
||||
//
|
||||
// spew.Dump(esc.Create("index", "", ))
|
||||
//
|
||||
// return nil
|
||||
//}
|
||||
118
extra/server-discovery/indexer/service.go
Normal file
118
extra/server-discovery/indexer/service.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package indexer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/cortezaproject/corteza/extra/server-discovery/pkg/api"
|
||||
"github.com/cortezaproject/corteza/extra/server-discovery/pkg/es"
|
||||
"github.com/cortezaproject/corteza/extra/server-discovery/pkg/es/mapping"
|
||||
"github.com/cortezaproject/corteza/extra/server-discovery/pkg/es/reindex"
|
||||
"github.com/cortezaproject/corteza/extra/server-discovery/pkg/options"
|
||||
"github.com/elastic/go-elasticsearch/v7"
|
||||
"github.com/elastic/go-elasticsearch/v7/esutil"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type (
|
||||
Config struct {
|
||||
Corteza options.CortezaOpt
|
||||
ES options.EsOpt
|
||||
Indexer options.IndexerOpt
|
||||
}
|
||||
|
||||
esService interface {
|
||||
BulkIndexer() (esutil.BulkIndexer, error)
|
||||
}
|
||||
|
||||
apiClientService interface {
|
||||
HttpClient() *http.Client
|
||||
Mappings() (*http.Request, error)
|
||||
Feed(url.Values) (*http.Request, error)
|
||||
Resources(string, url.Values) (*http.Request, error)
|
||||
Request(string) (*http.Request, error)
|
||||
Authenticate() error
|
||||
}
|
||||
|
||||
mappingService interface {
|
||||
Mappings(ctx context.Context, esc *elasticsearch.Client, indexPrefix string) (err error)
|
||||
ConfigurationMapping(ctx context.Context) (err error)
|
||||
}
|
||||
|
||||
reIndexService interface {
|
||||
ReindexAll(ctx context.Context, esb esutil.BulkIndexer, indexPrefix string) error
|
||||
Watch(ctx context.Context)
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
DefaultEs esService
|
||||
DefaultApiClient apiClientService
|
||||
DefaultMapper mappingService
|
||||
DefaultReIndexer reIndexService
|
||||
)
|
||||
|
||||
func Initialize(ctx context.Context, log *zap.Logger, c Config, esClient *elasticsearch.Client) (err error) {
|
||||
DefaultEs, err = es.ES(log, esClient)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
schema := c.Indexer.Schemas[0]
|
||||
if len(schema.ClientKey) == 0 || len(schema.ClientSecret) == 0 {
|
||||
return fmt.Errorf("client key and secret is missing")
|
||||
}
|
||||
DefaultApiClient, err = api.Client(c.Corteza, schema.ClientKey, schema.ClientSecret)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Map indexing for resources
|
||||
DefaultMapper = mapping.Mapper(log, esClient, DefaultApiClient)
|
||||
|
||||
err = DefaultMapper.ConfigurationMapping(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// @todo: private/public/protected indexing
|
||||
err = DefaultMapper.Mappings(ctx, esClient, "private")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Reindexing existing mapping if needed
|
||||
DefaultReIndexer = reindex.ReIndexer(log, DefaultEs, esClient, DefaultApiClient, c.ES, func(ctx context.Context) (err error) {
|
||||
err = DefaultMapper.Mappings(ctx, esClient, "private")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return
|
||||
})
|
||||
|
||||
esb, err := DefaultEs.BulkIndexer()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to prepare bulk indexer: %w", err)
|
||||
}
|
||||
|
||||
err = DefaultReIndexer.ReindexAll(ctx, esb, "private")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = esb.Close(ctx); err != nil {
|
||||
return fmt.Errorf("failed to close bulk indexer: %w", err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func Watchers(ctx context.Context) {
|
||||
// Initiate watcher for reindexing resource
|
||||
DefaultReIndexer.Watch(ctx)
|
||||
|
||||
return
|
||||
}
|
||||
47
extra/server-discovery/main.go
Normal file
47
extra/server-discovery/main.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/cortezaproject/corteza/extra/server-discovery/app"
|
||||
"github.com/cortezaproject/corteza/extra/server-discovery/pkg/api/server"
|
||||
"github.com/cortezaproject/corteza/server/pkg/cli"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/elastic/go-elasticsearch/v7/esutil"
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
var _ *spew.ConfigState = nil
|
||||
var _ esutil.BulkIndexer
|
||||
|
||||
func main() {
|
||||
_ = godotenv.Load()
|
||||
ctx := cli.Context()
|
||||
|
||||
a, err := app.New()
|
||||
cli.HandleError(err)
|
||||
|
||||
{
|
||||
wg := &sync.WaitGroup{}
|
||||
|
||||
{
|
||||
a.HttpServer = server.New(a.Log, a.Opt.Environment, a.Opt.HTTPServer, a.Opt.WaitFor, a.Opt.Searcher)
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
a.HttpServer.Serve(ctx)
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
err = a.Activate(ctx)
|
||||
cli.HandleError(err)
|
||||
|
||||
a.HttpServer.Activate(a.MountHttpRoutes)
|
||||
|
||||
// Wait for all servers to be done
|
||||
wg.Wait()
|
||||
|
||||
a.HttpServer.Shutdown()
|
||||
}
|
||||
}
|
||||
205
extra/server-discovery/pkg/api/client.go
Normal file
205
extra/server-discovery/pkg/api/client.go
Normal file
@@ -0,0 +1,205 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cortezaproject/corteza/extra/server-discovery/pkg/options"
|
||||
)
|
||||
|
||||
type (
|
||||
credentials struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
expiresAt time.Time
|
||||
|
||||
authBaseUri string
|
||||
key string
|
||||
secret string
|
||||
}
|
||||
|
||||
client struct {
|
||||
baseUri string
|
||||
discoveryBaseUrl string
|
||||
credentials *credentials
|
||||
}
|
||||
|
||||
ClientService interface {
|
||||
HttpClient() *http.Client
|
||||
Mappings() (*http.Request, error)
|
||||
Feed(url.Values) (*http.Request, error)
|
||||
Resources(string, url.Values) (*http.Request, error)
|
||||
Namespaces() (*http.Request, error)
|
||||
Modules(uint64) (*http.Request, error)
|
||||
Request(string) (*http.Request, error)
|
||||
Authenticate() error
|
||||
}
|
||||
)
|
||||
|
||||
func Client(opt options.CortezaOpt, key, secret string) (c *client, err error) {
|
||||
c = &client{baseUri: opt.BaseUrl, discoveryBaseUrl: opt.DiscoveryUrl}
|
||||
c.credentials = &credentials{
|
||||
authBaseUri: opt.AuthUrl,
|
||||
key: key,
|
||||
secret: secret,
|
||||
}
|
||||
return c, err
|
||||
}
|
||||
|
||||
func (*client) HttpClient() *http.Client {
|
||||
return http.DefaultClient
|
||||
}
|
||||
|
||||
func (c *client) Mappings() (*http.Request, error) {
|
||||
return c.Request(fmt.Sprintf("%s/mappings/", c.discoveryBaseUrl))
|
||||
}
|
||||
|
||||
func (c *client) Feed(qs url.Values) (*http.Request, error) {
|
||||
query := ""
|
||||
if len(qs.Encode()) > 0 {
|
||||
query = fmt.Sprintf("from=%s", qs.Get("from"))
|
||||
}
|
||||
return c.Request(fmt.Sprintf("%s/feed/?%s", c.discoveryBaseUrl, query))
|
||||
}
|
||||
|
||||
func (c *client) Resources(endpoint string, qs url.Values) (*http.Request, error) {
|
||||
return c.Request(fmt.Sprintf("%s/resources/%s?%s", c.discoveryBaseUrl, strings.TrimLeft(endpoint, "/"), qs.Encode()))
|
||||
}
|
||||
|
||||
func (c *client) Namespaces() (*http.Request, error) {
|
||||
return c.Request(fmt.Sprintf("%s/api/compose/namespace/", c.baseUri))
|
||||
}
|
||||
|
||||
func (c *client) Modules(namespaceID uint64) (*http.Request, error) {
|
||||
return c.Request(fmt.Sprintf("%s/api/compose/namespace/%d/module/?sort=name+ASC", c.baseUri, namespaceID))
|
||||
}
|
||||
|
||||
func (c *client) Request(endpoint string) (req *http.Request, err error) {
|
||||
if err = c.Authenticate(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if req, err = http.NewRequest(http.MethodGet, endpoint, nil); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
req.Header.Set("User-Agent", "corteza-server-discovery/0.1")
|
||||
req.Header.Set("Accept", "application/json")
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.credentials.AccessToken))
|
||||
return
|
||||
}
|
||||
|
||||
func (c *client) Authenticate() (err error) {
|
||||
if c.credentials == nil {
|
||||
return fmt.Errorf("missing credentials")
|
||||
}
|
||||
|
||||
tokenNeedsRefresh := c.credentials.expiresAt.IsZero() || time.Now().After(c.credentials.expiresAt.Add(-60*time.Second))
|
||||
|
||||
if tokenNeedsRefresh {
|
||||
newCredentials, err := c.authToken()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to refresh token: %w", err)
|
||||
}
|
||||
|
||||
c.credentials = newCredentials
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *client) authToken() (crd *credentials, err error) {
|
||||
var (
|
||||
req *http.Request
|
||||
rsp *http.Response
|
||||
form = url.Values{}
|
||||
authBaseUri = c.credentials.authBaseUri
|
||||
key = c.credentials.key
|
||||
secret = c.credentials.secret
|
||||
)
|
||||
|
||||
form.Set("grant_type", "client_credentials")
|
||||
form.Set("scope", "profile api discovery")
|
||||
|
||||
req, err = http.NewRequest(http.MethodPost, authBaseUri+"/oauth2/token", strings.NewReader(form.Encode()))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
req.SetBasicAuth(key, secret)
|
||||
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
//d, _ := httputil.DumpRequest(req, true)
|
||||
//println(string(d))
|
||||
|
||||
rsp, err = c.HttpClient().Do(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer rsp.Body.Close()
|
||||
crd = &credentials{
|
||||
authBaseUri: authBaseUri,
|
||||
key: key,
|
||||
secret: secret,
|
||||
}
|
||||
|
||||
if rsp.StatusCode != http.StatusOK {
|
||||
aux := struct{ Error string }{}
|
||||
if err = json.NewDecoder(rsp.Body).Decode(&aux); err != nil {
|
||||
return
|
||||
} else if aux.Error != "" {
|
||||
return nil, fmt.Errorf(aux.Error)
|
||||
} else {
|
||||
return nil, fmt.Errorf("can not authenticate, unexpected error")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//d, _ := httputil.DumpResponse(rsp, true)
|
||||
//println(string(d))
|
||||
|
||||
err = json.NewDecoder(rsp.Body).Decode(crd)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
crd.expiresAt, err = parseJWTExpiry(crd.AccessToken)
|
||||
if err != nil {
|
||||
crd.expiresAt = time.Now().Add(time.Second * time.Duration(crd.ExpiresIn))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func parseJWTExpiry(tokenString string) (time.Time, error) {
|
||||
parts := strings.Split(tokenString, ".")
|
||||
if len(parts) != 3 {
|
||||
return time.Time{}, fmt.Errorf("invalid JWT format: expected 3 parts, got %d", len(parts))
|
||||
}
|
||||
|
||||
payload, err := base64.RawURLEncoding.DecodeString(parts[1])
|
||||
if err != nil {
|
||||
return time.Time{}, fmt.Errorf("failed to decode JWT payload: %w", err)
|
||||
}
|
||||
|
||||
// Parse exp claim
|
||||
var claims struct {
|
||||
Exp int64 `json:"exp"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(payload, &claims); err != nil {
|
||||
return time.Time{}, fmt.Errorf("failed to unmarshal JWT claims: %w", err)
|
||||
}
|
||||
|
||||
if claims.Exp == 0 {
|
||||
return time.Time{}, fmt.Errorf("JWT missing exp claim")
|
||||
}
|
||||
|
||||
return time.Unix(claims.Exp, 0), nil
|
||||
}
|
||||
33
extra/server-discovery/pkg/api/server/cors.go
Normal file
33
extra/server-discovery/pkg/api/server/cors.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/cors"
|
||||
)
|
||||
|
||||
// Sets up default CORS rules to use as a middleware
|
||||
func handleCORS(next http.Handler) http.Handler {
|
||||
return cors.New(cors.Options{
|
||||
AllowedOrigins: []string{
|
||||
"http://*",
|
||||
"https://*",
|
||||
},
|
||||
AllowedMethods: []string{
|
||||
http.MethodHead,
|
||||
http.MethodGet,
|
||||
http.MethodPost,
|
||||
http.MethodPut,
|
||||
http.MethodPatch,
|
||||
http.MethodDelete,
|
||||
},
|
||||
AllowedHeaders: []string{
|
||||
"Accept",
|
||||
"Authorization",
|
||||
"Content-Type",
|
||||
"X-CSRF-ID",
|
||||
},
|
||||
AllowCredentials: true,
|
||||
MaxAge: 300, // Maximum value not ignored by any of major browsers
|
||||
}).Handler(next)
|
||||
}
|
||||
53
extra/server-discovery/pkg/api/server/demux.go
Normal file
53
extra/server-discovery/pkg/api/server/demux.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"go.uber.org/atomic"
|
||||
)
|
||||
|
||||
type (
|
||||
// demux (demultiplexer) routes request to one of the underlying routers
|
||||
// according to current state
|
||||
demux struct {
|
||||
state *atomic.Uint32
|
||||
routers map[uint32]chi.Router
|
||||
}
|
||||
)
|
||||
|
||||
var _ http.Handler = &demux{}
|
||||
|
||||
func Demux(state uint32, r chi.Router) *demux {
|
||||
return &demux{
|
||||
state: atomic.NewUint32(state),
|
||||
routers: map[uint32]chi.Router{state: r},
|
||||
}
|
||||
}
|
||||
|
||||
func (d *demux) State(s uint32) {
|
||||
d.state.Store(s)
|
||||
}
|
||||
|
||||
func (d *demux) Router(s uint32, r chi.Router) {
|
||||
d.routers[s] = r
|
||||
}
|
||||
|
||||
func (d *demux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
defer panicRecovery(r.Context(), w)
|
||||
|
||||
var (
|
||||
state = d.state.Load()
|
||||
router, exists = d.routers[state]
|
||||
)
|
||||
|
||||
if !exists {
|
||||
_, _ = fmt.Fprintf(w, "unconfigured request demultiplexor state (%d)", state)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
router.ServeHTTP(w, r)
|
||||
|
||||
}
|
||||
106
extra/server-discovery/pkg/api/server/handlers.go
Normal file
106
extra/server-discovery/pkg/api/server/handlers.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/cortezaproject/corteza/extra/server-discovery/pkg/auth"
|
||||
"github.com/cortezaproject/corteza/extra/server-discovery/pkg/healthcheck"
|
||||
"github.com/cortezaproject/corteza/extra/server-discovery/pkg/options"
|
||||
"github.com/cortezaproject/corteza/server/pkg/api"
|
||||
"github.com/cortezaproject/corteza/server/pkg/version"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"go.uber.org/zap"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// routes used when server is in waiting mode
|
||||
func waitingRoutes(log *zap.Logger, httpOpt options.HttpServerOpt) (r chi.Router) {
|
||||
r = chi.NewRouter()
|
||||
r.Use(handleCORS)
|
||||
|
||||
mountServiceHandlers(r, log, httpOpt, waiting)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// routes used when server in shutdown mode
|
||||
func shutdownRoutes() (r chi.Router) {
|
||||
r = chi.NewRouter()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// routes used when in active mode
|
||||
func activeRoutes(log *zap.Logger, mountable []func(r chi.Router), envOpt options.EnvironmentOpt, httpOpt options.HttpServerOpt, searcherOpt options.SearcherOpt) (r chi.Router) {
|
||||
r = chi.NewRouter()
|
||||
r.Use(handleCORS)
|
||||
|
||||
r.Route("/"+strings.TrimPrefix(httpOpt.BaseUrl, "/"), func(r chi.Router) {
|
||||
// Base middleware, CORS, RealIP, RequestID, context-logger
|
||||
r.Use(BaseMiddleware(envOpt.IsProduction(), log)...)
|
||||
|
||||
// Verifies JWT in headers, cookies, ...
|
||||
r.Use(auth.HttpTokenVerifier)
|
||||
|
||||
for _, mount := range mountable {
|
||||
mount(r)
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
if httpOpt.BaseUrl != "/" {
|
||||
r.Handle("/", http.RedirectHandler(httpOpt.BaseUrl, http.StatusTemporaryRedirect))
|
||||
}
|
||||
|
||||
mountServiceHandlers(r, log, httpOpt, active)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func mountServiceHandlers(r chi.Router, log *zap.Logger, opt options.HttpServerOpt, state uint32) {
|
||||
if opt.EnableVersionRoute {
|
||||
mountVersionHandler(r, log, opt.BaseUrl)
|
||||
}
|
||||
|
||||
if opt.EnableHealthcheckRoute {
|
||||
mountHealthCheckHandler(r, log, opt.BaseUrl)
|
||||
}
|
||||
}
|
||||
|
||||
func mountVersionHandler(r chi.Router, log *zap.Logger, basePath string) {
|
||||
var (
|
||||
dPath = "/version"
|
||||
sPath = path.Join(basePath, dPath)
|
||||
handler = func(w http.ResponseWriter, r *http.Request) {
|
||||
api.Send(w, r, struct {
|
||||
BuildTime string `json:"buildTime"`
|
||||
Version string `json:"version"`
|
||||
}{version.BuildTime, version.Version})
|
||||
}
|
||||
)
|
||||
|
||||
r.Get(dPath, handler)
|
||||
log.Debug("version route enabled: " + dPath)
|
||||
|
||||
if dPath != sPath {
|
||||
r.Get(sPath, handler)
|
||||
log.Debug("version route enabled: " + sPath)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func mountHealthCheckHandler(r chi.Router, log *zap.Logger, basePath string) {
|
||||
// default & sub path for health-check endpoint
|
||||
var (
|
||||
dPath = "/healthcheck"
|
||||
sPath = path.Join(basePath, dPath)
|
||||
)
|
||||
|
||||
r.Get(dPath, healthcheck.HttpHandler())
|
||||
log.Debug("health check route enabled: " + sPath)
|
||||
|
||||
if dPath != sPath {
|
||||
r.Get(sPath, healthcheck.HttpHandler())
|
||||
log.Debug("health check route enabled: " + sPath)
|
||||
}
|
||||
}
|
||||
76
extra/server-discovery/pkg/api/server/logger.go
Normal file
76
extra/server-discovery/pkg/api/server/logger.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/cortezaproject/corteza/server/pkg/logger"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"go.uber.org/zap"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// contextLogger middleware binds logger to request's context.
|
||||
//
|
||||
// This allows us to use logger from context (with requestID)
|
||||
// inside our (generated) handlers and controllers
|
||||
func contextLogger(log *zap.Logger) func(next http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
var requestID = middleware.GetReqID(req.Context())
|
||||
|
||||
w.Header().Add("X-Request-Id", requestID)
|
||||
|
||||
req = req.WithContext(logger.ContextWithValue(
|
||||
req.Context(),
|
||||
log.With(zap.String("requestID", requestID)).Named("rest"),
|
||||
))
|
||||
|
||||
next.ServeHTTP(w, req)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// LogRequest middleware logs request details
|
||||
//
|
||||
// It uses logger from context, see contextLogger()
|
||||
func LogRequest(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
var remote = req.RemoteAddr
|
||||
if l := strings.LastIndex(remote, ":"); l > -1 {
|
||||
remote = remote[:l]
|
||||
}
|
||||
|
||||
logger.ContextValue(req.Context()).Info(
|
||||
"HTTP request "+req.Method+" "+req.URL.Path,
|
||||
zap.String("method", req.Method),
|
||||
zap.String("path", req.URL.Path),
|
||||
zap.Int64("size", req.ContentLength),
|
||||
zap.String("remote", remote),
|
||||
)
|
||||
next.ServeHTTP(w, req)
|
||||
})
|
||||
}
|
||||
|
||||
// LogResponse middleware logs response details
|
||||
//
|
||||
// It uses logger from context, see contextLogger()
|
||||
func LogResponse(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
wrapped := middleware.NewWrapResponseWriter(w, req.ProtoMajor)
|
||||
t := time.Now()
|
||||
|
||||
defer func() {
|
||||
logger.ContextValue(req.Context()).Info(
|
||||
"HTTP response "+req.Method+" "+req.URL.Path,
|
||||
zap.String("method", req.Method),
|
||||
zap.String("path", req.URL.Path),
|
||||
zap.Int("status", wrapped.Status()),
|
||||
zap.Int("size", wrapped.BytesWritten()),
|
||||
zap.Float64("duration", time.Since(t).Seconds()),
|
||||
)
|
||||
}()
|
||||
|
||||
next.ServeHTTP(wrapped, req)
|
||||
})
|
||||
|
||||
}
|
||||
49
extra/server-discovery/pkg/api/server/middleware.go
Normal file
49
extra/server-discovery/pkg/api/server/middleware.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/pkg/api"
|
||||
"github.com/cortezaproject/corteza/server/pkg/locale"
|
||||
"github.com/cortezaproject/corteza/server/pkg/logger"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func BaseMiddleware(isProduction bool, log *zap.Logger) []func(http.Handler) http.Handler {
|
||||
return []func(http.Handler) http.Handler{
|
||||
handleCORS,
|
||||
locale.DetectLanguage(locale.Global()),
|
||||
middleware.RealIP,
|
||||
api.RemoteAddrToContext,
|
||||
middleware.RequestID,
|
||||
api.DebugToContext(isProduction),
|
||||
contextLogger(log),
|
||||
}
|
||||
}
|
||||
|
||||
func panicRecovery(ctx context.Context, w http.ResponseWriter) {
|
||||
if err := recover(); err != nil {
|
||||
log := logger.ContextValue(ctx)
|
||||
if err, ok := err.(error); ok {
|
||||
log = log.With(zap.Error(err))
|
||||
} else {
|
||||
log = log.With(zap.Any("recover-value", err))
|
||||
}
|
||||
|
||||
log.Debug("crashed on http request", zap.ByteString("stack", debug.Stack()))
|
||||
|
||||
w.WriteHeader(500)
|
||||
|
||||
if _, has := os.LookupEnv("DEBUG_DUMP_STACK_IN_RESPONSE"); has {
|
||||
// Provide nice call stack on endpoint when
|
||||
// we crash
|
||||
_, _ = w.Write(debug.Stack())
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
107
extra/server-discovery/pkg/api/server/server.go
Normal file
107
extra/server-discovery/pkg/api/server/server.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/cortezaproject/corteza/extra/server-discovery/pkg/options"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type (
|
||||
server struct {
|
||||
log *zap.Logger
|
||||
httpOpt options.HttpServerOpt
|
||||
waitForOpt options.WaitForOpt
|
||||
environmentOpt options.EnvironmentOpt
|
||||
searcherOpt options.SearcherOpt
|
||||
endpoints []func(r chi.Router)
|
||||
|
||||
demux *demux
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
waiting uint32 = iota
|
||||
active
|
||||
shutdown
|
||||
)
|
||||
|
||||
// New initializes new HTTP server with special powers
|
||||
// Server is started as early as possible and with a special request handler
|
||||
// that demultiplexes request to one of the configured routers according to the server state.
|
||||
//
|
||||
// Waiting state
|
||||
// This is initial state that with some simple route handlers:
|
||||
// - /version
|
||||
// - /healthcheck
|
||||
// - /healthcheck
|
||||
func New(log *zap.Logger, envOpt options.EnvironmentOpt, httpOpt options.HttpServerOpt, waitForOpt options.WaitForOpt, searcherOpt options.SearcherOpt) *server {
|
||||
s := &server{
|
||||
endpoints: make([]func(r chi.Router), 0),
|
||||
log: log.Named("http"),
|
||||
|
||||
environmentOpt: envOpt,
|
||||
httpOpt: httpOpt,
|
||||
searcherOpt: searcherOpt,
|
||||
waitForOpt: waitForOpt,
|
||||
}
|
||||
|
||||
s.demux = Demux(waiting, waitingRoutes(s.log.Named("waiting"), s.httpOpt))
|
||||
s.demux.Router(shutdown, shutdownRoutes())
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// Activate reconfigures server to use active routes
|
||||
func (s *server) Activate(mm ...func(chi.Router)) {
|
||||
s.demux.Router(active, activeRoutes(s.log, mm, s.environmentOpt, s.httpOpt, s.searcherOpt))
|
||||
|
||||
s.log.Debug("entering active state")
|
||||
s.demux.State(active)
|
||||
}
|
||||
|
||||
// Shutdown reconfigures server to use shutdown routes
|
||||
func (s *server) Shutdown() {
|
||||
s.log.Debug("entering shutdown state")
|
||||
s.demux.State(shutdown)
|
||||
}
|
||||
|
||||
func (s server) Serve(ctx context.Context) {
|
||||
s.log.Info(
|
||||
"starting HTTP server",
|
||||
|
||||
zap.String("path-prefix", s.httpOpt.BaseUrl),
|
||||
zap.String("address", s.httpOpt.Addr),
|
||||
)
|
||||
|
||||
listener, err := net.Listen("tcp", s.httpOpt.Addr)
|
||||
if err != nil {
|
||||
s.log.Error("cannot start server", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
srv := http.Server{
|
||||
Handler: s.demux,
|
||||
|
||||
// use root context as server's base context and as a basis for
|
||||
// context for all requests
|
||||
// this enables us to send cancellation down to every request
|
||||
BaseContext: func(listener net.Listener) context.Context { return ctx },
|
||||
}
|
||||
err = srv.Serve(listener)
|
||||
}()
|
||||
<-ctx.Done()
|
||||
|
||||
if err == nil {
|
||||
err = ctx.Err()
|
||||
if err == context.Canceled {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
|
||||
s.log.Info("HTTP server stopped", zap.Error(err))
|
||||
}
|
||||
34
extra/server-discovery/pkg/api/server/waitfor.go
Normal file
34
extra/server-discovery/pkg/api/server/waitfor.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"go.uber.org/zap"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
// WaitFor sets up a simple status page, delays execution and probes services
|
||||
func (s server) WaitFor(ctx context.Context) {
|
||||
var (
|
||||
err error
|
||||
)
|
||||
|
||||
// Set up a simple HTTP server that will inform the impatient users
|
||||
listener, err := net.Listen("tcp", s.httpOpt.Addr)
|
||||
if err != nil {
|
||||
s.log.Error("cannot start server", zap.Error(err))
|
||||
os.Exit(1)
|
||||
}
|
||||
defer listener.Close()
|
||||
go func() {
|
||||
router := chi.NewRouter()
|
||||
router.Get("/*", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusPreconditionFailed)
|
||||
w.Write([]byte("waiting for services..."))
|
||||
})
|
||||
_ = http.Serve(listener, router)
|
||||
}()
|
||||
<-ctx.Done()
|
||||
}
|
||||
60
extra/server-discovery/pkg/auth/identity.go
Normal file
60
extra/server-discovery/pkg/auth/identity.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type (
|
||||
identity struct {
|
||||
id uint64
|
||||
memberOf []uint64
|
||||
}
|
||||
)
|
||||
|
||||
// Anonymous constructs and returns new anonymous identity with system anonymous roles
|
||||
func Anonymous() *identity {
|
||||
return &identity{
|
||||
// memberOf: AnonymousRoles().IDs(),
|
||||
}
|
||||
}
|
||||
|
||||
// Authenticated constructs and returns new authenticated identity with assigned roles + system authenticated roles
|
||||
func Authenticated(id uint64, rr ...uint64) *identity {
|
||||
return &identity{
|
||||
id: id,
|
||||
// memberOf: append(rr, AuthenticatedRoles().IDs()...),
|
||||
}
|
||||
}
|
||||
|
||||
func (i identity) Identity() uint64 {
|
||||
return i.id
|
||||
}
|
||||
|
||||
func (i identity) Roles() []uint64 {
|
||||
return i.memberOf
|
||||
}
|
||||
|
||||
func (i identity) Valid() bool {
|
||||
return i.id > 0
|
||||
}
|
||||
|
||||
func (i identity) String() string {
|
||||
return fmt.Sprintf("%d", i.Identity())
|
||||
}
|
||||
|
||||
func ExtractFromSubClaim(sub string) (userID uint64, rr []uint64) {
|
||||
parts := strings.Split(sub, " ")
|
||||
rr = make([]uint64, len(parts)-1)
|
||||
for p := range parts {
|
||||
id, _ := strconv.ParseUint(parts[p], 10, 64)
|
||||
if p == 0 {
|
||||
userID = id
|
||||
} else {
|
||||
rr[p-1] = id
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
13
extra/server-discovery/pkg/auth/identity_context.go
Normal file
13
extra/server-discovery/pkg/auth/identity_context.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type (
|
||||
identityCtxKey struct{}
|
||||
)
|
||||
|
||||
func SetIdentityToContext(ctx context.Context, identity Identifiable) context.Context {
|
||||
return context.WithValue(ctx, identityCtxKey{}, identity)
|
||||
}
|
||||
26
extra/server-discovery/pkg/auth/interfaces.go
Normal file
26
extra/server-discovery/pkg/auth/interfaces.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package auth
|
||||
|
||||
type (
|
||||
Identifiable interface {
|
||||
Identity() uint64
|
||||
Roles() []uint64
|
||||
Valid() bool
|
||||
String() string
|
||||
}
|
||||
|
||||
//TokenGenerator interface {
|
||||
// Encode(i Identifiable, clientID uint64, scope ...string) (token []byte, err error)
|
||||
// Generate(ctx context.Context, i Identifiable, clientID uint64, scope ...string) (token []byte, err error)
|
||||
//}
|
||||
|
||||
//TokenHandler interface {
|
||||
// TokenGenerator
|
||||
// HttpVerifier() func(http.Handler) http.Handler
|
||||
// HttpAuthenticator() func(http.Handler) http.Handler
|
||||
//}
|
||||
|
||||
Signer interface {
|
||||
Sign(userID uint64, pp ...interface{}) string
|
||||
Verify(signature string, userID uint64, pp ...interface{}) bool
|
||||
}
|
||||
)
|
||||
394
extra/server-discovery/pkg/auth/token_issuer.go
Normal file
394
extra/server-discovery/pkg/auth/token_issuer.go
Normal file
@@ -0,0 +1,394 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/pkg/payload"
|
||||
"github.com/go-oauth2/oauth2/v4"
|
||||
"github.com/go-oauth2/oauth2/v4/generates"
|
||||
"github.com/go-oauth2/oauth2/v4/models"
|
||||
"github.com/lestrrat-go/jwx/jwa"
|
||||
"github.com/lestrrat-go/jwx/jwk"
|
||||
"github.com/lestrrat-go/jwx/jwt"
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
type (
|
||||
tokenIssuer struct {
|
||||
defaultRequest *TokenRequest
|
||||
|
||||
// store issued tokens
|
||||
store tokenIssuerStore
|
||||
|
||||
// lookup for issued tokens
|
||||
lookup tokenIssuerLookup
|
||||
|
||||
// generator for issued tokens
|
||||
generator tokenIssuerGenerator
|
||||
|
||||
// signer for issued tokens
|
||||
signer tokenIssuerSigner
|
||||
}
|
||||
|
||||
// IssuerOptFn modify toeknIssuer
|
||||
IssuerOptFn func(*tokenIssuer) error
|
||||
|
||||
TokenRequest struct {
|
||||
AccessToken string
|
||||
RefreshToken string
|
||||
Expiration time.Duration
|
||||
Audience string
|
||||
Issuer string
|
||||
IssuedAt time.Time
|
||||
ClientID uint64
|
||||
UserID uint64
|
||||
Roles []uint64
|
||||
Scope []string
|
||||
}
|
||||
|
||||
// IssueOptFn functions modify TokenRequest
|
||||
IssueOptFn func(*TokenRequest) error
|
||||
|
||||
tokenIssuerStore func(context.Context, TokenRequest) error
|
||||
tokenIssuerLookup func(context.Context, string) error
|
||||
tokenIssuerGenerator func(context.Context, TokenRequest) (string, string, error)
|
||||
tokenIssuerSigner func(token jwt.Token) ([]byte, error)
|
||||
)
|
||||
|
||||
var (
|
||||
TokenIssuer *tokenIssuer
|
||||
|
||||
// wrapper around time.Now() that will aid service testing
|
||||
now = func() *time.Time {
|
||||
c := time.Now().Truncate(time.Second)
|
||||
return &c
|
||||
}
|
||||
)
|
||||
|
||||
// NewTokenIssuer initializes and returns new instance of JWT manager
|
||||
func NewTokenIssuer(opt ...IssuerOptFn) (issuer *tokenIssuer, err error) {
|
||||
issuer = &tokenIssuer{
|
||||
defaultRequest: &TokenRequest{Issuer: "cortezaproject.org"},
|
||||
|
||||
store: func(ctx context.Context, request TokenRequest) error {
|
||||
// elegantly handle unconfigured store
|
||||
return fmt.Errorf("token issuer store not configured")
|
||||
},
|
||||
|
||||
lookup: func(context.Context, string) error {
|
||||
// elegantly handle unconfigured lookup
|
||||
return fmt.Errorf("token issuer lookup not configured")
|
||||
},
|
||||
|
||||
signer: func(token jwt.Token) ([]byte, error) {
|
||||
// elegantly handle unconfigured signer
|
||||
return nil, fmt.Errorf("token issuer signer not configured")
|
||||
},
|
||||
|
||||
generator: DefaultAccessTokenGenerator,
|
||||
}
|
||||
|
||||
return issuer, applyToIssuer(issuer, opt...)
|
||||
}
|
||||
|
||||
// Issue issues new access token, stores it and returns signed JWT.
|
||||
func (i *tokenIssuer) Issue(ctx context.Context, opt ...IssueOptFn) (_ []byte, err error) {
|
||||
var req = i.newTokenRequest()
|
||||
if err = req.apply(opt...); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.AccessToken+req.RefreshToken) > 0 {
|
||||
panic("can not issue new token with preset access and refresh tokens, " +
|
||||
"this is most likely an implementation mistake")
|
||||
}
|
||||
|
||||
if req.AccessToken, req.RefreshToken, err = i.generator(ctx, *req); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = i.store(ctx, *req); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return i.sign(req)
|
||||
}
|
||||
|
||||
func (i *tokenIssuer) Sign(opt ...IssueOptFn) (_ []byte, err error) {
|
||||
var req = i.newTokenRequest()
|
||||
if err = req.apply(opt...); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return i.sign(req)
|
||||
}
|
||||
|
||||
func (i *tokenIssuer) sign(req *TokenRequest) ([]byte, error) {
|
||||
if token, err := makeToken(req); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return i.signer(token)
|
||||
}
|
||||
}
|
||||
|
||||
// Returns new token request and copies relevant values from the default token request on the issuer
|
||||
func (i *tokenIssuer) newTokenRequest() *TokenRequest {
|
||||
return &TokenRequest{
|
||||
Issuer: i.defaultRequest.Issuer,
|
||||
ClientID: i.defaultRequest.ClientID,
|
||||
Expiration: i.defaultRequest.Expiration,
|
||||
IssuedAt: *now(),
|
||||
}
|
||||
}
|
||||
|
||||
// Validate performs token validation by checking existence of access-token in the store
|
||||
func (i *tokenIssuer) Validate(ctx context.Context, token jwt.Token) (err error) {
|
||||
if err = i.lookup(ctx, token.JwtID()); err != nil {
|
||||
return fmt.Errorf("unauthorized")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeToken(req *TokenRequest) (_ jwt.Token, err error) {
|
||||
var (
|
||||
roles = make([]string, len(req.Roles))
|
||||
token = jwt.New()
|
||||
|
||||
toString = func(i uint64) string {
|
||||
return strconv.FormatUint(i, 10)
|
||||
}
|
||||
)
|
||||
|
||||
if len(req.Scope) == 0 {
|
||||
// for backward compatibility we default
|
||||
// unset scope to profile & api
|
||||
req.Scope = []string{"profile", "api"}
|
||||
}
|
||||
|
||||
if req.IssuedAt.IsZero() {
|
||||
req.IssuedAt = *now()
|
||||
}
|
||||
|
||||
for i, r := range req.Roles {
|
||||
roles[i] = toString(r)
|
||||
}
|
||||
|
||||
// The key part: store access token as JWT ID claim.
|
||||
// Claim will be extracted when JWT is validated and checked
|
||||
if err = token.Set(jwt.JwtIDKey, req.AccessToken); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = token.Set(jwt.SubjectKey, toString(req.UserID)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = token.Set(jwt.ExpirationKey, now().Add(req.Expiration).Unix()); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if req.Audience != "" {
|
||||
if err = token.Set(jwt.AudienceKey, req.Audience); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err = token.Set(jwt.IssuerKey, req.Issuer); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = token.Set(jwt.IssuedAtKey, req.IssuedAt.Unix()); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = token.Set("clientID", toString(req.ClientID)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = token.Set("scope", strings.Join(req.Scope, " ")); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = token.Set("roles", roles); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// IdentityFromToken decodes sub & roles claims into identity
|
||||
func IdentityFromToken(token jwt.Token) *identity {
|
||||
if token == nil {
|
||||
return Anonymous()
|
||||
}
|
||||
|
||||
var (
|
||||
roles, _ = token.Get("roles")
|
||||
)
|
||||
|
||||
return Authenticated(
|
||||
cast.ToUint64(token.Subject()),
|
||||
payload.ParseUint64s(cast.ToStringSlice(roles))...,
|
||||
)
|
||||
}
|
||||
|
||||
// DefaultAccessTokenGenerator uses token generator from oauth2 lib
|
||||
func DefaultAccessTokenGenerator(ctx context.Context, req TokenRequest) (string, string, error) {
|
||||
return generates.NewAccessGenerate().Token(
|
||||
ctx,
|
||||
&oauth2.GenerateBasic{
|
||||
Client: &models.Client{
|
||||
ID: strconv.FormatUint(req.ClientID, 10),
|
||||
},
|
||||
UserID: strconv.FormatUint(req.UserID, 10),
|
||||
CreateAt: *now(),
|
||||
TokenInfo: nil,
|
||||
Request: nil,
|
||||
},
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
// WithSecretSigner configures token issuer with
|
||||
func WithSecretSigner(secret string) IssuerOptFn {
|
||||
return func(tm *tokenIssuer) (err error) {
|
||||
if len(secret) == 0 {
|
||||
return fmt.Errorf("JWK missing")
|
||||
}
|
||||
|
||||
var key jwk.Key
|
||||
if key, err = jwk.New([]byte(secret)); err != nil {
|
||||
return fmt.Errorf("could not parse JWK: %w", err)
|
||||
}
|
||||
|
||||
tm.signer = func(token jwt.Token) ([]byte, error) {
|
||||
return jwt.Sign(token, jwa.HS512, key)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithDefaultClientID configures ID of the default auth client
|
||||
func WithDefaultClientID(ID uint64) IssuerOptFn {
|
||||
return func(tm *tokenIssuer) (err error) {
|
||||
tm.defaultRequest.ClientID = ID
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// WithDefaultExpiration configures default token expiration time
|
||||
func WithDefaultExpiration(exp time.Duration) IssuerOptFn {
|
||||
return func(tm *tokenIssuer) (err error) {
|
||||
tm.defaultRequest.Expiration = exp
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// WithDefaultIssuer configures default issuer claim
|
||||
func WithDefaultIssuer(iss string) IssuerOptFn {
|
||||
return func(tm *tokenIssuer) (err error) {
|
||||
tm.defaultRequest.Issuer = iss
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// WithStore configures store function
|
||||
func WithStore(fn tokenIssuerStore) IssuerOptFn {
|
||||
return func(tm *tokenIssuer) (err error) {
|
||||
tm.store = fn
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// WithLookup configures lookup function
|
||||
func WithLookup(fn tokenIssuerLookup) IssuerOptFn {
|
||||
return func(tm *tokenIssuer) (err error) {
|
||||
tm.lookup = fn
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// WithGenerator configures generator function
|
||||
func WithGenerator(fn tokenIssuerGenerator) IssuerOptFn {
|
||||
return func(tm *tokenIssuer) (err error) {
|
||||
tm.generator = fn
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// WithSigner configures signer function
|
||||
func WithSigner(fn tokenIssuerSigner) IssuerOptFn {
|
||||
return func(tm *tokenIssuer) (err error) {
|
||||
tm.signer = fn
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (req *TokenRequest) apply(opt ...IssueOptFn) (err error) {
|
||||
for _, fn := range opt {
|
||||
if err = fn(req); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func WithExpiration(e time.Duration) IssueOptFn {
|
||||
return func(t *TokenRequest) (err error) {
|
||||
t.Expiration = e
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func WithIdentity(i Identifiable) IssueOptFn {
|
||||
return func(t *TokenRequest) (err error) {
|
||||
t.UserID = i.Identity()
|
||||
t.Roles = i.Roles()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func WithAccessToken(access string) IssueOptFn {
|
||||
return func(t *TokenRequest) (err error) {
|
||||
t.AccessToken = access
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func WithScope(ss ...string) IssueOptFn {
|
||||
return func(t *TokenRequest) (err error) {
|
||||
t.Scope = ss
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func WithAudience(aud string) IssueOptFn {
|
||||
return func(t *TokenRequest) (err error) {
|
||||
t.Audience = aud
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func WithClientID(id uint64) IssueOptFn {
|
||||
return func(t *TokenRequest) (err error) {
|
||||
t.ClientID = id
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func applyToIssuer(tm *tokenIssuer, opt ...IssuerOptFn) (err error) {
|
||||
for _, fn := range opt {
|
||||
if err = fn(tm); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
94
extra/server-discovery/pkg/auth/token_middleware.go
Normal file
94
extra/server-discovery/pkg/auth/token_middleware.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/lestrrat-go/jwx/jwa"
|
||||
"net/http"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/pkg/errors"
|
||||
"github.com/go-chi/jwtauth"
|
||||
"github.com/lestrrat-go/jwx/jwk"
|
||||
"github.com/lestrrat-go/jwx/jwt"
|
||||
)
|
||||
|
||||
var (
|
||||
HttpTokenVerifier func(http.Handler) http.Handler
|
||||
)
|
||||
|
||||
// verifier returns a jwt verification middleware
|
||||
//
|
||||
// Tasks
|
||||
// 1. picks token from header, query or cookie
|
||||
// 2. extracts identity (if any) and adds it into request context
|
||||
//
|
||||
// In there is no token
|
||||
func verifier(ja *jwtauth.JWTAuth) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
token, err := jwtauth.VerifyRequest(ja, r, jwtauth.TokenFromHeader, jwtauth.TokenFromQuery, jwtauth.TokenFromCookie)
|
||||
ctx := r.Context()
|
||||
|
||||
ctx = jwtauth.NewContext(ctx, token, err)
|
||||
ctx = SetIdentityToContext(ctx, IdentityFromToken(token))
|
||||
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TokenVerifierMiddlewareWithSecretSigner returns HTTP handler with simple jwa.HS512 + secret verifier
|
||||
//
|
||||
// This should be 1:1 with token issuer!
|
||||
func TokenVerifierMiddlewareWithSecretSigner(secret string) (_ func(http.Handler) http.Handler, err error) {
|
||||
if len(secret) == 0 {
|
||||
return nil, fmt.Errorf("JWK missing")
|
||||
}
|
||||
|
||||
var key jwk.Key
|
||||
if key, err = jwk.New([]byte(secret)); err != nil {
|
||||
return nil, fmt.Errorf("could not parse JWK: %w", err)
|
||||
}
|
||||
|
||||
return verifier(jwtauth.New(jwa.HS512.String(), key, nil)), nil
|
||||
}
|
||||
|
||||
// HttpTokenValidator checks if there is a token with identity and matching scope claim
|
||||
//
|
||||
// Empty scope defaults to "api"!
|
||||
func HttpTokenValidator(scope ...string) func(http.Handler) http.Handler {
|
||||
if len(scope) == 0 {
|
||||
// ensure that scope is not empty
|
||||
scope = []string{"api"}
|
||||
}
|
||||
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
err := verifyToken(r.Context(), scope...)
|
||||
if err != nil && !errors.Is(err, jwtauth.ErrNoTokenFound) {
|
||||
errors.ProperlyServeHTTP(w, r, err, false)
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// pulls token from context and validates scope & access-token
|
||||
func verifyToken(ctx context.Context, scope ...string) (err error) {
|
||||
var token jwt.Token
|
||||
if token, _, err = jwtauth.FromContext(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if token == nil {
|
||||
return fmt.Errorf("unauthorized")
|
||||
}
|
||||
|
||||
// if len(scope) > 0 && !CheckJwtScope(token, scope...) {
|
||||
// return errUnauthorizedScope()
|
||||
// }
|
||||
|
||||
return
|
||||
}
|
||||
52
extra/server-discovery/pkg/es/connect.go
Normal file
52
extra/server-discovery/pkg/es/connect.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package es
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/cortezaproject/corteza/extra/server-discovery/pkg/options"
|
||||
"github.com/elastic/go-elasticsearch/v7"
|
||||
)
|
||||
|
||||
func Connect(opt options.EsOpt) (*elasticsearch.Client, error) {
|
||||
config := elasticsearch.Config{
|
||||
Addresses: opt.Addresses,
|
||||
EnableRetryOnTimeout: opt.EnableRetryOnTimeout,
|
||||
MaxRetries: opt.MaxRetries,
|
||||
}
|
||||
if len(opt.Username) > 0 {
|
||||
config.Username = opt.Username
|
||||
config.Password = opt.Password
|
||||
}
|
||||
|
||||
// if the user provided a CA certificate file, we need to load it
|
||||
// and set it in the TLS config
|
||||
if opt.CertFile != "" {
|
||||
rootCACert, err := os.ReadFile(opt.CertFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read CA certificate file: %w", err)
|
||||
}
|
||||
|
||||
caCertPool := x509.NewCertPool()
|
||||
if !caCertPool.AppendCertsFromPEM(rootCACert) {
|
||||
return nil, fmt.Errorf("failed parse root certificate: %w", err)
|
||||
}
|
||||
|
||||
config.Transport = &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
RootCAs: caCertPool,
|
||||
},
|
||||
}
|
||||
} else if !opt.Secure {
|
||||
config.Transport = &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return elasticsearch.NewClient(config)
|
||||
}
|
||||
70
extra/server-discovery/pkg/es/es.go
Normal file
70
extra/server-discovery/pkg/es/es.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package es
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/elastic/go-elasticsearch/v7"
|
||||
"github.com/elastic/go-elasticsearch/v7/esapi"
|
||||
"github.com/elastic/go-elasticsearch/v7/esutil"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type (
|
||||
es struct {
|
||||
log *zap.Logger
|
||||
esc *elasticsearch.Client
|
||||
//esb esutil.BulkIndexer
|
||||
}
|
||||
|
||||
Service interface {
|
||||
EsClient() *elasticsearch.Client
|
||||
EsBulk() esutil.BulkIndexer
|
||||
Watch(ctx context.Context)
|
||||
}
|
||||
|
||||
//apiClientService interface {
|
||||
// HttpClient() *http.Client
|
||||
// Mappings() (*http.Request, error)
|
||||
// Resources(string, url.Values) (*http.Request, error)
|
||||
// Request(string) (*http.Request, error)
|
||||
// Authenticate() error
|
||||
//}
|
||||
)
|
||||
|
||||
func ES(log *zap.Logger, esc *elasticsearch.Client) (out *es, err error) {
|
||||
out = &es{log: log, esc: esc}
|
||||
return
|
||||
}
|
||||
|
||||
func (es *es) BulkIndexer() (esutil.BulkIndexer, error) {
|
||||
return esutil.NewBulkIndexer(esutil.BulkIndexerConfig{
|
||||
Client: es.esc,
|
||||
FlushBytes: 5e+5,
|
||||
})
|
||||
}
|
||||
|
||||
func ValidElasticResponse(res *esapi.Response, err error) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get response from search backend: %w", err)
|
||||
}
|
||||
|
||||
if res.IsError() {
|
||||
defer res.Body.Close()
|
||||
var rsp struct {
|
||||
Error struct {
|
||||
Type string
|
||||
Reason string
|
||||
}
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(res.Body).Decode(&rsp); err != nil {
|
||||
return fmt.Errorf("could not parse response body: %w", err)
|
||||
} else {
|
||||
return fmt.Errorf("search backend responded with an error: %s (type: %s, status: %s)", rsp.Error.Reason, rsp.Error.Type, res.Status())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
287
extra/server-discovery/pkg/es/mapping/mapping.go
Normal file
287
extra/server-discovery/pkg/es/mapping/mapping.go
Normal file
@@ -0,0 +1,287 @@
|
||||
package mapping
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/cortezaproject/corteza/extra/server-discovery/pkg/es/reindex"
|
||||
"github.com/elastic/go-elasticsearch/v7"
|
||||
"github.com/elastic/go-elasticsearch/v7/esapi"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type (
|
||||
esIndex struct {
|
||||
Name string `json:"index"`
|
||||
Health string `json:"health"`
|
||||
Status string `json:"status"`
|
||||
DocsCount string `json:"docs.count"`
|
||||
StoreSize string `json:"store.size"`
|
||||
}
|
||||
|
||||
rspDiscoveryMappings struct {
|
||||
Response []*mapping
|
||||
}
|
||||
|
||||
reqMapping struct {
|
||||
// @todo structure
|
||||
Settings map[string]any `json:"settings,omitempty"`
|
||||
|
||||
Mappings struct {
|
||||
Properties map[string]*property `json:"properties,omitempty"`
|
||||
} `json:"mappings,omitempty"`
|
||||
}
|
||||
|
||||
mapping struct {
|
||||
Index string `json:"index"`
|
||||
Properties map[string]*property `json:"properties"`
|
||||
Mapping map[string]*property `json:"mapping"`
|
||||
DocumentsURL string `json:"documentsURL"`
|
||||
}
|
||||
|
||||
property struct {
|
||||
// https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html
|
||||
Type string `json:"type,omitempty"`
|
||||
|
||||
Analyzer string `json:"analyzer,omitempty"`
|
||||
|
||||
// Boost factor
|
||||
// https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-boost.html
|
||||
Boost float32 `json:"boost,omitempty"`
|
||||
|
||||
Properties map[string]*property `json:"properties,omitempty"`
|
||||
|
||||
// for vector search
|
||||
Dimension int `json:"dimension,omitempty"`
|
||||
Method map[string]any `json:"method,omitempty"`
|
||||
}
|
||||
|
||||
apiClientService interface {
|
||||
HttpClient() *http.Client
|
||||
Mappings() (*http.Request, error)
|
||||
Resources(string, url.Values) (*http.Request, error)
|
||||
Request(string) (*http.Request, error)
|
||||
Authenticate() error
|
||||
}
|
||||
|
||||
mapper struct {
|
||||
log *zap.Logger
|
||||
esClient *elasticsearch.Client
|
||||
api apiClientService
|
||||
}
|
||||
)
|
||||
|
||||
func Mapper(log *zap.Logger, esClient *elasticsearch.Client, api apiClientService) *mapper {
|
||||
return &mapper{
|
||||
log: log,
|
||||
esClient: esClient,
|
||||
api: api,
|
||||
}
|
||||
}
|
||||
|
||||
// Mappings fetches mappings from discovery server and update elastic search indexes
|
||||
func (m *mapper) Mappings(ctx context.Context, esc *elasticsearch.Client, indexPrefix string) (err error) {
|
||||
var (
|
||||
req *http.Request
|
||||
rsp *http.Response
|
||||
rspPayload = &rspDiscoveryMappings{}
|
||||
buf = &bytes.Buffer{}
|
||||
esRsp *esapi.Response
|
||||
existingIndexes []*esIndex
|
||||
index string
|
||||
)
|
||||
|
||||
if req, err = m.api.Mappings(); err != nil {
|
||||
return fmt.Errorf("failed to prepare mapping request: %w", err)
|
||||
}
|
||||
|
||||
//d, _ := httputil.DumpRequest(req, true)
|
||||
//println(string(d))
|
||||
|
||||
if rsp, err = m.api.HttpClient().Do(req.WithContext(ctx)); err != nil {
|
||||
return fmt.Errorf("failed to send request: %w", err)
|
||||
}
|
||||
|
||||
if rsp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("request resulted in an unexpected status: %s", rsp.Status)
|
||||
}
|
||||
|
||||
//d, _ := httputil.DumpResponse(rsp, true)
|
||||
//println(string(d))
|
||||
|
||||
if err = json.NewDecoder(rsp.Body).Decode(rspPayload); err != nil {
|
||||
return fmt.Errorf("failed to decode mapping response: %w", err)
|
||||
}
|
||||
if err = rsp.Body.Close(); err != nil {
|
||||
return fmt.Errorf("failed to close mapping response body: %w", err)
|
||||
}
|
||||
|
||||
if existingIndexes, err = m.getExistingIndexes(ctx); err != nil {
|
||||
return fmt.Errorf("failed to fetch existing indexes: %w", err)
|
||||
}
|
||||
|
||||
indexMap := m.mapExistingIndexes(existingIndexes)
|
||||
|
||||
//esc, err := m.es.Client()
|
||||
//if err != nil {
|
||||
// return fmt.Errorf("failed to prepare es client: %w", err)
|
||||
//}
|
||||
|
||||
for _, im := range rspPayload.Response {
|
||||
buf.Reset()
|
||||
esReq := reqMapping{}
|
||||
//esReq.Mappings.Properties = im.Properties
|
||||
esReq.Settings = map[string]any{
|
||||
"analysis": map[string]any{
|
||||
"analyzer": map[string]any{
|
||||
"edge_ngram_analyzer": map[string]any{
|
||||
"tokenizer": "edge_ngram_tokenizer",
|
||||
"filter": []any{"lowercase"},
|
||||
},
|
||||
},
|
||||
"tokenizer": map[string]any{
|
||||
"edge_ngram_tokenizer": map[string]any{
|
||||
"type": "edge_ngram",
|
||||
"min_gram": 2,
|
||||
"max_gram": 15,
|
||||
"token_chars": []any{"letter", "digit"},
|
||||
},
|
||||
},
|
||||
},
|
||||
"index": map[string]any{
|
||||
"knn": true,
|
||||
},
|
||||
}
|
||||
esReq.Mappings.Properties = im.Mapping
|
||||
|
||||
if err = json.NewEncoder(buf).Encode(esReq); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
index = fmt.Sprintf(reindex.IndexTpl, indexPrefix, im.Index)
|
||||
iLog := m.log.With(zap.String("name", index))
|
||||
|
||||
if e := indexMap[index]; e != nil {
|
||||
// @todo upsert settings if not the same?
|
||||
iLog.Info("index exists",
|
||||
zap.String("health", e.Health),
|
||||
zap.String("status", e.Status),
|
||||
zap.String("size", e.StoreSize),
|
||||
zap.String("documents", e.DocsCount),
|
||||
)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if esRsp, err = esc.Indices.Create(index, esc.Indices.Create.WithBody(buf)); esRsp.IsError() || err != nil {
|
||||
if err != nil {
|
||||
iLog.Error("index creation failed", zap.Error(err))
|
||||
}
|
||||
if len(esRsp.String()) > 0 {
|
||||
iLog.Error(fmt.Sprintf("index creation failed due to %s", esRsp.String()))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if err = esRsp.Body.Close(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
iLog.Info("index created")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (m *mapper) mapExistingIndexes(ii []*esIndex) (im map[string]*esIndex) {
|
||||
im = make(map[string]*esIndex)
|
||||
for _, i := range ii {
|
||||
im[i.Name] = i
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (m *mapper) getExistingIndexes(ctx context.Context) (ii []*esIndex, err error) {
|
||||
var (
|
||||
esRsp *esapi.Response
|
||||
)
|
||||
|
||||
ii = make([]*esIndex, 100)
|
||||
|
||||
esc := m.esClient
|
||||
|
||||
esRsp, err = esc.Cat.Indices(
|
||||
esc.Cat.Indices.WithContext(ctx),
|
||||
esc.Cat.Indices.WithFormat("json"),
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer esRsp.Body.Close()
|
||||
|
||||
return ii, json.NewDecoder(esRsp.Body).Decode(&ii)
|
||||
}
|
||||
|
||||
// ConfigurationMapping ...
|
||||
func (m *mapper) ConfigurationMapping(ctx context.Context) (err error) {
|
||||
var (
|
||||
buf = &bytes.Buffer{}
|
||||
|
||||
// @todo better naming and ref please parent structs
|
||||
configMap = reqMapping{
|
||||
Mappings: struct {
|
||||
Properties map[string]*property `json:"properties,omitempty"`
|
||||
}(struct{ Properties map[string]*property }{Properties: map[string]*property{
|
||||
"incrementalIndex": {
|
||||
//Type: "object",
|
||||
Properties: map[string]*property{
|
||||
"updatedAt": {Type: "date"},
|
||||
},
|
||||
},
|
||||
}}),
|
||||
}
|
||||
)
|
||||
const (
|
||||
CortezaConfigurationIndex = "corteza-configuration-test"
|
||||
)
|
||||
|
||||
index := fmt.Sprintf(CortezaConfigurationIndex)
|
||||
esc := m.esClient
|
||||
|
||||
if err = json.NewEncoder(buf).Encode(configMap); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Check whether a particular configuration index exists
|
||||
res, err := esc.Indices.Exists([]string{index}, esc.Indices.Exists.WithContext(ctx))
|
||||
if res != nil && err != nil && !res.IsError() {
|
||||
if err != nil {
|
||||
m.log.Error("Configuration index exist check failed", zap.Error(err))
|
||||
} else {
|
||||
// Put mapping if exist otherwise create it
|
||||
if res, err := esc.Indices.Create(index, esc.Indices.Create.WithBody(buf)); res.IsError() || err != nil {
|
||||
if err != nil {
|
||||
m.log.Error("Configuration index creation failed", zap.Error(err))
|
||||
}
|
||||
if len(res.String()) > 0 {
|
||||
m.log.Error(fmt.Sprintf("Configuration index creation failed due to %s", res.String()))
|
||||
}
|
||||
}
|
||||
|
||||
m.log.Info("Configuration index created")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
m.log.Info("Configuration index exist")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// @todo: maybe add const for all es mapping property type
|
||||
// @todo: add helper methods for all Indices & use it instead of esc.Indices.
|
||||
106
extra/server-discovery/pkg/es/mapping/mapping_test.go
Normal file
106
extra/server-discovery/pkg/es/mapping/mapping_test.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package mapping
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/cortezaproject/corteza/server/pkg/options"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type (
|
||||
config struct {
|
||||
httpAddr string
|
||||
es struct {
|
||||
addresses []string
|
||||
}
|
||||
cortezaAuth string
|
||||
cortezaDiscoveryAPI string
|
||||
schemas []*schema
|
||||
}
|
||||
|
||||
schema struct {
|
||||
indexPrefix string
|
||||
clientKey string
|
||||
clientSecret string
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
envKeyHttpAddr = "HTTP_ADDR"
|
||||
)
|
||||
|
||||
// @todo use it properly
|
||||
func TestMappings(t *testing.T) {
|
||||
//var (
|
||||
// ctx = context.Background()
|
||||
// log = logger.Default()
|
||||
// req = require.New(t)
|
||||
//)
|
||||
//
|
||||
//cfg, err := GetConfig()
|
||||
//req.NoError(err)
|
||||
|
||||
//client, err := Es(cfg.es.addresses)
|
||||
//req.NoError(err)
|
||||
//
|
||||
//api, err := indexer.ApiClient(cfg.cortezaDiscoveryAPI, cfg.cortezaAuth, cfg.schemas[0].clientKey, cfg.schemas[0].clientSecret)
|
||||
//req.NoError(err)
|
||||
//
|
||||
//err = Mappings(ctx, log, client, api, "private")
|
||||
//req.NoError(err)
|
||||
}
|
||||
|
||||
// @todo reuse existing one
|
||||
func GetConfig() (*config, error) {
|
||||
c := &config{}
|
||||
return c, func() error {
|
||||
baseUrl := options.EnvString("CORTEZA_SERVER_BASE_URL", "http://server:80")
|
||||
|
||||
c.httpAddr = options.EnvString(envKeyHttpAddr, "127.0.0.1:3201")
|
||||
|
||||
c.cortezaAuth = options.EnvString("CORTEZA_SERVER_AUTH", baseUrl+"/auth")
|
||||
if c.cortezaAuth == "" {
|
||||
return fmt.Errorf("corteza Auth endpoint value empty, set it directly with CORTEZA_SERVER_AUTH or indirectly with CORTEZA_SERVER_BASE_URL")
|
||||
}
|
||||
|
||||
c.cortezaDiscoveryAPI = options.EnvString("CORTEZA_SERVER_API_DISCOVERY", baseUrl+"/api/discovery")
|
||||
if c.cortezaDiscoveryAPI == "" {
|
||||
return fmt.Errorf("corteza Discovery API endpoint value empty, set it directly with CORTEZA_SERVER_AUTH or indirectly with CORTEZA_SERVER_API_DISCOVERY")
|
||||
}
|
||||
|
||||
for _, ar := range []string{"public", "protected", "private"} {
|
||||
var (
|
||||
has bool
|
||||
ucAr = strings.ToUpper(ar)
|
||||
s = &schema{indexPrefix: ar}
|
||||
|
||||
keyEnv = ucAr + "_INDEX_CLIENT_KEY"
|
||||
secEnv = ucAr + "_INDEX_CLIENT_SECRET"
|
||||
)
|
||||
|
||||
if s.clientKey, has = os.LookupEnv(keyEnv); !has {
|
||||
continue
|
||||
} else if s.clientKey == "" {
|
||||
return fmt.Errorf("client key (%s) for '%s' is empty or missing", keyEnv, s.indexPrefix)
|
||||
}
|
||||
|
||||
if s.clientSecret = os.Getenv(secEnv); s.clientSecret == "" {
|
||||
return fmt.Errorf("client secret (%s) for '%s' is empty or missing", secEnv, s.indexPrefix)
|
||||
}
|
||||
|
||||
c.schemas = append(c.schemas, s)
|
||||
}
|
||||
|
||||
if len(c.schemas) == 0 {
|
||||
return fmt.Errorf("set at least one client secret pair using <PREFIX>_INDEX_CLIENT_KEY and <PREFIX>_INDEX_CLIENT_SECRET where prefix is one of 'public', 'protected' or 'private'")
|
||||
}
|
||||
|
||||
for _, a := range strings.Split(options.EnvString("ES_ADDRESS", "http://es:9200"), " ") {
|
||||
if a = strings.TrimSpace(a); a != "" {
|
||||
c.es.addresses = append(c.es.addresses, a)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}()
|
||||
}
|
||||
972
extra/server-discovery/pkg/es/reindex/reindex.go
Normal file
972
extra/server-discovery/pkg/es/reindex/reindex.go
Normal file
@@ -0,0 +1,972 @@
|
||||
package reindex
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/cortezaproject/corteza/extra/server-discovery/pkg/options"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/elastic/go-elasticsearch/v7"
|
||||
"github.com/elastic/go-elasticsearch/v7/esutil"
|
||||
"github.com/jmoiron/sqlx/types"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type (
|
||||
docsSources struct {
|
||||
endpoint string
|
||||
index string
|
||||
action string
|
||||
params map[string]string
|
||||
callback func(*document)
|
||||
}
|
||||
|
||||
rspDiscoveryDocuments struct {
|
||||
Error *struct {
|
||||
Message string
|
||||
}
|
||||
Response *struct {
|
||||
Filter struct {
|
||||
NextPage string
|
||||
}
|
||||
|
||||
Documents []*document
|
||||
}
|
||||
}
|
||||
|
||||
// auxiliary struct for parsing indexable documents from Discovery API
|
||||
document struct {
|
||||
ID string
|
||||
Index string
|
||||
Source json.RawMessage
|
||||
}
|
||||
|
||||
feedSources struct {
|
||||
endpoint string
|
||||
index string
|
||||
action string
|
||||
params map[string]string
|
||||
callback func(*document)
|
||||
}
|
||||
|
||||
feedResponse struct {
|
||||
Error *struct {
|
||||
Message string
|
||||
}
|
||||
Response *struct {
|
||||
Filter struct {
|
||||
NextPage string
|
||||
}
|
||||
|
||||
ActivityLogs []ActivityLog `json:"activityLogs"`
|
||||
}
|
||||
}
|
||||
|
||||
ActivityLog struct {
|
||||
ID uint64 `json:"activityID,string"`
|
||||
ResourceID uint64 `json:"resourceID,string"`
|
||||
ResourceType string `json:"resourceType"`
|
||||
ResourceAction string `json:"resourceAction"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Meta types.JSONText `json:"meta"`
|
||||
}
|
||||
|
||||
ActivityLogMeta struct {
|
||||
NamespaceID uint64 `json:"namespaceID,string"`
|
||||
ModuleID uint64 `json:"moduleID,string"`
|
||||
}
|
||||
|
||||
esService interface {
|
||||
BulkIndexer() (esutil.BulkIndexer, error)
|
||||
}
|
||||
|
||||
embedderService interface {
|
||||
GenerateEmbeddings(input string) ([]float64, error)
|
||||
}
|
||||
|
||||
apiClientService interface {
|
||||
HttpClient() *http.Client
|
||||
Mappings() (*http.Request, error)
|
||||
Feed(url.Values) (*http.Request, error)
|
||||
Resources(string, url.Values) (*http.Request, error)
|
||||
Request(string) (*http.Request, error)
|
||||
Authenticate() error
|
||||
}
|
||||
|
||||
reIndexer struct {
|
||||
log *zap.Logger
|
||||
esOpt options.EsOpt
|
||||
es esService
|
||||
esClient *elasticsearch.Client
|
||||
api apiClientService
|
||||
assureMappings func(context.Context) error
|
||||
}
|
||||
|
||||
NamespaceRef struct {
|
||||
NamespaceID string `json:"namespaceID,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
ModuleRef struct {
|
||||
ModuleID string `json:"moduleID,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Handle string `json:"handle,omitempty"`
|
||||
}
|
||||
|
||||
TimestampInfo struct {
|
||||
At time.Time `json:"at,omitempty"`
|
||||
By *User `json:"by,omitempty"`
|
||||
}
|
||||
|
||||
User struct {
|
||||
UserID string `json:"userID,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Handle string `json:"handle,omitempty"`
|
||||
}
|
||||
|
||||
SecurityRule struct {
|
||||
AllowedRoles []string `json:"allowedRoles,omitempty"`
|
||||
DeniedRoles []string `json:"deniedRoles,omitempty"`
|
||||
}
|
||||
|
||||
ComposeRecord struct {
|
||||
ResourceType string `json:"resourceType"`
|
||||
RecordID string `json:"recordID"`
|
||||
ValueLabels map[string]string `json:"valueLabels,omitempty"`
|
||||
Values map[string]any `json:"values,omitempty"`
|
||||
CatchAll []any `json:"catch_all,omitempty"`
|
||||
Updated *TimestampInfo `json:"updated,omitempty"`
|
||||
Created *TimestampInfo `json:"created,omitempty"`
|
||||
Deleted *TimestampInfo `json:"deleted,omitempty"`
|
||||
Security []SecurityRule `json:"security,omitempty"`
|
||||
Namespace *NamespaceRef `json:"namespace,omitempty"`
|
||||
Module *ModuleRef `json:"module,omitempty"`
|
||||
VectorsValue []float64 `json:"vectorsValue,omitempty"`
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
IndexTpl = "corteza-%s-%s"
|
||||
MarkerIndex = "corteza_indexer_state"
|
||||
MarkerID = "last_index_time"
|
||||
)
|
||||
|
||||
func ReIndexer(log *zap.Logger, es esService, esc *elasticsearch.Client, api apiClientService, esOpt options.EsOpt, assureMappings func(context.Context) error) *reIndexer {
|
||||
return &reIndexer{
|
||||
log: log,
|
||||
esOpt: esOpt,
|
||||
es: es,
|
||||
esClient: esc,
|
||||
api: api,
|
||||
assureMappings: assureMappings,
|
||||
}
|
||||
}
|
||||
|
||||
func (ri *reIndexer) ReindexAll(ctx context.Context, esb esutil.BulkIndexer, indexPrefix string) error {
|
||||
var (
|
||||
srcQueue = make(chan *docsSources, 100)
|
||||
bErr = ri.reindexManager(ctx, esb, indexPrefix, srcQueue)
|
||||
)
|
||||
|
||||
srcQueue <- &docsSources{
|
||||
endpoint: "/system/users",
|
||||
index: "system-users",
|
||||
}
|
||||
|
||||
postProcModules := func(namespaceID string) func(d *document) {
|
||||
return func(d *document) {
|
||||
srcQueue <- &docsSources{
|
||||
endpoint: fmt.Sprintf("/compose/namespaces/%s/modules/%s/records", namespaceID, d.ID),
|
||||
index: fmt.Sprintf("compose-records-%s-%s", namespaceID, d.ID),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
postProcNamespaces := func(d *document) {
|
||||
srcQueue <- &docsSources{
|
||||
endpoint: fmt.Sprintf("/compose/namespaces/%s/modules", d.ID),
|
||||
index: "compose-modules",
|
||||
callback: postProcModules(d.ID),
|
||||
}
|
||||
}
|
||||
|
||||
_ = postProcModules
|
||||
_ = postProcNamespaces
|
||||
|
||||
srcQueue <- &docsSources{
|
||||
endpoint: "/compose/namespaces",
|
||||
index: "compose-namespaces",
|
||||
callback: postProcNamespaces,
|
||||
}
|
||||
_ = fmt.Errorf("blocking error")
|
||||
return <-bErr
|
||||
}
|
||||
|
||||
func (ri *reIndexer) reindexManager(ctx context.Context, esb esutil.BulkIndexer, indexPrefix string, srcQueue chan *docsSources) chan error {
|
||||
var qErr = make(chan error)
|
||||
const maxQueueLen = 3
|
||||
|
||||
go func(esb esutil.BulkIndexer) {
|
||||
var (
|
||||
pQueueLen = -1
|
||||
pQueueStaleCount int
|
||||
|
||||
ticker = time.NewTicker(time.Second)
|
||||
)
|
||||
|
||||
defer ticker.Stop()
|
||||
defer func() {
|
||||
qErr <- nil
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
if ctx.Err() != context.Canceled {
|
||||
ri.log.Error(ctx.Err().Error())
|
||||
} else {
|
||||
ri.log.Info("stopped")
|
||||
}
|
||||
return
|
||||
|
||||
case ds := <-srcQueue:
|
||||
if ds == nil {
|
||||
// graceful termination
|
||||
ri.log.Info("done")
|
||||
return
|
||||
}
|
||||
|
||||
err := ri.reindex(ctx, esb, indexPrefix, ds)
|
||||
if err != nil {
|
||||
ri.log.Error("failed to reindex", zap.Error(err), zap.String("endpoint", ds.endpoint))
|
||||
return
|
||||
}
|
||||
|
||||
case <-ticker.C:
|
||||
if pQueueLen != len(srcQueue) {
|
||||
pQueueStaleCount = maxQueueLen
|
||||
} else {
|
||||
pQueueStaleCount--
|
||||
}
|
||||
|
||||
if pQueueStaleCount <= 0 {
|
||||
ri.log.Info("idle")
|
||||
return
|
||||
}
|
||||
|
||||
pQueueLen = len(srcQueue)
|
||||
|
||||
//esb, err := ri.es.BulkIndexer()
|
||||
//if err != nil {
|
||||
// qErr <- err
|
||||
//}
|
||||
|
||||
s := esb.Stats()
|
||||
ri.log.Debug("batch indexing stats",
|
||||
zap.Uint64("added", s.NumAdded),
|
||||
zap.Uint64("created", s.NumCreated),
|
||||
zap.Uint64("updated", s.NumUpdated),
|
||||
zap.Uint64("deleted", s.NumDeleted),
|
||||
zap.Uint64("flushed", s.NumFlushed),
|
||||
zap.Uint64("failed", s.NumFailed),
|
||||
zap.Uint64("indexed", s.NumIndexed),
|
||||
zap.Uint64("requests", s.NumRequests),
|
||||
zap.Int("queue length", pQueueLen),
|
||||
)
|
||||
}
|
||||
}
|
||||
}(esb)
|
||||
|
||||
println("returning")
|
||||
return qErr
|
||||
}
|
||||
|
||||
func (ri *reIndexer) reindex(ctx context.Context, esb esutil.BulkIndexer, indexPrefix string, ds *docsSources) (err error) {
|
||||
var (
|
||||
qs = url.Values{"limit": []string{"500"}}
|
||||
req *http.Request
|
||||
rsp *http.Response
|
||||
cursor string
|
||||
)
|
||||
|
||||
//esb, err := ri.es.BulkIndexer()
|
||||
//if err != nil {
|
||||
// return fmt.Errorf("failed to prepare bulk indexer: %w", err)
|
||||
//}
|
||||
|
||||
for {
|
||||
rspPayload := &rspDiscoveryDocuments{}
|
||||
|
||||
if cursor != "" {
|
||||
// set new cursor and update source URL
|
||||
qs.Set("pageCursor", cursor)
|
||||
}
|
||||
|
||||
if req, err = ri.api.Resources(ds.endpoint, qs); err != nil {
|
||||
return fmt.Errorf("failed to prepare resource request: %w", err)
|
||||
}
|
||||
|
||||
if rsp, err = ri.api.HttpClient().Do(req.WithContext(ctx)); err != nil {
|
||||
return fmt.Errorf("failed to send request: %w", err)
|
||||
}
|
||||
|
||||
if rsp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("request resulted in an unexpected status '%s' for url '%s'", rsp.Status, req.URL)
|
||||
}
|
||||
|
||||
//{
|
||||
// d, err := httputil.DumpRequestOut(req, true)
|
||||
// println(string(d))
|
||||
// spew.Dump(err)
|
||||
//}
|
||||
//{
|
||||
// d, err := httputil.DumpResponse(rsp, true)
|
||||
// println(string(d))
|
||||
// spew.Dump(err)
|
||||
//}
|
||||
|
||||
if err = json.NewDecoder(rsp.Body).Decode(rspPayload); err != nil {
|
||||
return fmt.Errorf("failed to decode reindexing response: %w", err)
|
||||
}
|
||||
|
||||
if err = rsp.Body.Close(); err != nil {
|
||||
return fmt.Errorf("failed to close reindexing response body: %w", err)
|
||||
}
|
||||
|
||||
var docs int
|
||||
if rspPayload.Error != nil {
|
||||
ri.log.Debug("skipping",
|
||||
zap.String("index", fmt.Sprintf(IndexTpl, indexPrefix, ds.index)),
|
||||
zap.String("error", rspPayload.Error.Message),
|
||||
)
|
||||
return
|
||||
} else if rspPayload.Response != nil {
|
||||
docs = len(rspPayload.Response.Documents)
|
||||
}
|
||||
|
||||
ri.log.Debug("reindexing",
|
||||
zap.Int("docs", docs),
|
||||
zap.String("index", fmt.Sprintf(IndexTpl, indexPrefix, ds.index)),
|
||||
)
|
||||
|
||||
if docs == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for _, doc := range rspPayload.Response.Documents {
|
||||
body, err := ri.processResource(doc.Source)
|
||||
if err != nil {
|
||||
ri.log.Error("failed to process record's for embeddings: ", zap.Error(err))
|
||||
}
|
||||
|
||||
err = esb.Add(ctx, esutil.BulkIndexerItem{
|
||||
Index: fmt.Sprintf(IndexTpl, indexPrefix, ds.index),
|
||||
Action: "index",
|
||||
DocumentID: doc.ID,
|
||||
Body: body,
|
||||
OnFailure: func(ctx context.Context, req esutil.BulkIndexerItem, rsp esutil.BulkIndexerResponseItem, err error) {
|
||||
spew.Dump(req)
|
||||
spew.Dump(rsp)
|
||||
spew.Dump(err)
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ds.callback != nil {
|
||||
go ds.callback(doc)
|
||||
}
|
||||
}
|
||||
|
||||
cursor = rspPayload.Response.Filter.NextPage
|
||||
if rspPayload.Response.Filter.NextPage == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
//if err = esb.Close(ctx); err != nil {
|
||||
// return fmt.Errorf("failed to close bulk indexer: %w", err)
|
||||
//}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ri *reIndexer) feedReindex(ctx context.Context, esb esutil.BulkIndexer, indexPrefix string, ds *feedSources) (err error) {
|
||||
var (
|
||||
qs = url.Values{"limit": []string{"500"}}
|
||||
req *http.Request
|
||||
rsp *http.Response
|
||||
cursor string
|
||||
)
|
||||
|
||||
if ds == nil {
|
||||
ri.log.Debug("invalid resource for feed update")
|
||||
return
|
||||
}
|
||||
|
||||
err = ri.assureMappings(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to assure mappings: %w", err)
|
||||
}
|
||||
|
||||
for {
|
||||
rspPayload := &rspDiscoveryDocuments{}
|
||||
|
||||
if cursor != "" {
|
||||
// set new cursor and update source URL
|
||||
qs.Set("pageCursor", cursor)
|
||||
}
|
||||
|
||||
if ds.params != nil {
|
||||
var (
|
||||
val string
|
||||
ok bool
|
||||
)
|
||||
|
||||
val, ok = ds.params["userID"]
|
||||
if ok {
|
||||
qs.Set("userID", val)
|
||||
}
|
||||
|
||||
val, ok = ds.params["namespaceID"]
|
||||
if ok {
|
||||
qs.Set("namespaceID", val)
|
||||
}
|
||||
|
||||
val, ok = ds.params["moduleID"]
|
||||
if ok {
|
||||
qs.Set("moduleID", val)
|
||||
}
|
||||
|
||||
val, ok = ds.params["recordID"]
|
||||
if ok {
|
||||
qs.Set("recordID", val)
|
||||
}
|
||||
|
||||
val, ok = ds.params["deleted"]
|
||||
if ok {
|
||||
qs.Set("deleted", val)
|
||||
}
|
||||
}
|
||||
|
||||
if req, err = ri.api.Resources(ds.endpoint, qs); err != nil {
|
||||
return fmt.Errorf("failed to prepare resource request: %w", err)
|
||||
}
|
||||
|
||||
if rsp, err = ri.api.HttpClient().Do(req.WithContext(ctx)); err != nil {
|
||||
return fmt.Errorf("failed to send request: %w", err)
|
||||
}
|
||||
|
||||
if rsp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("request resulted in an unexpected status '%s' for url '%s'", rsp.Status, req.URL)
|
||||
}
|
||||
|
||||
//{
|
||||
// d, err := httputil.DumpRequestOut(req, true)
|
||||
// println(string(d))
|
||||
// spew.Dump(err)
|
||||
//}
|
||||
//{
|
||||
// d, err := httputil.DumpResponse(rsp, true)
|
||||
// println(string(d))
|
||||
// spew.Dump(err)
|
||||
//}
|
||||
|
||||
if err = json.NewDecoder(rsp.Body).Decode(rspPayload); err != nil {
|
||||
return fmt.Errorf("failed to decode reindexing response: %w", err)
|
||||
}
|
||||
|
||||
if err = rsp.Body.Close(); err != nil {
|
||||
return fmt.Errorf("failed to close reindexing response body: %w", err)
|
||||
}
|
||||
|
||||
var (
|
||||
docs int
|
||||
docIndex = fmt.Sprintf(IndexTpl, indexPrefix, ds.index)
|
||||
)
|
||||
if rspPayload.Error != nil {
|
||||
ri.log.Debug("skipping",
|
||||
zap.String("index", docIndex),
|
||||
zap.String("error", rspPayload.Error.Message),
|
||||
)
|
||||
return
|
||||
} else if rspPayload.Response != nil {
|
||||
docs = len(rspPayload.Response.Documents)
|
||||
}
|
||||
|
||||
ri.log.Debug("feed reindexing",
|
||||
zap.Int("docs", docs),
|
||||
zap.String("index", docIndex),
|
||||
)
|
||||
|
||||
if docs == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
action := "index"
|
||||
if len(ds.action) > 0 {
|
||||
action = ds.action
|
||||
}
|
||||
|
||||
for _, doc := range rspPayload.Response.Documents {
|
||||
esbItem := esutil.BulkIndexerItem{
|
||||
Index: docIndex,
|
||||
Action: action,
|
||||
DocumentID: doc.ID,
|
||||
OnFailure: func(ctx context.Context, req esutil.BulkIndexerItem, rsp esutil.BulkIndexerResponseItem, err error) {
|
||||
spew.Dump(req)
|
||||
spew.Dump(rsp)
|
||||
spew.Dump(err)
|
||||
},
|
||||
}
|
||||
if action != "delete" {
|
||||
esbItem.Action = "index"
|
||||
body, err := ri.processResource(doc.Source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
esbItem.Body = body
|
||||
}
|
||||
|
||||
err = esb.Add(ctx, esbItem)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ds.callback != nil {
|
||||
go ds.callback(doc)
|
||||
}
|
||||
}
|
||||
|
||||
cursor = rspPayload.Response.Filter.NextPage
|
||||
if rspPayload.Response.Filter.NextPage == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ri *reIndexer) feedReindexManager(ctx context.Context, esb esutil.BulkIndexer, indexPrefix string, feedQueue chan *feedSources) chan error {
|
||||
var qErr = make(chan error)
|
||||
const maxQueueLen = 3
|
||||
|
||||
go func(esb esutil.BulkIndexer) {
|
||||
var (
|
||||
pQueueLen = -1
|
||||
pQueueStaleCount int
|
||||
|
||||
ticker = time.NewTicker(time.Second)
|
||||
)
|
||||
|
||||
defer ticker.Stop()
|
||||
defer func() {
|
||||
qErr <- nil
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
if ctx.Err() != context.Canceled {
|
||||
ri.log.Error(ctx.Err().Error())
|
||||
} else {
|
||||
ri.log.Info("feed changes stopped")
|
||||
}
|
||||
return
|
||||
|
||||
case ds := <-feedQueue:
|
||||
if ds == nil {
|
||||
// graceful termination
|
||||
ri.log.Info("feed changes done")
|
||||
return
|
||||
}
|
||||
|
||||
err := ri.feedReindex(ctx, esb, indexPrefix, ds)
|
||||
if err != nil {
|
||||
ri.log.Error("failed to reindex", zap.Error(err), zap.String("endpoint", ds.endpoint))
|
||||
return
|
||||
}
|
||||
|
||||
case <-ticker.C:
|
||||
if pQueueLen != len(feedQueue) {
|
||||
pQueueStaleCount = maxQueueLen
|
||||
} else {
|
||||
pQueueStaleCount--
|
||||
}
|
||||
|
||||
if pQueueStaleCount <= 0 {
|
||||
ri.log.Info("feed changes idle")
|
||||
return
|
||||
}
|
||||
|
||||
pQueueLen = len(feedQueue)
|
||||
|
||||
s := esb.Stats()
|
||||
ri.log.Debug("feed batch indexing stats",
|
||||
zap.Uint64("added", s.NumAdded),
|
||||
zap.Uint64("created", s.NumCreated),
|
||||
zap.Uint64("updated", s.NumUpdated),
|
||||
zap.Uint64("deleted", s.NumDeleted),
|
||||
zap.Uint64("flushed", s.NumFlushed),
|
||||
zap.Uint64("failed", s.NumFailed),
|
||||
zap.Uint64("indexed", s.NumIndexed),
|
||||
zap.Uint64("requests", s.NumRequests),
|
||||
zap.Int("queue length", pQueueLen),
|
||||
)
|
||||
}
|
||||
}
|
||||
}(esb)
|
||||
|
||||
println("feed changes returning")
|
||||
return qErr
|
||||
}
|
||||
|
||||
func (ri *reIndexer) feedReindexChanges(ctx context.Context, esb esutil.BulkIndexer, indexPrefix string, als []ActivityLog) error {
|
||||
var (
|
||||
updateQueue = make(chan *feedSources, 100)
|
||||
bErr = ri.feedReindexManager(ctx, esb, indexPrefix, updateQueue)
|
||||
|
||||
duplicateMap = make(map[string]ActivityLog)
|
||||
)
|
||||
|
||||
for _, al := range als {
|
||||
activityType := fmt.Sprintf("%d-%s", al.ResourceID, al.ResourceAction)
|
||||
if val, ok := duplicateMap[activityType]; ok {
|
||||
continue
|
||||
} else {
|
||||
duplicateMap[activityType] = val
|
||||
}
|
||||
|
||||
action := al.ResourceAction
|
||||
switch al.ResourceType {
|
||||
case "system:user":
|
||||
updateQueue <- &feedSources{
|
||||
endpoint: "/system/users",
|
||||
index: "system-users",
|
||||
action: action,
|
||||
params: map[string]string{
|
||||
"userID": fmt.Sprintf("%d", al.ResourceID),
|
||||
"deleted": "1",
|
||||
},
|
||||
}
|
||||
break
|
||||
|
||||
case "compose:namespace":
|
||||
updateQueue <- &feedSources{
|
||||
endpoint: "/compose/namespaces",
|
||||
index: "compose-namespaces",
|
||||
action: action,
|
||||
params: map[string]string{
|
||||
"namespaceID": fmt.Sprintf("%d", al.ResourceID),
|
||||
"deleted": "1",
|
||||
},
|
||||
}
|
||||
break
|
||||
|
||||
case "compose:module":
|
||||
var meta ActivityLogMeta
|
||||
err := al.Meta.Unmarshal(&meta)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
updateQueue <- &feedSources{
|
||||
endpoint: fmt.Sprintf("/compose/namespaces/%d/modules", meta.NamespaceID),
|
||||
index: "compose-modules",
|
||||
action: action,
|
||||
params: map[string]string{
|
||||
"moduleID": fmt.Sprintf("%d", al.ResourceID),
|
||||
"deleted": "1",
|
||||
},
|
||||
}
|
||||
break
|
||||
|
||||
case "compose:record":
|
||||
var meta ActivityLogMeta
|
||||
err := al.Meta.Unmarshal(&meta)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
updateQueue <- &feedSources{
|
||||
endpoint: fmt.Sprintf("/compose/namespaces/%d/modules/%d/records", meta.NamespaceID, meta.ModuleID),
|
||||
index: fmt.Sprintf("compose-records-%d-%d", meta.NamespaceID, meta.ModuleID),
|
||||
action: action,
|
||||
params: map[string]string{
|
||||
"recordID": fmt.Sprintf("%d", al.ResourceID),
|
||||
"deleted": "1",
|
||||
},
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return <-bErr
|
||||
}
|
||||
|
||||
func (ri *reIndexer) Watch(ctx context.Context) {
|
||||
var now time.Time
|
||||
|
||||
isFirst, err := ri.IsFirstRunWithMarker(ctx)
|
||||
if err != nil {
|
||||
ri.log.Warn(fmt.Sprintf("failed to check first run indexes marker: %s", err))
|
||||
isFirst = false
|
||||
}
|
||||
|
||||
if isFirst {
|
||||
startTime := time.Date(2018, time.January, 1, 0, 0, 0, 0, time.UTC) // Corteza start year
|
||||
if ri.esOpt.IndexBackFillMonths != 0 {
|
||||
startTime = time.Now().AddDate(0, -ri.esOpt.IndexBackFillMonths, 0)
|
||||
}
|
||||
|
||||
ri.log.Info(fmt.Sprintf("first run detected: starting from (%s)", startTime.UTC().Format(time.RFC3339)))
|
||||
|
||||
if err := ri.processBacklogInChunks(ctx, startTime, &now); err != nil {
|
||||
ri.log.Error(fmt.Sprintf("backlog processing failed: %s", err))
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := ri.SaveLastIndexTime(ctx); err != nil {
|
||||
ri.log.Error(fmt.Sprintf("failed to save indexer state to [corteza_indexer_state]: %s", err))
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
now = time.Now()
|
||||
ri.log.Info("continuing reindexing from current time")
|
||||
|
||||
timeOut := ri.esOpt.IndexInterval
|
||||
ticker := time.NewTicker(time.Second * time.Duration(timeOut))
|
||||
|
||||
go func() {
|
||||
defer ticker.Stop()
|
||||
processing := false
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
if processing {
|
||||
ri.log.Warn("skipping feed changes reindexing: already processing")
|
||||
continue
|
||||
}
|
||||
|
||||
processing = true
|
||||
go func() {
|
||||
defer func() {
|
||||
processing = false
|
||||
}()
|
||||
|
||||
tmpTime := time.Now()
|
||||
|
||||
if err := ri.fetchAndIndex(ctx, now, nil); err != nil {
|
||||
ri.log.Error(fmt.Sprintf("reindex error: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
now = tmpTime
|
||||
}()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (ri *reIndexer) processResource(source []byte) (*bytes.Reader, error) {
|
||||
var peekResource struct {
|
||||
ResourceType string `json:"resourceType"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(source, &peekResource); err != nil {
|
||||
return nil, fmt.Errorf("error unmarshaling: %v", err)
|
||||
}
|
||||
|
||||
if peekResource.ResourceType != "compose:record" {
|
||||
return bytes.NewReader(source), nil
|
||||
}
|
||||
|
||||
// process record's resource
|
||||
var record ComposeRecord
|
||||
if err := json.Unmarshal(source, &record); err != nil {
|
||||
return nil, fmt.Errorf("error unmarshaling record: %v", err)
|
||||
}
|
||||
|
||||
updatedJSON, err := json.Marshal(record)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error marshaling: %v", err)
|
||||
}
|
||||
|
||||
return bytes.NewReader(updatedJSON), nil
|
||||
}
|
||||
|
||||
func (ri *reIndexer) IsFirstRunWithMarker(ctx context.Context) (bool, error) {
|
||||
res, err := ri.esClient.Get(
|
||||
MarkerIndex,
|
||||
MarkerID,
|
||||
ri.esClient.Get.WithContext(ctx),
|
||||
)
|
||||
|
||||
// create the index and the fields.
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.IsError() {
|
||||
// marker index does'nt exist
|
||||
if res.StatusCode == http.StatusNotFound {
|
||||
return true, err
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (ri *reIndexer) SaveLastIndexTime(ctx context.Context) error {
|
||||
doc := map[string]interface{}{
|
||||
"last_run": time.Now().UTC().Format(time.RFC3339),
|
||||
"initialized": true,
|
||||
}
|
||||
|
||||
docBytes, err := json.Marshal(doc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal marker document: %w", err)
|
||||
}
|
||||
|
||||
// create/update the marker document
|
||||
res, err := ri.esClient.Index(
|
||||
MarkerIndex,
|
||||
bytes.NewReader(docBytes),
|
||||
ri.esClient.Index.WithContext(ctx),
|
||||
ri.esClient.Index.WithDocumentID(MarkerID),
|
||||
ri.esClient.Index.WithRefresh("true"),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to save marker index document: %w", err)
|
||||
}
|
||||
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.IsError() {
|
||||
return fmt.Errorf("failed to save marker index document: %s", res.Status())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ri *reIndexer) fetchAndIndex(ctx context.Context, fromTime time.Time, toTime *time.Time) error {
|
||||
esb, err := ri.es.BulkIndexer()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer esb.Close(ctx)
|
||||
|
||||
feeds, err := ri.fetchSinglePage(ctx, fromTime, toTime, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if feeds != nil && feeds.Response != nil && len(feeds.Response.ActivityLogs) > 0 {
|
||||
ri.log.Info(fmt.Sprintf("indexing %d records", len(feeds.Response.ActivityLogs)))
|
||||
return ri.feedReindexChanges(ctx, esb, "private", feeds.Response.ActivityLogs)
|
||||
} else {
|
||||
ri.log.Debug(fmt.Sprintf("no feed changes since %s; current time: %s", fromTime.UTC().String(), time.Now().UTC().String()))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ri *reIndexer) fetchSinglePage(ctx context.Context, fromTime time.Time, toTime *time.Time, cursor string) (*feedResponse, error) {
|
||||
qs := url.Values{
|
||||
"from": []string{fromTime.UTC().Format(time.RFC3339)},
|
||||
"limit": []string{"500"},
|
||||
}
|
||||
if toTime != nil {
|
||||
qs.Set("to", toTime.UTC().Format(time.RFC3339))
|
||||
}
|
||||
if cursor != "" {
|
||||
qs.Set("cursor", cursor)
|
||||
}
|
||||
|
||||
req, err := ri.api.Feed(qs)
|
||||
if err != nil {
|
||||
ri.log.Error(fmt.Sprintf("failed to prepare feed request: %s", err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rsp, err := ri.api.HttpClient().Do(req.WithContext(ctx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rsp.Body.Close()
|
||||
|
||||
if rsp.StatusCode != http.StatusOK {
|
||||
ri.log.Error(fmt.Sprintf("request resulted in an unexpected status '%s' for feed", rsp.Status))
|
||||
return nil, fmt.Errorf("unexpected status: %s", rsp.Status)
|
||||
}
|
||||
|
||||
feeds := &feedResponse{}
|
||||
if err = json.NewDecoder(rsp.Body).Decode(feeds); err != nil {
|
||||
ri.log.Error(fmt.Sprintf("failed to decode feed response: %s", err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return feeds, nil
|
||||
}
|
||||
|
||||
func (ri *reIndexer) processBacklogInChunks(ctx context.Context, fromTime time.Time, toTime *time.Time) error {
|
||||
esb, err := ri.es.BulkIndexer()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer esb.Close(ctx)
|
||||
|
||||
cursor := ""
|
||||
totalIndexed := 0
|
||||
|
||||
for {
|
||||
feeds, err := ri.fetchSinglePage(ctx, fromTime, toTime, cursor)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if feeds != nil && feeds.Response != nil && len(feeds.Response.ActivityLogs) > 0 {
|
||||
ri.log.Info(fmt.Sprintf("indexing %d records (page)", len(feeds.Response.ActivityLogs)))
|
||||
if err := ri.feedReindexChanges(ctx, esb, "private", feeds.Response.ActivityLogs); err != nil {
|
||||
return fmt.Errorf("failed to index records: %w", err)
|
||||
}
|
||||
totalIndexed += len(feeds.Response.ActivityLogs)
|
||||
}
|
||||
|
||||
if feeds == nil || feeds.Response == nil || feeds.Response.Filter.NextPage == "" {
|
||||
break
|
||||
}
|
||||
|
||||
cursor = feeds.Response.Filter.NextPage
|
||||
ri.log.Debug(fmt.Sprintf("fetching next page with cursor: %s", cursor))
|
||||
}
|
||||
|
||||
if totalIndexed > 0 {
|
||||
ri.log.Info(fmt.Sprintf("completed indexing %d total records for time chunk", totalIndexed))
|
||||
} else {
|
||||
ri.log.Debug(fmt.Sprintf("no feed changes since %s; current time: %s", fromTime.UTC().String(), time.Now().UTC().String()))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
120
extra/server-discovery/pkg/healthcheck/check.go
Normal file
120
extra/server-discovery/pkg/healthcheck/check.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package healthcheck
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type (
|
||||
checkFn func(ctx context.Context) error
|
||||
|
||||
Meta struct {
|
||||
Label string
|
||||
Description string
|
||||
}
|
||||
|
||||
check struct {
|
||||
fn checkFn
|
||||
*Meta
|
||||
}
|
||||
|
||||
result struct {
|
||||
err error
|
||||
*Meta
|
||||
}
|
||||
|
||||
results []*result
|
||||
checks struct {
|
||||
cc []*check
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
defaults *checks
|
||||
)
|
||||
|
||||
func init() {
|
||||
defaults = New()
|
||||
}
|
||||
|
||||
func Defaults() *checks {
|
||||
return defaults
|
||||
}
|
||||
|
||||
func New() *checks {
|
||||
return &checks{cc: []*check{}}
|
||||
}
|
||||
|
||||
// Add appends new check
|
||||
func (c *checks) Add(fn checkFn, label string, description ...string) {
|
||||
c.cc = append(c.cc, &check{fn, &Meta{Label: label, Description: strings.Join(description, "")}})
|
||||
}
|
||||
|
||||
func (c checks) Run(ctx context.Context) results {
|
||||
var rr = make([]*result, len(c.cc))
|
||||
|
||||
for i, c := range c.cc {
|
||||
rr[i] = &result{c.fn(ctx), c.Meta}
|
||||
}
|
||||
|
||||
return rr
|
||||
}
|
||||
|
||||
func (rr results) Healthy() bool {
|
||||
for _, c := range rr {
|
||||
if c.err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (rr results) String() string {
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
rr.WriteTo(buf)
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (rr results) WriteTo(w io.Writer) (int64, error) {
|
||||
var (
|
||||
p = func(f string, aa ...interface{}) {
|
||||
_, _ = fmt.Fprintf(w, f, aa...)
|
||||
}
|
||||
)
|
||||
|
||||
for _, r := range rr {
|
||||
if r.IsHealthy() {
|
||||
p("PASS")
|
||||
} else {
|
||||
p("FAIL")
|
||||
}
|
||||
|
||||
p(" %s", r.Label)
|
||||
|
||||
if !r.IsHealthy() {
|
||||
p(": %v", r.Error())
|
||||
}
|
||||
|
||||
p("\n")
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (r *result) IsHealthy() bool {
|
||||
return r != nil && r.err == nil
|
||||
}
|
||||
|
||||
func (r *result) Error() string {
|
||||
if r == nil || r.err == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return r.err.Error()
|
||||
}
|
||||
68
extra/server-discovery/pkg/healthcheck/check_test.go
Normal file
68
extra/server-discovery/pkg/healthcheck/check_test.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package healthcheck
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_Healthy(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
checks []*check
|
||||
healthy bool
|
||||
string string
|
||||
}{
|
||||
{
|
||||
"should be healthy with handle for stringer output",
|
||||
[]*check{{func(ctx context.Context) error { return nil }, &Meta{Label: "check01"}}},
|
||||
true,
|
||||
"PASS check01\n",
|
||||
},
|
||||
{
|
||||
"should handle multiple healthy checks",
|
||||
[]*check{
|
||||
{func(ctx context.Context) error { return nil }, &Meta{Label: "check01"}},
|
||||
{func(ctx context.Context) error { return nil }, &Meta{Label: "check02"}},
|
||||
},
|
||||
true,
|
||||
"PASS check01\nPASS check02\n",
|
||||
},
|
||||
{
|
||||
"should handle healthy and unhealthy checks",
|
||||
[]*check{
|
||||
{func(ctx context.Context) error { return nil }, &Meta{Label: "check01"}},
|
||||
{func(ctx context.Context) error { return fmt.Errorf("x") }, &Meta{Label: "check02"}},
|
||||
{func(ctx context.Context) error { return nil }, &Meta{Label: "check03"}},
|
||||
},
|
||||
false,
|
||||
"PASS check01\nFAIL check02: x\nPASS check03\n",
|
||||
},
|
||||
{
|
||||
"should handle labels",
|
||||
[]*check{
|
||||
{func(ctx context.Context) error { return nil }, &Meta{Label: "check01"}},
|
||||
{func(ctx context.Context) error { return nil }, &Meta{Label: "Pretty check"}},
|
||||
},
|
||||
true,
|
||||
"PASS check01\nPASS Pretty check\n",
|
||||
},
|
||||
{
|
||||
"should handle empty check list",
|
||||
[]*check{},
|
||||
true,
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
r := (&checks{cc: tt.checks}).Run(context.Background())
|
||||
a.Equal(tt.healthy, r.Healthy(), "healthy result failed")
|
||||
a.Equal(tt.string, r.String(), "stringer output match failed")
|
||||
})
|
||||
}
|
||||
}
|
||||
16
extra/server-discovery/pkg/healthcheck/http_handler.go
Normal file
16
extra/server-discovery/pkg/healthcheck/http_handler.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package healthcheck
|
||||
|
||||
import "net/http"
|
||||
|
||||
func HttpHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
results := Defaults().Run(r.Context())
|
||||
if results.Healthy() {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
results.WriteTo(w)
|
||||
}
|
||||
}
|
||||
43
extra/server-discovery/pkg/options/HttpServer.go
Normal file
43
extra/server-discovery/pkg/options/HttpServer.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package options
|
||||
|
||||
import "os"
|
||||
|
||||
type (
|
||||
HttpServerOpt struct {
|
||||
Addr string `env:"HTTP_ADDR"`
|
||||
EnableHealthcheckRoute bool `env:"HTTP_ENABLE_HEALTHCHECK_ROUTE"`
|
||||
EnableVersionRoute bool `env:"HTTP_ENABLE_VERSION_ROUTE"`
|
||||
BaseUrl string `env:"HTTP_BASE_URL"`
|
||||
ApiBaseUrl string `env:"HTTP_API_BASE_URL"`
|
||||
}
|
||||
)
|
||||
|
||||
// HttpServer initializes and returns a HTTPServerOpt with default values
|
||||
func HttpServer() (o *HttpServerOpt) {
|
||||
o = &HttpServerOpt{
|
||||
Addr: ":80",
|
||||
EnableHealthcheckRoute: true,
|
||||
EnableVersionRoute: true,
|
||||
BaseUrl: "/",
|
||||
ApiBaseUrl: "/",
|
||||
}
|
||||
|
||||
fill(o)
|
||||
|
||||
// Function that allows access to custom logic inside the parent function.
|
||||
// The custom logic in the other file should be like:
|
||||
// func (o *HTTPServer) Defaults() {...}
|
||||
func(o interface{}) {
|
||||
if def, ok := o.(interface{ Defaults() }); ok {
|
||||
def.Defaults()
|
||||
}
|
||||
}(o)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (o *HttpServerOpt) Defaults() {
|
||||
if val, has := os.LookupEnv("PORT"); has {
|
||||
o.Addr = ":" + val
|
||||
}
|
||||
}
|
||||
41
extra/server-discovery/pkg/options/corteza.go
Normal file
41
extra/server-discovery/pkg/options/corteza.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package options
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/cortezaproject/corteza/server/pkg/options"
|
||||
)
|
||||
|
||||
type (
|
||||
CortezaOpt struct {
|
||||
BaseUrl string
|
||||
AuthUrl string
|
||||
DiscoveryUrl string
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
envKeyBaseUrl = "CORTEZA_SERVER_BASE_URL"
|
||||
envKeyAuthUrl = "CORTEZA_SERVER_AUTH_URL"
|
||||
envKeyDiscoveryUrl = "CORTEZA_SERVER_DISCOVERY_URL"
|
||||
)
|
||||
|
||||
func Corteza() (o *CortezaOpt, err error) {
|
||||
o = &CortezaOpt{}
|
||||
|
||||
return o, func() error {
|
||||
baseUrl := options.EnvString(envKeyBaseUrl, "http://server:80")
|
||||
o.BaseUrl = baseUrl
|
||||
|
||||
o.AuthUrl = options.EnvString(envKeyAuthUrl, baseUrl+"/auth")
|
||||
if o.AuthUrl == "" {
|
||||
return fmt.Errorf("corteza Auth endpoint value empty, set it directly with %s or indirectly with %s", envKeyAuthUrl, envKeyBaseUrl)
|
||||
}
|
||||
|
||||
o.DiscoveryUrl = options.EnvString(envKeyDiscoveryUrl, baseUrl+"/api/discovery")
|
||||
if o.DiscoveryUrl == "" {
|
||||
return fmt.Errorf("corteza Discovery API endpoint value empty, set it directly with %s or indirectly with %s", envKeyDiscoveryUrl, envKeyBaseUrl)
|
||||
}
|
||||
|
||||
return nil
|
||||
}()
|
||||
}
|
||||
42
extra/server-discovery/pkg/options/environment.go
Normal file
42
extra/server-discovery/pkg/options/environment.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package options
|
||||
|
||||
import "strings"
|
||||
|
||||
type (
|
||||
EnvironmentOpt struct {
|
||||
Environment string `env:"ENVIRONMENT"`
|
||||
}
|
||||
)
|
||||
|
||||
// Environment initializes and returns a EnvironmentOpt with default values
|
||||
func Environment() (o *EnvironmentOpt) {
|
||||
o = &EnvironmentOpt{
|
||||
Environment: "production",
|
||||
}
|
||||
|
||||
fill(o)
|
||||
|
||||
// Function that allows access to custom logic inside the parent function.
|
||||
// The custom logic in the other file should be like:
|
||||
// func (o *Environment) Defaults() {...}
|
||||
func(o interface{}) {
|
||||
if def, ok := o.(interface{ Defaults() }); ok {
|
||||
def.Defaults()
|
||||
}
|
||||
}(o)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (e EnvironmentOpt) IsDevelopment() bool {
|
||||
return strings.HasPrefix(e.Environment, "dev")
|
||||
}
|
||||
|
||||
func (e EnvironmentOpt) IsTest() bool {
|
||||
return strings.HasPrefix(e.Environment, "test")
|
||||
}
|
||||
|
||||
func (e EnvironmentOpt) IsProduction() bool {
|
||||
return !e.IsDevelopment() &&
|
||||
!e.IsTest()
|
||||
}
|
||||
43
extra/server-discovery/pkg/options/es.go
Normal file
43
extra/server-discovery/pkg/options/es.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package options
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/pkg/options"
|
||||
)
|
||||
|
||||
type (
|
||||
EsOpt struct {
|
||||
Addresses []string `env:"ES_ADDRESS"`
|
||||
Username string `env:"ES_USERNAME"`
|
||||
Password string `env:"ES_PASSWORD"`
|
||||
CertFile string `env:"ES_CERT_FILE"`
|
||||
Secure bool `env:"ES_SECURE"`
|
||||
EnableRetryOnTimeout bool `env:"ES_ENABLE_RETRY_ON_TIMEOUT"`
|
||||
MaxRetries int `env:"ES_MAX_RETRIES"`
|
||||
IndexInterval int `env:"ES_INDEX_INTERVAL"`
|
||||
IndexBackFillMonths int `env:"ES_INDEX_BACKFILL_MONTHS"`
|
||||
}
|
||||
)
|
||||
|
||||
func ES() (o *EsOpt, err error) {
|
||||
o = &EsOpt{}
|
||||
return o, func() error {
|
||||
o.Username = options.EnvString("ES_USERNAME", "")
|
||||
o.Password = options.EnvString("ES_PASSWORD", "")
|
||||
o.Secure = options.EnvBool("ES_SECURE", true)
|
||||
o.CertFile = options.EnvString("ES_CERT_FILE", "")
|
||||
|
||||
o.EnableRetryOnTimeout = options.EnvBool("ES_ENABLE_RETRY_ON_TIMEOUT", true)
|
||||
o.MaxRetries = options.EnvInt("ES_MAX_RETRIES", 5)
|
||||
o.IndexInterval = options.EnvInt("ES_INDEX_INTERVAL", 30)
|
||||
o.IndexBackFillMonths = options.EnvInt("ES_INDEX_BACKFILL_MONTHS", 0)
|
||||
|
||||
for _, a := range strings.Split(options.EnvString("ES_ADDRESS", "http://es:9200"), " ") {
|
||||
if a = strings.TrimSpace(a); a != "" {
|
||||
o.Addresses = append(o.Addresses, a)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}()
|
||||
}
|
||||
169
extra/server-discovery/pkg/options/helpers.go
Normal file
169
extra/server-discovery/pkg/options/helpers.go
Normal file
@@ -0,0 +1,169 @@
|
||||
package options
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
func fill(opt interface{}) {
|
||||
v := reflect.ValueOf(opt)
|
||||
if v.Kind() != reflect.Ptr {
|
||||
panic("expecting a pointer, not a value")
|
||||
}
|
||||
|
||||
if v.IsNil() {
|
||||
panic("nil pointer passed")
|
||||
}
|
||||
|
||||
v = v.Elem()
|
||||
|
||||
length := v.NumField()
|
||||
for i := 0; i < length; i++ {
|
||||
f := v.Field(i)
|
||||
t := v.Type().Field(i)
|
||||
if tag := t.Tag.Get("env"); tag != "" {
|
||||
if !f.CanSet() {
|
||||
panic("unexpected pointer for field " + t.Name)
|
||||
}
|
||||
|
||||
if f.Type() == reflect.TypeOf(time.Duration(1)) {
|
||||
v.FieldByName(t.Name).SetInt(int64(EnvDuration(tag, time.Duration(f.Int()))))
|
||||
continue
|
||||
}
|
||||
|
||||
if f.Kind() == reflect.String {
|
||||
v.FieldByName(t.Name).SetString(EnvString(tag, f.String()))
|
||||
continue
|
||||
}
|
||||
|
||||
if f.Kind() == reflect.Bool {
|
||||
v.FieldByName(t.Name).SetBool(EnvBool(tag, f.Bool()))
|
||||
continue
|
||||
}
|
||||
|
||||
if f.Kind() == reflect.Int {
|
||||
v.FieldByName(t.Name).SetInt(int64(EnvInt(tag, int(f.Int()))))
|
||||
continue
|
||||
}
|
||||
|
||||
if f.Kind() == reflect.Float32 {
|
||||
v.FieldByName(t.Name).SetFloat(float64(EnvFloat32(tag, float32(f.Float()))))
|
||||
continue
|
||||
}
|
||||
|
||||
panic("unsupported type/kind for field " + t.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func guessHostname() string {
|
||||
// All env keys we'll check, first that has any value set, will be used as hostname
|
||||
candidates := []string{
|
||||
os.Getenv("DOMAIN"),
|
||||
os.Getenv("LETSENCRYPT_HOST"),
|
||||
os.Getenv("VIRTUAL_HOST"),
|
||||
os.Getenv("HOSTNAME"),
|
||||
os.Getenv("HOST"),
|
||||
}
|
||||
|
||||
for _, host := range candidates {
|
||||
if len(host) > 0 {
|
||||
return host
|
||||
}
|
||||
}
|
||||
|
||||
return "local.cortezaproject.org"
|
||||
}
|
||||
|
||||
// returns path prefix
|
||||
func pathPrefix(pp ...string) string {
|
||||
return CleanBase(append([]string{EnvString("HTTP_BASE_URL", "")}, pp...)...)
|
||||
}
|
||||
|
||||
// will return base URL with domain and prefix path
|
||||
func fullURL(pp ...string) string {
|
||||
var (
|
||||
full string
|
||||
host = guessHostname()
|
||||
)
|
||||
|
||||
if strings.Contains(host, "local.") || strings.Contains(host, "localhost") || !isSecure() {
|
||||
full = "http://" + host
|
||||
} else {
|
||||
full = "https://" + host
|
||||
}
|
||||
|
||||
if pfix := pathPrefix(pp...); pfix != "" {
|
||||
return full + pfix
|
||||
}
|
||||
|
||||
return full
|
||||
}
|
||||
|
||||
// Checks value of HTTP_SSL_TERMINATED and fallbacks to checking existence of LETSENCRYPT_HOST
|
||||
// to determinate we are behind a SSL termination infrastructure
|
||||
func isSecure() bool {
|
||||
var _, is = os.LookupEnv("LETSENCRYPT_HOST")
|
||||
|
||||
// We're accessing to HTTP_SSL_TERMINATED directly here and not via HttpServerOpt.SslTerminated
|
||||
// because we need to get to this value early and directly
|
||||
return EnvBool("HTTP_SSL_TERMINATED", is)
|
||||
}
|
||||
|
||||
// Path joins all parts with / and prefixes result (if not empty) with /
|
||||
func CleanBase(pp ...string) string {
|
||||
//if p := strings.Trim(path.Join(pp...), "/"); p != "" {
|
||||
// // prefix base with slash
|
||||
// return "/" + p
|
||||
//}
|
||||
|
||||
return "/" + strings.Trim(path.Join(pp...), "/")
|
||||
}
|
||||
|
||||
func EnvString(key string, def string) string {
|
||||
if val, has := os.LookupEnv(key); has {
|
||||
return val
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
func EnvBool(key string, def bool) bool {
|
||||
if val, has := os.LookupEnv(key); has {
|
||||
if b, err := cast.ToBoolE(val); err == nil {
|
||||
return b
|
||||
}
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
func EnvInt(key string, def int) int {
|
||||
if val, has := os.LookupEnv(key); has {
|
||||
if i, err := cast.ToIntE(val); err == nil {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
func EnvFloat32(key string, def float32) float32 {
|
||||
if val, has := os.LookupEnv(key); has {
|
||||
if i, err := cast.ToFloat32E(val); err == nil {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
func EnvDuration(key string, def time.Duration) time.Duration {
|
||||
if val, has := os.LookupEnv(key); has {
|
||||
if d, err := cast.ToDurationE(val); err == nil {
|
||||
return d
|
||||
}
|
||||
}
|
||||
return def
|
||||
}
|
||||
91
extra/server-discovery/pkg/options/indexer.go
Normal file
91
extra/server-discovery/pkg/options/indexer.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package options
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/cortezaproject/corteza/server/pkg/options"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type (
|
||||
IndexerOpt struct {
|
||||
Enabled bool
|
||||
//HttpAddr string
|
||||
CortezaServerBaseUrl string
|
||||
CortezaServerAuthUrl string
|
||||
CortezaDiscoveryAPI string
|
||||
|
||||
Schemas []*schema
|
||||
}
|
||||
|
||||
schema struct {
|
||||
IndexPrefix string
|
||||
ClientKey string
|
||||
ClientSecret string
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
discoveryIndexer = "DISCOVERY_INDEXER_"
|
||||
indexerEnvKeyEnabled = discoveryIndexer + "ENABLED"
|
||||
//indexerEnvKeyHttpAddr = discoveryIndexer + "HTTP_ADDR"
|
||||
)
|
||||
|
||||
func Indexer() (o *IndexerOpt, err error) {
|
||||
o = &IndexerOpt{}
|
||||
|
||||
return o, func() error {
|
||||
o.Enabled = options.EnvBool(indexerEnvKeyEnabled, true)
|
||||
|
||||
//baseUrl := options.EnvString(envKeyBaseUrl, "http://server:80")
|
||||
|
||||
//o.HttpAddr = options.EnvString(indexerEnvKeyHttpAddr, "0.0.0.0:80")
|
||||
|
||||
//o.CortezaServerAuthUrl = options.EnvString(envKeyAuthUrl, baseUrl+"/auth")
|
||||
//if o.CortezaServerAuthUrl == "" {
|
||||
// return fmt.Errorf("corteza Auth endpoint value empty, set it directly with %s or indirectly with %s", envKeyAuthUrl, envKeyBaseUrl)
|
||||
//}
|
||||
//
|
||||
//o.CortezaDiscoveryAPI = options.EnvString(envKeyDiscoveryUrl, baseUrl+"/api/discovery")
|
||||
//if o.CortezaDiscoveryAPI == "" {
|
||||
// return fmt.Errorf("corteza Discovery API endpoint value empty, set it directly with %s or indirectly with %s", envKeyDiscoveryUrl, envKeyBaseUrl)
|
||||
//}
|
||||
|
||||
//o.IndexInterval = options.EnvInt(indexerEnvKeyIndexInterval, 30)
|
||||
|
||||
for _, ar := range []string{"public", "protected", "private"} {
|
||||
var (
|
||||
has bool
|
||||
ucAr = strings.ToUpper(ar)
|
||||
s = &schema{IndexPrefix: ar}
|
||||
|
||||
keyEnv = discoveryIndexer + ucAr + "_INDEX_CLIENT_KEY"
|
||||
secEnv = discoveryIndexer + ucAr + "_INDEX_CLIENT_SECRET"
|
||||
)
|
||||
|
||||
if s.ClientKey, has = os.LookupEnv(keyEnv); !has {
|
||||
continue
|
||||
} else if s.ClientKey == "" {
|
||||
return fmt.Errorf("client key (%s) for '%s' is empty or missing", keyEnv, s.IndexPrefix)
|
||||
}
|
||||
|
||||
if s.ClientSecret = os.Getenv(secEnv); s.ClientSecret == "" {
|
||||
return fmt.Errorf("client secret (%s) for '%s' is empty or missing", secEnv, s.IndexPrefix)
|
||||
}
|
||||
|
||||
o.Schemas = append(o.Schemas, s)
|
||||
}
|
||||
|
||||
if len(o.Schemas) == 0 {
|
||||
return fmt.Errorf("set at least one client secret pair using %s<PREFIX>_INDEX_CLIENT_KEY and <PREFIX>_INDEX_CLIENT_SECRET where prefix is one of 'public', 'protected' or 'private'", discoveryIndexer)
|
||||
}
|
||||
|
||||
//for _, a := range strings.Split(options.EnvString(indexerEnvKeyEsAddr, "http://es:9200"), " ") {
|
||||
// if a = strings.TrimSpace(a); a != "" {
|
||||
// o.Es.Addresses = append(o.Es.Addresses, a)
|
||||
// }
|
||||
//}
|
||||
|
||||
return nil
|
||||
}()
|
||||
}
|
||||
47
extra/server-discovery/pkg/options/options.go
Normal file
47
extra/server-discovery/pkg/options/options.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package options
|
||||
|
||||
type (
|
||||
Options struct {
|
||||
Corteza CortezaOpt
|
||||
ES EsOpt
|
||||
Indexer IndexerOpt
|
||||
Searcher SearcherOpt
|
||||
Environment EnvironmentOpt
|
||||
HTTPServer HttpServerOpt
|
||||
WaitFor WaitForOpt
|
||||
}
|
||||
)
|
||||
|
||||
// @todo: add codegen for options
|
||||
|
||||
func Init() (opt *Options, err error) {
|
||||
indexer, err := Indexer()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
searcher, err := Searcher()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
es, err := ES()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
corteza, err := Corteza()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return &Options{
|
||||
Corteza: *corteza,
|
||||
ES: *es,
|
||||
Indexer: *indexer,
|
||||
Searcher: *searcher,
|
||||
Environment: *Environment(),
|
||||
HTTPServer: *HttpServer(),
|
||||
WaitFor: *WaitFor(),
|
||||
}, nil
|
||||
}
|
||||
82
extra/server-discovery/pkg/options/searcher.go
Normal file
82
extra/server-discovery/pkg/options/searcher.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package options
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/pkg/options"
|
||||
)
|
||||
|
||||
type (
|
||||
SearcherOpt struct {
|
||||
Enabled bool
|
||||
//HttpAddr string
|
||||
JwtSecret []byte
|
||||
ClientKey string
|
||||
ClientSecret string
|
||||
|
||||
// temp fix: remove it once allowed role is fixed on server
|
||||
AllowedRole map[interface{}]bool
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
discoverySearcher = "DISCOVERY_SEARCHER_"
|
||||
searcherEnvKeyEnabled = discoverySearcher + "ENABLED"
|
||||
//searcherEnvKeyHttpAddr = discoverySearcher + "HTTP_ADDR"
|
||||
envKeyJwtSecret = discoverySearcher + "JWT_SECRET"
|
||||
envKeyClientKey = discoverySearcher + "CLIENT_KEY"
|
||||
envKeyClientSecret = discoverySearcher + "CLIENT_SECRET"
|
||||
envKeyAllowedRole = discoverySearcher + "ALLOWED_ROLE"
|
||||
)
|
||||
|
||||
func Searcher() (*SearcherOpt, error) {
|
||||
o := &SearcherOpt{}
|
||||
|
||||
return o, func() error {
|
||||
o.Enabled = options.EnvBool(searcherEnvKeyEnabled, true)
|
||||
|
||||
//o.CortezaServerBaseUrl = options.EnvString(envKeyBaseUrl, "http://server:80")
|
||||
//if o.CortezaServerBaseUrl == "" {
|
||||
// return fmt.Errorf("endpoint URL for corteza (%s) is empty or missing", envKeyAuthUrl)
|
||||
//}
|
||||
|
||||
//o.HttpAddr = options.EnvString(searcherEnvKeyHttpAddr, "0.0.0.0:80")
|
||||
|
||||
//o.CortezaServerAuthUrl = options.EnvString(envKeyAuthUrl, o.CortezaServerBaseUrl+"/auth")
|
||||
//if o.CortezaServerAuthUrl == "" {
|
||||
// return fmt.Errorf("endpoint URL for corteza auth (%s) is empty or missing", envKeyAuthUrl)
|
||||
//}
|
||||
|
||||
if tmp := os.Getenv(envKeyJwtSecret); tmp != "" {
|
||||
o.JwtSecret = []byte(tmp)
|
||||
}
|
||||
|
||||
if o.ClientKey = os.Getenv(envKeyClientKey); o.ClientKey == "" {
|
||||
return fmt.Errorf("client key (%s) is empty or missing", envKeyClientKey)
|
||||
}
|
||||
|
||||
if o.ClientSecret = os.Getenv(envKeyClientSecret); o.ClientSecret == "" {
|
||||
return fmt.Errorf("client secret (%s) is empty or missing", envKeyClientSecret)
|
||||
}
|
||||
|
||||
for _, a := range strings.Split(options.EnvString(envKeyAllowedRole, ""), " ") {
|
||||
o.AllowedRole = make(map[interface{}]bool)
|
||||
if a = strings.TrimSpace(a); a != "" {
|
||||
o.AllowedRole[a] = false
|
||||
}
|
||||
}
|
||||
|
||||
//for _, a := range strings.Split(options.EnvString(envKeyEsAddr, "http://localhost:9200"), " ") {
|
||||
// if a = strings.TrimSpace(a); a != "" {
|
||||
// o.Es.Addresses = append(o.Es.Addresses, a)
|
||||
// }
|
||||
//}
|
||||
|
||||
//o.EnableRetryOnTimeout = options.EnvBool(searcherEnvKeyEsEnableRetryOnTimeout, true)
|
||||
//o.MaxRetries = options.EnvInt(searcherEnvKeyEsMaxRetries, 5)
|
||||
|
||||
return nil
|
||||
}()
|
||||
}
|
||||
33
extra/server-discovery/pkg/options/waitFor.go
Normal file
33
extra/server-discovery/pkg/options/waitFor.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package options
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
WaitForOpt struct {
|
||||
Delay time.Duration `env:"WAIT_FOR"`
|
||||
StatusPage bool `env:"WAIT_FOR_STATUS_PAGE"`
|
||||
}
|
||||
)
|
||||
|
||||
// WaitFor initializes and returns a WaitForOpt with default values
|
||||
func WaitFor() (o *WaitForOpt) {
|
||||
o = &WaitForOpt{
|
||||
Delay: 0,
|
||||
StatusPage: true,
|
||||
}
|
||||
|
||||
fill(o)
|
||||
|
||||
// Function that allows access to custom logic inside the parent function.
|
||||
// The custom logic in the other file should be like:
|
||||
// func (o *WaitFor) Defaults() {...}
|
||||
func(o interface{}) {
|
||||
if def, ok := o.(interface{ Defaults() }); ok {
|
||||
def.Defaults()
|
||||
}
|
||||
}(o)
|
||||
|
||||
return
|
||||
}
|
||||
26
extra/server-discovery/searcher/healthcheck.go
Normal file
26
extra/server-discovery/searcher/healthcheck.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package searcher
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Healthcheck for searcher
|
||||
func Healthcheck(ctx context.Context) error {
|
||||
if DefaultApiClient == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
esc := DefaultEsClient
|
||||
|
||||
res, err := esc.Ping(esc.Ping.WithContext(ctx))
|
||||
if err = validElasticResponse(res, err); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func(Body io.ReadCloser) {
|
||||
_ = Body.Close()
|
||||
}(res.Body)
|
||||
|
||||
return nil
|
||||
}
|
||||
446
extra/server-discovery/searcher/rest/conv.go
Normal file
446
extra/server-discovery/searcher/rest/conv.go
Normal file
@@ -0,0 +1,446 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
type (
|
||||
// corteza discovery results
|
||||
cdResults struct {
|
||||
Total struct {
|
||||
Value int `json:"value"`
|
||||
TotalOp string `json:"op"`
|
||||
} `json:"-"`
|
||||
|
||||
Hits []cdHit `json:"hits"`
|
||||
TotalHits int `json:"total_hits"`
|
||||
Aggregations []cdAggregation `json:"aggregations"`
|
||||
|
||||
pagination
|
||||
|
||||
// Context ldCtx `json:"@context"`
|
||||
}
|
||||
|
||||
pagination struct {
|
||||
Size int `json:"size"`
|
||||
From int `json:"from"`
|
||||
TotalResults int `json:"total_results"`
|
||||
}
|
||||
|
||||
cdHit struct {
|
||||
Type string `json:"type"`
|
||||
Value map[string]interface{} `json:"value"`
|
||||
}
|
||||
|
||||
cdAggregation struct {
|
||||
Resource string `json:"resource"`
|
||||
Name string `json:"name"`
|
||||
Hits int `json:"hits"`
|
||||
ResourceName []cdAggregationHits `json:"resource_name"`
|
||||
}
|
||||
|
||||
cdAggregationHits struct {
|
||||
Name string `json:"name"`
|
||||
Label string `json:"label"`
|
||||
Hits int `json:"hits"`
|
||||
}
|
||||
// ldCtx map[string]interface{}
|
||||
|
||||
createdBy struct {
|
||||
UserID uint64 `json:"userID,string"`
|
||||
Email string `json:"email,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Username string `json:"username,omitempty"`
|
||||
Handle string `json:"handle,omitempty"`
|
||||
}
|
||||
)
|
||||
|
||||
// conv converts results from the backend into corteza-discovery (jsonld-ish) format
|
||||
func conv(sr *esSearchResponse, aggregation *esSearchResponse, noHits bool, moduleMeta map[string][]string, nsHandleMap map[string]nsMeta, mHandleMap map[string]mMeta, page pagination) (out *cdResults, err error) {
|
||||
if sr == nil {
|
||||
return
|
||||
}
|
||||
|
||||
out = &cdResults{}
|
||||
out.Total.Value = sr.Hits.Total.Value
|
||||
out.Total.TotalOp = sr.Hits.Total.Relation
|
||||
|
||||
// Pagination
|
||||
out.TotalResults = sr.Hits.Total.Value
|
||||
out.From = page.From
|
||||
out.Size = page.Size
|
||||
|
||||
// Aggregation
|
||||
out.Aggregations = []cdAggregation{}
|
||||
|
||||
nsTotalHits := make(map[string]cdAggregationHits)
|
||||
mTotalHits := make(map[string]cdAggregationHits)
|
||||
|
||||
aggsRes := sr.Aggregations
|
||||
if aggregation != nil {
|
||||
aggsRes = aggregation.Aggregations
|
||||
}
|
||||
// for _, bucket := range aggsRes.Resource.Buckets {
|
||||
// bucketName := getResourceName(bucket.Key)
|
||||
// if bucketName == "User" {
|
||||
// continue
|
||||
// }
|
||||
//
|
||||
// for _, subBucket := range bucket.ResourceName.Buckets {
|
||||
// resourceName := subBucket.Key
|
||||
// if bucketName == "Namespace" {
|
||||
// if val, is := nsTotalHits[resourceName]; is {
|
||||
// val.Hits += subBucket.DocCount
|
||||
// nsTotalHits[resourceName] = val
|
||||
// } else {
|
||||
// nsTotalHits[resourceName] = cdAggregationHits{
|
||||
// Name: resourceName,
|
||||
// Label: nsHandleMap[resourceName],
|
||||
// Hits: subBucket.DocCount,
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if bucketName == "Module" {
|
||||
// if val, is := mTotalHits[resourceName]; is {
|
||||
// val.Hits += subBucket.DocCount
|
||||
// mTotalHits[resourceName] = val
|
||||
// } else {
|
||||
// mTotalHits[resourceName] = cdAggregationHits{
|
||||
// Name: resourceName,
|
||||
// Label: mHandleMap[resourceName],
|
||||
// Hits: subBucket.DocCount,
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // Namespace total aggs hit counts
|
||||
// for _, nsBucket := range bucket.Namespaces.Buckets {
|
||||
// resourceName := nsBucket.Key
|
||||
// if val, is := nsTotalHits[resourceName]; is {
|
||||
// val.Hits += nsBucket.DocCount
|
||||
// nsTotalHits[resourceName] = val
|
||||
// } else {
|
||||
// nsTotalHits[resourceName] = cdAggregationHits{
|
||||
// Name: resourceName,
|
||||
// Label: nsHandleMap[resourceName],
|
||||
// Hits: nsBucket.DocCount,
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // Module total aggs hit counts
|
||||
// for _, mBucket := range bucket.Modules.Buckets {
|
||||
// resourceName := mBucket.Key
|
||||
// if val, is := mTotalHits[resourceName]; is {
|
||||
// val.Hits += mBucket.DocCount
|
||||
// mTotalHits[resourceName] = val
|
||||
//
|
||||
// } else {
|
||||
// mTotalHits[resourceName] = cdAggregationHits{
|
||||
// Name: resourceName,
|
||||
// Label: mHandleMap[resourceName],
|
||||
// Hits: mBucket.DocCount,
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
nsAggregation := cdAggregation{
|
||||
Name: "Namespace",
|
||||
Resource: "compose:namespace",
|
||||
Hits: 0,
|
||||
ResourceName: []cdAggregationHits{},
|
||||
}
|
||||
for _, bucket := range aggsRes.Namespace.Namespace.Buckets {
|
||||
resourceName := bucket.Key
|
||||
|
||||
if val, is := nsTotalHits[resourceName]; is {
|
||||
val.Hits += bucket.DocCount
|
||||
nsTotalHits[resourceName] = val
|
||||
} else {
|
||||
var name string
|
||||
if nsHandleMap != nil {
|
||||
name = nsHandleMap[resourceName].Name
|
||||
}
|
||||
if len(name) > 0 {
|
||||
nsTotalHits[resourceName] = cdAggregationHits{
|
||||
Name: name,
|
||||
Label: resourceName,
|
||||
Hits: bucket.DocCount,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, nsHits := range nsTotalHits {
|
||||
nsAggregation.Hits += nsHits.Hits
|
||||
nsAggregation.ResourceName = append(nsAggregation.ResourceName, nsHits)
|
||||
}
|
||||
if len(nsAggregation.ResourceName) > 0 {
|
||||
sort.Slice(nsAggregation.ResourceName, func(i, j int) bool {
|
||||
return nsAggregation.ResourceName[i].Name < nsAggregation.ResourceName[j].Name
|
||||
})
|
||||
out.Aggregations = append(out.Aggregations, nsAggregation)
|
||||
}
|
||||
|
||||
mAggregation := cdAggregation{
|
||||
Name: "Module",
|
||||
Resource: "compose:module",
|
||||
Hits: 0,
|
||||
ResourceName: []cdAggregationHits{},
|
||||
}
|
||||
|
||||
for _, bucket := range aggsRes.Module.Module.Buckets {
|
||||
resourceName := bucket.Key
|
||||
|
||||
if val, is := mTotalHits[resourceName]; is {
|
||||
val.Hits += bucket.DocCount
|
||||
mTotalHits[resourceName] = val
|
||||
} else {
|
||||
var name string
|
||||
if mHandleMap != nil {
|
||||
name = mHandleMap[resourceName].Name
|
||||
}
|
||||
mTotalHits[resourceName] = cdAggregationHits{
|
||||
Name: name,
|
||||
Label: resourceName,
|
||||
Hits: bucket.DocCount,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, mHits := range mTotalHits {
|
||||
mAggregation.Hits += mHits.Hits
|
||||
mAggregation.ResourceName = append(mAggregation.ResourceName, mHits)
|
||||
}
|
||||
if len(mAggregation.ResourceName) > 0 {
|
||||
sort.Slice(mAggregation.ResourceName, func(i, j int) bool {
|
||||
return mAggregation.ResourceName[i].Name < mAggregation.ResourceName[j].Name
|
||||
})
|
||||
out.Aggregations = append(out.Aggregations, mAggregation)
|
||||
}
|
||||
|
||||
// if !noHits {
|
||||
hits:
|
||||
for _, h := range sr.Hits.Hits {
|
||||
aux := map[string]interface{}{}
|
||||
if err = json.Unmarshal(h.Source, &aux); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
resType := cast.ToString(aux["resourceType"])
|
||||
delete(aux, "resourceType")
|
||||
switch resType {
|
||||
case "system:user":
|
||||
aux["id"] = aux["userID"]
|
||||
delete(aux, "userID")
|
||||
|
||||
case "compose:record":
|
||||
// @todo: Remove below line and find proper solution for searsia as value needs to be in json
|
||||
ssVal := make(map[string]interface{})
|
||||
// @todo refactor record related values
|
||||
type (
|
||||
created struct {
|
||||
At *time.Time `json:"at,omitempty"`
|
||||
By string `json:"by,omitempty"`
|
||||
}
|
||||
|
||||
record struct {
|
||||
Created struct {
|
||||
At *time.Time `json:"at,omitempty"`
|
||||
By *createdBy `json:"by,omitempty"`
|
||||
} `json:"created"`
|
||||
Module struct {
|
||||
Name string `json:"name"`
|
||||
Handle string `json:"handle"`
|
||||
ModuleId uint64 `json:"moduleId,string"`
|
||||
} `json:"module"`
|
||||
Namespace struct {
|
||||
Name string `json:"name"`
|
||||
Handle string `json:"handle"`
|
||||
NamespaceId uint64 `json:"namespaceId,string"`
|
||||
} `json:"namespace"`
|
||||
Values map[string]interface{} `json:"values"`
|
||||
ValueLabels map[string]string `json:"valueLabels"`
|
||||
}
|
||||
)
|
||||
|
||||
var r record
|
||||
if err = json.Unmarshal(h.Source, &r); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
type valueJson struct {
|
||||
Name string `json:"name"`
|
||||
Label string `json:"label"`
|
||||
Value interface{} `json:"value"`
|
||||
}
|
||||
key := fmt.Sprintf("%d-%d", r.Namespace.NamespaceId, r.Module.ModuleId)
|
||||
var (
|
||||
slice []valueJson
|
||||
uc = created{
|
||||
At: r.Created.At,
|
||||
By: getCreatedBy(r.Created.By),
|
||||
}
|
||||
)
|
||||
|
||||
if val, is := moduleMeta[key]; is {
|
||||
for _, f := range val {
|
||||
slice = append(slice, valueJson{
|
||||
Name: f,
|
||||
Label: r.ValueLabels[f],
|
||||
Value: sanitize(r.Values[f]),
|
||||
})
|
||||
|
||||
if vv, ok := r.Values[f].([]interface{}); ok {
|
||||
if len(vv) > 0 {
|
||||
ssVal[f] = sanitize(vv[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for k, v := range r.Values {
|
||||
// @todo hardcoded value
|
||||
sanitizedVal := sanitize(v)
|
||||
if len(slice) < 5 && sanitizedVal != nil {
|
||||
slice = append(slice, valueJson{
|
||||
Name: k,
|
||||
Label: r.ValueLabels[k],
|
||||
Value: sanitizedVal,
|
||||
})
|
||||
|
||||
if vv, ok := v.([]interface{}); ok {
|
||||
if len(vv) > 0 {
|
||||
ssVal[k] = sanitize(vv[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
matching_fields := make(map[string]any)
|
||||
|
||||
for key, value := range h.Highlight {
|
||||
newKey := strings.TrimPrefix(key, "values.")
|
||||
matching_fields[newKey] = value
|
||||
}
|
||||
|
||||
aux["created"] = uc
|
||||
aux["customValues"] = ssVal
|
||||
aux["values"] = slice
|
||||
aux["matching_fields"] = matching_fields
|
||||
aux["@id"] = aux["_id"]
|
||||
delete(aux, "_id")
|
||||
delete(aux, "valueLabels")
|
||||
|
||||
case "compose:namespace":
|
||||
aux["@id"] = aux["_id"]
|
||||
delete(aux, "_id")
|
||||
delete(aux, "Namespace")
|
||||
|
||||
case "compose:module":
|
||||
aux["@id"] = aux["_id"]
|
||||
delete(aux, "_id")
|
||||
delete(aux, "Namespace")
|
||||
delete(aux, "Module")
|
||||
|
||||
default:
|
||||
continue hits
|
||||
}
|
||||
|
||||
out.Hits = append(out.Hits, cdHit{
|
||||
Type: resType,
|
||||
Value: aux,
|
||||
})
|
||||
}
|
||||
out.TotalHits = len(out.Hits)
|
||||
// }
|
||||
return
|
||||
}
|
||||
|
||||
// @todo use RBAC resource stringify
|
||||
// getResourceName return name of resource based on resource type
|
||||
func getResourceName(resType string) string {
|
||||
switch resType {
|
||||
case "system:user":
|
||||
return "User"
|
||||
case "compose:record":
|
||||
return "Record"
|
||||
case "compose:namespace":
|
||||
return "Namespace"
|
||||
case "compose:module":
|
||||
return "Module"
|
||||
default:
|
||||
return "Resource"
|
||||
}
|
||||
}
|
||||
|
||||
func getCreatedBy(user *createdBy) string {
|
||||
if user == nil {
|
||||
return ""
|
||||
}
|
||||
if len(user.Name) > 0 {
|
||||
return user.Name
|
||||
} else if len(user.Username) > 0 {
|
||||
return user.Username
|
||||
} else if len(user.Email) > 0 {
|
||||
return user.Email
|
||||
} else if user.UserID > 0 {
|
||||
return fmt.Sprintf("%d", user.UserID)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func sanitize(v interface{}) interface{} {
|
||||
if v == nil {
|
||||
return v
|
||||
}
|
||||
|
||||
switch v.(type) {
|
||||
case string:
|
||||
v = richText(v.(string))
|
||||
case []interface{}:
|
||||
vv := reflect.ValueOf(v)
|
||||
for i, val := range v.([]interface{}) {
|
||||
// Wee need to do a failsafe here because we're working with reflect values
|
||||
// In case sanitize returns nil, we'll get a runtime panic
|
||||
sanitized := sanitize(val)
|
||||
var v reflect.Value
|
||||
|
||||
// Since we're working with types, we need the zero value of interface{} over nil
|
||||
if sanitized == nil {
|
||||
v = reflect.Zero(reflect.TypeOf((*any)(nil)).Elem())
|
||||
} else {
|
||||
v = reflect.ValueOf(sanitized)
|
||||
}
|
||||
vv.Index(i).Set(v)
|
||||
}
|
||||
default:
|
||||
return v
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// RichText assures safe HTML content
|
||||
func richText(in string) string {
|
||||
// use standard html escaping policy
|
||||
p := bluemonday.StrictPolicy()
|
||||
|
||||
sanitized := p.Sanitize(in)
|
||||
|
||||
// handle escaped strings and unescape them
|
||||
// all the dangerous chars should have been stripped
|
||||
// by now
|
||||
return html.UnescapeString(sanitized)
|
||||
}
|
||||
645
extra/server-discovery/searcher/rest/es.go
Normal file
645
extra/server-discovery/searcher/rest/es.go
Normal file
@@ -0,0 +1,645 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/elastic/go-elasticsearch/v7"
|
||||
"github.com/elastic/go-elasticsearch/v7/esapi"
|
||||
"github.com/go-chi/jwtauth"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type (
|
||||
// EsSearchAggrTerms is aggregations parameter for es search api.
|
||||
EsSearchAggrTerms map[string]esSearchAggr
|
||||
EsSearchNestedAggrTerms map[string]esSearchNestedAggr
|
||||
|
||||
EsSearchHighlight struct {
|
||||
RequireFieldMatch bool `json:"require_field_match"`
|
||||
Fields map[string]highlightSetting `json:"fields"`
|
||||
}
|
||||
|
||||
esSearchParamsIndex struct {
|
||||
Prefix struct {
|
||||
Index struct {
|
||||
Value string `json:"value"`
|
||||
} `json:"_index"`
|
||||
} `json:"prefix"`
|
||||
}
|
||||
|
||||
esSimpleQueryString struct {
|
||||
Wrap struct {
|
||||
Query string `json:"query"`
|
||||
} `json:"simple_query_string"`
|
||||
}
|
||||
|
||||
esDisMax struct {
|
||||
TieBreaker float64 `json:"tie_breaker,omitempty"`
|
||||
Boost float64 `json:"boost,omitempty"`
|
||||
Queries []interface{} `json:"queries,omitempty"`
|
||||
}
|
||||
|
||||
esDisMaxWrap struct {
|
||||
Wrap esDisMax `json:"dis_max,omitempty"`
|
||||
}
|
||||
|
||||
esNestedDisMax struct {
|
||||
Wrap struct {
|
||||
IgnoreUnmapped bool `json:"ignore_unmapped"`
|
||||
Path string `json:"path,omitempty"`
|
||||
Query esDisMaxWrap `json:"query,omitempty"`
|
||||
} `json:"nested,omitempty"`
|
||||
}
|
||||
|
||||
esMultiMatch struct {
|
||||
Wrap struct {
|
||||
Query string `json:"query"`
|
||||
Type string `json:"type"`
|
||||
// Operator string `json:"operator"`
|
||||
Fields []string `json:"fields"`
|
||||
MinimumShouldMatch string `json:"minimum_should_match"`
|
||||
} `json:"multi_match"`
|
||||
}
|
||||
|
||||
VectorValue struct {
|
||||
Vector []float32 `json:"vector"`
|
||||
K int `json:"k"`
|
||||
Filter []interface{} `json:"filter,omitempty"`
|
||||
}
|
||||
|
||||
esSearchParams struct {
|
||||
Query struct {
|
||||
Bool *struct {
|
||||
// query context
|
||||
Must []interface{} `json:"must,omitempty"`
|
||||
Should []interface{} `json:"should,omitempty"`
|
||||
|
||||
// filter context
|
||||
Filter []interface{} `json:"filter,omitempty"`
|
||||
MustNot []interface{} `json:"must_not,omitempty"`
|
||||
} `json:"bool,omitempty"`
|
||||
} `json:"query"`
|
||||
|
||||
Aggregations EsSearchNestedAggrTerms `json:"aggs,omitempty"`
|
||||
Source SourceFilter `json:"_source,omitempty"`
|
||||
MinScore float32 `json:"min_score,omitempty"`
|
||||
Highlight EsSearchHighlight `json:"highlight,omitempty"`
|
||||
}
|
||||
|
||||
esSearchAggrTerm struct {
|
||||
Field string `json:"field,omitempty"`
|
||||
Size int `json:"size,omitempty"`
|
||||
}
|
||||
|
||||
esSearchAggrComposite struct {
|
||||
Sources interface{} `json:"sources"` // it can be esSearchAggrTerm,.. (Histogram, Date histogram, GeoTile grid)
|
||||
Size int `json:"size,omitempty"`
|
||||
}
|
||||
|
||||
SourceFilter struct {
|
||||
Excludes []string `json:"excludes"` //columns to exclude on the query response
|
||||
}
|
||||
|
||||
esSearchNestedAggs struct {
|
||||
Path string `json:"path,omitempty"`
|
||||
}
|
||||
|
||||
esSearchAggr struct {
|
||||
Nested esSearchNestedAggs `json:"nested,omitempty"`
|
||||
Terms esSearchAggrTerm `json:"terms,omitempty"`
|
||||
Aggregations EsSearchAggrTerms `json:"aggs,omitempty"`
|
||||
// Composite *esSearchAggrComposite `json:"composite"`
|
||||
}
|
||||
|
||||
esSearchNestedAggrTerm struct {
|
||||
Terms esSearchAggrTerm `json:"terms,omitempty"`
|
||||
}
|
||||
esSearchNestedAggr struct {
|
||||
Nested esSearchNestedAggs `json:"nested,omitempty"`
|
||||
Aggregations map[string]esSearchNestedAggrTerm `json:"aggs,omitempty"`
|
||||
// Composite *esSearchAggrComposite `json:"composite"`
|
||||
}
|
||||
|
||||
esSearchResponse struct {
|
||||
Took int `json:"took"`
|
||||
TimedOut bool `json:"timed_out"`
|
||||
Hits esSearchHits `json:"hits"`
|
||||
Aggregations esSearchAggregations `json:"aggregations"`
|
||||
}
|
||||
|
||||
esSearchTotal struct {
|
||||
Value int `json:"value"`
|
||||
Relation string `json:"relation"`
|
||||
}
|
||||
|
||||
esSearchHits struct {
|
||||
Total esSearchTotal `json:"total"`
|
||||
Hits []*esSearchHit `json:"hits"`
|
||||
}
|
||||
|
||||
esSearchHit struct {
|
||||
Index string `json:"_index"`
|
||||
ID string `json:"_id"`
|
||||
Source json.RawMessage `json:"_source"`
|
||||
Highlight map[string]any `json:"highlight,omitempty"`
|
||||
}
|
||||
|
||||
esSearchAggregations struct {
|
||||
Resource struct {
|
||||
DocCountErrorUpperBound int `json:"-"`
|
||||
SumOtherDocCount int `json:"-"`
|
||||
Buckets []struct {
|
||||
Key string `json:"key"`
|
||||
DocCount int `json:"doc_count"`
|
||||
ResourceName struct {
|
||||
DocCountErrorUpperBound int `json:"-"`
|
||||
SumOtherDocCount int `json:"-"`
|
||||
Buckets []struct {
|
||||
Key string `json:"key"`
|
||||
DocCount int `json:"doc_count"`
|
||||
} `json:"buckets"`
|
||||
} `json:"resourceName"`
|
||||
Namespaces struct {
|
||||
DocCountErrorUpperBound int `json:"-"`
|
||||
SumOtherDocCount int `json:"-"`
|
||||
Buckets []struct {
|
||||
Key string `json:"key"`
|
||||
DocCount int `json:"doc_count"`
|
||||
} `json:"buckets"`
|
||||
} `json:"namespaces"`
|
||||
Modules struct {
|
||||
DocCountErrorUpperBound int `json:"-"`
|
||||
SumOtherDocCount int `json:"-"`
|
||||
Buckets []struct {
|
||||
Key string `json:"key"`
|
||||
DocCount int `json:"doc_count"`
|
||||
} `json:"buckets"`
|
||||
} `json:"modules"`
|
||||
} `json:"buckets"`
|
||||
} `json:"resource"`
|
||||
Module struct {
|
||||
DocCount int `json:"doc_count"`
|
||||
Module struct {
|
||||
DocCountErrorUpperBound int `json:"doc_count_error_upper_bound"`
|
||||
SumOtherDocCount int `json:"sum_other_doc_count"`
|
||||
Buckets []struct {
|
||||
Key string `json:"key"`
|
||||
DocCount int `json:"doc_count"`
|
||||
} `json:"buckets"`
|
||||
} `json:"module"`
|
||||
} `json:"module"`
|
||||
|
||||
Namespace struct {
|
||||
DocCount int `json:"doc_count"`
|
||||
Namespace struct {
|
||||
DocCountErrorUpperBound int `json:"doc_count_error_upper_bound"`
|
||||
SumOtherDocCount int `json:"sum_other_doc_count"`
|
||||
Buckets []struct {
|
||||
Key string `json:"key"`
|
||||
DocCount int `json:"doc_count"`
|
||||
} `json:"buckets"`
|
||||
} `json:"namespace"`
|
||||
} `json:"namespace"`
|
||||
}
|
||||
|
||||
searchParams struct {
|
||||
title string
|
||||
query string
|
||||
moduleAggs []string
|
||||
namespaceAggs []string
|
||||
dumpRaw bool
|
||||
from int
|
||||
size int
|
||||
|
||||
resourceType []string
|
||||
|
||||
aggOnly bool
|
||||
mAggOnly bool
|
||||
|
||||
allowedRoles map[interface{}]bool
|
||||
searchMode string
|
||||
}
|
||||
|
||||
highlightSetting struct {
|
||||
PreTags []string `json:"pre_tags"`
|
||||
PostTags []string `json:"post_tags"`
|
||||
NumberOfFragments int `json:"number_of_fragments"`
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
rtIndexMapping = map[string]string{
|
||||
"compose:module": "compose-modules",
|
||||
"compose:record": "compose-records-*",
|
||||
"compose:namespace": "compose-namespaces",
|
||||
"system:user": "system-users",
|
||||
}
|
||||
)
|
||||
|
||||
func esSearch(ctx context.Context, log *zap.Logger, esc *elasticsearch.Client, p searchParams) (sr *esSearchResponse, page pagination, err error) {
|
||||
var (
|
||||
buf bytes.Buffer
|
||||
roles []string
|
||||
userID uint64
|
||||
_, claims, _ = jwtauth.FromContext(ctx)
|
||||
|
||||
allowedRoleExist = false
|
||||
)
|
||||
|
||||
if _, has := claims["roles"]; has {
|
||||
if rr, is := claims["roles"].([]interface{}); is {
|
||||
|
||||
for _, role := range rr {
|
||||
if _, ok := p.allowedRoles[role]; ok {
|
||||
allowedRoleExist = true
|
||||
}
|
||||
|
||||
roles = append(roles, fmt.Sprintf("%s", role))
|
||||
}
|
||||
}
|
||||
}
|
||||
if _, has := claims["sub"]; has {
|
||||
if sub, is := claims["sub"].(string); is {
|
||||
userID, _ = extractFromSubClaim(sub)
|
||||
}
|
||||
}
|
||||
|
||||
noQ := len(p.query) == 0
|
||||
noNSFilter := len(p.namespaceAggs) == 0
|
||||
// noMFilter := len(p.moduleAggs) == 0
|
||||
sqs := esSimpleQueryString{}
|
||||
sqs.Wrap.Query = strings.TrimSpace(strings.ToLower(p.query))
|
||||
|
||||
query := &esSearchParams{}
|
||||
index := esSearchParamsIndex{}
|
||||
|
||||
if query.Query.Bool == nil {
|
||||
query.Query.Bool = &struct {
|
||||
Must []interface{} `json:"must,omitempty"`
|
||||
Should []interface{} `json:"should,omitempty"`
|
||||
Filter []interface{} `json:"filter,omitempty"`
|
||||
MustNot []interface{} `json:"must_not,omitempty"`
|
||||
}{}
|
||||
}
|
||||
|
||||
// Decide what indexes we can use
|
||||
if userID == 0 {
|
||||
// Missing, invalid, expired access token (JWT)
|
||||
index.Prefix.Index.Value = "corteza-public-"
|
||||
} else {
|
||||
// Authenticated user
|
||||
index.Prefix.Index.Value = "corteza-private-"
|
||||
|
||||
if !allowedRoleExist {
|
||||
// Skip all documents that do not have baring roles in to allow list
|
||||
query.Query.Bool.Filter = append(query.Query.Bool.Filter, map[string]map[string]interface{}{
|
||||
"terms": {"security.allowedRoles": roles},
|
||||
})
|
||||
|
||||
// Skip all documents that have baring roles in to deny list
|
||||
query.Query.Bool.MustNot = append(query.Query.Bool.MustNot, map[string]map[string]interface{}{
|
||||
"terms": {"security.deniedRoles": roles},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Aggregations V1.0
|
||||
// if len(p.aggregations) > 0 {
|
||||
// query.Aggregations = make(map[string]esSearchAggr)
|
||||
//
|
||||
// for _, a := range p.aggregations {
|
||||
// query.Aggregations[a] = esSearchAggr{esSearchAggrTerm{Field: a + ".keyword"}}
|
||||
// }
|
||||
// }
|
||||
|
||||
// Search string filter
|
||||
if !noQ {
|
||||
applyTraditionalSearch(query, index, sqs.Wrap.Query)
|
||||
}
|
||||
|
||||
// add the highlight for the matched fields.
|
||||
query.Highlight = EsSearchHighlight{
|
||||
RequireFieldMatch: false,
|
||||
Fields: map[string]highlightSetting{
|
||||
"values.*": {
|
||||
PreTags: []string{"*"},
|
||||
PostTags: []string{"*"},
|
||||
NumberOfFragments: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var (
|
||||
mm = esMultiMatch{}
|
||||
mdd esNestedDisMax
|
||||
nsdd esNestedDisMax
|
||||
)
|
||||
for _, mAggs := range p.moduleAggs {
|
||||
mm.Wrap.Query = mAggs
|
||||
mm.Wrap.Type = "cross_fields"
|
||||
mm.Wrap.MinimumShouldMatch = "100%"
|
||||
mm.Wrap.Fields = []string{"module.name"}
|
||||
// query.Query.Bool.Must = append(query.Query.Bool.Must, mm)
|
||||
// query.Query.DisMax.Queries = append(query.Query.DisMax.Queries, mm)
|
||||
|
||||
// dd.Wrap.Queries = append(dd.Wrap.Queries, mm)
|
||||
mdd.Wrap.Path = "module"
|
||||
nsdd.Wrap.IgnoreUnmapped = true
|
||||
mdd.Wrap.Query.Wrap.Queries = append(mdd.Wrap.Query.Wrap.Queries, mm)
|
||||
}
|
||||
|
||||
if len(mdd.Wrap.Query.Wrap.Queries) > 0 {
|
||||
query.Query.Bool.Must = append(query.Query.Bool.Must, mdd)
|
||||
}
|
||||
|
||||
// no need now since we are adding below as filter
|
||||
// if p.aggOnly {
|
||||
for _, nAggs := range p.namespaceAggs {
|
||||
mm.Wrap.Query = nAggs
|
||||
mm.Wrap.Type = "cross_fields"
|
||||
mm.Wrap.MinimumShouldMatch = "100%"
|
||||
mm.Wrap.Fields = []string{"namespace.name"}
|
||||
// query.Query.Bool.Must = append(query.Query.Bool.Must, mm)
|
||||
// query.Query.DisMax.Queries = append(query.Query.DisMax.Queries, mm)
|
||||
|
||||
// dd.Wrap.Queries = append(dd.Wrap.Queries, mm)
|
||||
nsdd.Wrap.Path = "namespace"
|
||||
nsdd.Wrap.IgnoreUnmapped = true
|
||||
nsdd.Wrap.Query.Wrap.Queries = append(nsdd.Wrap.Query.Wrap.Queries, mm)
|
||||
}
|
||||
// }
|
||||
|
||||
if len(nsdd.Wrap.Query.Wrap.Queries) > 0 {
|
||||
query.Query.Bool.Must = append(query.Query.Bool.Must, nsdd)
|
||||
}
|
||||
|
||||
// if !p.aggOnly && !noNSFilter {
|
||||
// nsf := make(map[string]interface{})
|
||||
// nsf["terms"] = map[string][]string{
|
||||
// "namespace.name.keyword": p.namespaceAggs,
|
||||
// }
|
||||
// query.Query.Bool.Filter = append(query.Query.Bool.Filter, nsf)
|
||||
// }
|
||||
|
||||
// Aggregations V1.0 Improved
|
||||
// if len(p.aggregations) > 0 {
|
||||
// for _, a := range p.aggregations {
|
||||
// if len(a) > 0 {
|
||||
// sqs = esSimpleQueryString{}
|
||||
// sqs.Wrap.Query = a
|
||||
// query.Query.Bool.Must = append(query.Query.Bool.Must, sqs)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// if noQ == 0 && len(p.moduleAggs) == 0 && len(p.namespaceAggs) == 0 {
|
||||
// query.Query.DisMax.Queries = append(query.Query.DisMax.Queries, index)
|
||||
// }
|
||||
query.Aggregations = make(map[string]esSearchNestedAggr)
|
||||
query.Aggregations["namespace"] = esSearchNestedAggr{
|
||||
Nested: esSearchNestedAggs{Path: "namespace"},
|
||||
Aggregations: map[string]esSearchNestedAggrTerm{
|
||||
"namespace": {
|
||||
Terms: esSearchAggrTerm{
|
||||
// Field: "namespace.name.keyword",
|
||||
Field: "namespace.handle",
|
||||
Size: 999,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
query.Source = SourceFilter{
|
||||
Excludes: []string{"vectorsValue"},
|
||||
}
|
||||
|
||||
if !noQ || !noNSFilter {
|
||||
query.Aggregations["module"] = esSearchNestedAggr{
|
||||
Nested: esSearchNestedAggs{Path: "module"},
|
||||
Aggregations: map[string]esSearchNestedAggrTerm{
|
||||
"module": {
|
||||
Terms: esSearchAggrTerm{
|
||||
// Field: "module.name.keyword",
|
||||
Field: "module.handle",
|
||||
Size: 999,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// query.Aggregations["resource"] = esSearchAggr{
|
||||
// Terms: esSearchAggrTerm{
|
||||
// Field: "resourceType.keyword",
|
||||
// Size: 999,
|
||||
// },
|
||||
// Aggregations: EsSearchAggrTerms{
|
||||
// "resourceName": esSearchAggr{
|
||||
// Terms: esSearchAggrTerm{
|
||||
// Field: "name.keyword",
|
||||
// Size: 999,
|
||||
// },
|
||||
// },
|
||||
// "modules": esSearchAggr{
|
||||
// Terms: esSearchAggrTerm{
|
||||
// Field: "module.name.keyword",
|
||||
// Size: 999,
|
||||
// },
|
||||
// },
|
||||
// "namespaces": esSearchAggr{
|
||||
// Terms: esSearchAggrTerm{
|
||||
// Field: "namespace.name.keyword",
|
||||
// Size: 999,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
|
||||
// Aggregations V2.0
|
||||
// if len(p.aggregations) > 0 {
|
||||
// query.Aggregations = (Aggregations{}).encodeTerms(p.aggregations)
|
||||
// }
|
||||
|
||||
// spew.Dump("query: ", query)
|
||||
if err = json.NewEncoder(&buf).Encode(query); err != nil {
|
||||
err = fmt.Errorf("could not encode query: %q", err)
|
||||
return
|
||||
}
|
||||
spew.Dump("buf: ", buf)
|
||||
|
||||
log.Debug("searching ",
|
||||
zap.String("for", p.title),
|
||||
// zap.String("open search body", buf.String()),
|
||||
)
|
||||
|
||||
// Why set size to 999? default value for size is 10,
|
||||
// so we needed to set value till we add (@todo) pagination to search result
|
||||
if p.from > 0 && p.from >= p.size {
|
||||
p.size = p.from + 100
|
||||
}
|
||||
if p.size == 0 {
|
||||
p.size = 999
|
||||
}
|
||||
page.From = p.from
|
||||
page.Size = p.size
|
||||
|
||||
// In case of requesting specific resource types, use only the appropriate indexes
|
||||
indexes := []string{}
|
||||
if len(p.resourceType) > 0 {
|
||||
for _, rt := range p.resourceType {
|
||||
if _, ok := rtIndexMapping[rt]; !ok {
|
||||
err = fmt.Errorf("invalid resource type: %s", rt)
|
||||
return
|
||||
}
|
||||
|
||||
indexes = append(indexes, fmt.Sprintf("%s%s", index.Prefix.Index.Value, rtIndexMapping[rt]))
|
||||
}
|
||||
}
|
||||
|
||||
sReqArgs := []func(*esapi.SearchRequest){
|
||||
esc.Search.WithContext(ctx),
|
||||
esc.Search.WithBody(&buf),
|
||||
esc.Search.WithTrackTotalHits(true),
|
||||
// esc.Search.WithScroll(),
|
||||
esc.Search.WithSize(p.size),
|
||||
esc.Search.WithFrom(p.from),
|
||||
esc.Search.WithIndex(indexes...),
|
||||
// esc.Search.WithExplain(true), // debug
|
||||
}
|
||||
|
||||
if p.dumpRaw {
|
||||
sReqArgs = append(
|
||||
sReqArgs,
|
||||
esc.Search.WithSourceExcludes("security"),
|
||||
esc.Search.WithPretty(),
|
||||
)
|
||||
}
|
||||
|
||||
// Perform the search request.
|
||||
res, err := esc.Search(sReqArgs...)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = validElasticResponse(res, err); err != nil {
|
||||
err = fmt.Errorf("invalid search response: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
defer res.Body.Close()
|
||||
|
||||
if p.dumpRaw {
|
||||
// Copy body buf and then restore it
|
||||
bodyBytes, _ := ioutil.ReadAll(res.Body)
|
||||
res.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
|
||||
os.Stdout.Write(bodyBytes)
|
||||
}
|
||||
|
||||
sr = &esSearchResponse{}
|
||||
if err = json.NewDecoder(res.Body).Decode(sr); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Print the response status, number of results, and request duration.
|
||||
log.Debug("search completed",
|
||||
zap.String("for", p.title),
|
||||
zap.String("query", sqs.Wrap.Query),
|
||||
zap.String("indexPrefix", index.Prefix.Index.Value),
|
||||
zap.Int("from", p.from),
|
||||
zap.Int("size", p.size),
|
||||
zap.String("status", res.Status()),
|
||||
zap.Int("took", sr.Took),
|
||||
zap.Bool("timedOut", sr.TimedOut),
|
||||
zap.Int("hits", sr.Hits.Total.Value),
|
||||
zap.String("hitsRelation", sr.Hits.Total.Relation),
|
||||
zap.Int("namespaceAggs", len(sr.Aggregations.Namespace.Namespace.Buckets)),
|
||||
zap.Int("moduleAggs", len(sr.Aggregations.Module.Module.Buckets)),
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// @todo move this to es service
|
||||
func validElasticResponse(res *esapi.Response, err error) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get response from search backend: %w", err)
|
||||
}
|
||||
|
||||
if res.IsError() {
|
||||
defer res.Body.Close()
|
||||
var rsp struct {
|
||||
Error struct {
|
||||
Type string
|
||||
Reason string
|
||||
}
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(res.Body).Decode(&rsp); err != nil {
|
||||
return fmt.Errorf("could not parse response body: %w", err)
|
||||
} else {
|
||||
return fmt.Errorf("search backend responded with an error: %s (type: %s, status: %s)", rsp.Error.Reason, rsp.Error.Type, res.Status())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func extractFromSubClaim(sub string) (userID uint64, rr []uint64) {
|
||||
parts := strings.Split(sub, " ")
|
||||
rr = make([]uint64, len(parts)-1)
|
||||
for p := range parts {
|
||||
id, _ := strconv.ParseUint(parts[p], 10, 64)
|
||||
if p == 0 {
|
||||
userID = id
|
||||
} else {
|
||||
rr[p-1] = id
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func applyTraditionalSearch(query *esSearchParams, index interface{}, searchQuery string) {
|
||||
query.Query.Bool.Must = []interface{}{index}
|
||||
query.Query.Bool.Must = append(query.Query.Bool.Must, buildWildcardQuery(searchQuery))
|
||||
}
|
||||
|
||||
func buildWildcardQuery(searchQuery string) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"wildcard": map[string]interface{}{
|
||||
"catch_all": map[string]interface{}{
|
||||
"value": fmt.Sprintf("*%s*", searchQuery),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func buildKNNQuery(vector interface{}) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"knn": map[string]interface{}{
|
||||
"vectorsValue": map[string]interface{}{
|
||||
"vector": vector,
|
||||
"k": 10,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func buildMultiMatchQuery(searchQuery string) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"multi_match": map[string]interface{}{
|
||||
"query": searchQuery,
|
||||
"fields": []string{"catch_all"},
|
||||
"boost": 1.0,
|
||||
},
|
||||
}
|
||||
}
|
||||
49
extra/server-discovery/searcher/rest/handlers/search.go
Normal file
49
extra/server-discovery/searcher/rest/handlers/search.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/cortezaproject/corteza/extra/server-discovery/searcher/rest/request"
|
||||
"github.com/cortezaproject/corteza/server/pkg/api"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type (
|
||||
// SearchAPI Internal API interface
|
||||
SearchAPI interface {
|
||||
SearchResources(context.Context, *request.SearchResources) (interface{}, error)
|
||||
}
|
||||
|
||||
// Search HTTP API interface
|
||||
Search struct {
|
||||
SearchResources func(http.ResponseWriter, *http.Request)
|
||||
}
|
||||
)
|
||||
|
||||
func NewSearch(h SearchAPI) *Search {
|
||||
return &Search{
|
||||
SearchResources: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewSearchListResources()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.SearchResources(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (h Search) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) {
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(middlewares...)
|
||||
r.Get("/", h.SearchResources)
|
||||
})
|
||||
}
|
||||
139
extra/server-discovery/searcher/rest/request/request.go
Normal file
139
extra/server-discovery/searcher/rest/request/request.go
Normal file
@@ -0,0 +1,139 @@
|
||||
package request
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/pkg/payload"
|
||||
)
|
||||
|
||||
type (
|
||||
SearchResources struct {
|
||||
Q string
|
||||
From int
|
||||
Size int
|
||||
Rt []string
|
||||
NamespaceAggs []string
|
||||
ModuleAggs []string
|
||||
DumpRaw string
|
||||
}
|
||||
)
|
||||
|
||||
// NewSearchListResources request
|
||||
func NewSearchListResources() *SearchResources {
|
||||
return &SearchResources{}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r SearchResources) Auditable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"q": r.Q,
|
||||
"from": r.From,
|
||||
"size": r.Size,
|
||||
"namespaceAggs": r.NamespaceAggs,
|
||||
"resourceTypes": r.Rt,
|
||||
"moduleAggs": r.ModuleAggs,
|
||||
"dump": r.DumpRaw,
|
||||
}
|
||||
}
|
||||
|
||||
func (r SearchResources) GetQuery() string {
|
||||
return r.Q
|
||||
}
|
||||
|
||||
func (r SearchResources) GetSize() int {
|
||||
return r.Size
|
||||
}
|
||||
|
||||
func (r SearchResources) GetFrom() int {
|
||||
return r.From
|
||||
}
|
||||
|
||||
func (r SearchResources) GetResourceTypes() []string {
|
||||
return r.Rt
|
||||
}
|
||||
|
||||
func (r SearchResources) GetNamespaceAggs() []string {
|
||||
return r.NamespaceAggs
|
||||
}
|
||||
|
||||
func (r SearchResources) GetModuleAggs() []string {
|
||||
return r.ModuleAggs
|
||||
}
|
||||
|
||||
func (r SearchResources) GetDumpRaw() string {
|
||||
return r.DumpRaw
|
||||
}
|
||||
|
||||
// Fill processes request and fills internal variables
|
||||
func (r *SearchResources) Fill(req *http.Request) (err error) {
|
||||
{
|
||||
// GET params
|
||||
tmp := req.URL.Query()
|
||||
|
||||
if val, ok := tmp["q"]; ok && len(val) > 0 {
|
||||
r.Q, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := tmp["size"]; ok && len(val) > 0 {
|
||||
r.Size, err = payload.ParseInt(val[0]), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := tmp["from"]; ok && len(val) > 0 {
|
||||
r.From, err = payload.ParseInt(val[0]), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := tmp["resourceTypes[]"]; ok {
|
||||
r.Rt, err = val, nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if val, ok = tmp["resourceTypes"]; ok {
|
||||
r.Rt, err = val, nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := tmp["namespaceAggs[]"]; ok {
|
||||
r.NamespaceAggs, err = val, nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if val, ok = tmp["namespaceAggs"]; ok {
|
||||
r.NamespaceAggs, err = val, nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := tmp["moduleAggs[]"]; ok {
|
||||
r.ModuleAggs, err = val, nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if val, ok = tmp["moduleAggs"]; ok {
|
||||
r.ModuleAggs, err = val, nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := tmp["dump"]; ok && len(val) > 0 {
|
||||
r.DumpRaw, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
21
extra/server-discovery/searcher/rest/router.go
Normal file
21
extra/server-discovery/searcher/rest/router.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"github.com/cortezaproject/corteza/extra/server-discovery/pkg/auth"
|
||||
"github.com/cortezaproject/corteza/extra/server-discovery/searcher/rest/handlers"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func MountRoutes(log *zap.Logger) func(r chi.Router) {
|
||||
return func(r chi.Router) {
|
||||
r.Group(func(r chi.Router) {
|
||||
search := Search()
|
||||
|
||||
r.Use(auth.HttpTokenValidator("discovery"))
|
||||
|
||||
// traditional search endpoint for Traditional and vectorsearch
|
||||
handlers.NewSearch(search).MountRoutes(r)
|
||||
})
|
||||
}
|
||||
}
|
||||
336
extra/server-discovery/searcher/rest/search.go
Normal file
336
extra/server-discovery/searcher/rest/search.go
Normal file
@@ -0,0 +1,336 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/cortezaproject/corteza/extra/server-discovery/searcher"
|
||||
"github.com/cortezaproject/corteza/extra/server-discovery/searcher/rest/request"
|
||||
types2 "github.com/cortezaproject/corteza/server/compose/types"
|
||||
)
|
||||
|
||||
type (
|
||||
search struct{}
|
||||
|
||||
cResponse struct {
|
||||
Response struct {
|
||||
Set []struct {
|
||||
NamespaceID uint64 `json:",string"`
|
||||
Slug string `json:"slug"`
|
||||
|
||||
Name string `json:"name"`
|
||||
ModuleID uint64 `json:",string"`
|
||||
Handle string `json:"handle"`
|
||||
Config types2.ModuleConfig `json:"config"`
|
||||
} `json:"set,omitempty"`
|
||||
} `json:"response,omitempty"`
|
||||
}
|
||||
|
||||
moduleMeta struct {
|
||||
Discovery ModuleMeta `json:"discovery"`
|
||||
}
|
||||
|
||||
ModuleMeta struct {
|
||||
Public struct {
|
||||
Result []Result `json:"result"`
|
||||
} `json:"public"`
|
||||
Private struct {
|
||||
Result []Result `json:"result"`
|
||||
} `json:"private"`
|
||||
Protected struct {
|
||||
Result []Result `json:"result"`
|
||||
} `json:"protected"`
|
||||
}
|
||||
|
||||
Result struct {
|
||||
Lang string `json:"lang"`
|
||||
Fields []string `json:"fields"`
|
||||
// @todo? TBD? excludeModuleFields, includeModuleFields <- if passed filter module field accordingly.
|
||||
}
|
||||
|
||||
nsMeta struct {
|
||||
Name string
|
||||
Handle string
|
||||
}
|
||||
mMeta struct {
|
||||
Name string
|
||||
Handle string
|
||||
}
|
||||
)
|
||||
|
||||
func Search() *search {
|
||||
return &search{}
|
||||
}
|
||||
|
||||
func (s search) SearchResources(ctx context.Context, r *request.SearchResources) (out interface{}, err error) {
|
||||
var (
|
||||
log = searcher.DefaultLogger
|
||||
allowedRoles = searcher.DefaultConfig.Searcher.AllowedRole
|
||||
searchString = r.GetQuery()
|
||||
size = r.GetSize()
|
||||
from = r.GetFrom()
|
||||
namespaceAggs = r.GetNamespaceAggs()
|
||||
moduleAggs = r.GetModuleAggs()
|
||||
resourceTypes = r.GetResourceTypes()
|
||||
validDumpRaw = r.GetDumpRaw() != ""
|
||||
|
||||
page pagination
|
||||
results *esSearchResponse
|
||||
aggregation *esSearchResponse
|
||||
nsAggregation *esSearchResponse
|
||||
mAggregation *esSearchResponse
|
||||
|
||||
nsReq *http.Request
|
||||
nsRes *http.Response
|
||||
mReq *http.Request
|
||||
mRes *http.Response
|
||||
nsResponse cResponse
|
||||
mResponse cResponse
|
||||
moduleMap = make(map[string][]string)
|
||||
|
||||
nsHandleMap = make(map[string]nsMeta)
|
||||
mHandleMap = make(map[string]mMeta)
|
||||
)
|
||||
|
||||
esc := searcher.DefaultEsClient
|
||||
|
||||
results, page, err = esSearch(ctx, log, esc, searchParams{
|
||||
title: "results",
|
||||
query: searchString,
|
||||
resourceType: resourceTypes,
|
||||
from: from,
|
||||
size: size,
|
||||
moduleAggs: moduleAggs,
|
||||
namespaceAggs: namespaceAggs,
|
||||
dumpRaw: validDumpRaw,
|
||||
allowedRoles: allowedRoles,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not execute search: %w", err)
|
||||
}
|
||||
|
||||
if len(searchString) == 0 {
|
||||
aggregation, _, err = esSearch(ctx, log, esc, searchParams{
|
||||
title: "aggregation",
|
||||
// size: size,
|
||||
dumpRaw: validDumpRaw,
|
||||
namespaceAggs: namespaceAggs,
|
||||
aggOnly: true,
|
||||
allowedRoles: allowedRoles,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not execute aggregation search: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// append all namespace agg with counts no matter what
|
||||
nsAggregation, _, err = esSearch(ctx, log, esc, searchParams{
|
||||
title: "nsAggregation",
|
||||
// size: size,
|
||||
dumpRaw: validDumpRaw,
|
||||
aggOnly: true,
|
||||
allowedRoles: allowedRoles,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not execute namespace aggregation search: %w", err)
|
||||
}
|
||||
|
||||
if len(searchString) == 0 {
|
||||
if aggregation != nil && nsAggregation != nil {
|
||||
aggregation.Aggregations.Namespace = nsAggregation.Aggregations.Namespace
|
||||
}
|
||||
} else {
|
||||
if results != nil && nsAggregation != nil {
|
||||
nsMap := make(map[string]struct {
|
||||
Key string `json:"key"`
|
||||
DocCount int `json:"doc_count"`
|
||||
})
|
||||
for _, bucket := range results.Aggregations.Namespace.Namespace.Buckets {
|
||||
nsMap[bucket.Key] = bucket
|
||||
}
|
||||
|
||||
var buckets []struct {
|
||||
Key string `json:"key"`
|
||||
DocCount int `json:"doc_count"`
|
||||
}
|
||||
for _, bucket := range nsAggregation.Aggregations.Namespace.Namespace.Buckets {
|
||||
val, ok := nsMap[bucket.Key]
|
||||
if ok {
|
||||
val.DocCount = nsMap[bucket.Key].DocCount
|
||||
} else {
|
||||
val.Key = bucket.Key
|
||||
val.DocCount = 0
|
||||
}
|
||||
buckets = append(buckets, val)
|
||||
}
|
||||
|
||||
results.Aggregations.Namespace.Namespace.Buckets = buckets
|
||||
}
|
||||
}
|
||||
// append namespace agg response which are not in es response
|
||||
if results != nil && len(namespaceAggs) > 0 {
|
||||
nsMap := make(map[string]struct {
|
||||
Key string `json:"key"`
|
||||
DocCount int `json:"doc_count"`
|
||||
})
|
||||
var bb []struct {
|
||||
Key string `json:"key"`
|
||||
DocCount int `json:"doc_count"`
|
||||
}
|
||||
for _, b := range results.Aggregations.Namespace.Namespace.Buckets {
|
||||
nsMap = map[string]struct {
|
||||
Key string `json:"key"`
|
||||
DocCount int `json:"doc_count"`
|
||||
}{
|
||||
b.Key: b,
|
||||
}
|
||||
bb = append(bb, b)
|
||||
}
|
||||
|
||||
for _, agg := range namespaceAggs {
|
||||
if _, ok := nsMap[agg]; !ok {
|
||||
nsMap = map[string]struct {
|
||||
Key string `json:"key"`
|
||||
DocCount int `json:"doc_count"`
|
||||
}{
|
||||
agg: {Key: agg, DocCount: 0},
|
||||
}
|
||||
bb = append(bb, struct {
|
||||
Key string `json:"key"`
|
||||
DocCount int `json:"doc_count"`
|
||||
}{Key: agg, DocCount: 0})
|
||||
}
|
||||
}
|
||||
|
||||
if len(bb) > 0 {
|
||||
results.Aggregations.Namespace.Namespace.Buckets = bb
|
||||
}
|
||||
}
|
||||
|
||||
mAggregation, _, err = esSearch(ctx, log, esc, searchParams{
|
||||
title: "mAggregation",
|
||||
// size: size,
|
||||
dumpRaw: validDumpRaw,
|
||||
query: searchString,
|
||||
namespaceAggs: namespaceAggs,
|
||||
aggOnly: true,
|
||||
mAggOnly: true,
|
||||
allowedRoles: allowedRoles,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not execute module aggregation search: %w", err)
|
||||
}
|
||||
|
||||
if len(searchString) > 0 {
|
||||
if results != nil && mAggregation != nil {
|
||||
results.Aggregations.Module = mAggregation.Aggregations.Module
|
||||
}
|
||||
}
|
||||
|
||||
// append module agg response which are not in es response
|
||||
if results != nil && len(moduleAggs) > 0 {
|
||||
mMap := make(map[string]struct {
|
||||
Key string `json:"key"`
|
||||
DocCount int `json:"doc_count"`
|
||||
})
|
||||
var bb []struct {
|
||||
Key string `json:"key"`
|
||||
DocCount int `json:"doc_count"`
|
||||
}
|
||||
for _, b := range results.Aggregations.Module.Module.Buckets {
|
||||
mMap = map[string]struct {
|
||||
Key string `json:"key"`
|
||||
DocCount int `json:"doc_count"`
|
||||
}{
|
||||
b.Key: b,
|
||||
}
|
||||
bb = append(bb, b)
|
||||
}
|
||||
|
||||
for _, agg := range moduleAggs {
|
||||
if _, ok := mMap[agg]; !ok {
|
||||
mMap = map[string]struct {
|
||||
Key string `json:"key"`
|
||||
DocCount int `json:"doc_count"`
|
||||
}{
|
||||
agg: {Key: agg, DocCount: 0},
|
||||
}
|
||||
bb = append(bb, struct {
|
||||
Key string `json:"key"`
|
||||
DocCount int `json:"doc_count"`
|
||||
}{Key: agg, DocCount: 0})
|
||||
}
|
||||
}
|
||||
|
||||
if len(bb) > 0 {
|
||||
results.Aggregations.Module.Module.Buckets = bb
|
||||
}
|
||||
}
|
||||
|
||||
noHits := len(searchString) == 0 && len(moduleAggs) == 0 && len(namespaceAggs) == 0
|
||||
// if !noHits {
|
||||
// @todo only fetch module from result but that requires another loop to fetch module Id from es response
|
||||
// TEMP fix, I have solution use elastic for the same but different index
|
||||
nsReq, err = searcher.DefaultApiClient.Namespaces()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to prepare namespace request: %w", err)
|
||||
} else {
|
||||
if nsRes, err = searcher.DefaultApiClient.HttpClient().Do(nsReq.WithContext(ctx)); err != nil {
|
||||
return nil, fmt.Errorf("failed to send namespace request: %w", err)
|
||||
}
|
||||
if nsRes.StatusCode != http.StatusOK {
|
||||
fmt.Println("err: ", err)
|
||||
return nil, fmt.Errorf("request resulted in an unexpected status: %s: %w", err)
|
||||
}
|
||||
if err = json.NewDecoder(nsRes.Body).Decode(&nsResponse); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode namespace response: %w", err)
|
||||
}
|
||||
|
||||
if err = nsRes.Body.Close(); err != nil {
|
||||
return nil, fmt.Errorf("failed to close namespace response body: %w", err)
|
||||
}
|
||||
|
||||
for _, s := range nsResponse.Response.Set {
|
||||
// Get the module handles for aggs response
|
||||
nsHandleMap[s.Slug] = nsMeta{
|
||||
Name: s.Name,
|
||||
Handle: s.Slug,
|
||||
}
|
||||
if mReq, err = searcher.DefaultApiClient.Modules(s.NamespaceID); err != nil {
|
||||
return nil, fmt.Errorf("failed to prepare module meta request: %w", err)
|
||||
}
|
||||
if mRes, err = searcher.DefaultApiClient.HttpClient().Do(mReq.WithContext(ctx)); err != nil {
|
||||
return nil, fmt.Errorf("failed to send module request: %w", err)
|
||||
}
|
||||
if mRes.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("request resulted in an unexpected status: %s: %w", err)
|
||||
}
|
||||
if err = json.NewDecoder(mRes.Body).Decode(&mResponse); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||
}
|
||||
if err = mRes.Body.Close(); err != nil {
|
||||
return nil, fmt.Errorf("failed to close response body: %w", err)
|
||||
}
|
||||
|
||||
for _, m := range mResponse.Response.Set {
|
||||
// Get the module handles for aggs response
|
||||
mHandleMap[m.Handle] = mMeta{
|
||||
Name: m.Name,
|
||||
Handle: m.Slug,
|
||||
}
|
||||
|
||||
var (
|
||||
key = fmt.Sprintf("%d-%d", s.NamespaceID, m.ModuleID)
|
||||
)
|
||||
if len(m.Config.Discovery.Private.Result) > 0 && len(m.Config.Discovery.Private.Result[0].Fields) > 0 {
|
||||
moduleMap[key] = m.Config.Discovery.Private.Result[0].Fields
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// }
|
||||
|
||||
return conv(results, aggregation, noHits, moduleMap, nsHandleMap, mHandleMap, page)
|
||||
}
|
||||
81
extra/server-discovery/searcher/service.go
Normal file
81
extra/server-discovery/searcher/service.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package searcher
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/cortezaproject/corteza/extra/server-discovery/pkg/api"
|
||||
"github.com/cortezaproject/corteza/extra/server-discovery/pkg/options"
|
||||
"github.com/elastic/go-elasticsearch/v7"
|
||||
"github.com/elastic/go-elasticsearch/v7/esapi"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type (
|
||||
Config struct {
|
||||
Corteza options.CortezaOpt
|
||||
ES options.EsOpt
|
||||
HttpServer options.HttpServerOpt
|
||||
Searcher options.SearcherOpt
|
||||
}
|
||||
|
||||
apiClientService interface {
|
||||
HttpClient() *http.Client
|
||||
Namespaces() (*http.Request, error)
|
||||
Modules(uint64) (*http.Request, error)
|
||||
Request(string) (*http.Request, error)
|
||||
Authenticate() error
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
DefaultLogger *zap.Logger
|
||||
DefaultConfig Config
|
||||
|
||||
DefaultEsClient *elasticsearch.Client
|
||||
DefaultApiClient apiClientService
|
||||
)
|
||||
|
||||
func Initialize(_ context.Context, log *zap.Logger, c Config, esClient *elasticsearch.Client) (err error) {
|
||||
DefaultLogger = log.Named("service")
|
||||
DefaultConfig = c
|
||||
|
||||
DefaultEsClient = esClient
|
||||
|
||||
DefaultApiClient, err = api.Client(c.Corteza, c.Searcher.ClientKey, c.Searcher.ClientSecret)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// @todo move this to es service
|
||||
func validElasticResponse(res *esapi.Response, err error) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get response from search backend: %w", err)
|
||||
}
|
||||
|
||||
if res.IsError() {
|
||||
defer func(Body io.ReadCloser) {
|
||||
_ = Body.Close()
|
||||
}(res.Body)
|
||||
var rsp struct {
|
||||
Error struct {
|
||||
Type string
|
||||
Reason string
|
||||
}
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(res.Body).Decode(&rsp); err != nil {
|
||||
return fmt.Errorf("could not parse response body: %w", err)
|
||||
} else {
|
||||
return fmt.Errorf("search backend responded with an error: %s (type: %s, status: %s)", rsp.Error.Reason, rsp.Error.Type, res.Status())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
30
extra/server-discovery/vendor/github.com/PaesslerAG/gval/.gitignore
generated
vendored
Normal file
30
extra/server-discovery/vendor/github.com/PaesslerAG/gval/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
coverage.out
|
||||
|
||||
manual_test.go
|
||||
*.out
|
||||
*.err
|
||||
|
||||
.vscode
|
||||
12
extra/server-discovery/vendor/github.com/PaesslerAG/gval/.travis.yml
generated
vendored
Normal file
12
extra/server-discovery/vendor/github.com/PaesslerAG/gval/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
language: go
|
||||
|
||||
before_install:
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
- go get github.com/mattn/goveralls
|
||||
|
||||
script:
|
||||
- go test -bench=. -benchmem -timeout 9m -coverprofile coverage.out
|
||||
- $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci -repotoken=$COVERALLS_TOKEN
|
||||
- go test -bench=Random -benchtime 3m -timeout 9m -benchmem -coverprofile coverage.out
|
||||
|
||||
go: "1.15"
|
||||
12
extra/server-discovery/vendor/github.com/PaesslerAG/gval/LICENSE
generated
vendored
Normal file
12
extra/server-discovery/vendor/github.com/PaesslerAG/gval/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
Copyright (c) 2017, Paessler AG <support@paessler.com>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
163
extra/server-discovery/vendor/github.com/PaesslerAG/gval/README.md
generated
vendored
Normal file
163
extra/server-discovery/vendor/github.com/PaesslerAG/gval/README.md
generated
vendored
Normal file
@@ -0,0 +1,163 @@
|
||||
# Gval
|
||||
|
||||
[](https://pkg.go.dev/github.com/PaesslerAG/gval)
|
||||
[](https://travis-ci.org/PaesslerAG/gval)
|
||||
[](https://coveralls.io/github/PaesslerAG/gval?branch=master)
|
||||
[](https://goreportcard.com/report/github.com/PaesslerAG/gval)
|
||||
|
||||
Gval (Go eVALuate) provides support for evaluating arbitrary expressions, in particular Go-like expressions.
|
||||
|
||||

|
||||
|
||||
## Evaluate
|
||||
|
||||
Gval can evaluate expressions with parameters, arimethetic, logical, and string operations:
|
||||
|
||||
- basic expression: [10 > 0](https://pkg.go.dev/github.com/PaesslerAG/gval/#example-Evaluate-Basic)
|
||||
- parameterized expression: [foo > 0](https://pkg.go.dev/github.com/PaesslerAG/gval/#example-Evaluate-Parameter)
|
||||
- nested parameterized expression: [foo.bar > 0](https://pkg.go.dev/github.com/PaesslerAG/gval/#example-Evaluate-NestedParameter)
|
||||
- arithmetic expression: [(requests_made * requests_succeeded / 100) >= 90](https://pkg.go.dev/github.com/PaesslerAG/gval/#example-Evaluate-Arithmetic)
|
||||
- string expression: [http_response_body == "service is ok"](https://pkg.go.dev/github.com/PaesslerAG/gval/#example-Evaluate-String)
|
||||
- float64 expression: [(mem_used / total_mem) * 100](https://pkg.go.dev/github.com/PaesslerAG/gval/#example-Evaluate-Float64)
|
||||
|
||||
It can easily be extended with custom functions or operators:
|
||||
|
||||
- custom date comparator: [date(\`2014-01-02\`) > date(\`2014-01-01 23:59:59\`)](https://pkg.go.dev/github.com/PaesslerAG/gval/#example-Evaluate-DateComparison)
|
||||
- string length: [strlen("someReallyLongInputString") <= 16](https://pkg.go.dev/github.com/PaesslerAG/gval/#example-Evaluate-Strlen)
|
||||
|
||||
You can parse gval.Expressions once and re-use them multiple times. Parsing is the compute-intensive phase of the process, so if you intend to use the same expression with different parameters, just parse it once:
|
||||
|
||||
- [Parsing and Evaluation](https://pkg.go.dev/github.com/PaesslerAG/gval/#example-Evaluable)
|
||||
|
||||
The normal Go-standard order of operators is respected. When writing an expression, be sure that you either order the operators correctly, or use parentheses to clarify which portions of an expression should be run first.
|
||||
|
||||
Strings, numbers, and booleans can be used like in Go:
|
||||
|
||||
- [(7 < "47" == true ? "hello world!\n\u263a") + \` more text\`](https://pkg.go.dev/github.com/PaesslerAG/gval/#example-Evaluate-Encoding)
|
||||
|
||||
## Parameter
|
||||
|
||||
Variables can be accessed via string literals. They can be used for values with string keys if the parameter is a `map[string]interface{}` or `map[interface{}]interface{}` and for fields or methods if the parameter is a struct.
|
||||
|
||||
- [foo > 0](https://pkg.go.dev/github.com/PaesslerAG/gval/#example-Evaluate-Parameter)
|
||||
|
||||
### Bracket Selector
|
||||
|
||||
Map and array elements and Struct Field can be accessed via `[]`.
|
||||
|
||||
- [foo[0]](https://pkg.go.dev/github.com/PaesslerAG/gval/#example-Evaluate-Array)
|
||||
- [foo["b" + "a" + "r"]](https://pkg.go.dev/github.com/PaesslerAG/gval/#example-Evaluate-ExampleEvaluate_ComplexAccessor)
|
||||
|
||||
### Dot Selector
|
||||
|
||||
A nested variable with a name containing only letters and underscores can be accessed via a dot selector.
|
||||
|
||||
- [foo.bar > 0](https://pkg.go.dev/github.com/PaesslerAG/gval/#example-Evaluate-NestedParameter)
|
||||
|
||||
### Custom Selector
|
||||
|
||||
Parameter names like `response-time` will be interpreted as `response` minus `time`. While gval doesn't support these parameter names directly, you can easily access them via a custom extension like [JSON Path](https://github.com/PaesslerAG/jsonpath):
|
||||
|
||||
- [$["response-time"]](https://pkg.go.dev/github.com/PaesslerAG/gval/#example-Evaluate-Jsonpath)
|
||||
|
||||
Jsonpath is also suitable for accessing array elements.
|
||||
|
||||
### Fields and Methods
|
||||
|
||||
If you have structs in your parameters, you can access their fields and methods in the usual way:
|
||||
|
||||
- [foo.Hello + foo.World()](https://pkg.go.dev/github.com/PaesslerAG/gval/#example-Evaluate-FlatAccessor)
|
||||
|
||||
It also works if the parameter is a struct directly
|
||||
[Hello + World()](https://pkg.go.dev/github.com/PaesslerAG/gval/#example-Evaluate-Accessor)
|
||||
or if the fields are nested
|
||||
[foo.Hello + foo.World()](https://pkg.go.dev/github.com/PaesslerAG/gval/#example-Evaluate-NestedAccessor)
|
||||
|
||||
This may be convenient but note that using accessors on strucs makes the expression about four times slower than just using a parameter (consult the benchmarks for more precise measurements on your system). If there are functions you want to use, it's faster (and probably cleaner) to define them as functions (see the Evaluate section). These approaches use no reflection, and are designed to be fast and clean.
|
||||
|
||||
## Default Language
|
||||
|
||||
The default language is in serveral sub languages like text, arithmetic or propositional logic defined. See [Godoc](https://pkg.go.dev/github.com/PaesslerAG/gval/#Gval) for details. All sub languages are merged into gval.Full which contains the following elements:
|
||||
|
||||
- Modifiers: `+` `-` `/` `*` `&` `|` `^` `**` `%` `>>` `<<`
|
||||
- Comparators: `>` `>=` `<` `<=` `==` `!=` `=~` `!~`
|
||||
- Logical ops: `||` `&&`
|
||||
- Numeric constants, as 64-bit floating point (`12345.678`)
|
||||
- String constants (double quotes: `"foobar"`)
|
||||
- Date function 'Date(x)', using any permutation of RFC3339, ISO8601, ruby date, or unix date
|
||||
- Boolean constants: `true` `false`
|
||||
- Parentheses to control order of evaluation `(` `)`
|
||||
- Json Arrays : `[1, 2, "foo"]`
|
||||
- Json Objects : `{"a":1, "b":2, "c":"foo"}`
|
||||
- Prefixes: `!` `-` `~`
|
||||
- Ternary conditional: `?` `:`
|
||||
- Null coalescence: `??`
|
||||
|
||||
## Customize
|
||||
|
||||
Gval is completly customizable. Every constant, function or operator can be defined separately and existing expression languages can be reused:
|
||||
|
||||
- [foo.Hello + foo.World()](https://pkg.go.dev/github.com/PaesslerAG/gval/#example-Language)
|
||||
|
||||
For details see [Godoc](https://pkg.go.dev/github.com/PaesslerAG/gval).
|
||||
|
||||
### Implementing custom selector
|
||||
|
||||
In a case you want to provide custom logic for selectors you can implement `SelectGVal(ctx context.Context, k string) (interface{}, error)` on your struct.
|
||||
Function receives next part of the path and can return any type of var that is again evaluated through standard gval procedures.
|
||||
|
||||
[Example Custom Selector](https://pkg.go.dev/github.com/PaesslerAG/gval/#example-custom-selector)
|
||||
|
||||
### External gval Languages
|
||||
|
||||
A list of external libraries for gval. Feel free to add your own library.
|
||||
|
||||
- [gvalstrings](https://github.com/generikvault/gvalstrings) parse single quoted strings in gval.
|
||||
- [jsonpath](https://github.com/PaesslerAG/jsonpath) full support for jsonpath in gval.
|
||||
|
||||
## Performance
|
||||
|
||||
The library is built with the intention of being quick but has not been aggressively profiled and optimized. For most applications, though, it is completely fine.
|
||||
If performance is an issue, make sure to create your expression language with all functions, constants and operators only once. Evaluating an expression like gval.Evaluate("expression, const1, func1, func2, ...) creates a new gval.Language everytime it is called and slows execution.
|
||||
|
||||
The library comes with a bunch of benchmarks to measure the performance of parsing and evaluating expressions. You can run them with `go test -bench=.`.
|
||||
|
||||
For a very rough idea of performance, here are the results from a benchmark run on a Dell Latitude E7470 Win 10 i5-6300U.
|
||||
|
||||
``` text
|
||||
BenchmarkGval/const_evaluation-4 500000000 3.57 ns/op
|
||||
BenchmarkGval/const_parsing-4 1000000 1144 ns/op
|
||||
BenchmarkGval/single_parameter_evaluation-4 10000000 165 ns/op
|
||||
BenchmarkGval/single_parameter_parsing-4 1000000 1648 ns/op
|
||||
BenchmarkGval/parameter_evaluation-4 5000000 352 ns/op
|
||||
BenchmarkGval/parameter_parsing-4 500000 2773 ns/op
|
||||
BenchmarkGval/common_evaluation-4 3000000 434 ns/op
|
||||
BenchmarkGval/common_parsing-4 300000 4419 ns/op
|
||||
BenchmarkGval/complex_evaluation-4 100000000 11.6 ns/op
|
||||
BenchmarkGval/complex_parsing-4 100000 17936 ns/op
|
||||
BenchmarkGval/literal_evaluation-4 300000000 3.84 ns/op
|
||||
BenchmarkGval/literal_parsing-4 500000 2559 ns/op
|
||||
BenchmarkGval/modifier_evaluation-4 500000000 3.54 ns/op
|
||||
BenchmarkGval/modifier_parsing-4 500000 3755 ns/op
|
||||
BenchmarkGval/regex_evaluation-4 50000 21347 ns/op
|
||||
BenchmarkGval/regex_parsing-4 200000 6480 ns/op
|
||||
BenchmarkGval/constant_regex_evaluation-4 1000000 1000 ns/op
|
||||
BenchmarkGval/constant_regex_parsing-4 200000 9417 ns/op
|
||||
BenchmarkGval/accessors_evaluation-4 3000000 417 ns/op
|
||||
BenchmarkGval/accessors_parsing-4 1000000 1778 ns/op
|
||||
BenchmarkGval/accessors_method_evaluation-4 1000000 1931 ns/op
|
||||
BenchmarkGval/accessors_method_parsing-4 1000000 1729 ns/op
|
||||
BenchmarkGval/accessors_method_parameter_evaluation-4 1000000 2162 ns/op
|
||||
BenchmarkGval/accessors_method_parameter_parsing-4 500000 2618 ns/op
|
||||
BenchmarkGval/nested_accessors_evaluation-4 2000000 681 ns/op
|
||||
BenchmarkGval/nested_accessors_parsing-4 1000000 2115 ns/op
|
||||
BenchmarkRandom-4 500000 3631 ns/op
|
||||
ok
|
||||
```
|
||||
|
||||
## API Breaks
|
||||
|
||||
Gval is designed with easy expandability in mind and API breaks will be avoided if possible. If API breaks are unavoidable they wil be explicitly stated via an increased major version number.
|
||||
|
||||
-------------------------------------
|
||||
Credits to Reene French for the gophers.
|
||||
348
extra/server-discovery/vendor/github.com/PaesslerAG/gval/evaluable.go
generated
vendored
Normal file
348
extra/server-discovery/vendor/github.com/PaesslerAG/gval/evaluable.go
generated
vendored
Normal file
@@ -0,0 +1,348 @@
|
||||
package gval
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Selector allows for custom variable selection from structs
|
||||
//
|
||||
// Return value is again handled with variable() until end of the given path
|
||||
type Selector interface {
|
||||
SelectGVal(c context.Context, key string) (interface{}, error)
|
||||
}
|
||||
|
||||
// Evaluable evaluates given parameter
|
||||
type Evaluable func(c context.Context, parameter interface{}) (interface{}, error)
|
||||
|
||||
//EvalInt evaluates given parameter to an int
|
||||
func (e Evaluable) EvalInt(c context.Context, parameter interface{}) (int, error) {
|
||||
v, err := e(c, parameter)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
f, ok := convertToFloat(v)
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("expected number but got %v (%T)", v, v)
|
||||
}
|
||||
return int(f), nil
|
||||
}
|
||||
|
||||
//EvalFloat64 evaluates given parameter to a float64
|
||||
func (e Evaluable) EvalFloat64(c context.Context, parameter interface{}) (float64, error) {
|
||||
v, err := e(c, parameter)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
f, ok := convertToFloat(v)
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("expected number but got %v (%T)", v, v)
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
//EvalBool evaluates given parameter to a bool
|
||||
func (e Evaluable) EvalBool(c context.Context, parameter interface{}) (bool, error) {
|
||||
v, err := e(c, parameter)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
b, ok := convertToBool(v)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("expected bool but got %v (%T)", v, v)
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
//EvalString evaluates given parameter to a string
|
||||
func (e Evaluable) EvalString(c context.Context, parameter interface{}) (string, error) {
|
||||
o, err := e(c, parameter)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%v", o), nil
|
||||
}
|
||||
|
||||
//Const Evaluable represents given constant
|
||||
func (*Parser) Const(value interface{}) Evaluable {
|
||||
return constant(value)
|
||||
}
|
||||
|
||||
//go:noinline
|
||||
func constant(value interface{}) Evaluable {
|
||||
return func(c context.Context, v interface{}) (interface{}, error) {
|
||||
return value, nil
|
||||
}
|
||||
}
|
||||
|
||||
//Var Evaluable represents value at given path.
|
||||
//It supports with default language VariableSelector:
|
||||
// map[interface{}]interface{},
|
||||
// map[string]interface{} and
|
||||
// []interface{} and via reflect
|
||||
// struct fields,
|
||||
// struct methods,
|
||||
// slices and
|
||||
// map with int or string key.
|
||||
func (p *Parser) Var(path ...Evaluable) Evaluable {
|
||||
if p.selector == nil {
|
||||
return variable(path)
|
||||
}
|
||||
return p.selector(path)
|
||||
}
|
||||
|
||||
// Evaluables is a slice of Evaluable.
|
||||
type Evaluables []Evaluable
|
||||
|
||||
// EvalStrings evaluates given parameter to a string slice
|
||||
func (evs Evaluables) EvalStrings(c context.Context, parameter interface{}) ([]string, error) {
|
||||
strs := make([]string, len(evs))
|
||||
for i, p := range evs {
|
||||
k, err := p.EvalString(c, parameter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
strs[i] = k
|
||||
}
|
||||
return strs, nil
|
||||
}
|
||||
|
||||
func variable(path Evaluables) Evaluable {
|
||||
return func(c context.Context, v interface{}) (interface{}, error) {
|
||||
keys, err := path.EvalStrings(c, v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i, k := range keys {
|
||||
switch o := v.(type) {
|
||||
case Selector:
|
||||
v, err = o.SelectGVal(c, k)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to select '%s' on %T: %w", k, o, err)
|
||||
}
|
||||
continue
|
||||
case map[interface{}]interface{}:
|
||||
v = o[k]
|
||||
continue
|
||||
case map[string]interface{}:
|
||||
v = o[k]
|
||||
continue
|
||||
case []interface{}:
|
||||
if i, err := strconv.Atoi(k); err == nil && i >= 0 && len(o) > i {
|
||||
v = o[i]
|
||||
continue
|
||||
}
|
||||
default:
|
||||
var ok bool
|
||||
v, ok = reflectSelect(k, o)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown parameter %s", strings.Join(keys[:i+1], "."))
|
||||
}
|
||||
}
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
|
||||
func reflectSelect(key string, value interface{}) (selection interface{}, ok bool) {
|
||||
vv := reflect.ValueOf(value)
|
||||
vvElem := resolvePotentialPointer(vv)
|
||||
|
||||
switch vvElem.Kind() {
|
||||
case reflect.Map:
|
||||
mapKey, ok := reflectConvertTo(vv.Type().Key().Kind(), key)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
vvElem = vv.MapIndex(reflect.ValueOf(mapKey))
|
||||
vvElem = resolvePotentialPointer(vvElem)
|
||||
|
||||
if vvElem.IsValid() {
|
||||
return vvElem.Interface(), true
|
||||
}
|
||||
case reflect.Slice:
|
||||
if i, err := strconv.Atoi(key); err == nil && i >= 0 && vv.Len() > i {
|
||||
vvElem = resolvePotentialPointer(vv.Index(i))
|
||||
return vvElem.Interface(), true
|
||||
}
|
||||
case reflect.Struct:
|
||||
field := vvElem.FieldByName(key)
|
||||
if field.IsValid() {
|
||||
return field.Interface(), true
|
||||
}
|
||||
|
||||
method := vv.MethodByName(key)
|
||||
if method.IsValid() {
|
||||
return method.Interface(), true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func resolvePotentialPointer(value reflect.Value) reflect.Value {
|
||||
if value.Kind() == reflect.Ptr {
|
||||
return value.Elem()
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func reflectConvertTo(k reflect.Kind, value string) (interface{}, bool) {
|
||||
switch k {
|
||||
case reflect.String:
|
||||
return value, true
|
||||
case reflect.Int:
|
||||
if i, err := strconv.Atoi(value); err == nil {
|
||||
return i, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (*Parser) callFunc(fun function, args ...Evaluable) Evaluable {
|
||||
return func(c context.Context, v interface{}) (ret interface{}, err error) {
|
||||
a := make([]interface{}, len(args))
|
||||
for i, arg := range args {
|
||||
ai, err := arg(c, v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a[i] = ai
|
||||
}
|
||||
return fun(c, a...)
|
||||
}
|
||||
}
|
||||
|
||||
func (*Parser) callEvaluable(fullname string, fun Evaluable, args ...Evaluable) Evaluable {
|
||||
return func(c context.Context, v interface{}) (ret interface{}, err error) {
|
||||
f, err := fun(c, v)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not call function: %v", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("failed to execute function '%s': %s", fullname, r)
|
||||
ret = nil
|
||||
}
|
||||
}()
|
||||
|
||||
ff := reflect.ValueOf(f)
|
||||
|
||||
if ff.Kind() != reflect.Func {
|
||||
return nil, fmt.Errorf("could not call '%s' type %T", fullname, f)
|
||||
}
|
||||
|
||||
a := make([]reflect.Value, len(args))
|
||||
for i := range args {
|
||||
arg, err := args[i](c, v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a[i] = reflect.ValueOf(arg)
|
||||
}
|
||||
|
||||
rr := ff.Call(a)
|
||||
|
||||
r := make([]interface{}, len(rr))
|
||||
for i, e := range rr {
|
||||
r[i] = e.Interface()
|
||||
}
|
||||
|
||||
errorInterface := reflect.TypeOf((*error)(nil)).Elem()
|
||||
if len(r) > 0 && ff.Type().Out(len(r)-1).Implements(errorInterface) {
|
||||
if r[len(r)-1] != nil {
|
||||
err = r[len(r)-1].(error)
|
||||
}
|
||||
r = r[0 : len(r)-1]
|
||||
}
|
||||
|
||||
switch len(r) {
|
||||
case 0:
|
||||
return err, nil
|
||||
case 1:
|
||||
return r[0], err
|
||||
default:
|
||||
return r, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//IsConst returns if the Evaluable is a Parser.Const() value
|
||||
func (e Evaluable) IsConst() bool {
|
||||
pc := reflect.ValueOf(constant(nil)).Pointer()
|
||||
pe := reflect.ValueOf(e).Pointer()
|
||||
return pc == pe
|
||||
}
|
||||
|
||||
func regEx(a, b Evaluable) (Evaluable, error) {
|
||||
if !b.IsConst() {
|
||||
return func(c context.Context, o interface{}) (interface{}, error) {
|
||||
a, err := a.EvalString(c, o)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b, err := b.EvalString(c, o)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
matched, err := regexp.MatchString(b, a)
|
||||
return matched, err
|
||||
}, nil
|
||||
}
|
||||
s, err := b.EvalString(context.TODO(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
regex, err := regexp.Compile(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return func(c context.Context, v interface{}) (interface{}, error) {
|
||||
s, err := a.EvalString(c, v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return regex.MatchString(s), nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
func notRegEx(a, b Evaluable) (Evaluable, error) {
|
||||
if !b.IsConst() {
|
||||
return func(c context.Context, o interface{}) (interface{}, error) {
|
||||
a, err := a.EvalString(c, o)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b, err := b.EvalString(c, o)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
matched, err := regexp.MatchString(b, a)
|
||||
return !matched, err
|
||||
}, nil
|
||||
}
|
||||
s, err := b.EvalString(context.TODO(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
regex, err := regexp.Compile(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return func(c context.Context, v interface{}) (interface{}, error) {
|
||||
s, err := a.EvalString(c, v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return !regex.MatchString(s), nil
|
||||
}, nil
|
||||
}
|
||||
126
extra/server-discovery/vendor/github.com/PaesslerAG/gval/functions.go
generated
vendored
Normal file
126
extra/server-discovery/vendor/github.com/PaesslerAG/gval/functions.go
generated
vendored
Normal file
@@ -0,0 +1,126 @@
|
||||
package gval
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type function func(ctx context.Context, arguments ...interface{}) (interface{}, error)
|
||||
|
||||
func toFunc(f interface{}) function {
|
||||
if f, ok := f.(func(arguments ...interface{}) (interface{}, error)); ok {
|
||||
return function(func(ctx context.Context, arguments ...interface{}) (interface{}, error) {
|
||||
var v interface{}
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
defer func() {
|
||||
if recovered := recover(); recovered != nil {
|
||||
errCh <- fmt.Errorf("%v", recovered)
|
||||
}
|
||||
}()
|
||||
result, err := f(arguments...)
|
||||
v = result
|
||||
errCh <- err
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case err := <-errCh:
|
||||
return v, err
|
||||
}
|
||||
})
|
||||
}
|
||||
if f, ok := f.(func(ctx context.Context, arguments ...interface{}) (interface{}, error)); ok {
|
||||
return function(f)
|
||||
}
|
||||
|
||||
fun := reflect.ValueOf(f)
|
||||
t := fun.Type()
|
||||
return func(ctx context.Context, args ...interface{}) (interface{}, error) {
|
||||
var v interface{}
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
defer func() {
|
||||
if recovered := recover(); recovered != nil {
|
||||
errCh <- fmt.Errorf("%v", recovered)
|
||||
}
|
||||
}()
|
||||
in, err := createCallArguments(ctx, t, args)
|
||||
if err != nil {
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
out := fun.Call(in)
|
||||
|
||||
r := make([]interface{}, len(out))
|
||||
for i, e := range out {
|
||||
r[i] = e.Interface()
|
||||
}
|
||||
|
||||
err = nil
|
||||
errorInterface := reflect.TypeOf((*error)(nil)).Elem()
|
||||
if len(r) > 0 && t.Out(len(r)-1).Implements(errorInterface) {
|
||||
if r[len(r)-1] != nil {
|
||||
err = r[len(r)-1].(error)
|
||||
}
|
||||
r = r[0 : len(r)-1]
|
||||
}
|
||||
|
||||
switch len(r) {
|
||||
case 0:
|
||||
v = nil
|
||||
case 1:
|
||||
v = r[0]
|
||||
default:
|
||||
v = r
|
||||
}
|
||||
errCh <- err
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case err := <-errCh:
|
||||
return v, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createCallArguments(ctx context.Context, t reflect.Type, args []interface{}) ([]reflect.Value, error) {
|
||||
variadic := t.IsVariadic()
|
||||
numIn := t.NumIn()
|
||||
|
||||
// if first argument is a context, use the given execution context
|
||||
if numIn > 0 {
|
||||
thisFun := reflect.ValueOf(createCallArguments)
|
||||
thisT := thisFun.Type()
|
||||
if t.In(0) == thisT.In(0) {
|
||||
args = append([]interface{}{ctx}, args...)
|
||||
}
|
||||
}
|
||||
|
||||
if (!variadic && len(args) != numIn) || (variadic && len(args) < numIn-1) {
|
||||
return nil, fmt.Errorf("invalid number of parameters")
|
||||
}
|
||||
|
||||
in := make([]reflect.Value, len(args))
|
||||
var inType reflect.Type
|
||||
for i, arg := range args {
|
||||
if !variadic || i < numIn-1 {
|
||||
inType = t.In(i)
|
||||
} else if i == numIn-1 {
|
||||
inType = t.In(numIn - 1).Elem()
|
||||
}
|
||||
argVal := reflect.ValueOf(arg)
|
||||
if arg == nil {
|
||||
argVal = reflect.ValueOf(reflect.Interface)
|
||||
} else if !argVal.Type().AssignableTo(inType) {
|
||||
return nil, fmt.Errorf("expected type %s for parameter %d but got %T",
|
||||
inType.String(), i, arg)
|
||||
}
|
||||
in[i] = argVal
|
||||
}
|
||||
return in, nil
|
||||
}
|
||||
327
extra/server-discovery/vendor/github.com/PaesslerAG/gval/gval.go
generated
vendored
Normal file
327
extra/server-discovery/vendor/github.com/PaesslerAG/gval/gval.go
generated
vendored
Normal file
@@ -0,0 +1,327 @@
|
||||
// Package gval provides a generic expression language.
|
||||
// All functions, infix and prefix operators can be replaced by composing languages into a new one.
|
||||
//
|
||||
// The package contains concrete expression languages for common application in text, arithmetic, decimal arithmetic, propositional logic and so on.
|
||||
// They can be used as basis for a custom expression language or to evaluate expressions directly.
|
||||
package gval
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"text/scanner"
|
||||
"time"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
//Evaluate given parameter with given expression in gval full language
|
||||
func Evaluate(expression string, parameter interface{}, opts ...Language) (interface{}, error) {
|
||||
return EvaluateWithContext(context.Background(), expression, parameter, opts...)
|
||||
}
|
||||
|
||||
//Evaluate given parameter with given expression in gval full language using a context
|
||||
func EvaluateWithContext(c context.Context, expression string, parameter interface{}, opts ...Language) (interface{}, error) {
|
||||
l := full
|
||||
if len(opts) > 0 {
|
||||
l = NewLanguage(append([]Language{l}, opts...)...)
|
||||
}
|
||||
return l.EvaluateWithContext(c, expression, parameter)
|
||||
}
|
||||
|
||||
// Full is the union of Arithmetic, Bitmask, Text, PropositionalLogic, and Json
|
||||
// Operator in: a in b is true iff value a is an element of array b
|
||||
// Operator ??: a ?? b returns a if a is not false or nil, otherwise n
|
||||
// Operator ?: a ? b : c returns b if bool a is true, otherwise b
|
||||
//
|
||||
// Function Date: Date(a) parses string a. a must match RFC3339, ISO8601, ruby date, or unix date
|
||||
func Full(extensions ...Language) Language {
|
||||
if len(extensions) == 0 {
|
||||
return full
|
||||
}
|
||||
return NewLanguage(append([]Language{full}, extensions...)...)
|
||||
}
|
||||
|
||||
// Arithmetic contains base, plus(+), minus(-), divide(/), power(**), negative(-)
|
||||
// and numerical order (<=,<,>,>=)
|
||||
//
|
||||
// Arithmetic operators expect float64 operands.
|
||||
// Called with unfitting input, they try to convert the input to float64.
|
||||
// They can parse strings and convert any type of int or float.
|
||||
func Arithmetic() Language {
|
||||
return arithmetic
|
||||
}
|
||||
|
||||
// DecimalArithmetic contains base, plus(+), minus(-), divide(/), power(**), negative(-)
|
||||
// and numerical order (<=,<,>,>=)
|
||||
//
|
||||
// DecimalArithmetic operators expect decimal.Decimal operands (github.com/shopspring/decimal)
|
||||
// and are used to calculate money/decimal rather than floating point calculations.
|
||||
// Called with unfitting input, they try to convert the input to decimal.Decimal.
|
||||
// They can parse strings and convert any type of int or float.
|
||||
func DecimalArithmetic() Language {
|
||||
return decimalArithmetic
|
||||
}
|
||||
|
||||
// Bitmask contains base, bitwise and(&), bitwise or(|) and bitwise not(^).
|
||||
//
|
||||
// Bitmask operators expect float64 operands.
|
||||
// Called with unfitting input they try to convert the input to float64.
|
||||
// They can parse strings and convert any type of int or float.
|
||||
func Bitmask() Language {
|
||||
return bitmask
|
||||
}
|
||||
|
||||
// Text contains base, lexical order on strings (<=,<,>,>=),
|
||||
// regex match (=~) and regex not match (!~)
|
||||
func Text() Language {
|
||||
return text
|
||||
}
|
||||
|
||||
// PropositionalLogic contains base, not(!), and (&&), or (||) and Base.
|
||||
//
|
||||
// Propositional operator expect bool operands.
|
||||
// Called with unfitting input they try to convert the input to bool.
|
||||
// Numbers other than 0 and the strings "TRUE" and "true" are interpreted as true.
|
||||
// 0 and the strings "FALSE" and "false" are interpreted as false.
|
||||
func PropositionalLogic() Language {
|
||||
return propositionalLogic
|
||||
}
|
||||
|
||||
// JSON contains json objects ({string:expression,...})
|
||||
// and json arrays ([expression, ...])
|
||||
func JSON() Language {
|
||||
return ljson
|
||||
}
|
||||
|
||||
// Parentheses contains support for parentheses.
|
||||
func Parentheses() Language {
|
||||
return parentheses
|
||||
}
|
||||
|
||||
// Ident contains support for variables and functions.
|
||||
func Ident() Language {
|
||||
return ident
|
||||
}
|
||||
|
||||
// Base contains equal (==) and not equal (!=), perentheses and general support for variables, constants and functions
|
||||
// It contains true, false, (floating point) number, string ("" or ``) and char ('') constants
|
||||
func Base() Language {
|
||||
return base
|
||||
}
|
||||
|
||||
var full = NewLanguage(arithmetic, bitmask, text, propositionalLogic, ljson,
|
||||
|
||||
InfixOperator("in", inArray),
|
||||
|
||||
InfixShortCircuit("??", func(a interface{}) (interface{}, bool) {
|
||||
v := reflect.ValueOf(a)
|
||||
return a, a != nil && !v.IsZero()
|
||||
}),
|
||||
InfixOperator("??", func(a, b interface{}) (interface{}, error) {
|
||||
if v := reflect.ValueOf(a); a == nil || v.IsZero() {
|
||||
return b, nil
|
||||
}
|
||||
return a, nil
|
||||
}),
|
||||
|
||||
PostfixOperator("?", parseIf),
|
||||
|
||||
Function("date", func(arguments ...interface{}) (interface{}, error) {
|
||||
if len(arguments) != 1 {
|
||||
return nil, fmt.Errorf("date() expects exactly one string argument")
|
||||
}
|
||||
s, ok := arguments[0].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("date() expects exactly one string argument")
|
||||
}
|
||||
for _, format := range [...]string{
|
||||
time.ANSIC,
|
||||
time.UnixDate,
|
||||
time.RubyDate,
|
||||
time.Kitchen,
|
||||
time.RFC3339,
|
||||
time.RFC3339Nano,
|
||||
"2006-01-02", // RFC 3339
|
||||
"2006-01-02 15:04", // RFC 3339 with minutes
|
||||
"2006-01-02 15:04:05", // RFC 3339 with seconds
|
||||
"2006-01-02 15:04:05-07:00", // RFC 3339 with seconds and timezone
|
||||
"2006-01-02T15Z0700", // ISO8601 with hour
|
||||
"2006-01-02T15:04Z0700", // ISO8601 with minutes
|
||||
"2006-01-02T15:04:05Z0700", // ISO8601 with seconds
|
||||
"2006-01-02T15:04:05.999999999Z0700", // ISO8601 with nanoseconds
|
||||
} {
|
||||
ret, err := time.ParseInLocation(format, s, time.Local)
|
||||
if err == nil {
|
||||
return ret, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("date() could not parse %s", s)
|
||||
}),
|
||||
)
|
||||
|
||||
var ljson = NewLanguage(
|
||||
PrefixExtension('[', parseJSONArray),
|
||||
PrefixExtension('{', parseJSONObject),
|
||||
)
|
||||
|
||||
var arithmetic = NewLanguage(
|
||||
InfixNumberOperator("+", func(a, b float64) (interface{}, error) { return a + b, nil }),
|
||||
InfixNumberOperator("-", func(a, b float64) (interface{}, error) { return a - b, nil }),
|
||||
InfixNumberOperator("*", func(a, b float64) (interface{}, error) { return a * b, nil }),
|
||||
InfixNumberOperator("/", func(a, b float64) (interface{}, error) { return a / b, nil }),
|
||||
InfixNumberOperator("%", func(a, b float64) (interface{}, error) { return math.Mod(a, b), nil }),
|
||||
InfixNumberOperator("**", func(a, b float64) (interface{}, error) { return math.Pow(a, b), nil }),
|
||||
|
||||
InfixNumberOperator(">", func(a, b float64) (interface{}, error) { return a > b, nil }),
|
||||
InfixNumberOperator(">=", func(a, b float64) (interface{}, error) { return a >= b, nil }),
|
||||
InfixNumberOperator("<", func(a, b float64) (interface{}, error) { return a < b, nil }),
|
||||
InfixNumberOperator("<=", func(a, b float64) (interface{}, error) { return a <= b, nil }),
|
||||
|
||||
InfixNumberOperator("==", func(a, b float64) (interface{}, error) { return a == b, nil }),
|
||||
InfixNumberOperator("!=", func(a, b float64) (interface{}, error) { return a != b, nil }),
|
||||
|
||||
base,
|
||||
)
|
||||
|
||||
var decimalArithmetic = NewLanguage(
|
||||
InfixDecimalOperator("+", func(a, b decimal.Decimal) (interface{}, error) { return a.Add(b), nil }),
|
||||
InfixDecimalOperator("-", func(a, b decimal.Decimal) (interface{}, error) { return a.Sub(b), nil }),
|
||||
InfixDecimalOperator("*", func(a, b decimal.Decimal) (interface{}, error) { return a.Mul(b), nil }),
|
||||
InfixDecimalOperator("/", func(a, b decimal.Decimal) (interface{}, error) { return a.Div(b), nil }),
|
||||
InfixDecimalOperator("%", func(a, b decimal.Decimal) (interface{}, error) { return a.Mod(b), nil }),
|
||||
InfixDecimalOperator("**", func(a, b decimal.Decimal) (interface{}, error) { return a.Pow(b), nil }),
|
||||
|
||||
InfixDecimalOperator(">", func(a, b decimal.Decimal) (interface{}, error) { return a.GreaterThan(b), nil }),
|
||||
InfixDecimalOperator(">=", func(a, b decimal.Decimal) (interface{}, error) { return a.GreaterThanOrEqual(b), nil }),
|
||||
InfixDecimalOperator("<", func(a, b decimal.Decimal) (interface{}, error) { return a.LessThan(b), nil }),
|
||||
InfixDecimalOperator("<=", func(a, b decimal.Decimal) (interface{}, error) { return a.LessThanOrEqual(b), nil }),
|
||||
|
||||
InfixDecimalOperator("==", func(a, b decimal.Decimal) (interface{}, error) { return a.Equal(b), nil }),
|
||||
InfixDecimalOperator("!=", func(a, b decimal.Decimal) (interface{}, error) { return !a.Equal(b), nil }),
|
||||
base,
|
||||
//Base is before these overrides so that the Base options are overridden
|
||||
PrefixExtension(scanner.Int, parseDecimal),
|
||||
PrefixExtension(scanner.Float, parseDecimal),
|
||||
PrefixOperator("-", func(c context.Context, v interface{}) (interface{}, error) {
|
||||
i, ok := convertToFloat(v)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected %v(%T) expected number", v, v)
|
||||
}
|
||||
return decimal.NewFromFloat(i).Neg(), nil
|
||||
}),
|
||||
)
|
||||
|
||||
var bitmask = NewLanguage(
|
||||
InfixNumberOperator("^", func(a, b float64) (interface{}, error) { return float64(int64(a) ^ int64(b)), nil }),
|
||||
InfixNumberOperator("&", func(a, b float64) (interface{}, error) { return float64(int64(a) & int64(b)), nil }),
|
||||
InfixNumberOperator("|", func(a, b float64) (interface{}, error) { return float64(int64(a) | int64(b)), nil }),
|
||||
InfixNumberOperator("<<", func(a, b float64) (interface{}, error) { return float64(int64(a) << uint64(b)), nil }),
|
||||
InfixNumberOperator(">>", func(a, b float64) (interface{}, error) { return float64(int64(a) >> uint64(b)), nil }),
|
||||
|
||||
PrefixOperator("~", func(c context.Context, v interface{}) (interface{}, error) {
|
||||
i, ok := convertToFloat(v)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected %T expected number", v)
|
||||
}
|
||||
return float64(^int64(i)), nil
|
||||
}),
|
||||
)
|
||||
|
||||
var text = NewLanguage(
|
||||
InfixTextOperator("+", func(a, b string) (interface{}, error) { return fmt.Sprintf("%v%v", a, b), nil }),
|
||||
|
||||
InfixTextOperator("<", func(a, b string) (interface{}, error) { return a < b, nil }),
|
||||
InfixTextOperator("<=", func(a, b string) (interface{}, error) { return a <= b, nil }),
|
||||
InfixTextOperator(">", func(a, b string) (interface{}, error) { return a > b, nil }),
|
||||
InfixTextOperator(">=", func(a, b string) (interface{}, error) { return a >= b, nil }),
|
||||
|
||||
InfixEvalOperator("=~", regEx),
|
||||
InfixEvalOperator("!~", notRegEx),
|
||||
base,
|
||||
)
|
||||
|
||||
var propositionalLogic = NewLanguage(
|
||||
PrefixOperator("!", func(c context.Context, v interface{}) (interface{}, error) {
|
||||
b, ok := convertToBool(v)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected %T expected bool", v)
|
||||
}
|
||||
return !b, nil
|
||||
}),
|
||||
|
||||
InfixShortCircuit("&&", func(a interface{}) (interface{}, bool) { return false, a == false }),
|
||||
InfixBoolOperator("&&", func(a, b bool) (interface{}, error) { return a && b, nil }),
|
||||
InfixShortCircuit("||", func(a interface{}) (interface{}, bool) { return true, a == true }),
|
||||
InfixBoolOperator("||", func(a, b bool) (interface{}, error) { return a || b, nil }),
|
||||
|
||||
InfixBoolOperator("==", func(a, b bool) (interface{}, error) { return a == b, nil }),
|
||||
InfixBoolOperator("!=", func(a, b bool) (interface{}, error) { return a != b, nil }),
|
||||
|
||||
base,
|
||||
)
|
||||
|
||||
var parentheses = NewLanguage(
|
||||
PrefixExtension('(', parseParentheses),
|
||||
)
|
||||
|
||||
var ident = NewLanguage(
|
||||
PrefixMetaPrefix(scanner.Ident, parseIdent),
|
||||
)
|
||||
|
||||
var base = NewLanguage(
|
||||
PrefixExtension(scanner.Int, parseNumber),
|
||||
PrefixExtension(scanner.Float, parseNumber),
|
||||
PrefixOperator("-", func(c context.Context, v interface{}) (interface{}, error) {
|
||||
i, ok := convertToFloat(v)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected %v(%T) expected number", v, v)
|
||||
}
|
||||
return -i, nil
|
||||
}),
|
||||
|
||||
PrefixExtension(scanner.String, parseString),
|
||||
PrefixExtension(scanner.Char, parseString),
|
||||
PrefixExtension(scanner.RawString, parseString),
|
||||
|
||||
Constant("true", true),
|
||||
Constant("false", false),
|
||||
|
||||
InfixOperator("==", func(a, b interface{}) (interface{}, error) { return reflect.DeepEqual(a, b), nil }),
|
||||
InfixOperator("!=", func(a, b interface{}) (interface{}, error) { return !reflect.DeepEqual(a, b), nil }),
|
||||
parentheses,
|
||||
|
||||
Precedence("??", 0),
|
||||
|
||||
Precedence("||", 20),
|
||||
Precedence("&&", 21),
|
||||
|
||||
Precedence("==", 40),
|
||||
Precedence("!=", 40),
|
||||
Precedence(">", 40),
|
||||
Precedence(">=", 40),
|
||||
Precedence("<", 40),
|
||||
Precedence("<=", 40),
|
||||
Precedence("=~", 40),
|
||||
Precedence("!~", 40),
|
||||
Precedence("in", 40),
|
||||
|
||||
Precedence("^", 60),
|
||||
Precedence("&", 60),
|
||||
Precedence("|", 60),
|
||||
|
||||
Precedence("<<", 90),
|
||||
Precedence(">>", 90),
|
||||
|
||||
Precedence("+", 120),
|
||||
Precedence("-", 120),
|
||||
|
||||
Precedence("*", 150),
|
||||
Precedence("/", 150),
|
||||
Precedence("%", 150),
|
||||
|
||||
Precedence("**", 200),
|
||||
|
||||
ident,
|
||||
)
|
||||
281
extra/server-discovery/vendor/github.com/PaesslerAG/gval/language.go
generated
vendored
Normal file
281
extra/server-discovery/vendor/github.com/PaesslerAG/gval/language.go
generated
vendored
Normal file
@@ -0,0 +1,281 @@
|
||||
package gval
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"text/scanner"
|
||||
"unicode"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
// Language is an expression language
|
||||
type Language struct {
|
||||
prefixes map[interface{}]extension
|
||||
operators map[string]operator
|
||||
operatorSymbols map[rune]struct{}
|
||||
init extension
|
||||
def extension
|
||||
selector func(Evaluables) Evaluable
|
||||
}
|
||||
|
||||
// NewLanguage returns the union of given Languages as new Language.
|
||||
func NewLanguage(bases ...Language) Language {
|
||||
l := newLanguage()
|
||||
for _, base := range bases {
|
||||
for i, e := range base.prefixes {
|
||||
l.prefixes[i] = e
|
||||
}
|
||||
for i, e := range base.operators {
|
||||
l.operators[i] = e.merge(l.operators[i])
|
||||
l.operators[i].initiate(i)
|
||||
}
|
||||
for i := range base.operatorSymbols {
|
||||
l.operatorSymbols[i] = struct{}{}
|
||||
}
|
||||
if base.init != nil {
|
||||
l.init = base.init
|
||||
}
|
||||
if base.def != nil {
|
||||
l.def = base.def
|
||||
}
|
||||
if base.selector != nil {
|
||||
l.selector = base.selector
|
||||
}
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func newLanguage() Language {
|
||||
return Language{
|
||||
prefixes: map[interface{}]extension{},
|
||||
operators: map[string]operator{},
|
||||
operatorSymbols: map[rune]struct{}{},
|
||||
}
|
||||
}
|
||||
|
||||
// NewEvaluable returns an Evaluable for given expression in the specified language
|
||||
func (l Language) NewEvaluable(expression string) (Evaluable, error) {
|
||||
return l.NewEvaluableWithContext(context.Background(), expression)
|
||||
}
|
||||
|
||||
// NewEvaluableWithContext returns an Evaluable for given expression in the specified language using context
|
||||
func (l Language) NewEvaluableWithContext(c context.Context, expression string) (Evaluable, error) {
|
||||
p := newParser(expression, l)
|
||||
|
||||
eval, err := p.parse(c)
|
||||
if err == nil && p.isCamouflaged() && p.lastScan != scanner.EOF {
|
||||
err = p.camouflage
|
||||
}
|
||||
if err != nil {
|
||||
pos := p.scanner.Pos()
|
||||
return nil, fmt.Errorf("parsing error: %s - %d:%d %s", p.scanner.Position, pos.Line, pos.Column, err)
|
||||
}
|
||||
|
||||
return eval, nil
|
||||
}
|
||||
|
||||
// Evaluate given parameter with given expression
|
||||
func (l Language) Evaluate(expression string, parameter interface{}) (interface{}, error) {
|
||||
return l.EvaluateWithContext(context.Background(), expression, parameter)
|
||||
}
|
||||
|
||||
// Evaluate given parameter with given expression using context
|
||||
func (l Language) EvaluateWithContext(c context.Context, expression string, parameter interface{}) (interface{}, error) {
|
||||
eval, err := l.NewEvaluableWithContext(c, expression)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v, err := eval(c, parameter)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can not evaluate %s: %v", expression, err)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// Function returns a Language with given function.
|
||||
// Function has no conversion for input types.
|
||||
//
|
||||
// If the function returns an error it must be the last return parameter.
|
||||
//
|
||||
// If the function has (without the error) more then one return parameter,
|
||||
// it returns them as []interface{}.
|
||||
func Function(name string, function interface{}) Language {
|
||||
l := newLanguage()
|
||||
l.prefixes[name] = func(c context.Context, p *Parser) (eval Evaluable, err error) {
|
||||
args := []Evaluable{}
|
||||
scan := p.Scan()
|
||||
switch scan {
|
||||
case '(':
|
||||
args, err = p.parseArguments(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
p.Camouflage("function call", '(')
|
||||
}
|
||||
return p.callFunc(toFunc(function), args...), nil
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// Constant returns a Language with given constant
|
||||
func Constant(name string, value interface{}) Language {
|
||||
l := newLanguage()
|
||||
l.prefixes[l.makePrefixKey(name)] = func(c context.Context, p *Parser) (eval Evaluable, err error) {
|
||||
return p.Const(value), nil
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// PrefixExtension extends a Language
|
||||
func PrefixExtension(r rune, ext func(context.Context, *Parser) (Evaluable, error)) Language {
|
||||
l := newLanguage()
|
||||
l.prefixes[r] = ext
|
||||
return l
|
||||
}
|
||||
|
||||
// Init is a language that does no parsing, but invokes the given function when
|
||||
// parsing starts. It is incumbent upon the function to call ParseExpression to
|
||||
// continue parsing.
|
||||
//
|
||||
// This function can be used to customize the parser settings, such as
|
||||
// whitespace or ident behavior.
|
||||
func Init(ext func(context.Context, *Parser) (Evaluable, error)) Language {
|
||||
l := newLanguage()
|
||||
l.init = ext
|
||||
return l
|
||||
}
|
||||
|
||||
// DefaultExtension is a language that runs the given function if no other
|
||||
// prefix matches.
|
||||
func DefaultExtension(ext func(context.Context, *Parser) (Evaluable, error)) Language {
|
||||
l := newLanguage()
|
||||
l.def = ext
|
||||
return l
|
||||
}
|
||||
|
||||
// PrefixMetaPrefix chooses a Prefix to be executed
|
||||
func PrefixMetaPrefix(r rune, ext func(context.Context, *Parser) (call string, alternative func() (Evaluable, error), err error)) Language {
|
||||
l := newLanguage()
|
||||
l.prefixes[r] = func(c context.Context, p *Parser) (Evaluable, error) {
|
||||
call, alternative, err := ext(c, p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if prefix, ok := p.prefixes[l.makePrefixKey(call)]; ok {
|
||||
return prefix(c, p)
|
||||
}
|
||||
return alternative()
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
//PrefixOperator returns a Language with given prefix
|
||||
func PrefixOperator(name string, e Evaluable) Language {
|
||||
l := newLanguage()
|
||||
l.prefixes[l.makePrefixKey(name)] = func(c context.Context, p *Parser) (Evaluable, error) {
|
||||
eval, err := p.ParseNextExpression(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
prefix := func(c context.Context, v interface{}) (interface{}, error) {
|
||||
a, err := eval(c, v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return e(c, a)
|
||||
}
|
||||
if eval.IsConst() {
|
||||
v, err := prefix(c, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
prefix = p.Const(v)
|
||||
}
|
||||
return prefix, nil
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// PostfixOperator extends a Language.
|
||||
func PostfixOperator(name string, ext func(context.Context, *Parser, Evaluable) (Evaluable, error)) Language {
|
||||
l := newLanguage()
|
||||
l.operators[l.makeInfixKey(name)] = postfix{
|
||||
f: func(c context.Context, p *Parser, eval Evaluable, pre operatorPrecedence) (Evaluable, error) {
|
||||
return ext(c, p, eval)
|
||||
},
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// InfixOperator for two arbitrary values.
|
||||
func InfixOperator(name string, f func(a, b interface{}) (interface{}, error)) Language {
|
||||
return newLanguageOperator(name, &infix{arbitrary: f})
|
||||
}
|
||||
|
||||
// InfixShortCircuit operator is called after the left operand is evaluated.
|
||||
func InfixShortCircuit(name string, f func(a interface{}) (interface{}, bool)) Language {
|
||||
return newLanguageOperator(name, &infix{shortCircuit: f})
|
||||
}
|
||||
|
||||
// InfixTextOperator for two text values.
|
||||
func InfixTextOperator(name string, f func(a, b string) (interface{}, error)) Language {
|
||||
return newLanguageOperator(name, &infix{text: f})
|
||||
}
|
||||
|
||||
// InfixNumberOperator for two number values.
|
||||
func InfixNumberOperator(name string, f func(a, b float64) (interface{}, error)) Language {
|
||||
return newLanguageOperator(name, &infix{number: f})
|
||||
}
|
||||
|
||||
// InfixDecimalOperator for two decimal values.
|
||||
func InfixDecimalOperator(name string, f func(a, b decimal.Decimal) (interface{}, error)) Language {
|
||||
return newLanguageOperator(name, &infix{decimal: f})
|
||||
}
|
||||
|
||||
// InfixBoolOperator for two bool values.
|
||||
func InfixBoolOperator(name string, f func(a, b bool) (interface{}, error)) Language {
|
||||
return newLanguageOperator(name, &infix{boolean: f})
|
||||
}
|
||||
|
||||
// Precedence of operator. The Operator with higher operatorPrecedence is evaluated first.
|
||||
func Precedence(name string, operatorPrecendence uint8) Language {
|
||||
return newLanguageOperator(name, operatorPrecedence(operatorPrecendence))
|
||||
}
|
||||
|
||||
// InfixEvalOperator operates on the raw operands.
|
||||
// Therefore it cannot be combined with operators for other operand types.
|
||||
func InfixEvalOperator(name string, f func(a, b Evaluable) (Evaluable, error)) Language {
|
||||
return newLanguageOperator(name, directInfix{infixBuilder: f})
|
||||
}
|
||||
|
||||
func newLanguageOperator(name string, op operator) Language {
|
||||
op.initiate(name)
|
||||
l := newLanguage()
|
||||
l.operators[l.makeInfixKey(name)] = op
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *Language) makePrefixKey(key string) interface{} {
|
||||
runes := []rune(key)
|
||||
if len(runes) == 1 && !unicode.IsLetter(runes[0]) {
|
||||
return runes[0]
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
func (l *Language) makeInfixKey(key string) string {
|
||||
for _, r := range key {
|
||||
l.operatorSymbols[r] = struct{}{}
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
// VariableSelector returns a Language which uses given variable selector.
|
||||
// It must be combined with a Language that uses the vatiable selector. E.g. gval.Base().
|
||||
func VariableSelector(selector func(path Evaluables) Evaluable) Language {
|
||||
l := newLanguage()
|
||||
l.selector = selector
|
||||
return l
|
||||
}
|
||||
391
extra/server-discovery/vendor/github.com/PaesslerAG/gval/operator.go
generated
vendored
Normal file
391
extra/server-discovery/vendor/github.com/PaesslerAG/gval/operator.go
generated
vendored
Normal file
@@ -0,0 +1,391 @@
|
||||
package gval
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
type stage struct {
|
||||
Evaluable
|
||||
infixBuilder
|
||||
operatorPrecedence
|
||||
}
|
||||
|
||||
type stageStack []stage //operatorPrecedence in stacktStage is continuously, monotone ascending
|
||||
|
||||
func (s *stageStack) push(b stage) error {
|
||||
for len(*s) > 0 && s.peek().operatorPrecedence >= b.operatorPrecedence {
|
||||
a := s.pop()
|
||||
eval, err := a.infixBuilder(a.Evaluable, b.Evaluable)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if a.IsConst() && b.IsConst() {
|
||||
v, err := eval(nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Evaluable = constant(v)
|
||||
continue
|
||||
}
|
||||
b.Evaluable = eval
|
||||
}
|
||||
*s = append(*s, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stageStack) peek() stage {
|
||||
return (*s)[len(*s)-1]
|
||||
}
|
||||
|
||||
func (s *stageStack) pop() stage {
|
||||
a := s.peek()
|
||||
(*s) = (*s)[:len(*s)-1]
|
||||
return a
|
||||
}
|
||||
|
||||
type infixBuilder func(a, b Evaluable) (Evaluable, error)
|
||||
|
||||
func (l Language) isSymbolOperation(r rune) bool {
|
||||
_, in := l.operatorSymbols[r]
|
||||
return in
|
||||
}
|
||||
|
||||
func (l Language) isOperatorPrefix(op string) bool {
|
||||
for k := range l.operators {
|
||||
if strings.HasPrefix(k, op) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (op *infix) initiate(name string) {
|
||||
f := func(a, b interface{}) (interface{}, error) {
|
||||
return nil, fmt.Errorf("invalid operation (%T) %s (%T)", a, name, b)
|
||||
}
|
||||
if op.arbitrary != nil {
|
||||
f = op.arbitrary
|
||||
}
|
||||
for _, typeConvertion := range []bool{true, false} {
|
||||
if op.text != nil && (!typeConvertion || op.arbitrary == nil) {
|
||||
f = getStringOpFunc(op.text, f, typeConvertion)
|
||||
}
|
||||
if op.boolean != nil {
|
||||
f = getBoolOpFunc(op.boolean, f, typeConvertion)
|
||||
}
|
||||
if op.number != nil {
|
||||
f = getFloatOpFunc(op.number, f, typeConvertion)
|
||||
}
|
||||
if op.decimal != nil {
|
||||
f = getDecimalOpFunc(op.decimal, f, typeConvertion)
|
||||
}
|
||||
}
|
||||
if op.shortCircuit == nil {
|
||||
op.builder = func(a, b Evaluable) (Evaluable, error) {
|
||||
return func(c context.Context, x interface{}) (interface{}, error) {
|
||||
a, err := a(c, x)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b, err := b(c, x)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f(a, b)
|
||||
}, nil
|
||||
}
|
||||
return
|
||||
}
|
||||
shortF := op.shortCircuit
|
||||
op.builder = func(a, b Evaluable) (Evaluable, error) {
|
||||
return func(c context.Context, x interface{}) (interface{}, error) {
|
||||
a, err := a(c, x)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if r, ok := shortF(a); ok {
|
||||
return r, nil
|
||||
}
|
||||
b, err := b(c, x)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f(a, b)
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
type opFunc func(a, b interface{}) (interface{}, error)
|
||||
|
||||
func getStringOpFunc(s func(a, b string) (interface{}, error), f opFunc, typeConversion bool) opFunc {
|
||||
if typeConversion {
|
||||
return func(a, b interface{}) (interface{}, error) {
|
||||
if a != nil && b != nil {
|
||||
return s(fmt.Sprintf("%v", a), fmt.Sprintf("%v", b))
|
||||
}
|
||||
return f(a, b)
|
||||
}
|
||||
}
|
||||
return func(a, b interface{}) (interface{}, error) {
|
||||
s1, k := a.(string)
|
||||
s2, l := b.(string)
|
||||
if k && l {
|
||||
return s(s1, s2)
|
||||
}
|
||||
return f(a, b)
|
||||
}
|
||||
}
|
||||
func convertToBool(o interface{}) (bool, bool) {
|
||||
if b, ok := o.(bool); ok {
|
||||
return b, true
|
||||
}
|
||||
v := reflect.ValueOf(o)
|
||||
for o != nil && v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
if !v.IsValid() {
|
||||
return false, false
|
||||
}
|
||||
o = v.Interface()
|
||||
}
|
||||
if o == false || o == nil || o == "false" || o == "FALSE" {
|
||||
return false, true
|
||||
}
|
||||
if o == true || o == "true" || o == "TRUE" {
|
||||
return true, true
|
||||
}
|
||||
if f, ok := convertToFloat(o); ok {
|
||||
return f != 0., true
|
||||
}
|
||||
return false, false
|
||||
}
|
||||
func getBoolOpFunc(o func(a, b bool) (interface{}, error), f opFunc, typeConversion bool) opFunc {
|
||||
if typeConversion {
|
||||
return func(a, b interface{}) (interface{}, error) {
|
||||
x, k := convertToBool(a)
|
||||
y, l := convertToBool(b)
|
||||
if k && l {
|
||||
return o(x, y)
|
||||
}
|
||||
return f(a, b)
|
||||
}
|
||||
}
|
||||
return func(a, b interface{}) (interface{}, error) {
|
||||
x, k := a.(bool)
|
||||
y, l := b.(bool)
|
||||
if k && l {
|
||||
return o(x, y)
|
||||
}
|
||||
return f(a, b)
|
||||
}
|
||||
}
|
||||
func convertToFloat(o interface{}) (float64, bool) {
|
||||
if i, ok := o.(float64); ok {
|
||||
return i, true
|
||||
}
|
||||
v := reflect.ValueOf(o)
|
||||
for o != nil && v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
if !v.IsValid() {
|
||||
return 0, false
|
||||
}
|
||||
o = v.Interface()
|
||||
}
|
||||
switch v.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return float64(v.Int()), true
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
return float64(v.Uint()), true
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v.Float(), true
|
||||
}
|
||||
if s, ok := o.(string); ok {
|
||||
f, err := strconv.ParseFloat(s, 64)
|
||||
if err == nil {
|
||||
return f, true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
func getFloatOpFunc(o func(a, b float64) (interface{}, error), f opFunc, typeConversion bool) opFunc {
|
||||
if typeConversion {
|
||||
return func(a, b interface{}) (interface{}, error) {
|
||||
x, k := convertToFloat(a)
|
||||
y, l := convertToFloat(b)
|
||||
if k && l {
|
||||
return o(x, y)
|
||||
}
|
||||
|
||||
return f(a, b)
|
||||
}
|
||||
}
|
||||
return func(a, b interface{}) (interface{}, error) {
|
||||
x, k := a.(float64)
|
||||
y, l := b.(float64)
|
||||
if k && l {
|
||||
return o(x, y)
|
||||
}
|
||||
|
||||
return f(a, b)
|
||||
}
|
||||
}
|
||||
func convertToDecimal(o interface{}) (decimal.Decimal, bool) {
|
||||
if i, ok := o.(decimal.Decimal); ok {
|
||||
return i, true
|
||||
}
|
||||
if i, ok := o.(float64); ok {
|
||||
return decimal.NewFromFloat(i), true
|
||||
}
|
||||
v := reflect.ValueOf(o)
|
||||
for o != nil && v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
if !v.IsValid() {
|
||||
return decimal.Zero, false
|
||||
}
|
||||
o = v.Interface()
|
||||
}
|
||||
switch v.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return decimal.NewFromInt(v.Int()), true
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
return decimal.NewFromFloat(float64(v.Uint())), true
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return decimal.NewFromFloat(v.Float()), true
|
||||
}
|
||||
if s, ok := o.(string); ok {
|
||||
f, err := strconv.ParseFloat(s, 64)
|
||||
if err == nil {
|
||||
return decimal.NewFromFloat(f), true
|
||||
}
|
||||
}
|
||||
return decimal.Zero, false
|
||||
}
|
||||
func getDecimalOpFunc(o func(a, b decimal.Decimal) (interface{}, error), f opFunc, typeConversion bool) opFunc {
|
||||
if typeConversion {
|
||||
return func(a, b interface{}) (interface{}, error) {
|
||||
x, k := convertToDecimal(a)
|
||||
y, l := convertToDecimal(b)
|
||||
if k && l {
|
||||
return o(x, y)
|
||||
}
|
||||
|
||||
return f(a, b)
|
||||
}
|
||||
}
|
||||
return func(a, b interface{}) (interface{}, error) {
|
||||
x, k := a.(decimal.Decimal)
|
||||
y, l := b.(decimal.Decimal)
|
||||
if k && l {
|
||||
return o(x, y)
|
||||
}
|
||||
|
||||
return f(a, b)
|
||||
}
|
||||
}
|
||||
|
||||
type operator interface {
|
||||
merge(operator) operator
|
||||
precedence() operatorPrecedence
|
||||
initiate(name string)
|
||||
}
|
||||
|
||||
type operatorPrecedence uint8
|
||||
|
||||
func (pre operatorPrecedence) merge(op operator) operator {
|
||||
if op, ok := op.(operatorPrecedence); ok {
|
||||
if op > pre {
|
||||
return op
|
||||
}
|
||||
return pre
|
||||
}
|
||||
if op == nil {
|
||||
return pre
|
||||
}
|
||||
return op.merge(pre)
|
||||
}
|
||||
|
||||
func (pre operatorPrecedence) precedence() operatorPrecedence {
|
||||
return pre
|
||||
}
|
||||
|
||||
func (pre operatorPrecedence) initiate(name string) {}
|
||||
|
||||
type infix struct {
|
||||
operatorPrecedence
|
||||
number func(a, b float64) (interface{}, error)
|
||||
decimal func(a, b decimal.Decimal) (interface{}, error)
|
||||
boolean func(a, b bool) (interface{}, error)
|
||||
text func(a, b string) (interface{}, error)
|
||||
arbitrary func(a, b interface{}) (interface{}, error)
|
||||
shortCircuit func(a interface{}) (interface{}, bool)
|
||||
builder infixBuilder
|
||||
}
|
||||
|
||||
func (op infix) merge(op2 operator) operator {
|
||||
switch op2 := op2.(type) {
|
||||
case *infix:
|
||||
if op.number == nil {
|
||||
op.number = op2.number
|
||||
}
|
||||
if op.decimal == nil {
|
||||
op.decimal = op2.decimal
|
||||
}
|
||||
if op.boolean == nil {
|
||||
op.boolean = op2.boolean
|
||||
}
|
||||
if op.text == nil {
|
||||
op.text = op2.text
|
||||
}
|
||||
if op.arbitrary == nil {
|
||||
op.arbitrary = op2.arbitrary
|
||||
}
|
||||
if op.shortCircuit == nil {
|
||||
op.shortCircuit = op2.shortCircuit
|
||||
}
|
||||
}
|
||||
if op2 != nil && op2.precedence() > op.operatorPrecedence {
|
||||
op.operatorPrecedence = op2.precedence()
|
||||
}
|
||||
return &op
|
||||
}
|
||||
|
||||
type directInfix struct {
|
||||
operatorPrecedence
|
||||
infixBuilder
|
||||
}
|
||||
|
||||
func (op directInfix) merge(op2 operator) operator {
|
||||
switch op2 := op2.(type) {
|
||||
case operatorPrecedence:
|
||||
op.operatorPrecedence = op2
|
||||
}
|
||||
if op2 != nil && op2.precedence() > op.operatorPrecedence {
|
||||
op.operatorPrecedence = op2.precedence()
|
||||
}
|
||||
return op
|
||||
}
|
||||
|
||||
type extension func(context.Context, *Parser) (Evaluable, error)
|
||||
|
||||
type postfix struct {
|
||||
operatorPrecedence
|
||||
f func(context.Context, *Parser, Evaluable, operatorPrecedence) (Evaluable, error)
|
||||
}
|
||||
|
||||
func (op postfix) merge(op2 operator) operator {
|
||||
switch op2 := op2.(type) {
|
||||
case postfix:
|
||||
if op2.f != nil {
|
||||
op.f = op2.f
|
||||
}
|
||||
}
|
||||
if op2 != nil && op2.precedence() > op.operatorPrecedence {
|
||||
op.operatorPrecedence = op2.precedence()
|
||||
}
|
||||
return op
|
||||
}
|
||||
347
extra/server-discovery/vendor/github.com/PaesslerAG/gval/parse.go
generated
vendored
Normal file
347
extra/server-discovery/vendor/github.com/PaesslerAG/gval/parse.go
generated
vendored
Normal file
@@ -0,0 +1,347 @@
|
||||
package gval
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"text/scanner"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
//ParseExpression scans an expression into an Evaluable.
|
||||
func (p *Parser) ParseExpression(c context.Context) (eval Evaluable, err error) {
|
||||
stack := stageStack{}
|
||||
for {
|
||||
eval, err = p.ParseNextExpression(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if stage, err := p.parseOperator(c, &stack, eval); err != nil {
|
||||
return nil, err
|
||||
} else if err = stack.push(stage); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if stack.peek().infixBuilder == nil {
|
||||
return stack.pop().Evaluable, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//ParseNextExpression scans the expression ignoring following operators
|
||||
func (p *Parser) ParseNextExpression(c context.Context) (eval Evaluable, err error) {
|
||||
scan := p.Scan()
|
||||
ex, ok := p.prefixes[scan]
|
||||
if !ok {
|
||||
if scan != scanner.EOF && p.def != nil {
|
||||
return p.def(c, p)
|
||||
}
|
||||
return nil, p.Expected("extensions")
|
||||
}
|
||||
return ex(c, p)
|
||||
}
|
||||
|
||||
// ParseSublanguage sets the next language for this parser to parse and calls
|
||||
// its initialization function, usually ParseExpression.
|
||||
func (p *Parser) ParseSublanguage(c context.Context, l Language) (Evaluable, error) {
|
||||
if p.isCamouflaged() {
|
||||
panic("can not ParseSublanguage() on camouflaged Parser")
|
||||
}
|
||||
curLang := p.Language
|
||||
curWhitespace := p.scanner.Whitespace
|
||||
curMode := p.scanner.Mode
|
||||
curIsIdentRune := p.scanner.IsIdentRune
|
||||
|
||||
p.Language = l
|
||||
p.resetScannerProperties()
|
||||
|
||||
defer func() {
|
||||
p.Language = curLang
|
||||
p.scanner.Whitespace = curWhitespace
|
||||
p.scanner.Mode = curMode
|
||||
p.scanner.IsIdentRune = curIsIdentRune
|
||||
}()
|
||||
|
||||
return p.parse(c)
|
||||
}
|
||||
|
||||
func (p *Parser) parse(c context.Context) (Evaluable, error) {
|
||||
if p.init != nil {
|
||||
return p.init(c, p)
|
||||
}
|
||||
|
||||
return p.ParseExpression(c)
|
||||
}
|
||||
|
||||
func parseString(c context.Context, p *Parser) (Evaluable, error) {
|
||||
s, err := strconv.Unquote(p.TokenText())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse string: %s", err)
|
||||
}
|
||||
return p.Const(s), nil
|
||||
}
|
||||
|
||||
func parseNumber(c context.Context, p *Parser) (Evaluable, error) {
|
||||
n, err := strconv.ParseFloat(p.TokenText(), 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.Const(n), nil
|
||||
}
|
||||
|
||||
func parseDecimal(c context.Context, p *Parser) (Evaluable, error) {
|
||||
n, err := strconv.ParseFloat(p.TokenText(), 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.Const(decimal.NewFromFloat(n)), nil
|
||||
}
|
||||
|
||||
func parseParentheses(c context.Context, p *Parser) (Evaluable, error) {
|
||||
eval, err := p.ParseExpression(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch p.Scan() {
|
||||
case ')':
|
||||
return eval, nil
|
||||
default:
|
||||
return nil, p.Expected("parentheses", ')')
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) parseOperator(c context.Context, stack *stageStack, eval Evaluable) (st stage, err error) {
|
||||
for {
|
||||
scan := p.Scan()
|
||||
op := p.TokenText()
|
||||
mustOp := false
|
||||
if p.isSymbolOperation(scan) {
|
||||
scan = p.Peek()
|
||||
for p.isSymbolOperation(scan) && p.isOperatorPrefix(op+string(scan)) {
|
||||
mustOp = true
|
||||
op += string(scan)
|
||||
p.Next()
|
||||
scan = p.Peek()
|
||||
}
|
||||
} else if scan != scanner.Ident {
|
||||
p.Camouflage("operator")
|
||||
return stage{Evaluable: eval}, nil
|
||||
}
|
||||
switch operator := p.operators[op].(type) {
|
||||
case *infix:
|
||||
return stage{
|
||||
Evaluable: eval,
|
||||
infixBuilder: operator.builder,
|
||||
operatorPrecedence: operator.operatorPrecedence,
|
||||
}, nil
|
||||
case directInfix:
|
||||
return stage{
|
||||
Evaluable: eval,
|
||||
infixBuilder: operator.infixBuilder,
|
||||
operatorPrecedence: operator.operatorPrecedence,
|
||||
}, nil
|
||||
case postfix:
|
||||
if err = stack.push(stage{
|
||||
operatorPrecedence: operator.operatorPrecedence,
|
||||
Evaluable: eval,
|
||||
}); err != nil {
|
||||
return stage{}, err
|
||||
}
|
||||
eval, err = operator.f(c, p, stack.pop().Evaluable, operator.operatorPrecedence)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if !mustOp {
|
||||
p.Camouflage("operator")
|
||||
return stage{Evaluable: eval}, nil
|
||||
}
|
||||
return stage{}, fmt.Errorf("unknown operator %s", op)
|
||||
}
|
||||
}
|
||||
|
||||
func parseIdent(c context.Context, p *Parser) (call string, alternative func() (Evaluable, error), err error) {
|
||||
token := p.TokenText()
|
||||
return token,
|
||||
func() (Evaluable, error) {
|
||||
fullname := token
|
||||
|
||||
keys := []Evaluable{p.Const(token)}
|
||||
for {
|
||||
scan := p.Scan()
|
||||
switch scan {
|
||||
case '.':
|
||||
scan = p.Scan()
|
||||
switch scan {
|
||||
case scanner.Ident:
|
||||
token = p.TokenText()
|
||||
keys = append(keys, p.Const(token))
|
||||
default:
|
||||
return nil, p.Expected("field", scanner.Ident)
|
||||
}
|
||||
case '(':
|
||||
args, err := p.parseArguments(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.callEvaluable(fullname, p.Var(keys...), args...), nil
|
||||
case '[':
|
||||
key, err := p.ParseExpression(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch p.Scan() {
|
||||
case ']':
|
||||
keys = append(keys, key)
|
||||
default:
|
||||
return nil, p.Expected("array key", ']')
|
||||
}
|
||||
default:
|
||||
p.Camouflage("variable", '.', '(', '[')
|
||||
return p.Var(keys...), nil
|
||||
}
|
||||
}
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
func (p *Parser) parseArguments(c context.Context) (args []Evaluable, err error) {
|
||||
if p.Scan() == ')' {
|
||||
return
|
||||
}
|
||||
p.Camouflage("scan arguments", ')')
|
||||
for {
|
||||
arg, err := p.ParseExpression(c)
|
||||
args = append(args, arg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch p.Scan() {
|
||||
case ')':
|
||||
return args, nil
|
||||
case ',':
|
||||
default:
|
||||
return nil, p.Expected("arguments", ')', ',')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func inArray(a, b interface{}) (interface{}, error) {
|
||||
col, ok := b.([]interface{})
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected type []interface{} for in operator but got %T", b)
|
||||
}
|
||||
for _, value := range col {
|
||||
if reflect.DeepEqual(a, value) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func parseIf(c context.Context, p *Parser, e Evaluable) (Evaluable, error) {
|
||||
a, err := p.ParseExpression(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b := p.Const(nil)
|
||||
switch p.Scan() {
|
||||
case ':':
|
||||
b, err = p.ParseExpression(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case scanner.EOF:
|
||||
default:
|
||||
return nil, p.Expected("<> ? <> : <>", ':', scanner.EOF)
|
||||
}
|
||||
return func(c context.Context, v interface{}) (interface{}, error) {
|
||||
x, err := e(c, v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if valX := reflect.ValueOf(x); x == nil || valX.IsZero() {
|
||||
return b(c, v)
|
||||
}
|
||||
return a(c, v)
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseJSONArray(c context.Context, p *Parser) (Evaluable, error) {
|
||||
evals := []Evaluable{}
|
||||
for {
|
||||
switch p.Scan() {
|
||||
default:
|
||||
p.Camouflage("array", ',', ']')
|
||||
eval, err := p.ParseExpression(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
evals = append(evals, eval)
|
||||
case ',':
|
||||
case ']':
|
||||
return func(c context.Context, v interface{}) (interface{}, error) {
|
||||
vs := make([]interface{}, len(evals))
|
||||
for i, e := range evals {
|
||||
eval, err := e(c, v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vs[i] = eval
|
||||
}
|
||||
|
||||
return vs, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parseJSONObject(c context.Context, p *Parser) (Evaluable, error) {
|
||||
type kv struct {
|
||||
key Evaluable
|
||||
value Evaluable
|
||||
}
|
||||
evals := []kv{}
|
||||
for {
|
||||
switch p.Scan() {
|
||||
default:
|
||||
p.Camouflage("object", ',', '}')
|
||||
key, err := p.ParseExpression(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if p.Scan() != ':' {
|
||||
if err != nil {
|
||||
return nil, p.Expected("object", ':')
|
||||
}
|
||||
}
|
||||
value, err := p.ParseExpression(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
evals = append(evals, kv{key, value})
|
||||
case ',':
|
||||
case '}':
|
||||
return func(c context.Context, v interface{}) (interface{}, error) {
|
||||
vs := map[string]interface{}{}
|
||||
for _, e := range evals {
|
||||
value, err := e.value(c, v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key, err := e.key.EvalString(c, v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vs[key] = value
|
||||
}
|
||||
return vs, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
147
extra/server-discovery/vendor/github.com/PaesslerAG/gval/parser.go
generated
vendored
Normal file
147
extra/server-discovery/vendor/github.com/PaesslerAG/gval/parser.go
generated
vendored
Normal file
@@ -0,0 +1,147 @@
|
||||
package gval
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"text/scanner"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
//Parser parses expressions in a Language into an Evaluable
|
||||
type Parser struct {
|
||||
scanner scanner.Scanner
|
||||
Language
|
||||
lastScan rune
|
||||
camouflage error
|
||||
}
|
||||
|
||||
func newParser(expression string, l Language) *Parser {
|
||||
sc := scanner.Scanner{}
|
||||
sc.Init(strings.NewReader(expression))
|
||||
sc.Error = func(*scanner.Scanner, string) {}
|
||||
sc.Filename = expression + "\t"
|
||||
p := &Parser{scanner: sc, Language: l}
|
||||
p.resetScannerProperties()
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Parser) resetScannerProperties() {
|
||||
p.scanner.Whitespace = scanner.GoWhitespace
|
||||
p.scanner.Mode = scanner.GoTokens
|
||||
p.scanner.IsIdentRune = func(r rune, pos int) bool {
|
||||
return unicode.IsLetter(r) || r == '_' || (pos > 0 && unicode.IsDigit(r))
|
||||
}
|
||||
}
|
||||
|
||||
// SetWhitespace sets the behavior of the whitespace matcher. The given
|
||||
// characters must be less than or equal to 0x20 (' ').
|
||||
func (p *Parser) SetWhitespace(chars ...rune) {
|
||||
var mask uint64
|
||||
for _, char := range chars {
|
||||
mask |= 1 << char
|
||||
}
|
||||
|
||||
p.scanner.Whitespace = mask
|
||||
}
|
||||
|
||||
// SetMode sets the tokens that the underlying scanner will match.
|
||||
func (p *Parser) SetMode(mode uint) {
|
||||
p.scanner.Mode = mode
|
||||
}
|
||||
|
||||
// SetIsIdentRuneFunc sets the function that matches ident characters in the
|
||||
// underlying scanner.
|
||||
func (p *Parser) SetIsIdentRuneFunc(fn func(ch rune, i int) bool) {
|
||||
p.scanner.IsIdentRune = fn
|
||||
}
|
||||
|
||||
// Scan reads the next token or Unicode character from source and returns it.
|
||||
// It only recognizes tokens t for which the respective Mode bit (1<<-t) is set.
|
||||
// It returns scanner.EOF at the end of the source.
|
||||
func (p *Parser) Scan() rune {
|
||||
if p.isCamouflaged() {
|
||||
p.camouflage = nil
|
||||
return p.lastScan
|
||||
}
|
||||
p.camouflage = nil
|
||||
p.lastScan = p.scanner.Scan()
|
||||
return p.lastScan
|
||||
}
|
||||
|
||||
func (p *Parser) isCamouflaged() bool {
|
||||
return p.camouflage != nil && p.camouflage != errCamouflageAfterNext
|
||||
}
|
||||
|
||||
// Camouflage rewind the last Scan(). The Parser holds the camouflage error until
|
||||
// the next Scan()
|
||||
// Do not call Rewind() on a camouflaged Parser
|
||||
func (p *Parser) Camouflage(unit string, expected ...rune) {
|
||||
if p.isCamouflaged() {
|
||||
panic(fmt.Errorf("can only Camouflage() after Scan(): %v", p.camouflage))
|
||||
}
|
||||
p.camouflage = p.Expected(unit, expected...)
|
||||
}
|
||||
|
||||
// Peek returns the next Unicode character in the source without advancing
|
||||
// the scanner. It returns EOF if the scanner's position is at the last
|
||||
// character of the source.
|
||||
// Do not call Peek() on a camouflaged Parser
|
||||
func (p *Parser) Peek() rune {
|
||||
if p.isCamouflaged() {
|
||||
panic("can not Peek() on camouflaged Parser")
|
||||
}
|
||||
return p.scanner.Peek()
|
||||
}
|
||||
|
||||
var errCamouflageAfterNext = fmt.Errorf("Camouflage() after Next()")
|
||||
|
||||
// Next reads and returns the next Unicode character.
|
||||
// It returns EOF at the end of the source.
|
||||
// Do not call Next() on a camouflaged Parser
|
||||
func (p *Parser) Next() rune {
|
||||
if p.isCamouflaged() {
|
||||
panic("can not Next() on camouflaged Parser")
|
||||
}
|
||||
p.camouflage = errCamouflageAfterNext
|
||||
return p.scanner.Next()
|
||||
}
|
||||
|
||||
// TokenText returns the string corresponding to the most recently scanned token.
|
||||
// Valid after calling Scan().
|
||||
func (p *Parser) TokenText() string {
|
||||
return p.scanner.TokenText()
|
||||
}
|
||||
|
||||
//Expected returns an error signaling an unexpected Scan() result
|
||||
func (p *Parser) Expected(unit string, expected ...rune) error {
|
||||
return unexpectedRune{unit, expected, p.lastScan}
|
||||
}
|
||||
|
||||
type unexpectedRune struct {
|
||||
unit string
|
||||
expected []rune
|
||||
got rune
|
||||
}
|
||||
|
||||
func (err unexpectedRune) Error() string {
|
||||
exp := bytes.Buffer{}
|
||||
runes := err.expected
|
||||
switch len(runes) {
|
||||
default:
|
||||
for _, r := range runes[:len(runes)-2] {
|
||||
exp.WriteString(scanner.TokenString(r))
|
||||
exp.WriteString(", ")
|
||||
}
|
||||
fallthrough
|
||||
case 2:
|
||||
exp.WriteString(scanner.TokenString(runes[len(runes)-2]))
|
||||
exp.WriteString(" or ")
|
||||
fallthrough
|
||||
case 1:
|
||||
exp.WriteString(scanner.TokenString(runes[len(runes)-1]))
|
||||
case 0:
|
||||
return fmt.Sprintf("unexpected %s while scanning %s", scanner.TokenString(err.got), err.unit)
|
||||
}
|
||||
return fmt.Sprintf("unexpected %s while scanning %s expected %s", scanner.TokenString(err.got), err.unit, exp.String())
|
||||
}
|
||||
BIN
extra/server-discovery/vendor/github.com/PaesslerAG/gval/prtg-batmin-gopher.png
generated
vendored
Normal file
BIN
extra/server-discovery/vendor/github.com/PaesslerAG/gval/prtg-batmin-gopher.png
generated
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
14
extra/server-discovery/vendor/github.com/SentimensRG/ctx/.gitignore
generated
vendored
Normal file
14
extra/server-discovery/vendor/github.com/SentimensRG/ctx/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
||||
.glide/
|
||||
21
extra/server-discovery/vendor/github.com/SentimensRG/ctx/LICENSE
generated
vendored
Normal file
21
extra/server-discovery/vendor/github.com/SentimensRG/ctx/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Sentimens Research Group
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
63
extra/server-discovery/vendor/github.com/SentimensRG/ctx/README.md
generated
vendored
Normal file
63
extra/server-discovery/vendor/github.com/SentimensRG/ctx/README.md
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
# ctx
|
||||
|
||||
Composable utilities for Go contexts.
|
||||
|
||||
[](https://godoc.org/github.com/SentimensRG/ctx)
|
||||
[](https://goreportcard.com/report/github.com/SentimensRG/ctx)
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
go get -u github.com/SentimensRG/ctx
|
||||
```
|
||||
|
||||
## Overview
|
||||
|
||||
The `ctx` package provides utilites for working with data structures satisfying
|
||||
the `ctx.Doner` interface, most notably `context.Context`:
|
||||
|
||||
```go
|
||||
type Doner interface {
|
||||
Done() <-chan struct{}
|
||||
}
|
||||
```
|
||||
|
||||
The functions in `ctx` are appropriate for operations that do not preserve the
|
||||
values in a context, e.g.: joining several contexts together.
|
||||
|
||||
## Subpackages
|
||||
|
||||
- [sigctx](https://github.com/SentimensRG/ctx/tree/master/sigctx): contexts for graceful shutdown
|
||||
- [refctx](https://github.com/SentimensRG/ctx/tree/master/refctx): contexts linked to a reference-counter
|
||||
- [mergectx](https://github.com/SentimensRG/ctx/tree/master/mergectx): utilities for merging `context.Context` instances while preserving values, errors and deadlines.
|
||||
|
||||
## RFC
|
||||
|
||||
If you find this useful please let me know: <l.thibault@sentimens.com>
|
||||
|
||||
Seriously, even if you just used it in your weekend project, I'd like to hear
|
||||
about it :)
|
||||
|
||||
## License
|
||||
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2017 Sentimens Research Group, LLC
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
184
extra/server-discovery/vendor/github.com/SentimensRG/ctx/ctx.go
generated
vendored
Normal file
184
extra/server-discovery/vendor/github.com/SentimensRG/ctx/ctx.go
generated
vendored
Normal file
@@ -0,0 +1,184 @@
|
||||
package ctx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Binder is the interface that wraps the basic Bind method.
|
||||
// Bind executes logic until the Doner completes. Implementations of Bind must
|
||||
// not return until the Doner has completed.
|
||||
type Binder interface {
|
||||
Bind(Doner)
|
||||
}
|
||||
|
||||
// BindFunc is an adapter to allow the use of ordinary functions as Binders.
|
||||
type BindFunc func(Doner)
|
||||
|
||||
// Bind executes logic until the Doner completes. It satisfies the Binder
|
||||
// interface.
|
||||
func (f BindFunc) Bind(d Doner) { f(d) }
|
||||
|
||||
// Doner can block until something is done
|
||||
type Doner interface {
|
||||
Done() <-chan struct{}
|
||||
}
|
||||
|
||||
// C is a basic implementation of Doner
|
||||
type C <-chan struct{}
|
||||
|
||||
// Background is the ctx analog to context.Background(). It never fires.
|
||||
func Background() C {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Done returns a channel that receives when an action is complete
|
||||
func (dc C) Done() <-chan struct{} { return dc }
|
||||
|
||||
type ctx struct {
|
||||
Doner
|
||||
}
|
||||
|
||||
// Deadline returns the time when work done on behalf of this context
|
||||
// should be canceled. Deadline returns ok==false when no deadline is
|
||||
// set. Successive calls to Deadline return the same results.
|
||||
func (ctx) Deadline() (deadline time.Time, ok bool) {
|
||||
return
|
||||
}
|
||||
|
||||
func (c ctx) Err() error {
|
||||
select {
|
||||
case <-c.Done():
|
||||
return context.Canceled
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c ctx) Value(interface{}) (v interface{}) {
|
||||
return
|
||||
}
|
||||
|
||||
// AsContext creates a context that fires when the Doner fires
|
||||
func AsContext(d Doner) context.Context {
|
||||
return ctx{d}
|
||||
}
|
||||
|
||||
// After time time has elapsed, the Doner fires
|
||||
func After(d time.Duration) C {
|
||||
ch := make(chan struct{})
|
||||
go func() {
|
||||
<-time.After(d)
|
||||
close(ch)
|
||||
}()
|
||||
return ch
|
||||
}
|
||||
|
||||
// WithCancel returns a new Doner that can be cancelled via the associated
|
||||
// function
|
||||
func WithCancel(d Doner) (C, func()) {
|
||||
var closer sync.Once
|
||||
cq := make(chan struct{})
|
||||
cancel := func() { closer.Do(func() { close(cq) }) }
|
||||
|
||||
go func() {
|
||||
select {
|
||||
case <-cq:
|
||||
case <-d.Done():
|
||||
cancel()
|
||||
}
|
||||
}()
|
||||
|
||||
return cq, cancel
|
||||
}
|
||||
|
||||
// Tick returns a <-chan whose range ends when the underlying context cancels
|
||||
func Tick(d Doner) <-chan struct{} {
|
||||
c := make(chan struct{})
|
||||
cq := d.Done()
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-cq:
|
||||
close(c)
|
||||
return
|
||||
default:
|
||||
select {
|
||||
case c <- struct{}{}:
|
||||
case <-cq:
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
return c
|
||||
}
|
||||
|
||||
// Defer guarantees that a function will be called after a context has cancelled
|
||||
func Defer(d Doner, cb func()) {
|
||||
go func() {
|
||||
<-d.Done()
|
||||
cb()
|
||||
}()
|
||||
}
|
||||
|
||||
// Link returns a channel that fires if ANY of the constituent Doners have fired
|
||||
func Link(doners ...Doner) C {
|
||||
c := make(chan struct{})
|
||||
cancel := func() { close(c) }
|
||||
|
||||
var once sync.Once
|
||||
for _, d := range doners {
|
||||
Defer(d, func() { once.Do(cancel) })
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// Join returns a channel that receives when all constituent Doners have fired
|
||||
func Join(doners ...Doner) C {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(doners))
|
||||
for _, d := range doners {
|
||||
Defer(d, wg.Done)
|
||||
}
|
||||
|
||||
cq := make(chan struct{})
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(cq)
|
||||
}()
|
||||
return cq
|
||||
}
|
||||
|
||||
// FTick calls a function in a loop until the Doner has fired
|
||||
func FTick(d Doner, f func()) {
|
||||
for range Tick(d) {
|
||||
f()
|
||||
}
|
||||
}
|
||||
|
||||
// FTickInterval calls a function repeatedly at a given internval, until the Doner
|
||||
// has fired. Note that FTickInterval ignores the time spent executing a function,
|
||||
// and instead guarantees an interval of `t` between of return of the previous
|
||||
// function call and the invocation of the next function call.
|
||||
func FTickInterval(d Doner, t time.Duration, f func()) {
|
||||
for {
|
||||
select {
|
||||
case <-d.Done():
|
||||
return
|
||||
case <-time.After(t):
|
||||
f()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FDone returns a doner that fires when the function returns or panics
|
||||
func FDone(f func()) C {
|
||||
ch := make(chan struct{})
|
||||
go func() {
|
||||
defer close(ch)
|
||||
f()
|
||||
}()
|
||||
return ch
|
||||
}
|
||||
63
extra/server-discovery/vendor/github.com/SentimensRG/ctx/sigctx/README.md
generated
vendored
Normal file
63
extra/server-discovery/vendor/github.com/SentimensRG/ctx/sigctx/README.md
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
# sigctx
|
||||
|
||||
Package `sigctx` provides contexts for graceful shutdown.
|
||||
|
||||
The `sigctx` package provides a context that terminates when it receives a
|
||||
SIGINT or SIGTERM. This provides a convenient mechanism for triggering
|
||||
graceful application shutdown.
|
||||
|
||||
`sigctx.New` returns a `ctx.C`, which implements the ubiquitous `ctx.Doner`
|
||||
interface. It fires when either SIGINT or SIGTERM is caught.
|
||||
|
||||
## Examples
|
||||
|
||||
```go
|
||||
import (
|
||||
"log"
|
||||
"github.com/SentimensRG/ctx/sigctx"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx := sigctx.New() // returns a regular context.Context
|
||||
|
||||
<-ctx.Done() // will unblock on SIGINT and SIGTERM
|
||||
log.Println("exiting.")
|
||||
}
|
||||
```
|
||||
|
||||
`sigctx.Tick` can be used to react to streams of signals. For example, you can
|
||||
implement a graceful shutdown attempt, followed by a forced shutdown.
|
||||
|
||||
```go
|
||||
import (
|
||||
"log"
|
||||
"github.com/SentimensRG/ctx/sigctx"
|
||||
"github.com/SentimensRG/ctx"
|
||||
)
|
||||
|
||||
func main() {
|
||||
t := sigctx.Tick()
|
||||
d, cancel := ctx.WithCancel(ctx.Background())
|
||||
|
||||
go func() {
|
||||
defer cancel()
|
||||
|
||||
go func() {
|
||||
// business logic goes here
|
||||
}()
|
||||
|
||||
<-t
|
||||
log.Println("attempting graceful shutdown - press Ctrl + c again to force quit")
|
||||
go func() {
|
||||
defer cancel()
|
||||
// cleanup logic goes here
|
||||
}()
|
||||
|
||||
<-t
|
||||
log.Println("forcing close")
|
||||
}()
|
||||
|
||||
<-d.Done()
|
||||
}
|
||||
|
||||
```
|
||||
60
extra/server-discovery/vendor/github.com/SentimensRG/ctx/sigctx/sigctx.go
generated
vendored
Normal file
60
extra/server-discovery/vendor/github.com/SentimensRG/ctx/sigctx/sigctx.go
generated
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
// Package sigctx provides a context that expires when a SIGINT or SIGTERM is
|
||||
// received.
|
||||
package sigctx
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/SentimensRG/ctx"
|
||||
)
|
||||
|
||||
var (
|
||||
c ctx.C
|
||||
sigCh chan os.Signal
|
||||
initC, initSig sync.Once
|
||||
)
|
||||
|
||||
func initSigCh() {
|
||||
sigCh = make(chan os.Signal, 1)
|
||||
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
|
||||
}
|
||||
|
||||
// New signal-bound ctx.C that terminates when either SIGINT or SIGTERM
|
||||
// is caught.
|
||||
func New() ctx.C {
|
||||
initC.Do(func() {
|
||||
initSig.Do(initSigCh)
|
||||
|
||||
dc := make(chan struct{})
|
||||
c = dc
|
||||
|
||||
go func() {
|
||||
select {
|
||||
case <-sigCh:
|
||||
close(dc)
|
||||
case <-c.Done():
|
||||
}
|
||||
}()
|
||||
})
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// Tick returns a channel that recvs each time a either SIGINT or SIGTERM are
|
||||
// caught.
|
||||
func Tick() <-chan struct{} {
|
||||
initSig.Do(initSigCh)
|
||||
|
||||
dc := make(chan struct{})
|
||||
go func() {
|
||||
for {
|
||||
<-sigCh
|
||||
dc <- struct{}{}
|
||||
}
|
||||
}()
|
||||
|
||||
return dc
|
||||
}
|
||||
15
extra/server-discovery/vendor/github.com/asaskevich/govalidator/.gitignore
generated
vendored
Normal file
15
extra/server-discovery/vendor/github.com/asaskevich/govalidator/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
bin/
|
||||
.idea/
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
12
extra/server-discovery/vendor/github.com/asaskevich/govalidator/.travis.yml
generated
vendored
Normal file
12
extra/server-discovery/vendor/github.com/asaskevich/govalidator/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
language: go
|
||||
dist: xenial
|
||||
go:
|
||||
- '1.10'
|
||||
- '1.11'
|
||||
- '1.12'
|
||||
- '1.13'
|
||||
- 'tip'
|
||||
|
||||
script:
|
||||
- go test -coverpkg=./... -coverprofile=coverage.info -timeout=5s
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
43
extra/server-discovery/vendor/github.com/asaskevich/govalidator/CODE_OF_CONDUCT.md
generated
vendored
Normal file
43
extra/server-discovery/vendor/github.com/asaskevich/govalidator/CODE_OF_CONDUCT.md
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
# Contributor Code of Conduct
|
||||
|
||||
This project adheres to [The Code Manifesto](http://codemanifesto.com)
|
||||
as its guidelines for contributor interactions.
|
||||
|
||||
## The Code Manifesto
|
||||
|
||||
We want to work in an ecosystem that empowers developers to reach their
|
||||
potential — one that encourages growth and effective collaboration. A space
|
||||
that is safe for all.
|
||||
|
||||
A space such as this benefits everyone that participates in it. It encourages
|
||||
new developers to enter our field. It is through discussion and collaboration
|
||||
that we grow, and through growth that we improve.
|
||||
|
||||
In the effort to create such a place, we hold to these values:
|
||||
|
||||
1. **Discrimination limits us.** This includes discrimination on the basis of
|
||||
race, gender, sexual orientation, gender identity, age, nationality,
|
||||
technology and any other arbitrary exclusion of a group of people.
|
||||
2. **Boundaries honor us.** Your comfort levels are not everyone’s comfort
|
||||
levels. Remember that, and if brought to your attention, heed it.
|
||||
3. **We are our biggest assets.** None of us were born masters of our trade.
|
||||
Each of us has been helped along the way. Return that favor, when and where
|
||||
you can.
|
||||
4. **We are resources for the future.** As an extension of #3, share what you
|
||||
know. Make yourself a resource to help those that come after you.
|
||||
5. **Respect defines us.** Treat others as you wish to be treated. Make your
|
||||
discussions, criticisms and debates from a position of respectfulness. Ask
|
||||
yourself, is it true? Is it necessary? Is it constructive? Anything less is
|
||||
unacceptable.
|
||||
6. **Reactions require grace.** Angry responses are valid, but abusive language
|
||||
and vindictive actions are toxic. When something happens that offends you,
|
||||
handle it assertively, but be respectful. Escalate reasonably, and try to
|
||||
allow the offender an opportunity to explain themselves, and possibly
|
||||
correct the issue.
|
||||
7. **Opinions are just that: opinions.** Each and every one of us, due to our
|
||||
background and upbringing, have varying opinions. That is perfectly
|
||||
acceptable. Remember this: if you respect your own opinions, you should
|
||||
respect the opinions of others.
|
||||
8. **To err is human.** You might not intend it, but mistakes do happen and
|
||||
contribute to build experience. Tolerate honest mistakes, and don't
|
||||
hesitate to apologize if you make one yourself.
|
||||
63
extra/server-discovery/vendor/github.com/asaskevich/govalidator/CONTRIBUTING.md
generated
vendored
Normal file
63
extra/server-discovery/vendor/github.com/asaskevich/govalidator/CONTRIBUTING.md
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
#### Support
|
||||
If you do have a contribution to the package, feel free to create a Pull Request or an Issue.
|
||||
|
||||
#### What to contribute
|
||||
If you don't know what to do, there are some features and functions that need to be done
|
||||
|
||||
- [ ] Refactor code
|
||||
- [ ] Edit docs and [README](https://github.com/asaskevich/govalidator/README.md): spellcheck, grammar and typo check
|
||||
- [ ] Create actual list of contributors and projects that currently using this package
|
||||
- [ ] Resolve [issues and bugs](https://github.com/asaskevich/govalidator/issues)
|
||||
- [ ] Update actual [list of functions](https://github.com/asaskevich/govalidator#list-of-functions)
|
||||
- [ ] Update [list of validators](https://github.com/asaskevich/govalidator#validatestruct-2) that available for `ValidateStruct` and add new
|
||||
- [ ] Implement new validators: `IsFQDN`, `IsIMEI`, `IsPostalCode`, `IsISIN`, `IsISRC` etc
|
||||
- [x] Implement [validation by maps](https://github.com/asaskevich/govalidator/issues/224)
|
||||
- [ ] Implement fuzzing testing
|
||||
- [ ] Implement some struct/map/array utilities
|
||||
- [ ] Implement map/array validation
|
||||
- [ ] Implement benchmarking
|
||||
- [ ] Implement batch of examples
|
||||
- [ ] Look at forks for new features and fixes
|
||||
|
||||
#### Advice
|
||||
Feel free to create what you want, but keep in mind when you implement new features:
|
||||
- Code must be clear and readable, names of variables/constants clearly describes what they are doing
|
||||
- Public functions must be documented and described in source file and added to README.md to the list of available functions
|
||||
- There are must be unit-tests for any new functions and improvements
|
||||
|
||||
## Financial contributions
|
||||
|
||||
We also welcome financial contributions in full transparency on our [open collective](https://opencollective.com/govalidator).
|
||||
Anyone can file an expense. If the expense makes sense for the development of the community, it will be "merged" in the ledger of our open collective by the core contributors and the person who filed the expense will be reimbursed.
|
||||
|
||||
|
||||
## Credits
|
||||
|
||||
|
||||
### Contributors
|
||||
|
||||
Thank you to all the people who have already contributed to govalidator!
|
||||
<a href="https://github.com/asaskevich/govalidator/graphs/contributors"><img src="https://opencollective.com/govalidator/contributors.svg?width=890" /></a>
|
||||
|
||||
|
||||
### Backers
|
||||
|
||||
Thank you to all our backers! [[Become a backer](https://opencollective.com/govalidator#backer)]
|
||||
|
||||
<a href="https://opencollective.com/govalidator#backers" target="_blank"><img src="https://opencollective.com/govalidator/backers.svg?width=890"></a>
|
||||
|
||||
|
||||
### Sponsors
|
||||
|
||||
Thank you to all our sponsors! (please ask your company to also support this open source project by [becoming a sponsor](https://opencollective.com/govalidator#sponsor))
|
||||
|
||||
<a href="https://opencollective.com/govalidator/sponsor/0/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/0/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/govalidator/sponsor/1/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/1/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/govalidator/sponsor/2/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/2/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/govalidator/sponsor/3/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/3/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/govalidator/sponsor/4/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/4/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/govalidator/sponsor/5/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/5/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/govalidator/sponsor/6/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/6/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/govalidator/sponsor/7/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/7/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/govalidator/sponsor/8/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/8/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/govalidator/sponsor/9/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/9/avatar.svg"></a>
|
||||
21
extra/server-discovery/vendor/github.com/asaskevich/govalidator/LICENSE
generated
vendored
Normal file
21
extra/server-discovery/vendor/github.com/asaskevich/govalidator/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014-2020 Alex Saskevich
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
622
extra/server-discovery/vendor/github.com/asaskevich/govalidator/README.md
generated
vendored
Normal file
622
extra/server-discovery/vendor/github.com/asaskevich/govalidator/README.md
generated
vendored
Normal file
@@ -0,0 +1,622 @@
|
||||
govalidator
|
||||
===========
|
||||
[](https://gitter.im/asaskevich/govalidator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [](https://godoc.org/github.com/asaskevich/govalidator)
|
||||
[](https://travis-ci.org/asaskevich/govalidator)
|
||||
[](https://codecov.io/gh/asaskevich/govalidator) [](https://goreportcard.com/report/github.com/asaskevich/govalidator) [](http://go-search.org/view?id=github.com%2Fasaskevich%2Fgovalidator) [](#backers) [](#sponsors) [](https://app.fossa.io/projects/git%2Bgithub.com%2Fasaskevich%2Fgovalidator?ref=badge_shield)
|
||||
|
||||
A package of validators and sanitizers for strings, structs and collections. Based on [validator.js](https://github.com/chriso/validator.js).
|
||||
|
||||
#### Installation
|
||||
Make sure that Go is installed on your computer.
|
||||
Type the following command in your terminal:
|
||||
|
||||
go get github.com/asaskevich/govalidator
|
||||
|
||||
or you can get specified release of the package with `gopkg.in`:
|
||||
|
||||
go get gopkg.in/asaskevich/govalidator.v10
|
||||
|
||||
After it the package is ready to use.
|
||||
|
||||
|
||||
#### Import package in your project
|
||||
Add following line in your `*.go` file:
|
||||
```go
|
||||
import "github.com/asaskevich/govalidator"
|
||||
```
|
||||
If you are unhappy to use long `govalidator`, you can do something like this:
|
||||
```go
|
||||
import (
|
||||
valid "github.com/asaskevich/govalidator"
|
||||
)
|
||||
```
|
||||
|
||||
#### Activate behavior to require all fields have a validation tag by default
|
||||
`SetFieldsRequiredByDefault` causes validation to fail when struct fields do not include validations or are not explicitly marked as exempt (using `valid:"-"` or `valid:"email,optional"`). A good place to activate this is a package init function or the main() function.
|
||||
|
||||
`SetNilPtrAllowedByRequired` causes validation to pass when struct fields marked by `required` are set to nil. This is disabled by default for consistency, but some packages that need to be able to determine between `nil` and `zero value` state can use this. If disabled, both `nil` and `zero` values cause validation errors.
|
||||
|
||||
```go
|
||||
import "github.com/asaskevich/govalidator"
|
||||
|
||||
func init() {
|
||||
govalidator.SetFieldsRequiredByDefault(true)
|
||||
}
|
||||
```
|
||||
|
||||
Here's some code to explain it:
|
||||
```go
|
||||
// this struct definition will fail govalidator.ValidateStruct() (and the field values do not matter):
|
||||
type exampleStruct struct {
|
||||
Name string ``
|
||||
Email string `valid:"email"`
|
||||
}
|
||||
|
||||
// this, however, will only fail when Email is empty or an invalid email address:
|
||||
type exampleStruct2 struct {
|
||||
Name string `valid:"-"`
|
||||
Email string `valid:"email"`
|
||||
}
|
||||
|
||||
// lastly, this will only fail when Email is an invalid email address but not when it's empty:
|
||||
type exampleStruct2 struct {
|
||||
Name string `valid:"-"`
|
||||
Email string `valid:"email,optional"`
|
||||
}
|
||||
```
|
||||
|
||||
#### Recent breaking changes (see [#123](https://github.com/asaskevich/govalidator/pull/123))
|
||||
##### Custom validator function signature
|
||||
A context was added as the second parameter, for structs this is the object being validated – this makes dependent validation possible.
|
||||
```go
|
||||
import "github.com/asaskevich/govalidator"
|
||||
|
||||
// old signature
|
||||
func(i interface{}) bool
|
||||
|
||||
// new signature
|
||||
func(i interface{}, o interface{}) bool
|
||||
```
|
||||
|
||||
##### Adding a custom validator
|
||||
This was changed to prevent data races when accessing custom validators.
|
||||
```go
|
||||
import "github.com/asaskevich/govalidator"
|
||||
|
||||
// before
|
||||
govalidator.CustomTypeTagMap["customByteArrayValidator"] = func(i interface{}, o interface{}) bool {
|
||||
// ...
|
||||
}
|
||||
|
||||
// after
|
||||
govalidator.CustomTypeTagMap.Set("customByteArrayValidator", func(i interface{}, o interface{}) bool {
|
||||
// ...
|
||||
})
|
||||
```
|
||||
|
||||
#### List of functions:
|
||||
```go
|
||||
func Abs(value float64) float64
|
||||
func BlackList(str, chars string) string
|
||||
func ByteLength(str string, params ...string) bool
|
||||
func CamelCaseToUnderscore(str string) string
|
||||
func Contains(str, substring string) bool
|
||||
func Count(array []interface{}, iterator ConditionIterator) int
|
||||
func Each(array []interface{}, iterator Iterator)
|
||||
func ErrorByField(e error, field string) string
|
||||
func ErrorsByField(e error) map[string]string
|
||||
func Filter(array []interface{}, iterator ConditionIterator) []interface{}
|
||||
func Find(array []interface{}, iterator ConditionIterator) interface{}
|
||||
func GetLine(s string, index int) (string, error)
|
||||
func GetLines(s string) []string
|
||||
func HasLowerCase(str string) bool
|
||||
func HasUpperCase(str string) bool
|
||||
func HasWhitespace(str string) bool
|
||||
func HasWhitespaceOnly(str string) bool
|
||||
func InRange(value interface{}, left interface{}, right interface{}) bool
|
||||
func InRangeFloat32(value, left, right float32) bool
|
||||
func InRangeFloat64(value, left, right float64) bool
|
||||
func InRangeInt(value, left, right interface{}) bool
|
||||
func IsASCII(str string) bool
|
||||
func IsAlpha(str string) bool
|
||||
func IsAlphanumeric(str string) bool
|
||||
func IsBase64(str string) bool
|
||||
func IsByteLength(str string, min, max int) bool
|
||||
func IsCIDR(str string) bool
|
||||
func IsCRC32(str string) bool
|
||||
func IsCRC32b(str string) bool
|
||||
func IsCreditCard(str string) bool
|
||||
func IsDNSName(str string) bool
|
||||
func IsDataURI(str string) bool
|
||||
func IsDialString(str string) bool
|
||||
func IsDivisibleBy(str, num string) bool
|
||||
func IsEmail(str string) bool
|
||||
func IsExistingEmail(email string) bool
|
||||
func IsFilePath(str string) (bool, int)
|
||||
func IsFloat(str string) bool
|
||||
func IsFullWidth(str string) bool
|
||||
func IsHalfWidth(str string) bool
|
||||
func IsHash(str string, algorithm string) bool
|
||||
func IsHexadecimal(str string) bool
|
||||
func IsHexcolor(str string) bool
|
||||
func IsHost(str string) bool
|
||||
func IsIP(str string) bool
|
||||
func IsIPv4(str string) bool
|
||||
func IsIPv6(str string) bool
|
||||
func IsISBN(str string, version int) bool
|
||||
func IsISBN10(str string) bool
|
||||
func IsISBN13(str string) bool
|
||||
func IsISO3166Alpha2(str string) bool
|
||||
func IsISO3166Alpha3(str string) bool
|
||||
func IsISO4217(str string) bool
|
||||
func IsISO693Alpha2(str string) bool
|
||||
func IsISO693Alpha3b(str string) bool
|
||||
func IsIn(str string, params ...string) bool
|
||||
func IsInRaw(str string, params ...string) bool
|
||||
func IsInt(str string) bool
|
||||
func IsJSON(str string) bool
|
||||
func IsLatitude(str string) bool
|
||||
func IsLongitude(str string) bool
|
||||
func IsLowerCase(str string) bool
|
||||
func IsMAC(str string) bool
|
||||
func IsMD4(str string) bool
|
||||
func IsMD5(str string) bool
|
||||
func IsMagnetURI(str string) bool
|
||||
func IsMongoID(str string) bool
|
||||
func IsMultibyte(str string) bool
|
||||
func IsNatural(value float64) bool
|
||||
func IsNegative(value float64) bool
|
||||
func IsNonNegative(value float64) bool
|
||||
func IsNonPositive(value float64) bool
|
||||
func IsNotNull(str string) bool
|
||||
func IsNull(str string) bool
|
||||
func IsNumeric(str string) bool
|
||||
func IsPort(str string) bool
|
||||
func IsPositive(value float64) bool
|
||||
func IsPrintableASCII(str string) bool
|
||||
func IsRFC3339(str string) bool
|
||||
func IsRFC3339WithoutZone(str string) bool
|
||||
func IsRGBcolor(str string) bool
|
||||
func IsRegex(str string) bool
|
||||
func IsRequestURI(rawurl string) bool
|
||||
func IsRequestURL(rawurl string) bool
|
||||
func IsRipeMD128(str string) bool
|
||||
func IsRipeMD160(str string) bool
|
||||
func IsRsaPub(str string, params ...string) bool
|
||||
func IsRsaPublicKey(str string, keylen int) bool
|
||||
func IsSHA1(str string) bool
|
||||
func IsSHA256(str string) bool
|
||||
func IsSHA384(str string) bool
|
||||
func IsSHA512(str string) bool
|
||||
func IsSSN(str string) bool
|
||||
func IsSemver(str string) bool
|
||||
func IsTiger128(str string) bool
|
||||
func IsTiger160(str string) bool
|
||||
func IsTiger192(str string) bool
|
||||
func IsTime(str string, format string) bool
|
||||
func IsType(v interface{}, params ...string) bool
|
||||
func IsURL(str string) bool
|
||||
func IsUTFDigit(str string) bool
|
||||
func IsUTFLetter(str string) bool
|
||||
func IsUTFLetterNumeric(str string) bool
|
||||
func IsUTFNumeric(str string) bool
|
||||
func IsUUID(str string) bool
|
||||
func IsUUIDv3(str string) bool
|
||||
func IsUUIDv4(str string) bool
|
||||
func IsUUIDv5(str string) bool
|
||||
func IsULID(str string) bool
|
||||
func IsUnixTime(str string) bool
|
||||
func IsUpperCase(str string) bool
|
||||
func IsVariableWidth(str string) bool
|
||||
func IsWhole(value float64) bool
|
||||
func LeftTrim(str, chars string) string
|
||||
func Map(array []interface{}, iterator ResultIterator) []interface{}
|
||||
func Matches(str, pattern string) bool
|
||||
func MaxStringLength(str string, params ...string) bool
|
||||
func MinStringLength(str string, params ...string) bool
|
||||
func NormalizeEmail(str string) (string, error)
|
||||
func PadBoth(str string, padStr string, padLen int) string
|
||||
func PadLeft(str string, padStr string, padLen int) string
|
||||
func PadRight(str string, padStr string, padLen int) string
|
||||
func PrependPathToErrors(err error, path string) error
|
||||
func Range(str string, params ...string) bool
|
||||
func RemoveTags(s string) string
|
||||
func ReplacePattern(str, pattern, replace string) string
|
||||
func Reverse(s string) string
|
||||
func RightTrim(str, chars string) string
|
||||
func RuneLength(str string, params ...string) bool
|
||||
func SafeFileName(str string) string
|
||||
func SetFieldsRequiredByDefault(value bool)
|
||||
func SetNilPtrAllowedByRequired(value bool)
|
||||
func Sign(value float64) float64
|
||||
func StringLength(str string, params ...string) bool
|
||||
func StringMatches(s string, params ...string) bool
|
||||
func StripLow(str string, keepNewLines bool) string
|
||||
func ToBoolean(str string) (bool, error)
|
||||
func ToFloat(str string) (float64, error)
|
||||
func ToInt(value interface{}) (res int64, err error)
|
||||
func ToJSON(obj interface{}) (string, error)
|
||||
func ToString(obj interface{}) string
|
||||
func Trim(str, chars string) string
|
||||
func Truncate(str string, length int, ending string) string
|
||||
func TruncatingErrorf(str string, args ...interface{}) error
|
||||
func UnderscoreToCamelCase(s string) string
|
||||
func ValidateMap(inputMap map[string]interface{}, validationMap map[string]interface{}) (bool, error)
|
||||
func ValidateStruct(s interface{}) (bool, error)
|
||||
func WhiteList(str, chars string) string
|
||||
type ConditionIterator
|
||||
type CustomTypeValidator
|
||||
type Error
|
||||
func (e Error) Error() string
|
||||
type Errors
|
||||
func (es Errors) Error() string
|
||||
func (es Errors) Errors() []error
|
||||
type ISO3166Entry
|
||||
type ISO693Entry
|
||||
type InterfaceParamValidator
|
||||
type Iterator
|
||||
type ParamValidator
|
||||
type ResultIterator
|
||||
type UnsupportedTypeError
|
||||
func (e *UnsupportedTypeError) Error() string
|
||||
type Validator
|
||||
```
|
||||
|
||||
#### Examples
|
||||
###### IsURL
|
||||
```go
|
||||
println(govalidator.IsURL(`http://user@pass:domain.com/path/page`))
|
||||
```
|
||||
###### IsType
|
||||
```go
|
||||
println(govalidator.IsType("Bob", "string"))
|
||||
println(govalidator.IsType(1, "int"))
|
||||
i := 1
|
||||
println(govalidator.IsType(&i, "*int"))
|
||||
```
|
||||
|
||||
IsType can be used through the tag `type` which is essential for map validation:
|
||||
```go
|
||||
type User struct {
|
||||
Name string `valid:"type(string)"`
|
||||
Age int `valid:"type(int)"`
|
||||
Meta interface{} `valid:"type(string)"`
|
||||
}
|
||||
result, err := govalidator.ValidateStruct(User{"Bob", 20, "meta"})
|
||||
if err != nil {
|
||||
println("error: " + err.Error())
|
||||
}
|
||||
println(result)
|
||||
```
|
||||
###### ToString
|
||||
```go
|
||||
type User struct {
|
||||
FirstName string
|
||||
LastName string
|
||||
}
|
||||
|
||||
str := govalidator.ToString(&User{"John", "Juan"})
|
||||
println(str)
|
||||
```
|
||||
###### Each, Map, Filter, Count for slices
|
||||
Each iterates over the slice/array and calls Iterator for every item
|
||||
```go
|
||||
data := []interface{}{1, 2, 3, 4, 5}
|
||||
var fn govalidator.Iterator = func(value interface{}, index int) {
|
||||
println(value.(int))
|
||||
}
|
||||
govalidator.Each(data, fn)
|
||||
```
|
||||
```go
|
||||
data := []interface{}{1, 2, 3, 4, 5}
|
||||
var fn govalidator.ResultIterator = func(value interface{}, index int) interface{} {
|
||||
return value.(int) * 3
|
||||
}
|
||||
_ = govalidator.Map(data, fn) // result = []interface{}{1, 6, 9, 12, 15}
|
||||
```
|
||||
```go
|
||||
data := []interface{}{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
|
||||
var fn govalidator.ConditionIterator = func(value interface{}, index int) bool {
|
||||
return value.(int)%2 == 0
|
||||
}
|
||||
_ = govalidator.Filter(data, fn) // result = []interface{}{2, 4, 6, 8, 10}
|
||||
_ = govalidator.Count(data, fn) // result = 5
|
||||
```
|
||||
###### ValidateStruct [#2](https://github.com/asaskevich/govalidator/pull/2)
|
||||
If you want to validate structs, you can use tag `valid` for any field in your structure. All validators used with this field in one tag are separated by comma. If you want to skip validation, place `-` in your tag. If you need a validator that is not on the list below, you can add it like this:
|
||||
```go
|
||||
govalidator.TagMap["duck"] = govalidator.Validator(func(str string) bool {
|
||||
return str == "duck"
|
||||
})
|
||||
```
|
||||
For completely custom validators (interface-based), see below.
|
||||
|
||||
Here is a list of available validators for struct fields (validator - used function):
|
||||
```go
|
||||
"email": IsEmail,
|
||||
"url": IsURL,
|
||||
"dialstring": IsDialString,
|
||||
"requrl": IsRequestURL,
|
||||
"requri": IsRequestURI,
|
||||
"alpha": IsAlpha,
|
||||
"utfletter": IsUTFLetter,
|
||||
"alphanum": IsAlphanumeric,
|
||||
"utfletternum": IsUTFLetterNumeric,
|
||||
"numeric": IsNumeric,
|
||||
"utfnumeric": IsUTFNumeric,
|
||||
"utfdigit": IsUTFDigit,
|
||||
"hexadecimal": IsHexadecimal,
|
||||
"hexcolor": IsHexcolor,
|
||||
"rgbcolor": IsRGBcolor,
|
||||
"lowercase": IsLowerCase,
|
||||
"uppercase": IsUpperCase,
|
||||
"int": IsInt,
|
||||
"float": IsFloat,
|
||||
"null": IsNull,
|
||||
"uuid": IsUUID,
|
||||
"uuidv3": IsUUIDv3,
|
||||
"uuidv4": IsUUIDv4,
|
||||
"uuidv5": IsUUIDv5,
|
||||
"creditcard": IsCreditCard,
|
||||
"isbn10": IsISBN10,
|
||||
"isbn13": IsISBN13,
|
||||
"json": IsJSON,
|
||||
"multibyte": IsMultibyte,
|
||||
"ascii": IsASCII,
|
||||
"printableascii": IsPrintableASCII,
|
||||
"fullwidth": IsFullWidth,
|
||||
"halfwidth": IsHalfWidth,
|
||||
"variablewidth": IsVariableWidth,
|
||||
"base64": IsBase64,
|
||||
"datauri": IsDataURI,
|
||||
"ip": IsIP,
|
||||
"port": IsPort,
|
||||
"ipv4": IsIPv4,
|
||||
"ipv6": IsIPv6,
|
||||
"dns": IsDNSName,
|
||||
"host": IsHost,
|
||||
"mac": IsMAC,
|
||||
"latitude": IsLatitude,
|
||||
"longitude": IsLongitude,
|
||||
"ssn": IsSSN,
|
||||
"semver": IsSemver,
|
||||
"rfc3339": IsRFC3339,
|
||||
"rfc3339WithoutZone": IsRFC3339WithoutZone,
|
||||
"ISO3166Alpha2": IsISO3166Alpha2,
|
||||
"ISO3166Alpha3": IsISO3166Alpha3,
|
||||
"ulid": IsULID,
|
||||
```
|
||||
Validators with parameters
|
||||
|
||||
```go
|
||||
"range(min|max)": Range,
|
||||
"length(min|max)": ByteLength,
|
||||
"runelength(min|max)": RuneLength,
|
||||
"stringlength(min|max)": StringLength,
|
||||
"matches(pattern)": StringMatches,
|
||||
"in(string1|string2|...|stringN)": IsIn,
|
||||
"rsapub(keylength)" : IsRsaPub,
|
||||
"minstringlength(int): MinStringLength,
|
||||
"maxstringlength(int): MaxStringLength,
|
||||
```
|
||||
Validators with parameters for any type
|
||||
|
||||
```go
|
||||
"type(type)": IsType,
|
||||
```
|
||||
|
||||
And here is small example of usage:
|
||||
```go
|
||||
type Post struct {
|
||||
Title string `valid:"alphanum,required"`
|
||||
Message string `valid:"duck,ascii"`
|
||||
Message2 string `valid:"animal(dog)"`
|
||||
AuthorIP string `valid:"ipv4"`
|
||||
Date string `valid:"-"`
|
||||
}
|
||||
post := &Post{
|
||||
Title: "My Example Post",
|
||||
Message: "duck",
|
||||
Message2: "dog",
|
||||
AuthorIP: "123.234.54.3",
|
||||
}
|
||||
|
||||
// Add your own struct validation tags
|
||||
govalidator.TagMap["duck"] = govalidator.Validator(func(str string) bool {
|
||||
return str == "duck"
|
||||
})
|
||||
|
||||
// Add your own struct validation tags with parameter
|
||||
govalidator.ParamTagMap["animal"] = govalidator.ParamValidator(func(str string, params ...string) bool {
|
||||
species := params[0]
|
||||
return str == species
|
||||
})
|
||||
govalidator.ParamTagRegexMap["animal"] = regexp.MustCompile("^animal\\((\\w+)\\)$")
|
||||
|
||||
result, err := govalidator.ValidateStruct(post)
|
||||
if err != nil {
|
||||
println("error: " + err.Error())
|
||||
}
|
||||
println(result)
|
||||
```
|
||||
###### ValidateMap [#2](https://github.com/asaskevich/govalidator/pull/338)
|
||||
If you want to validate maps, you can use the map to be validated and a validation map that contain the same tags used in ValidateStruct, both maps have to be in the form `map[string]interface{}`
|
||||
|
||||
So here is small example of usage:
|
||||
```go
|
||||
var mapTemplate = map[string]interface{}{
|
||||
"name":"required,alpha",
|
||||
"family":"required,alpha",
|
||||
"email":"required,email",
|
||||
"cell-phone":"numeric",
|
||||
"address":map[string]interface{}{
|
||||
"line1":"required,alphanum",
|
||||
"line2":"alphanum",
|
||||
"postal-code":"numeric",
|
||||
},
|
||||
}
|
||||
|
||||
var inputMap = map[string]interface{}{
|
||||
"name":"Bob",
|
||||
"family":"Smith",
|
||||
"email":"foo@bar.baz",
|
||||
"address":map[string]interface{}{
|
||||
"line1":"",
|
||||
"line2":"",
|
||||
"postal-code":"",
|
||||
},
|
||||
}
|
||||
|
||||
result, err := govalidator.ValidateMap(inputMap, mapTemplate)
|
||||
if err != nil {
|
||||
println("error: " + err.Error())
|
||||
}
|
||||
println(result)
|
||||
```
|
||||
|
||||
###### WhiteList
|
||||
```go
|
||||
// Remove all characters from string ignoring characters between "a" and "z"
|
||||
println(govalidator.WhiteList("a3a43a5a4a3a2a23a4a5a4a3a4", "a-z") == "aaaaaaaaaaaa")
|
||||
```
|
||||
|
||||
###### Custom validation functions
|
||||
Custom validation using your own domain specific validators is also available - here's an example of how to use it:
|
||||
```go
|
||||
import "github.com/asaskevich/govalidator"
|
||||
|
||||
type CustomByteArray [6]byte // custom types are supported and can be validated
|
||||
|
||||
type StructWithCustomByteArray struct {
|
||||
ID CustomByteArray `valid:"customByteArrayValidator,customMinLengthValidator"` // multiple custom validators are possible as well and will be evaluated in sequence
|
||||
Email string `valid:"email"`
|
||||
CustomMinLength int `valid:"-"`
|
||||
}
|
||||
|
||||
govalidator.CustomTypeTagMap.Set("customByteArrayValidator", func(i interface{}, context interface{}) bool {
|
||||
switch v := context.(type) { // you can type switch on the context interface being validated
|
||||
case StructWithCustomByteArray:
|
||||
// you can check and validate against some other field in the context,
|
||||
// return early or not validate against the context at all – your choice
|
||||
case SomeOtherType:
|
||||
// ...
|
||||
default:
|
||||
// expecting some other type? Throw/panic here or continue
|
||||
}
|
||||
|
||||
switch v := i.(type) { // type switch on the struct field being validated
|
||||
case CustomByteArray:
|
||||
for _, e := range v { // this validator checks that the byte array is not empty, i.e. not all zeroes
|
||||
if e != 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
govalidator.CustomTypeTagMap.Set("customMinLengthValidator", func(i interface{}, context interface{}) bool {
|
||||
switch v := context.(type) { // this validates a field against the value in another field, i.e. dependent validation
|
||||
case StructWithCustomByteArray:
|
||||
return len(v.ID) >= v.CustomMinLength
|
||||
}
|
||||
return false
|
||||
})
|
||||
```
|
||||
|
||||
###### Loop over Error()
|
||||
By default .Error() returns all errors in a single String. To access each error you can do this:
|
||||
```go
|
||||
if err != nil {
|
||||
errs := err.(govalidator.Errors).Errors()
|
||||
for _, e := range errs {
|
||||
fmt.Println(e.Error())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
###### Custom error messages
|
||||
Custom error messages are supported via annotations by adding the `~` separator - here's an example of how to use it:
|
||||
```go
|
||||
type Ticket struct {
|
||||
Id int64 `json:"id"`
|
||||
FirstName string `json:"firstname" valid:"required~First name is blank"`
|
||||
}
|
||||
```
|
||||
|
||||
#### Notes
|
||||
Documentation is available here: [godoc.org](https://godoc.org/github.com/asaskevich/govalidator).
|
||||
Full information about code coverage is also available here: [govalidator on gocover.io](http://gocover.io/github.com/asaskevich/govalidator).
|
||||
|
||||
#### Support
|
||||
If you do have a contribution to the package, feel free to create a Pull Request or an Issue.
|
||||
|
||||
#### What to contribute
|
||||
If you don't know what to do, there are some features and functions that need to be done
|
||||
|
||||
- [ ] Refactor code
|
||||
- [ ] Edit docs and [README](https://github.com/asaskevich/govalidator/README.md): spellcheck, grammar and typo check
|
||||
- [ ] Create actual list of contributors and projects that currently using this package
|
||||
- [ ] Resolve [issues and bugs](https://github.com/asaskevich/govalidator/issues)
|
||||
- [ ] Update actual [list of functions](https://github.com/asaskevich/govalidator#list-of-functions)
|
||||
- [ ] Update [list of validators](https://github.com/asaskevich/govalidator#validatestruct-2) that available for `ValidateStruct` and add new
|
||||
- [ ] Implement new validators: `IsFQDN`, `IsIMEI`, `IsPostalCode`, `IsISIN`, `IsISRC` etc
|
||||
- [x] Implement [validation by maps](https://github.com/asaskevich/govalidator/issues/224)
|
||||
- [ ] Implement fuzzing testing
|
||||
- [ ] Implement some struct/map/array utilities
|
||||
- [ ] Implement map/array validation
|
||||
- [ ] Implement benchmarking
|
||||
- [ ] Implement batch of examples
|
||||
- [ ] Look at forks for new features and fixes
|
||||
|
||||
#### Advice
|
||||
Feel free to create what you want, but keep in mind when you implement new features:
|
||||
- Code must be clear and readable, names of variables/constants clearly describes what they are doing
|
||||
- Public functions must be documented and described in source file and added to README.md to the list of available functions
|
||||
- There are must be unit-tests for any new functions and improvements
|
||||
|
||||
## Credits
|
||||
### Contributors
|
||||
|
||||
This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].
|
||||
|
||||
#### Special thanks to [contributors](https://github.com/asaskevich/govalidator/graphs/contributors)
|
||||
* [Daniel Lohse](https://github.com/annismckenzie)
|
||||
* [Attila Oláh](https://github.com/attilaolah)
|
||||
* [Daniel Korner](https://github.com/Dadie)
|
||||
* [Steven Wilkin](https://github.com/stevenwilkin)
|
||||
* [Deiwin Sarjas](https://github.com/deiwin)
|
||||
* [Noah Shibley](https://github.com/slugmobile)
|
||||
* [Nathan Davies](https://github.com/nathj07)
|
||||
* [Matt Sanford](https://github.com/mzsanford)
|
||||
* [Simon ccl1115](https://github.com/ccl1115)
|
||||
|
||||
<a href="https://github.com/asaskevich/govalidator/graphs/contributors"><img src="https://opencollective.com/govalidator/contributors.svg?width=890" /></a>
|
||||
|
||||
|
||||
### Backers
|
||||
|
||||
Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/govalidator#backer)]
|
||||
|
||||
<a href="https://opencollective.com/govalidator#backers" target="_blank"><img src="https://opencollective.com/govalidator/backers.svg?width=890"></a>
|
||||
|
||||
|
||||
### Sponsors
|
||||
|
||||
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/govalidator#sponsor)]
|
||||
|
||||
<a href="https://opencollective.com/govalidator/sponsor/0/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/0/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/govalidator/sponsor/1/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/1/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/govalidator/sponsor/2/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/2/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/govalidator/sponsor/3/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/3/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/govalidator/sponsor/4/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/4/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/govalidator/sponsor/5/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/5/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/govalidator/sponsor/6/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/6/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/govalidator/sponsor/7/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/7/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/govalidator/sponsor/8/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/8/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/govalidator/sponsor/9/website" target="_blank"><img src="https://opencollective.com/govalidator/sponsor/9/avatar.svg"></a>
|
||||
|
||||
|
||||
|
||||
|
||||
## License
|
||||
[](https://app.fossa.io/projects/git%2Bgithub.com%2Fasaskevich%2Fgovalidator?ref=badge_large)
|
||||
87
extra/server-discovery/vendor/github.com/asaskevich/govalidator/arrays.go
generated
vendored
Normal file
87
extra/server-discovery/vendor/github.com/asaskevich/govalidator/arrays.go
generated
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
package govalidator
|
||||
|
||||
// Iterator is the function that accepts element of slice/array and its index
|
||||
type Iterator func(interface{}, int)
|
||||
|
||||
// ResultIterator is the function that accepts element of slice/array and its index and returns any result
|
||||
type ResultIterator func(interface{}, int) interface{}
|
||||
|
||||
// ConditionIterator is the function that accepts element of slice/array and its index and returns boolean
|
||||
type ConditionIterator func(interface{}, int) bool
|
||||
|
||||
// ReduceIterator is the function that accepts two element of slice/array and returns result of merging those values
|
||||
type ReduceIterator func(interface{}, interface{}) interface{}
|
||||
|
||||
// Some validates that any item of array corresponds to ConditionIterator. Returns boolean.
|
||||
func Some(array []interface{}, iterator ConditionIterator) bool {
|
||||
res := false
|
||||
for index, data := range array {
|
||||
res = res || iterator(data, index)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// Every validates that every item of array corresponds to ConditionIterator. Returns boolean.
|
||||
func Every(array []interface{}, iterator ConditionIterator) bool {
|
||||
res := true
|
||||
for index, data := range array {
|
||||
res = res && iterator(data, index)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// Reduce boils down a list of values into a single value by ReduceIterator
|
||||
func Reduce(array []interface{}, iterator ReduceIterator, initialValue interface{}) interface{} {
|
||||
for _, data := range array {
|
||||
initialValue = iterator(initialValue, data)
|
||||
}
|
||||
return initialValue
|
||||
}
|
||||
|
||||
// Each iterates over the slice and apply Iterator to every item
|
||||
func Each(array []interface{}, iterator Iterator) {
|
||||
for index, data := range array {
|
||||
iterator(data, index)
|
||||
}
|
||||
}
|
||||
|
||||
// Map iterates over the slice and apply ResultIterator to every item. Returns new slice as a result.
|
||||
func Map(array []interface{}, iterator ResultIterator) []interface{} {
|
||||
var result = make([]interface{}, len(array))
|
||||
for index, data := range array {
|
||||
result[index] = iterator(data, index)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Find iterates over the slice and apply ConditionIterator to every item. Returns first item that meet ConditionIterator or nil otherwise.
|
||||
func Find(array []interface{}, iterator ConditionIterator) interface{} {
|
||||
for index, data := range array {
|
||||
if iterator(data, index) {
|
||||
return data
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Filter iterates over the slice and apply ConditionIterator to every item. Returns new slice.
|
||||
func Filter(array []interface{}, iterator ConditionIterator) []interface{} {
|
||||
var result = make([]interface{}, 0)
|
||||
for index, data := range array {
|
||||
if iterator(data, index) {
|
||||
result = append(result, data)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Count iterates over the slice and apply ConditionIterator to every item. Returns count of items that meets ConditionIterator.
|
||||
func Count(array []interface{}, iterator ConditionIterator) int {
|
||||
count := 0
|
||||
for index, data := range array {
|
||||
if iterator(data, index) {
|
||||
count = count + 1
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
81
extra/server-discovery/vendor/github.com/asaskevich/govalidator/converter.go
generated
vendored
Normal file
81
extra/server-discovery/vendor/github.com/asaskevich/govalidator/converter.go
generated
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
package govalidator
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// ToString convert the input to a string.
|
||||
func ToString(obj interface{}) string {
|
||||
res := fmt.Sprintf("%v", obj)
|
||||
return res
|
||||
}
|
||||
|
||||
// ToJSON convert the input to a valid JSON string
|
||||
func ToJSON(obj interface{}) (string, error) {
|
||||
res, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
res = []byte("")
|
||||
}
|
||||
return string(res), err
|
||||
}
|
||||
|
||||
// ToFloat convert the input string to a float, or 0.0 if the input is not a float.
|
||||
func ToFloat(value interface{}) (res float64, err error) {
|
||||
val := reflect.ValueOf(value)
|
||||
|
||||
switch value.(type) {
|
||||
case int, int8, int16, int32, int64:
|
||||
res = float64(val.Int())
|
||||
case uint, uint8, uint16, uint32, uint64:
|
||||
res = float64(val.Uint())
|
||||
case float32, float64:
|
||||
res = val.Float()
|
||||
case string:
|
||||
res, err = strconv.ParseFloat(val.String(), 64)
|
||||
if err != nil {
|
||||
res = 0
|
||||
}
|
||||
default:
|
||||
err = fmt.Errorf("ToInt: unknown interface type %T", value)
|
||||
res = 0
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ToInt convert the input string or any int type to an integer type 64, or 0 if the input is not an integer.
|
||||
func ToInt(value interface{}) (res int64, err error) {
|
||||
val := reflect.ValueOf(value)
|
||||
|
||||
switch value.(type) {
|
||||
case int, int8, int16, int32, int64:
|
||||
res = val.Int()
|
||||
case uint, uint8, uint16, uint32, uint64:
|
||||
res = int64(val.Uint())
|
||||
case float32, float64:
|
||||
res = int64(val.Float())
|
||||
case string:
|
||||
if IsInt(val.String()) {
|
||||
res, err = strconv.ParseInt(val.String(), 0, 64)
|
||||
if err != nil {
|
||||
res = 0
|
||||
}
|
||||
} else {
|
||||
err = fmt.Errorf("ToInt: invalid numeric format %g", value)
|
||||
res = 0
|
||||
}
|
||||
default:
|
||||
err = fmt.Errorf("ToInt: unknown interface type %T", value)
|
||||
res = 0
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ToBoolean convert the input string to a boolean.
|
||||
func ToBoolean(str string) (bool, error) {
|
||||
return strconv.ParseBool(str)
|
||||
}
|
||||
3
extra/server-discovery/vendor/github.com/asaskevich/govalidator/doc.go
generated
vendored
Normal file
3
extra/server-discovery/vendor/github.com/asaskevich/govalidator/doc.go
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
package govalidator
|
||||
|
||||
// A package of validators and sanitizers for strings, structures and collections.
|
||||
47
extra/server-discovery/vendor/github.com/asaskevich/govalidator/error.go
generated
vendored
Normal file
47
extra/server-discovery/vendor/github.com/asaskevich/govalidator/error.go
generated
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
package govalidator
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Errors is an array of multiple errors and conforms to the error interface.
|
||||
type Errors []error
|
||||
|
||||
// Errors returns itself.
|
||||
func (es Errors) Errors() []error {
|
||||
return es
|
||||
}
|
||||
|
||||
func (es Errors) Error() string {
|
||||
var errs []string
|
||||
for _, e := range es {
|
||||
errs = append(errs, e.Error())
|
||||
}
|
||||
sort.Strings(errs)
|
||||
return strings.Join(errs, ";")
|
||||
}
|
||||
|
||||
// Error encapsulates a name, an error and whether there's a custom error message or not.
|
||||
type Error struct {
|
||||
Name string
|
||||
Err error
|
||||
CustomErrorMessageExists bool
|
||||
|
||||
// Validator indicates the name of the validator that failed
|
||||
Validator string
|
||||
Path []string
|
||||
}
|
||||
|
||||
func (e Error) Error() string {
|
||||
if e.CustomErrorMessageExists {
|
||||
return e.Err.Error()
|
||||
}
|
||||
|
||||
errName := e.Name
|
||||
if len(e.Path) > 0 {
|
||||
errName = strings.Join(append(e.Path, e.Name), ".")
|
||||
}
|
||||
|
||||
return errName + ": " + e.Err.Error()
|
||||
}
|
||||
100
extra/server-discovery/vendor/github.com/asaskevich/govalidator/numerics.go
generated
vendored
Normal file
100
extra/server-discovery/vendor/github.com/asaskevich/govalidator/numerics.go
generated
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
package govalidator
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
// Abs returns absolute value of number
|
||||
func Abs(value float64) float64 {
|
||||
return math.Abs(value)
|
||||
}
|
||||
|
||||
// Sign returns signum of number: 1 in case of value > 0, -1 in case of value < 0, 0 otherwise
|
||||
func Sign(value float64) float64 {
|
||||
if value > 0 {
|
||||
return 1
|
||||
} else if value < 0 {
|
||||
return -1
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// IsNegative returns true if value < 0
|
||||
func IsNegative(value float64) bool {
|
||||
return value < 0
|
||||
}
|
||||
|
||||
// IsPositive returns true if value > 0
|
||||
func IsPositive(value float64) bool {
|
||||
return value > 0
|
||||
}
|
||||
|
||||
// IsNonNegative returns true if value >= 0
|
||||
func IsNonNegative(value float64) bool {
|
||||
return value >= 0
|
||||
}
|
||||
|
||||
// IsNonPositive returns true if value <= 0
|
||||
func IsNonPositive(value float64) bool {
|
||||
return value <= 0
|
||||
}
|
||||
|
||||
// InRangeInt returns true if value lies between left and right border
|
||||
func InRangeInt(value, left, right interface{}) bool {
|
||||
value64, _ := ToInt(value)
|
||||
left64, _ := ToInt(left)
|
||||
right64, _ := ToInt(right)
|
||||
if left64 > right64 {
|
||||
left64, right64 = right64, left64
|
||||
}
|
||||
return value64 >= left64 && value64 <= right64
|
||||
}
|
||||
|
||||
// InRangeFloat32 returns true if value lies between left and right border
|
||||
func InRangeFloat32(value, left, right float32) bool {
|
||||
if left > right {
|
||||
left, right = right, left
|
||||
}
|
||||
return value >= left && value <= right
|
||||
}
|
||||
|
||||
// InRangeFloat64 returns true if value lies between left and right border
|
||||
func InRangeFloat64(value, left, right float64) bool {
|
||||
if left > right {
|
||||
left, right = right, left
|
||||
}
|
||||
return value >= left && value <= right
|
||||
}
|
||||
|
||||
// InRange returns true if value lies between left and right border, generic type to handle int, float32, float64 and string.
|
||||
// All types must the same type.
|
||||
// False if value doesn't lie in range or if it incompatible or not comparable
|
||||
func InRange(value interface{}, left interface{}, right interface{}) bool {
|
||||
switch value.(type) {
|
||||
case int:
|
||||
intValue, _ := ToInt(value)
|
||||
intLeft, _ := ToInt(left)
|
||||
intRight, _ := ToInt(right)
|
||||
return InRangeInt(intValue, intLeft, intRight)
|
||||
case float32, float64:
|
||||
intValue, _ := ToFloat(value)
|
||||
intLeft, _ := ToFloat(left)
|
||||
intRight, _ := ToFloat(right)
|
||||
return InRangeFloat64(intValue, intLeft, intRight)
|
||||
case string:
|
||||
return value.(string) >= left.(string) && value.(string) <= right.(string)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// IsWhole returns true if value is whole number
|
||||
func IsWhole(value float64) bool {
|
||||
return math.Remainder(value, 1) == 0
|
||||
}
|
||||
|
||||
// IsNatural returns true if value is natural number (positive and whole)
|
||||
func IsNatural(value float64) bool {
|
||||
return IsWhole(value) && IsPositive(value)
|
||||
}
|
||||
113
extra/server-discovery/vendor/github.com/asaskevich/govalidator/patterns.go
generated
vendored
Normal file
113
extra/server-discovery/vendor/github.com/asaskevich/govalidator/patterns.go
generated
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
package govalidator
|
||||
|
||||
import "regexp"
|
||||
|
||||
// Basic regular expressions for validating strings
|
||||
const (
|
||||
Email string = "^(((([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$"
|
||||
CreditCard string = "^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|(222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11}|6[27][0-9]{14})$"
|
||||
ISBN10 string = "^(?:[0-9]{9}X|[0-9]{10})$"
|
||||
ISBN13 string = "^(?:[0-9]{13})$"
|
||||
UUID3 string = "^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$"
|
||||
UUID4 string = "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
|
||||
UUID5 string = "^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
|
||||
UUID string = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
|
||||
Alpha string = "^[a-zA-Z]+$"
|
||||
Alphanumeric string = "^[a-zA-Z0-9]+$"
|
||||
Numeric string = "^[0-9]+$"
|
||||
Int string = "^(?:[-+]?(?:0|[1-9][0-9]*))$"
|
||||
Float string = "^(?:[-+]?(?:[0-9]+))?(?:\\.[0-9]*)?(?:[eE][\\+\\-]?(?:[0-9]+))?$"
|
||||
Hexadecimal string = "^[0-9a-fA-F]+$"
|
||||
Hexcolor string = "^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"
|
||||
RGBcolor string = "^rgb\\(\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*\\)$"
|
||||
ASCII string = "^[\x00-\x7F]+$"
|
||||
Multibyte string = "[^\x00-\x7F]"
|
||||
FullWidth string = "[^\u0020-\u007E\uFF61-\uFF9F\uFFA0-\uFFDC\uFFE8-\uFFEE0-9a-zA-Z]"
|
||||
HalfWidth string = "[\u0020-\u007E\uFF61-\uFF9F\uFFA0-\uFFDC\uFFE8-\uFFEE0-9a-zA-Z]"
|
||||
Base64 string = "^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$"
|
||||
PrintableASCII string = "^[\x20-\x7E]+$"
|
||||
DataURI string = "^data:.+\\/(.+);base64$"
|
||||
MagnetURI string = "^magnet:\\?xt=urn:[a-zA-Z0-9]+:[a-zA-Z0-9]{32,40}&dn=.+&tr=.+$"
|
||||
Latitude string = "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$"
|
||||
Longitude string = "^[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$"
|
||||
DNSName string = `^([a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62}){1}(\.[a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62})*[\._]?$`
|
||||
IP string = `(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))`
|
||||
URLSchema string = `((ftp|tcp|udp|wss?|https?):\/\/)`
|
||||
URLUsername string = `(\S+(:\S*)?@)`
|
||||
URLPath string = `((\/|\?|#)[^\s]*)`
|
||||
URLPort string = `(:(\d{1,5}))`
|
||||
URLIP string = `([1-9]\d?|1\d\d|2[01]\d|22[0-3]|24\d|25[0-5])(\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])){2}(?:\.([0-9]\d?|1\d\d|2[0-4]\d|25[0-5]))`
|
||||
URLSubdomain string = `((www\.)|([a-zA-Z0-9]+([-_\.]?[a-zA-Z0-9])*[a-zA-Z0-9]\.[a-zA-Z0-9]+))`
|
||||
URL = `^` + URLSchema + `?` + URLUsername + `?` + `((` + URLIP + `|(\[` + IP + `\])|(([a-zA-Z0-9]([a-zA-Z0-9-_]+)?[a-zA-Z0-9]([-\.][a-zA-Z0-9]+)*)|(` + URLSubdomain + `?))?(([a-zA-Z\x{00a1}-\x{ffff}0-9]+-?-?)*[a-zA-Z\x{00a1}-\x{ffff}0-9]+)(?:\.([a-zA-Z\x{00a1}-\x{ffff}]{1,}))?))\.?` + URLPort + `?` + URLPath + `?$`
|
||||
SSN string = `^\d{3}[- ]?\d{2}[- ]?\d{4}$`
|
||||
WinPath string = `^[a-zA-Z]:\\(?:[^\\/:*?"<>|\r\n]+\\)*[^\\/:*?"<>|\r\n]*$`
|
||||
UnixPath string = `^(/[^/\x00]*)+/?$`
|
||||
WinARPath string = `^(?:(?:[a-zA-Z]:|\\\\[a-z0-9_.$●-]+\\[a-z0-9_.$●-]+)\\|\\?[^\\/:*?"<>|\r\n]+\\?)(?:[^\\/:*?"<>|\r\n]+\\)*[^\\/:*?"<>|\r\n]*$`
|
||||
UnixARPath string = `^((\.{0,2}/)?([^/\x00]*))+/?$`
|
||||
Semver string = "^v?(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(-(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(\\.(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\\+[0-9a-zA-Z-]+(\\.[0-9a-zA-Z-]+)*)?$"
|
||||
tagName string = "valid"
|
||||
hasLowerCase string = ".*[[:lower:]]"
|
||||
hasUpperCase string = ".*[[:upper:]]"
|
||||
hasWhitespace string = ".*[[:space:]]"
|
||||
hasWhitespaceOnly string = "^[[:space:]]+$"
|
||||
IMEI string = "^[0-9a-f]{14}$|^\\d{15}$|^\\d{18}$"
|
||||
IMSI string = "^\\d{14,15}$"
|
||||
E164 string = `^\+?[1-9]\d{1,14}$`
|
||||
)
|
||||
|
||||
// Used by IsFilePath func
|
||||
const (
|
||||
// Unknown is unresolved OS type
|
||||
Unknown = iota
|
||||
// Win is Windows type
|
||||
Win
|
||||
// Unix is *nix OS types
|
||||
Unix
|
||||
)
|
||||
|
||||
var (
|
||||
userRegexp = regexp.MustCompile("^[a-zA-Z0-9!#$%&'*+/=?^_`{|}~.-]+$")
|
||||
hostRegexp = regexp.MustCompile("^[^\\s]+\\.[^\\s]+$")
|
||||
userDotRegexp = regexp.MustCompile("(^[.]{1})|([.]{1}$)|([.]{2,})")
|
||||
rxEmail = regexp.MustCompile(Email)
|
||||
rxCreditCard = regexp.MustCompile(CreditCard)
|
||||
rxISBN10 = regexp.MustCompile(ISBN10)
|
||||
rxISBN13 = regexp.MustCompile(ISBN13)
|
||||
rxUUID3 = regexp.MustCompile(UUID3)
|
||||
rxUUID4 = regexp.MustCompile(UUID4)
|
||||
rxUUID5 = regexp.MustCompile(UUID5)
|
||||
rxUUID = regexp.MustCompile(UUID)
|
||||
rxAlpha = regexp.MustCompile(Alpha)
|
||||
rxAlphanumeric = regexp.MustCompile(Alphanumeric)
|
||||
rxNumeric = regexp.MustCompile(Numeric)
|
||||
rxInt = regexp.MustCompile(Int)
|
||||
rxFloat = regexp.MustCompile(Float)
|
||||
rxHexadecimal = regexp.MustCompile(Hexadecimal)
|
||||
rxHexcolor = regexp.MustCompile(Hexcolor)
|
||||
rxRGBcolor = regexp.MustCompile(RGBcolor)
|
||||
rxASCII = regexp.MustCompile(ASCII)
|
||||
rxPrintableASCII = regexp.MustCompile(PrintableASCII)
|
||||
rxMultibyte = regexp.MustCompile(Multibyte)
|
||||
rxFullWidth = regexp.MustCompile(FullWidth)
|
||||
rxHalfWidth = regexp.MustCompile(HalfWidth)
|
||||
rxBase64 = regexp.MustCompile(Base64)
|
||||
rxDataURI = regexp.MustCompile(DataURI)
|
||||
rxMagnetURI = regexp.MustCompile(MagnetURI)
|
||||
rxLatitude = regexp.MustCompile(Latitude)
|
||||
rxLongitude = regexp.MustCompile(Longitude)
|
||||
rxDNSName = regexp.MustCompile(DNSName)
|
||||
rxURL = regexp.MustCompile(URL)
|
||||
rxSSN = regexp.MustCompile(SSN)
|
||||
rxWinPath = regexp.MustCompile(WinPath)
|
||||
rxUnixPath = regexp.MustCompile(UnixPath)
|
||||
rxARWinPath = regexp.MustCompile(WinARPath)
|
||||
rxARUnixPath = regexp.MustCompile(UnixARPath)
|
||||
rxSemver = regexp.MustCompile(Semver)
|
||||
rxHasLowerCase = regexp.MustCompile(hasLowerCase)
|
||||
rxHasUpperCase = regexp.MustCompile(hasUpperCase)
|
||||
rxHasWhitespace = regexp.MustCompile(hasWhitespace)
|
||||
rxHasWhitespaceOnly = regexp.MustCompile(hasWhitespaceOnly)
|
||||
rxIMEI = regexp.MustCompile(IMEI)
|
||||
rxIMSI = regexp.MustCompile(IMSI)
|
||||
rxE164 = regexp.MustCompile(E164)
|
||||
)
|
||||
656
extra/server-discovery/vendor/github.com/asaskevich/govalidator/types.go
generated
vendored
Normal file
656
extra/server-discovery/vendor/github.com/asaskevich/govalidator/types.go
generated
vendored
Normal file
@@ -0,0 +1,656 @@
|
||||
package govalidator
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Validator is a wrapper for a validator function that returns bool and accepts string.
|
||||
type Validator func(str string) bool
|
||||
|
||||
// CustomTypeValidator is a wrapper for validator functions that returns bool and accepts any type.
|
||||
// The second parameter should be the context (in the case of validating a struct: the whole object being validated).
|
||||
type CustomTypeValidator func(i interface{}, o interface{}) bool
|
||||
|
||||
// ParamValidator is a wrapper for validator functions that accept additional parameters.
|
||||
type ParamValidator func(str string, params ...string) bool
|
||||
|
||||
// InterfaceParamValidator is a wrapper for functions that accept variants parameters for an interface value
|
||||
type InterfaceParamValidator func(in interface{}, params ...string) bool
|
||||
type tagOptionsMap map[string]tagOption
|
||||
|
||||
func (t tagOptionsMap) orderedKeys() []string {
|
||||
var keys []string
|
||||
for k := range t {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
sort.Slice(keys, func(a, b int) bool {
|
||||
return t[keys[a]].order < t[keys[b]].order
|
||||
})
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
type tagOption struct {
|
||||
name string
|
||||
customErrorMessage string
|
||||
order int
|
||||
}
|
||||
|
||||
// UnsupportedTypeError is a wrapper for reflect.Type
|
||||
type UnsupportedTypeError struct {
|
||||
Type reflect.Type
|
||||
}
|
||||
|
||||
// stringValues is a slice of reflect.Value holding *reflect.StringValue.
|
||||
// It implements the methods to sort by string.
|
||||
type stringValues []reflect.Value
|
||||
|
||||
// InterfaceParamTagMap is a map of functions accept variants parameters for an interface value
|
||||
var InterfaceParamTagMap = map[string]InterfaceParamValidator{
|
||||
"type": IsType,
|
||||
}
|
||||
|
||||
// InterfaceParamTagRegexMap maps interface param tags to their respective regexes.
|
||||
var InterfaceParamTagRegexMap = map[string]*regexp.Regexp{
|
||||
"type": regexp.MustCompile(`^type\((.*)\)$`),
|
||||
}
|
||||
|
||||
// ParamTagMap is a map of functions accept variants parameters
|
||||
var ParamTagMap = map[string]ParamValidator{
|
||||
"length": ByteLength,
|
||||
"range": Range,
|
||||
"runelength": RuneLength,
|
||||
"stringlength": StringLength,
|
||||
"matches": StringMatches,
|
||||
"in": IsInRaw,
|
||||
"rsapub": IsRsaPub,
|
||||
"minstringlength": MinStringLength,
|
||||
"maxstringlength": MaxStringLength,
|
||||
}
|
||||
|
||||
// ParamTagRegexMap maps param tags to their respective regexes.
|
||||
var ParamTagRegexMap = map[string]*regexp.Regexp{
|
||||
"range": regexp.MustCompile("^range\\((\\d+)\\|(\\d+)\\)$"),
|
||||
"length": regexp.MustCompile("^length\\((\\d+)\\|(\\d+)\\)$"),
|
||||
"runelength": regexp.MustCompile("^runelength\\((\\d+)\\|(\\d+)\\)$"),
|
||||
"stringlength": regexp.MustCompile("^stringlength\\((\\d+)\\|(\\d+)\\)$"),
|
||||
"in": regexp.MustCompile(`^in\((.*)\)`),
|
||||
"matches": regexp.MustCompile(`^matches\((.+)\)$`),
|
||||
"rsapub": regexp.MustCompile("^rsapub\\((\\d+)\\)$"),
|
||||
"minstringlength": regexp.MustCompile("^minstringlength\\((\\d+)\\)$"),
|
||||
"maxstringlength": regexp.MustCompile("^maxstringlength\\((\\d+)\\)$"),
|
||||
}
|
||||
|
||||
type customTypeTagMap struct {
|
||||
validators map[string]CustomTypeValidator
|
||||
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
func (tm *customTypeTagMap) Get(name string) (CustomTypeValidator, bool) {
|
||||
tm.RLock()
|
||||
defer tm.RUnlock()
|
||||
v, ok := tm.validators[name]
|
||||
return v, ok
|
||||
}
|
||||
|
||||
func (tm *customTypeTagMap) Set(name string, ctv CustomTypeValidator) {
|
||||
tm.Lock()
|
||||
defer tm.Unlock()
|
||||
tm.validators[name] = ctv
|
||||
}
|
||||
|
||||
// CustomTypeTagMap is a map of functions that can be used as tags for ValidateStruct function.
|
||||
// Use this to validate compound or custom types that need to be handled as a whole, e.g.
|
||||
// `type UUID [16]byte` (this would be handled as an array of bytes).
|
||||
var CustomTypeTagMap = &customTypeTagMap{validators: make(map[string]CustomTypeValidator)}
|
||||
|
||||
// TagMap is a map of functions, that can be used as tags for ValidateStruct function.
|
||||
var TagMap = map[string]Validator{
|
||||
"email": IsEmail,
|
||||
"url": IsURL,
|
||||
"dialstring": IsDialString,
|
||||
"requrl": IsRequestURL,
|
||||
"requri": IsRequestURI,
|
||||
"alpha": IsAlpha,
|
||||
"utfletter": IsUTFLetter,
|
||||
"alphanum": IsAlphanumeric,
|
||||
"utfletternum": IsUTFLetterNumeric,
|
||||
"numeric": IsNumeric,
|
||||
"utfnumeric": IsUTFNumeric,
|
||||
"utfdigit": IsUTFDigit,
|
||||
"hexadecimal": IsHexadecimal,
|
||||
"hexcolor": IsHexcolor,
|
||||
"rgbcolor": IsRGBcolor,
|
||||
"lowercase": IsLowerCase,
|
||||
"uppercase": IsUpperCase,
|
||||
"int": IsInt,
|
||||
"float": IsFloat,
|
||||
"null": IsNull,
|
||||
"notnull": IsNotNull,
|
||||
"uuid": IsUUID,
|
||||
"uuidv3": IsUUIDv3,
|
||||
"uuidv4": IsUUIDv4,
|
||||
"uuidv5": IsUUIDv5,
|
||||
"creditcard": IsCreditCard,
|
||||
"isbn10": IsISBN10,
|
||||
"isbn13": IsISBN13,
|
||||
"json": IsJSON,
|
||||
"multibyte": IsMultibyte,
|
||||
"ascii": IsASCII,
|
||||
"printableascii": IsPrintableASCII,
|
||||
"fullwidth": IsFullWidth,
|
||||
"halfwidth": IsHalfWidth,
|
||||
"variablewidth": IsVariableWidth,
|
||||
"base64": IsBase64,
|
||||
"datauri": IsDataURI,
|
||||
"ip": IsIP,
|
||||
"port": IsPort,
|
||||
"ipv4": IsIPv4,
|
||||
"ipv6": IsIPv6,
|
||||
"dns": IsDNSName,
|
||||
"host": IsHost,
|
||||
"mac": IsMAC,
|
||||
"latitude": IsLatitude,
|
||||
"longitude": IsLongitude,
|
||||
"ssn": IsSSN,
|
||||
"semver": IsSemver,
|
||||
"rfc3339": IsRFC3339,
|
||||
"rfc3339WithoutZone": IsRFC3339WithoutZone,
|
||||
"ISO3166Alpha2": IsISO3166Alpha2,
|
||||
"ISO3166Alpha3": IsISO3166Alpha3,
|
||||
"ISO4217": IsISO4217,
|
||||
"IMEI": IsIMEI,
|
||||
"ulid": IsULID,
|
||||
}
|
||||
|
||||
// ISO3166Entry stores country codes
|
||||
type ISO3166Entry struct {
|
||||
EnglishShortName string
|
||||
FrenchShortName string
|
||||
Alpha2Code string
|
||||
Alpha3Code string
|
||||
Numeric string
|
||||
}
|
||||
|
||||
//ISO3166List based on https://www.iso.org/obp/ui/#search/code/ Code Type "Officially Assigned Codes"
|
||||
var ISO3166List = []ISO3166Entry{
|
||||
{"Afghanistan", "Afghanistan (l')", "AF", "AFG", "004"},
|
||||
{"Albania", "Albanie (l')", "AL", "ALB", "008"},
|
||||
{"Antarctica", "Antarctique (l')", "AQ", "ATA", "010"},
|
||||
{"Algeria", "Algérie (l')", "DZ", "DZA", "012"},
|
||||
{"American Samoa", "Samoa américaines (les)", "AS", "ASM", "016"},
|
||||
{"Andorra", "Andorre (l')", "AD", "AND", "020"},
|
||||
{"Angola", "Angola (l')", "AO", "AGO", "024"},
|
||||
{"Antigua and Barbuda", "Antigua-et-Barbuda", "AG", "ATG", "028"},
|
||||
{"Azerbaijan", "Azerbaïdjan (l')", "AZ", "AZE", "031"},
|
||||
{"Argentina", "Argentine (l')", "AR", "ARG", "032"},
|
||||
{"Australia", "Australie (l')", "AU", "AUS", "036"},
|
||||
{"Austria", "Autriche (l')", "AT", "AUT", "040"},
|
||||
{"Bahamas (the)", "Bahamas (les)", "BS", "BHS", "044"},
|
||||
{"Bahrain", "Bahreïn", "BH", "BHR", "048"},
|
||||
{"Bangladesh", "Bangladesh (le)", "BD", "BGD", "050"},
|
||||
{"Armenia", "Arménie (l')", "AM", "ARM", "051"},
|
||||
{"Barbados", "Barbade (la)", "BB", "BRB", "052"},
|
||||
{"Belgium", "Belgique (la)", "BE", "BEL", "056"},
|
||||
{"Bermuda", "Bermudes (les)", "BM", "BMU", "060"},
|
||||
{"Bhutan", "Bhoutan (le)", "BT", "BTN", "064"},
|
||||
{"Bolivia (Plurinational State of)", "Bolivie (État plurinational de)", "BO", "BOL", "068"},
|
||||
{"Bosnia and Herzegovina", "Bosnie-Herzégovine (la)", "BA", "BIH", "070"},
|
||||
{"Botswana", "Botswana (le)", "BW", "BWA", "072"},
|
||||
{"Bouvet Island", "Bouvet (l'Île)", "BV", "BVT", "074"},
|
||||
{"Brazil", "Brésil (le)", "BR", "BRA", "076"},
|
||||
{"Belize", "Belize (le)", "BZ", "BLZ", "084"},
|
||||
{"British Indian Ocean Territory (the)", "Indien (le Territoire britannique de l'océan)", "IO", "IOT", "086"},
|
||||
{"Solomon Islands", "Salomon (Îles)", "SB", "SLB", "090"},
|
||||
{"Virgin Islands (British)", "Vierges britanniques (les Îles)", "VG", "VGB", "092"},
|
||||
{"Brunei Darussalam", "Brunéi Darussalam (le)", "BN", "BRN", "096"},
|
||||
{"Bulgaria", "Bulgarie (la)", "BG", "BGR", "100"},
|
||||
{"Myanmar", "Myanmar (le)", "MM", "MMR", "104"},
|
||||
{"Burundi", "Burundi (le)", "BI", "BDI", "108"},
|
||||
{"Belarus", "Bélarus (le)", "BY", "BLR", "112"},
|
||||
{"Cambodia", "Cambodge (le)", "KH", "KHM", "116"},
|
||||
{"Cameroon", "Cameroun (le)", "CM", "CMR", "120"},
|
||||
{"Canada", "Canada (le)", "CA", "CAN", "124"},
|
||||
{"Cabo Verde", "Cabo Verde", "CV", "CPV", "132"},
|
||||
{"Cayman Islands (the)", "Caïmans (les Îles)", "KY", "CYM", "136"},
|
||||
{"Central African Republic (the)", "République centrafricaine (la)", "CF", "CAF", "140"},
|
||||
{"Sri Lanka", "Sri Lanka", "LK", "LKA", "144"},
|
||||
{"Chad", "Tchad (le)", "TD", "TCD", "148"},
|
||||
{"Chile", "Chili (le)", "CL", "CHL", "152"},
|
||||
{"China", "Chine (la)", "CN", "CHN", "156"},
|
||||
{"Taiwan (Province of China)", "Taïwan (Province de Chine)", "TW", "TWN", "158"},
|
||||
{"Christmas Island", "Christmas (l'Île)", "CX", "CXR", "162"},
|
||||
{"Cocos (Keeling) Islands (the)", "Cocos (les Îles)/ Keeling (les Îles)", "CC", "CCK", "166"},
|
||||
{"Colombia", "Colombie (la)", "CO", "COL", "170"},
|
||||
{"Comoros (the)", "Comores (les)", "KM", "COM", "174"},
|
||||
{"Mayotte", "Mayotte", "YT", "MYT", "175"},
|
||||
{"Congo (the)", "Congo (le)", "CG", "COG", "178"},
|
||||
{"Congo (the Democratic Republic of the)", "Congo (la République démocratique du)", "CD", "COD", "180"},
|
||||
{"Cook Islands (the)", "Cook (les Îles)", "CK", "COK", "184"},
|
||||
{"Costa Rica", "Costa Rica (le)", "CR", "CRI", "188"},
|
||||
{"Croatia", "Croatie (la)", "HR", "HRV", "191"},
|
||||
{"Cuba", "Cuba", "CU", "CUB", "192"},
|
||||
{"Cyprus", "Chypre", "CY", "CYP", "196"},
|
||||
{"Czech Republic (the)", "tchèque (la République)", "CZ", "CZE", "203"},
|
||||
{"Benin", "Bénin (le)", "BJ", "BEN", "204"},
|
||||
{"Denmark", "Danemark (le)", "DK", "DNK", "208"},
|
||||
{"Dominica", "Dominique (la)", "DM", "DMA", "212"},
|
||||
{"Dominican Republic (the)", "dominicaine (la République)", "DO", "DOM", "214"},
|
||||
{"Ecuador", "Équateur (l')", "EC", "ECU", "218"},
|
||||
{"El Salvador", "El Salvador", "SV", "SLV", "222"},
|
||||
{"Equatorial Guinea", "Guinée équatoriale (la)", "GQ", "GNQ", "226"},
|
||||
{"Ethiopia", "Éthiopie (l')", "ET", "ETH", "231"},
|
||||
{"Eritrea", "Érythrée (l')", "ER", "ERI", "232"},
|
||||
{"Estonia", "Estonie (l')", "EE", "EST", "233"},
|
||||
{"Faroe Islands (the)", "Féroé (les Îles)", "FO", "FRO", "234"},
|
||||
{"Falkland Islands (the) [Malvinas]", "Falkland (les Îles)/Malouines (les Îles)", "FK", "FLK", "238"},
|
||||
{"South Georgia and the South Sandwich Islands", "Géorgie du Sud-et-les Îles Sandwich du Sud (la)", "GS", "SGS", "239"},
|
||||
{"Fiji", "Fidji (les)", "FJ", "FJI", "242"},
|
||||
{"Finland", "Finlande (la)", "FI", "FIN", "246"},
|
||||
{"Åland Islands", "Åland(les Îles)", "AX", "ALA", "248"},
|
||||
{"France", "France (la)", "FR", "FRA", "250"},
|
||||
{"French Guiana", "Guyane française (la )", "GF", "GUF", "254"},
|
||||
{"French Polynesia", "Polynésie française (la)", "PF", "PYF", "258"},
|
||||
{"French Southern Territories (the)", "Terres australes françaises (les)", "TF", "ATF", "260"},
|
||||
{"Djibouti", "Djibouti", "DJ", "DJI", "262"},
|
||||
{"Gabon", "Gabon (le)", "GA", "GAB", "266"},
|
||||
{"Georgia", "Géorgie (la)", "GE", "GEO", "268"},
|
||||
{"Gambia (the)", "Gambie (la)", "GM", "GMB", "270"},
|
||||
{"Palestine, State of", "Palestine, État de", "PS", "PSE", "275"},
|
||||
{"Germany", "Allemagne (l')", "DE", "DEU", "276"},
|
||||
{"Ghana", "Ghana (le)", "GH", "GHA", "288"},
|
||||
{"Gibraltar", "Gibraltar", "GI", "GIB", "292"},
|
||||
{"Kiribati", "Kiribati", "KI", "KIR", "296"},
|
||||
{"Greece", "Grèce (la)", "GR", "GRC", "300"},
|
||||
{"Greenland", "Groenland (le)", "GL", "GRL", "304"},
|
||||
{"Grenada", "Grenade (la)", "GD", "GRD", "308"},
|
||||
{"Guadeloupe", "Guadeloupe (la)", "GP", "GLP", "312"},
|
||||
{"Guam", "Guam", "GU", "GUM", "316"},
|
||||
{"Guatemala", "Guatemala (le)", "GT", "GTM", "320"},
|
||||
{"Guinea", "Guinée (la)", "GN", "GIN", "324"},
|
||||
{"Guyana", "Guyana (le)", "GY", "GUY", "328"},
|
||||
{"Haiti", "Haïti", "HT", "HTI", "332"},
|
||||
{"Heard Island and McDonald Islands", "Heard-et-Îles MacDonald (l'Île)", "HM", "HMD", "334"},
|
||||
{"Holy See (the)", "Saint-Siège (le)", "VA", "VAT", "336"},
|
||||
{"Honduras", "Honduras (le)", "HN", "HND", "340"},
|
||||
{"Hong Kong", "Hong Kong", "HK", "HKG", "344"},
|
||||
{"Hungary", "Hongrie (la)", "HU", "HUN", "348"},
|
||||
{"Iceland", "Islande (l')", "IS", "ISL", "352"},
|
||||
{"India", "Inde (l')", "IN", "IND", "356"},
|
||||
{"Indonesia", "Indonésie (l')", "ID", "IDN", "360"},
|
||||
{"Iran (Islamic Republic of)", "Iran (République Islamique d')", "IR", "IRN", "364"},
|
||||
{"Iraq", "Iraq (l')", "IQ", "IRQ", "368"},
|
||||
{"Ireland", "Irlande (l')", "IE", "IRL", "372"},
|
||||
{"Israel", "Israël", "IL", "ISR", "376"},
|
||||
{"Italy", "Italie (l')", "IT", "ITA", "380"},
|
||||
{"Côte d'Ivoire", "Côte d'Ivoire (la)", "CI", "CIV", "384"},
|
||||
{"Jamaica", "Jamaïque (la)", "JM", "JAM", "388"},
|
||||
{"Japan", "Japon (le)", "JP", "JPN", "392"},
|
||||
{"Kazakhstan", "Kazakhstan (le)", "KZ", "KAZ", "398"},
|
||||
{"Jordan", "Jordanie (la)", "JO", "JOR", "400"},
|
||||
{"Kenya", "Kenya (le)", "KE", "KEN", "404"},
|
||||
{"Korea (the Democratic People's Republic of)", "Corée (la République populaire démocratique de)", "KP", "PRK", "408"},
|
||||
{"Korea (the Republic of)", "Corée (la République de)", "KR", "KOR", "410"},
|
||||
{"Kuwait", "Koweït (le)", "KW", "KWT", "414"},
|
||||
{"Kyrgyzstan", "Kirghizistan (le)", "KG", "KGZ", "417"},
|
||||
{"Lao People's Democratic Republic (the)", "Lao, République démocratique populaire", "LA", "LAO", "418"},
|
||||
{"Lebanon", "Liban (le)", "LB", "LBN", "422"},
|
||||
{"Lesotho", "Lesotho (le)", "LS", "LSO", "426"},
|
||||
{"Latvia", "Lettonie (la)", "LV", "LVA", "428"},
|
||||
{"Liberia", "Libéria (le)", "LR", "LBR", "430"},
|
||||
{"Libya", "Libye (la)", "LY", "LBY", "434"},
|
||||
{"Liechtenstein", "Liechtenstein (le)", "LI", "LIE", "438"},
|
||||
{"Lithuania", "Lituanie (la)", "LT", "LTU", "440"},
|
||||
{"Luxembourg", "Luxembourg (le)", "LU", "LUX", "442"},
|
||||
{"Macao", "Macao", "MO", "MAC", "446"},
|
||||
{"Madagascar", "Madagascar", "MG", "MDG", "450"},
|
||||
{"Malawi", "Malawi (le)", "MW", "MWI", "454"},
|
||||
{"Malaysia", "Malaisie (la)", "MY", "MYS", "458"},
|
||||
{"Maldives", "Maldives (les)", "MV", "MDV", "462"},
|
||||
{"Mali", "Mali (le)", "ML", "MLI", "466"},
|
||||
{"Malta", "Malte", "MT", "MLT", "470"},
|
||||
{"Martinique", "Martinique (la)", "MQ", "MTQ", "474"},
|
||||
{"Mauritania", "Mauritanie (la)", "MR", "MRT", "478"},
|
||||
{"Mauritius", "Maurice", "MU", "MUS", "480"},
|
||||
{"Mexico", "Mexique (le)", "MX", "MEX", "484"},
|
||||
{"Monaco", "Monaco", "MC", "MCO", "492"},
|
||||
{"Mongolia", "Mongolie (la)", "MN", "MNG", "496"},
|
||||
{"Moldova (the Republic of)", "Moldova , République de", "MD", "MDA", "498"},
|
||||
{"Montenegro", "Monténégro (le)", "ME", "MNE", "499"},
|
||||
{"Montserrat", "Montserrat", "MS", "MSR", "500"},
|
||||
{"Morocco", "Maroc (le)", "MA", "MAR", "504"},
|
||||
{"Mozambique", "Mozambique (le)", "MZ", "MOZ", "508"},
|
||||
{"Oman", "Oman", "OM", "OMN", "512"},
|
||||
{"Namibia", "Namibie (la)", "NA", "NAM", "516"},
|
||||
{"Nauru", "Nauru", "NR", "NRU", "520"},
|
||||
{"Nepal", "Népal (le)", "NP", "NPL", "524"},
|
||||
{"Netherlands (the)", "Pays-Bas (les)", "NL", "NLD", "528"},
|
||||
{"Curaçao", "Curaçao", "CW", "CUW", "531"},
|
||||
{"Aruba", "Aruba", "AW", "ABW", "533"},
|
||||
{"Sint Maarten (Dutch part)", "Saint-Martin (partie néerlandaise)", "SX", "SXM", "534"},
|
||||
{"Bonaire, Sint Eustatius and Saba", "Bonaire, Saint-Eustache et Saba", "BQ", "BES", "535"},
|
||||
{"New Caledonia", "Nouvelle-Calédonie (la)", "NC", "NCL", "540"},
|
||||
{"Vanuatu", "Vanuatu (le)", "VU", "VUT", "548"},
|
||||
{"New Zealand", "Nouvelle-Zélande (la)", "NZ", "NZL", "554"},
|
||||
{"Nicaragua", "Nicaragua (le)", "NI", "NIC", "558"},
|
||||
{"Niger (the)", "Niger (le)", "NE", "NER", "562"},
|
||||
{"Nigeria", "Nigéria (le)", "NG", "NGA", "566"},
|
||||
{"Niue", "Niue", "NU", "NIU", "570"},
|
||||
{"Norfolk Island", "Norfolk (l'Île)", "NF", "NFK", "574"},
|
||||
{"Norway", "Norvège (la)", "NO", "NOR", "578"},
|
||||
{"Northern Mariana Islands (the)", "Mariannes du Nord (les Îles)", "MP", "MNP", "580"},
|
||||
{"United States Minor Outlying Islands (the)", "Îles mineures éloignées des États-Unis (les)", "UM", "UMI", "581"},
|
||||
{"Micronesia (Federated States of)", "Micronésie (États fédérés de)", "FM", "FSM", "583"},
|
||||
{"Marshall Islands (the)", "Marshall (Îles)", "MH", "MHL", "584"},
|
||||
{"Palau", "Palaos (les)", "PW", "PLW", "585"},
|
||||
{"Pakistan", "Pakistan (le)", "PK", "PAK", "586"},
|
||||
{"Panama", "Panama (le)", "PA", "PAN", "591"},
|
||||
{"Papua New Guinea", "Papouasie-Nouvelle-Guinée (la)", "PG", "PNG", "598"},
|
||||
{"Paraguay", "Paraguay (le)", "PY", "PRY", "600"},
|
||||
{"Peru", "Pérou (le)", "PE", "PER", "604"},
|
||||
{"Philippines (the)", "Philippines (les)", "PH", "PHL", "608"},
|
||||
{"Pitcairn", "Pitcairn", "PN", "PCN", "612"},
|
||||
{"Poland", "Pologne (la)", "PL", "POL", "616"},
|
||||
{"Portugal", "Portugal (le)", "PT", "PRT", "620"},
|
||||
{"Guinea-Bissau", "Guinée-Bissau (la)", "GW", "GNB", "624"},
|
||||
{"Timor-Leste", "Timor-Leste (le)", "TL", "TLS", "626"},
|
||||
{"Puerto Rico", "Porto Rico", "PR", "PRI", "630"},
|
||||
{"Qatar", "Qatar (le)", "QA", "QAT", "634"},
|
||||
{"Réunion", "Réunion (La)", "RE", "REU", "638"},
|
||||
{"Romania", "Roumanie (la)", "RO", "ROU", "642"},
|
||||
{"Russian Federation (the)", "Russie (la Fédération de)", "RU", "RUS", "643"},
|
||||
{"Rwanda", "Rwanda (le)", "RW", "RWA", "646"},
|
||||
{"Saint Barthélemy", "Saint-Barthélemy", "BL", "BLM", "652"},
|
||||
{"Saint Helena, Ascension and Tristan da Cunha", "Sainte-Hélène, Ascension et Tristan da Cunha", "SH", "SHN", "654"},
|
||||
{"Saint Kitts and Nevis", "Saint-Kitts-et-Nevis", "KN", "KNA", "659"},
|
||||
{"Anguilla", "Anguilla", "AI", "AIA", "660"},
|
||||
{"Saint Lucia", "Sainte-Lucie", "LC", "LCA", "662"},
|
||||
{"Saint Martin (French part)", "Saint-Martin (partie française)", "MF", "MAF", "663"},
|
||||
{"Saint Pierre and Miquelon", "Saint-Pierre-et-Miquelon", "PM", "SPM", "666"},
|
||||
{"Saint Vincent and the Grenadines", "Saint-Vincent-et-les Grenadines", "VC", "VCT", "670"},
|
||||
{"San Marino", "Saint-Marin", "SM", "SMR", "674"},
|
||||
{"Sao Tome and Principe", "Sao Tomé-et-Principe", "ST", "STP", "678"},
|
||||
{"Saudi Arabia", "Arabie saoudite (l')", "SA", "SAU", "682"},
|
||||
{"Senegal", "Sénégal (le)", "SN", "SEN", "686"},
|
||||
{"Serbia", "Serbie (la)", "RS", "SRB", "688"},
|
||||
{"Seychelles", "Seychelles (les)", "SC", "SYC", "690"},
|
||||
{"Sierra Leone", "Sierra Leone (la)", "SL", "SLE", "694"},
|
||||
{"Singapore", "Singapour", "SG", "SGP", "702"},
|
||||
{"Slovakia", "Slovaquie (la)", "SK", "SVK", "703"},
|
||||
{"Viet Nam", "Viet Nam (le)", "VN", "VNM", "704"},
|
||||
{"Slovenia", "Slovénie (la)", "SI", "SVN", "705"},
|
||||
{"Somalia", "Somalie (la)", "SO", "SOM", "706"},
|
||||
{"South Africa", "Afrique du Sud (l')", "ZA", "ZAF", "710"},
|
||||
{"Zimbabwe", "Zimbabwe (le)", "ZW", "ZWE", "716"},
|
||||
{"Spain", "Espagne (l')", "ES", "ESP", "724"},
|
||||
{"South Sudan", "Soudan du Sud (le)", "SS", "SSD", "728"},
|
||||
{"Sudan (the)", "Soudan (le)", "SD", "SDN", "729"},
|
||||
{"Western Sahara*", "Sahara occidental (le)*", "EH", "ESH", "732"},
|
||||
{"Suriname", "Suriname (le)", "SR", "SUR", "740"},
|
||||
{"Svalbard and Jan Mayen", "Svalbard et l'Île Jan Mayen (le)", "SJ", "SJM", "744"},
|
||||
{"Swaziland", "Swaziland (le)", "SZ", "SWZ", "748"},
|
||||
{"Sweden", "Suède (la)", "SE", "SWE", "752"},
|
||||
{"Switzerland", "Suisse (la)", "CH", "CHE", "756"},
|
||||
{"Syrian Arab Republic", "République arabe syrienne (la)", "SY", "SYR", "760"},
|
||||
{"Tajikistan", "Tadjikistan (le)", "TJ", "TJK", "762"},
|
||||
{"Thailand", "Thaïlande (la)", "TH", "THA", "764"},
|
||||
{"Togo", "Togo (le)", "TG", "TGO", "768"},
|
||||
{"Tokelau", "Tokelau (les)", "TK", "TKL", "772"},
|
||||
{"Tonga", "Tonga (les)", "TO", "TON", "776"},
|
||||
{"Trinidad and Tobago", "Trinité-et-Tobago (la)", "TT", "TTO", "780"},
|
||||
{"United Arab Emirates (the)", "Émirats arabes unis (les)", "AE", "ARE", "784"},
|
||||
{"Tunisia", "Tunisie (la)", "TN", "TUN", "788"},
|
||||
{"Turkey", "Turquie (la)", "TR", "TUR", "792"},
|
||||
{"Turkmenistan", "Turkménistan (le)", "TM", "TKM", "795"},
|
||||
{"Turks and Caicos Islands (the)", "Turks-et-Caïcos (les Îles)", "TC", "TCA", "796"},
|
||||
{"Tuvalu", "Tuvalu (les)", "TV", "TUV", "798"},
|
||||
{"Uganda", "Ouganda (l')", "UG", "UGA", "800"},
|
||||
{"Ukraine", "Ukraine (l')", "UA", "UKR", "804"},
|
||||
{"Macedonia (the former Yugoslav Republic of)", "Macédoine (l'ex‑République yougoslave de)", "MK", "MKD", "807"},
|
||||
{"Egypt", "Égypte (l')", "EG", "EGY", "818"},
|
||||
{"United Kingdom of Great Britain and Northern Ireland (the)", "Royaume-Uni de Grande-Bretagne et d'Irlande du Nord (le)", "GB", "GBR", "826"},
|
||||
{"Guernsey", "Guernesey", "GG", "GGY", "831"},
|
||||
{"Jersey", "Jersey", "JE", "JEY", "832"},
|
||||
{"Isle of Man", "Île de Man", "IM", "IMN", "833"},
|
||||
{"Tanzania, United Republic of", "Tanzanie, République-Unie de", "TZ", "TZA", "834"},
|
||||
{"United States of America (the)", "États-Unis d'Amérique (les)", "US", "USA", "840"},
|
||||
{"Virgin Islands (U.S.)", "Vierges des États-Unis (les Îles)", "VI", "VIR", "850"},
|
||||
{"Burkina Faso", "Burkina Faso (le)", "BF", "BFA", "854"},
|
||||
{"Uruguay", "Uruguay (l')", "UY", "URY", "858"},
|
||||
{"Uzbekistan", "Ouzbékistan (l')", "UZ", "UZB", "860"},
|
||||
{"Venezuela (Bolivarian Republic of)", "Venezuela (République bolivarienne du)", "VE", "VEN", "862"},
|
||||
{"Wallis and Futuna", "Wallis-et-Futuna", "WF", "WLF", "876"},
|
||||
{"Samoa", "Samoa (le)", "WS", "WSM", "882"},
|
||||
{"Yemen", "Yémen (le)", "YE", "YEM", "887"},
|
||||
{"Zambia", "Zambie (la)", "ZM", "ZMB", "894"},
|
||||
}
|
||||
|
||||
// ISO4217List is the list of ISO currency codes
|
||||
var ISO4217List = []string{
|
||||
"AED", "AFN", "ALL", "AMD", "ANG", "AOA", "ARS", "AUD", "AWG", "AZN",
|
||||
"BAM", "BBD", "BDT", "BGN", "BHD", "BIF", "BMD", "BND", "BOB", "BOV", "BRL", "BSD", "BTN", "BWP", "BYN", "BZD",
|
||||
"CAD", "CDF", "CHE", "CHF", "CHW", "CLF", "CLP", "CNY", "COP", "COU", "CRC", "CUC", "CUP", "CVE", "CZK",
|
||||
"DJF", "DKK", "DOP", "DZD",
|
||||
"EGP", "ERN", "ETB", "EUR",
|
||||
"FJD", "FKP",
|
||||
"GBP", "GEL", "GHS", "GIP", "GMD", "GNF", "GTQ", "GYD",
|
||||
"HKD", "HNL", "HRK", "HTG", "HUF",
|
||||
"IDR", "ILS", "INR", "IQD", "IRR", "ISK",
|
||||
"JMD", "JOD", "JPY",
|
||||
"KES", "KGS", "KHR", "KMF", "KPW", "KRW", "KWD", "KYD", "KZT",
|
||||
"LAK", "LBP", "LKR", "LRD", "LSL", "LYD",
|
||||
"MAD", "MDL", "MGA", "MKD", "MMK", "MNT", "MOP", "MRO", "MUR", "MVR", "MWK", "MXN", "MXV", "MYR", "MZN",
|
||||
"NAD", "NGN", "NIO", "NOK", "NPR", "NZD",
|
||||
"OMR",
|
||||
"PAB", "PEN", "PGK", "PHP", "PKR", "PLN", "PYG",
|
||||
"QAR",
|
||||
"RON", "RSD", "RUB", "RWF",
|
||||
"SAR", "SBD", "SCR", "SDG", "SEK", "SGD", "SHP", "SLL", "SOS", "SRD", "SSP", "STD", "STN", "SVC", "SYP", "SZL",
|
||||
"THB", "TJS", "TMT", "TND", "TOP", "TRY", "TTD", "TWD", "TZS",
|
||||
"UAH", "UGX", "USD", "USN", "UYI", "UYU", "UYW", "UZS",
|
||||
"VEF", "VES", "VND", "VUV",
|
||||
"WST",
|
||||
"XAF", "XAG", "XAU", "XBA", "XBB", "XBC", "XBD", "XCD", "XDR", "XOF", "XPD", "XPF", "XPT", "XSU", "XTS", "XUA", "XXX",
|
||||
"YER",
|
||||
"ZAR", "ZMW", "ZWL",
|
||||
}
|
||||
|
||||
// ISO693Entry stores ISO language codes
|
||||
type ISO693Entry struct {
|
||||
Alpha3bCode string
|
||||
Alpha2Code string
|
||||
English string
|
||||
}
|
||||
|
||||
//ISO693List based on http://data.okfn.org/data/core/language-codes/r/language-codes-3b2.json
|
||||
var ISO693List = []ISO693Entry{
|
||||
{Alpha3bCode: "aar", Alpha2Code: "aa", English: "Afar"},
|
||||
{Alpha3bCode: "abk", Alpha2Code: "ab", English: "Abkhazian"},
|
||||
{Alpha3bCode: "afr", Alpha2Code: "af", English: "Afrikaans"},
|
||||
{Alpha3bCode: "aka", Alpha2Code: "ak", English: "Akan"},
|
||||
{Alpha3bCode: "alb", Alpha2Code: "sq", English: "Albanian"},
|
||||
{Alpha3bCode: "amh", Alpha2Code: "am", English: "Amharic"},
|
||||
{Alpha3bCode: "ara", Alpha2Code: "ar", English: "Arabic"},
|
||||
{Alpha3bCode: "arg", Alpha2Code: "an", English: "Aragonese"},
|
||||
{Alpha3bCode: "arm", Alpha2Code: "hy", English: "Armenian"},
|
||||
{Alpha3bCode: "asm", Alpha2Code: "as", English: "Assamese"},
|
||||
{Alpha3bCode: "ava", Alpha2Code: "av", English: "Avaric"},
|
||||
{Alpha3bCode: "ave", Alpha2Code: "ae", English: "Avestan"},
|
||||
{Alpha3bCode: "aym", Alpha2Code: "ay", English: "Aymara"},
|
||||
{Alpha3bCode: "aze", Alpha2Code: "az", English: "Azerbaijani"},
|
||||
{Alpha3bCode: "bak", Alpha2Code: "ba", English: "Bashkir"},
|
||||
{Alpha3bCode: "bam", Alpha2Code: "bm", English: "Bambara"},
|
||||
{Alpha3bCode: "baq", Alpha2Code: "eu", English: "Basque"},
|
||||
{Alpha3bCode: "bel", Alpha2Code: "be", English: "Belarusian"},
|
||||
{Alpha3bCode: "ben", Alpha2Code: "bn", English: "Bengali"},
|
||||
{Alpha3bCode: "bih", Alpha2Code: "bh", English: "Bihari languages"},
|
||||
{Alpha3bCode: "bis", Alpha2Code: "bi", English: "Bislama"},
|
||||
{Alpha3bCode: "bos", Alpha2Code: "bs", English: "Bosnian"},
|
||||
{Alpha3bCode: "bre", Alpha2Code: "br", English: "Breton"},
|
||||
{Alpha3bCode: "bul", Alpha2Code: "bg", English: "Bulgarian"},
|
||||
{Alpha3bCode: "bur", Alpha2Code: "my", English: "Burmese"},
|
||||
{Alpha3bCode: "cat", Alpha2Code: "ca", English: "Catalan; Valencian"},
|
||||
{Alpha3bCode: "cha", Alpha2Code: "ch", English: "Chamorro"},
|
||||
{Alpha3bCode: "che", Alpha2Code: "ce", English: "Chechen"},
|
||||
{Alpha3bCode: "chi", Alpha2Code: "zh", English: "Chinese"},
|
||||
{Alpha3bCode: "chu", Alpha2Code: "cu", English: "Church Slavic; Old Slavonic; Church Slavonic; Old Bulgarian; Old Church Slavonic"},
|
||||
{Alpha3bCode: "chv", Alpha2Code: "cv", English: "Chuvash"},
|
||||
{Alpha3bCode: "cor", Alpha2Code: "kw", English: "Cornish"},
|
||||
{Alpha3bCode: "cos", Alpha2Code: "co", English: "Corsican"},
|
||||
{Alpha3bCode: "cre", Alpha2Code: "cr", English: "Cree"},
|
||||
{Alpha3bCode: "cze", Alpha2Code: "cs", English: "Czech"},
|
||||
{Alpha3bCode: "dan", Alpha2Code: "da", English: "Danish"},
|
||||
{Alpha3bCode: "div", Alpha2Code: "dv", English: "Divehi; Dhivehi; Maldivian"},
|
||||
{Alpha3bCode: "dut", Alpha2Code: "nl", English: "Dutch; Flemish"},
|
||||
{Alpha3bCode: "dzo", Alpha2Code: "dz", English: "Dzongkha"},
|
||||
{Alpha3bCode: "eng", Alpha2Code: "en", English: "English"},
|
||||
{Alpha3bCode: "epo", Alpha2Code: "eo", English: "Esperanto"},
|
||||
{Alpha3bCode: "est", Alpha2Code: "et", English: "Estonian"},
|
||||
{Alpha3bCode: "ewe", Alpha2Code: "ee", English: "Ewe"},
|
||||
{Alpha3bCode: "fao", Alpha2Code: "fo", English: "Faroese"},
|
||||
{Alpha3bCode: "fij", Alpha2Code: "fj", English: "Fijian"},
|
||||
{Alpha3bCode: "fin", Alpha2Code: "fi", English: "Finnish"},
|
||||
{Alpha3bCode: "fre", Alpha2Code: "fr", English: "French"},
|
||||
{Alpha3bCode: "fry", Alpha2Code: "fy", English: "Western Frisian"},
|
||||
{Alpha3bCode: "ful", Alpha2Code: "ff", English: "Fulah"},
|
||||
{Alpha3bCode: "geo", Alpha2Code: "ka", English: "Georgian"},
|
||||
{Alpha3bCode: "ger", Alpha2Code: "de", English: "German"},
|
||||
{Alpha3bCode: "gla", Alpha2Code: "gd", English: "Gaelic; Scottish Gaelic"},
|
||||
{Alpha3bCode: "gle", Alpha2Code: "ga", English: "Irish"},
|
||||
{Alpha3bCode: "glg", Alpha2Code: "gl", English: "Galician"},
|
||||
{Alpha3bCode: "glv", Alpha2Code: "gv", English: "Manx"},
|
||||
{Alpha3bCode: "gre", Alpha2Code: "el", English: "Greek, Modern (1453-)"},
|
||||
{Alpha3bCode: "grn", Alpha2Code: "gn", English: "Guarani"},
|
||||
{Alpha3bCode: "guj", Alpha2Code: "gu", English: "Gujarati"},
|
||||
{Alpha3bCode: "hat", Alpha2Code: "ht", English: "Haitian; Haitian Creole"},
|
||||
{Alpha3bCode: "hau", Alpha2Code: "ha", English: "Hausa"},
|
||||
{Alpha3bCode: "heb", Alpha2Code: "he", English: "Hebrew"},
|
||||
{Alpha3bCode: "her", Alpha2Code: "hz", English: "Herero"},
|
||||
{Alpha3bCode: "hin", Alpha2Code: "hi", English: "Hindi"},
|
||||
{Alpha3bCode: "hmo", Alpha2Code: "ho", English: "Hiri Motu"},
|
||||
{Alpha3bCode: "hrv", Alpha2Code: "hr", English: "Croatian"},
|
||||
{Alpha3bCode: "hun", Alpha2Code: "hu", English: "Hungarian"},
|
||||
{Alpha3bCode: "ibo", Alpha2Code: "ig", English: "Igbo"},
|
||||
{Alpha3bCode: "ice", Alpha2Code: "is", English: "Icelandic"},
|
||||
{Alpha3bCode: "ido", Alpha2Code: "io", English: "Ido"},
|
||||
{Alpha3bCode: "iii", Alpha2Code: "ii", English: "Sichuan Yi; Nuosu"},
|
||||
{Alpha3bCode: "iku", Alpha2Code: "iu", English: "Inuktitut"},
|
||||
{Alpha3bCode: "ile", Alpha2Code: "ie", English: "Interlingue; Occidental"},
|
||||
{Alpha3bCode: "ina", Alpha2Code: "ia", English: "Interlingua (International Auxiliary Language Association)"},
|
||||
{Alpha3bCode: "ind", Alpha2Code: "id", English: "Indonesian"},
|
||||
{Alpha3bCode: "ipk", Alpha2Code: "ik", English: "Inupiaq"},
|
||||
{Alpha3bCode: "ita", Alpha2Code: "it", English: "Italian"},
|
||||
{Alpha3bCode: "jav", Alpha2Code: "jv", English: "Javanese"},
|
||||
{Alpha3bCode: "jpn", Alpha2Code: "ja", English: "Japanese"},
|
||||
{Alpha3bCode: "kal", Alpha2Code: "kl", English: "Kalaallisut; Greenlandic"},
|
||||
{Alpha3bCode: "kan", Alpha2Code: "kn", English: "Kannada"},
|
||||
{Alpha3bCode: "kas", Alpha2Code: "ks", English: "Kashmiri"},
|
||||
{Alpha3bCode: "kau", Alpha2Code: "kr", English: "Kanuri"},
|
||||
{Alpha3bCode: "kaz", Alpha2Code: "kk", English: "Kazakh"},
|
||||
{Alpha3bCode: "khm", Alpha2Code: "km", English: "Central Khmer"},
|
||||
{Alpha3bCode: "kik", Alpha2Code: "ki", English: "Kikuyu; Gikuyu"},
|
||||
{Alpha3bCode: "kin", Alpha2Code: "rw", English: "Kinyarwanda"},
|
||||
{Alpha3bCode: "kir", Alpha2Code: "ky", English: "Kirghiz; Kyrgyz"},
|
||||
{Alpha3bCode: "kom", Alpha2Code: "kv", English: "Komi"},
|
||||
{Alpha3bCode: "kon", Alpha2Code: "kg", English: "Kongo"},
|
||||
{Alpha3bCode: "kor", Alpha2Code: "ko", English: "Korean"},
|
||||
{Alpha3bCode: "kua", Alpha2Code: "kj", English: "Kuanyama; Kwanyama"},
|
||||
{Alpha3bCode: "kur", Alpha2Code: "ku", English: "Kurdish"},
|
||||
{Alpha3bCode: "lao", Alpha2Code: "lo", English: "Lao"},
|
||||
{Alpha3bCode: "lat", Alpha2Code: "la", English: "Latin"},
|
||||
{Alpha3bCode: "lav", Alpha2Code: "lv", English: "Latvian"},
|
||||
{Alpha3bCode: "lim", Alpha2Code: "li", English: "Limburgan; Limburger; Limburgish"},
|
||||
{Alpha3bCode: "lin", Alpha2Code: "ln", English: "Lingala"},
|
||||
{Alpha3bCode: "lit", Alpha2Code: "lt", English: "Lithuanian"},
|
||||
{Alpha3bCode: "ltz", Alpha2Code: "lb", English: "Luxembourgish; Letzeburgesch"},
|
||||
{Alpha3bCode: "lub", Alpha2Code: "lu", English: "Luba-Katanga"},
|
||||
{Alpha3bCode: "lug", Alpha2Code: "lg", English: "Ganda"},
|
||||
{Alpha3bCode: "mac", Alpha2Code: "mk", English: "Macedonian"},
|
||||
{Alpha3bCode: "mah", Alpha2Code: "mh", English: "Marshallese"},
|
||||
{Alpha3bCode: "mal", Alpha2Code: "ml", English: "Malayalam"},
|
||||
{Alpha3bCode: "mao", Alpha2Code: "mi", English: "Maori"},
|
||||
{Alpha3bCode: "mar", Alpha2Code: "mr", English: "Marathi"},
|
||||
{Alpha3bCode: "may", Alpha2Code: "ms", English: "Malay"},
|
||||
{Alpha3bCode: "mlg", Alpha2Code: "mg", English: "Malagasy"},
|
||||
{Alpha3bCode: "mlt", Alpha2Code: "mt", English: "Maltese"},
|
||||
{Alpha3bCode: "mon", Alpha2Code: "mn", English: "Mongolian"},
|
||||
{Alpha3bCode: "nau", Alpha2Code: "na", English: "Nauru"},
|
||||
{Alpha3bCode: "nav", Alpha2Code: "nv", English: "Navajo; Navaho"},
|
||||
{Alpha3bCode: "nbl", Alpha2Code: "nr", English: "Ndebele, South; South Ndebele"},
|
||||
{Alpha3bCode: "nde", Alpha2Code: "nd", English: "Ndebele, North; North Ndebele"},
|
||||
{Alpha3bCode: "ndo", Alpha2Code: "ng", English: "Ndonga"},
|
||||
{Alpha3bCode: "nep", Alpha2Code: "ne", English: "Nepali"},
|
||||
{Alpha3bCode: "nno", Alpha2Code: "nn", English: "Norwegian Nynorsk; Nynorsk, Norwegian"},
|
||||
{Alpha3bCode: "nob", Alpha2Code: "nb", English: "Bokmål, Norwegian; Norwegian Bokmål"},
|
||||
{Alpha3bCode: "nor", Alpha2Code: "no", English: "Norwegian"},
|
||||
{Alpha3bCode: "nya", Alpha2Code: "ny", English: "Chichewa; Chewa; Nyanja"},
|
||||
{Alpha3bCode: "oci", Alpha2Code: "oc", English: "Occitan (post 1500); Provençal"},
|
||||
{Alpha3bCode: "oji", Alpha2Code: "oj", English: "Ojibwa"},
|
||||
{Alpha3bCode: "ori", Alpha2Code: "or", English: "Oriya"},
|
||||
{Alpha3bCode: "orm", Alpha2Code: "om", English: "Oromo"},
|
||||
{Alpha3bCode: "oss", Alpha2Code: "os", English: "Ossetian; Ossetic"},
|
||||
{Alpha3bCode: "pan", Alpha2Code: "pa", English: "Panjabi; Punjabi"},
|
||||
{Alpha3bCode: "per", Alpha2Code: "fa", English: "Persian"},
|
||||
{Alpha3bCode: "pli", Alpha2Code: "pi", English: "Pali"},
|
||||
{Alpha3bCode: "pol", Alpha2Code: "pl", English: "Polish"},
|
||||
{Alpha3bCode: "por", Alpha2Code: "pt", English: "Portuguese"},
|
||||
{Alpha3bCode: "pus", Alpha2Code: "ps", English: "Pushto; Pashto"},
|
||||
{Alpha3bCode: "que", Alpha2Code: "qu", English: "Quechua"},
|
||||
{Alpha3bCode: "roh", Alpha2Code: "rm", English: "Romansh"},
|
||||
{Alpha3bCode: "rum", Alpha2Code: "ro", English: "Romanian; Moldavian; Moldovan"},
|
||||
{Alpha3bCode: "run", Alpha2Code: "rn", English: "Rundi"},
|
||||
{Alpha3bCode: "rus", Alpha2Code: "ru", English: "Russian"},
|
||||
{Alpha3bCode: "sag", Alpha2Code: "sg", English: "Sango"},
|
||||
{Alpha3bCode: "san", Alpha2Code: "sa", English: "Sanskrit"},
|
||||
{Alpha3bCode: "sin", Alpha2Code: "si", English: "Sinhala; Sinhalese"},
|
||||
{Alpha3bCode: "slo", Alpha2Code: "sk", English: "Slovak"},
|
||||
{Alpha3bCode: "slv", Alpha2Code: "sl", English: "Slovenian"},
|
||||
{Alpha3bCode: "sme", Alpha2Code: "se", English: "Northern Sami"},
|
||||
{Alpha3bCode: "smo", Alpha2Code: "sm", English: "Samoan"},
|
||||
{Alpha3bCode: "sna", Alpha2Code: "sn", English: "Shona"},
|
||||
{Alpha3bCode: "snd", Alpha2Code: "sd", English: "Sindhi"},
|
||||
{Alpha3bCode: "som", Alpha2Code: "so", English: "Somali"},
|
||||
{Alpha3bCode: "sot", Alpha2Code: "st", English: "Sotho, Southern"},
|
||||
{Alpha3bCode: "spa", Alpha2Code: "es", English: "Spanish; Castilian"},
|
||||
{Alpha3bCode: "srd", Alpha2Code: "sc", English: "Sardinian"},
|
||||
{Alpha3bCode: "srp", Alpha2Code: "sr", English: "Serbian"},
|
||||
{Alpha3bCode: "ssw", Alpha2Code: "ss", English: "Swati"},
|
||||
{Alpha3bCode: "sun", Alpha2Code: "su", English: "Sundanese"},
|
||||
{Alpha3bCode: "swa", Alpha2Code: "sw", English: "Swahili"},
|
||||
{Alpha3bCode: "swe", Alpha2Code: "sv", English: "Swedish"},
|
||||
{Alpha3bCode: "tah", Alpha2Code: "ty", English: "Tahitian"},
|
||||
{Alpha3bCode: "tam", Alpha2Code: "ta", English: "Tamil"},
|
||||
{Alpha3bCode: "tat", Alpha2Code: "tt", English: "Tatar"},
|
||||
{Alpha3bCode: "tel", Alpha2Code: "te", English: "Telugu"},
|
||||
{Alpha3bCode: "tgk", Alpha2Code: "tg", English: "Tajik"},
|
||||
{Alpha3bCode: "tgl", Alpha2Code: "tl", English: "Tagalog"},
|
||||
{Alpha3bCode: "tha", Alpha2Code: "th", English: "Thai"},
|
||||
{Alpha3bCode: "tib", Alpha2Code: "bo", English: "Tibetan"},
|
||||
{Alpha3bCode: "tir", Alpha2Code: "ti", English: "Tigrinya"},
|
||||
{Alpha3bCode: "ton", Alpha2Code: "to", English: "Tonga (Tonga Islands)"},
|
||||
{Alpha3bCode: "tsn", Alpha2Code: "tn", English: "Tswana"},
|
||||
{Alpha3bCode: "tso", Alpha2Code: "ts", English: "Tsonga"},
|
||||
{Alpha3bCode: "tuk", Alpha2Code: "tk", English: "Turkmen"},
|
||||
{Alpha3bCode: "tur", Alpha2Code: "tr", English: "Turkish"},
|
||||
{Alpha3bCode: "twi", Alpha2Code: "tw", English: "Twi"},
|
||||
{Alpha3bCode: "uig", Alpha2Code: "ug", English: "Uighur; Uyghur"},
|
||||
{Alpha3bCode: "ukr", Alpha2Code: "uk", English: "Ukrainian"},
|
||||
{Alpha3bCode: "urd", Alpha2Code: "ur", English: "Urdu"},
|
||||
{Alpha3bCode: "uzb", Alpha2Code: "uz", English: "Uzbek"},
|
||||
{Alpha3bCode: "ven", Alpha2Code: "ve", English: "Venda"},
|
||||
{Alpha3bCode: "vie", Alpha2Code: "vi", English: "Vietnamese"},
|
||||
{Alpha3bCode: "vol", Alpha2Code: "vo", English: "Volapük"},
|
||||
{Alpha3bCode: "wel", Alpha2Code: "cy", English: "Welsh"},
|
||||
{Alpha3bCode: "wln", Alpha2Code: "wa", English: "Walloon"},
|
||||
{Alpha3bCode: "wol", Alpha2Code: "wo", English: "Wolof"},
|
||||
{Alpha3bCode: "xho", Alpha2Code: "xh", English: "Xhosa"},
|
||||
{Alpha3bCode: "yid", Alpha2Code: "yi", English: "Yiddish"},
|
||||
{Alpha3bCode: "yor", Alpha2Code: "yo", English: "Yoruba"},
|
||||
{Alpha3bCode: "zha", Alpha2Code: "za", English: "Zhuang; Chuang"},
|
||||
{Alpha3bCode: "zul", Alpha2Code: "zu", English: "Zulu"},
|
||||
}
|
||||
270
extra/server-discovery/vendor/github.com/asaskevich/govalidator/utils.go
generated
vendored
Normal file
270
extra/server-discovery/vendor/github.com/asaskevich/govalidator/utils.go
generated
vendored
Normal file
@@ -0,0 +1,270 @@
|
||||
package govalidator
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"html"
|
||||
"math"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// Contains checks if the string contains the substring.
|
||||
func Contains(str, substring string) bool {
|
||||
return strings.Contains(str, substring)
|
||||
}
|
||||
|
||||
// Matches checks if string matches the pattern (pattern is regular expression)
|
||||
// In case of error return false
|
||||
func Matches(str, pattern string) bool {
|
||||
match, _ := regexp.MatchString(pattern, str)
|
||||
return match
|
||||
}
|
||||
|
||||
// LeftTrim trims characters from the left side of the input.
|
||||
// If second argument is empty, it will remove leading spaces.
|
||||
func LeftTrim(str, chars string) string {
|
||||
if chars == "" {
|
||||
return strings.TrimLeftFunc(str, unicode.IsSpace)
|
||||
}
|
||||
r, _ := regexp.Compile("^[" + chars + "]+")
|
||||
return r.ReplaceAllString(str, "")
|
||||
}
|
||||
|
||||
// RightTrim trims characters from the right side of the input.
|
||||
// If second argument is empty, it will remove trailing spaces.
|
||||
func RightTrim(str, chars string) string {
|
||||
if chars == "" {
|
||||
return strings.TrimRightFunc(str, unicode.IsSpace)
|
||||
}
|
||||
r, _ := regexp.Compile("[" + chars + "]+$")
|
||||
return r.ReplaceAllString(str, "")
|
||||
}
|
||||
|
||||
// Trim trims characters from both sides of the input.
|
||||
// If second argument is empty, it will remove spaces.
|
||||
func Trim(str, chars string) string {
|
||||
return LeftTrim(RightTrim(str, chars), chars)
|
||||
}
|
||||
|
||||
// WhiteList removes characters that do not appear in the whitelist.
|
||||
func WhiteList(str, chars string) string {
|
||||
pattern := "[^" + chars + "]+"
|
||||
r, _ := regexp.Compile(pattern)
|
||||
return r.ReplaceAllString(str, "")
|
||||
}
|
||||
|
||||
// BlackList removes characters that appear in the blacklist.
|
||||
func BlackList(str, chars string) string {
|
||||
pattern := "[" + chars + "]+"
|
||||
r, _ := regexp.Compile(pattern)
|
||||
return r.ReplaceAllString(str, "")
|
||||
}
|
||||
|
||||
// StripLow removes characters with a numerical value < 32 and 127, mostly control characters.
|
||||
// If keep_new_lines is true, newline characters are preserved (\n and \r, hex 0xA and 0xD).
|
||||
func StripLow(str string, keepNewLines bool) string {
|
||||
chars := ""
|
||||
if keepNewLines {
|
||||
chars = "\x00-\x09\x0B\x0C\x0E-\x1F\x7F"
|
||||
} else {
|
||||
chars = "\x00-\x1F\x7F"
|
||||
}
|
||||
return BlackList(str, chars)
|
||||
}
|
||||
|
||||
// ReplacePattern replaces regular expression pattern in string
|
||||
func ReplacePattern(str, pattern, replace string) string {
|
||||
r, _ := regexp.Compile(pattern)
|
||||
return r.ReplaceAllString(str, replace)
|
||||
}
|
||||
|
||||
// Escape replaces <, >, & and " with HTML entities.
|
||||
var Escape = html.EscapeString
|
||||
|
||||
func addSegment(inrune, segment []rune) []rune {
|
||||
if len(segment) == 0 {
|
||||
return inrune
|
||||
}
|
||||
if len(inrune) != 0 {
|
||||
inrune = append(inrune, '_')
|
||||
}
|
||||
inrune = append(inrune, segment...)
|
||||
return inrune
|
||||
}
|
||||
|
||||
// UnderscoreToCamelCase converts from underscore separated form to camel case form.
|
||||
// Ex.: my_func => MyFunc
|
||||
func UnderscoreToCamelCase(s string) string {
|
||||
return strings.Replace(strings.Title(strings.Replace(strings.ToLower(s), "_", " ", -1)), " ", "", -1)
|
||||
}
|
||||
|
||||
// CamelCaseToUnderscore converts from camel case form to underscore separated form.
|
||||
// Ex.: MyFunc => my_func
|
||||
func CamelCaseToUnderscore(str string) string {
|
||||
var output []rune
|
||||
var segment []rune
|
||||
for _, r := range str {
|
||||
|
||||
// not treat number as separate segment
|
||||
if !unicode.IsLower(r) && string(r) != "_" && !unicode.IsNumber(r) {
|
||||
output = addSegment(output, segment)
|
||||
segment = nil
|
||||
}
|
||||
segment = append(segment, unicode.ToLower(r))
|
||||
}
|
||||
output = addSegment(output, segment)
|
||||
return string(output)
|
||||
}
|
||||
|
||||
// Reverse returns reversed string
|
||||
func Reverse(s string) string {
|
||||
r := []rune(s)
|
||||
for i, j := 0, len(r)-1; i < j; i, j = i+1, j-1 {
|
||||
r[i], r[j] = r[j], r[i]
|
||||
}
|
||||
return string(r)
|
||||
}
|
||||
|
||||
// GetLines splits string by "\n" and return array of lines
|
||||
func GetLines(s string) []string {
|
||||
return strings.Split(s, "\n")
|
||||
}
|
||||
|
||||
// GetLine returns specified line of multiline string
|
||||
func GetLine(s string, index int) (string, error) {
|
||||
lines := GetLines(s)
|
||||
if index < 0 || index >= len(lines) {
|
||||
return "", errors.New("line index out of bounds")
|
||||
}
|
||||
return lines[index], nil
|
||||
}
|
||||
|
||||
// RemoveTags removes all tags from HTML string
|
||||
func RemoveTags(s string) string {
|
||||
return ReplacePattern(s, "<[^>]*>", "")
|
||||
}
|
||||
|
||||
// SafeFileName returns safe string that can be used in file names
|
||||
func SafeFileName(str string) string {
|
||||
name := strings.ToLower(str)
|
||||
name = path.Clean(path.Base(name))
|
||||
name = strings.Trim(name, " ")
|
||||
separators, err := regexp.Compile(`[ &_=+:]`)
|
||||
if err == nil {
|
||||
name = separators.ReplaceAllString(name, "-")
|
||||
}
|
||||
legal, err := regexp.Compile(`[^[:alnum:]-.]`)
|
||||
if err == nil {
|
||||
name = legal.ReplaceAllString(name, "")
|
||||
}
|
||||
for strings.Contains(name, "--") {
|
||||
name = strings.Replace(name, "--", "-", -1)
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
// NormalizeEmail canonicalize an email address.
|
||||
// The local part of the email address is lowercased for all domains; the hostname is always lowercased and
|
||||
// the local part of the email address is always lowercased for hosts that are known to be case-insensitive (currently only GMail).
|
||||
// Normalization follows special rules for known providers: currently, GMail addresses have dots removed in the local part and
|
||||
// are stripped of tags (e.g. some.one+tag@gmail.com becomes someone@gmail.com) and all @googlemail.com addresses are
|
||||
// normalized to @gmail.com.
|
||||
func NormalizeEmail(str string) (string, error) {
|
||||
if !IsEmail(str) {
|
||||
return "", fmt.Errorf("%s is not an email", str)
|
||||
}
|
||||
parts := strings.Split(str, "@")
|
||||
parts[0] = strings.ToLower(parts[0])
|
||||
parts[1] = strings.ToLower(parts[1])
|
||||
if parts[1] == "gmail.com" || parts[1] == "googlemail.com" {
|
||||
parts[1] = "gmail.com"
|
||||
parts[0] = strings.Split(ReplacePattern(parts[0], `\.`, ""), "+")[0]
|
||||
}
|
||||
return strings.Join(parts, "@"), nil
|
||||
}
|
||||
|
||||
// Truncate a string to the closest length without breaking words.
|
||||
func Truncate(str string, length int, ending string) string {
|
||||
var aftstr, befstr string
|
||||
if len(str) > length {
|
||||
words := strings.Fields(str)
|
||||
before, present := 0, 0
|
||||
for i := range words {
|
||||
befstr = aftstr
|
||||
before = present
|
||||
aftstr = aftstr + words[i] + " "
|
||||
present = len(aftstr)
|
||||
if present > length && i != 0 {
|
||||
if (length - before) < (present - length) {
|
||||
return Trim(befstr, " /\\.,\"'#!?&@+-") + ending
|
||||
}
|
||||
return Trim(aftstr, " /\\.,\"'#!?&@+-") + ending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
// PadLeft pads left side of a string if size of string is less then indicated pad length
|
||||
func PadLeft(str string, padStr string, padLen int) string {
|
||||
return buildPadStr(str, padStr, padLen, true, false)
|
||||
}
|
||||
|
||||
// PadRight pads right side of a string if size of string is less then indicated pad length
|
||||
func PadRight(str string, padStr string, padLen int) string {
|
||||
return buildPadStr(str, padStr, padLen, false, true)
|
||||
}
|
||||
|
||||
// PadBoth pads both sides of a string if size of string is less then indicated pad length
|
||||
func PadBoth(str string, padStr string, padLen int) string {
|
||||
return buildPadStr(str, padStr, padLen, true, true)
|
||||
}
|
||||
|
||||
// PadString either left, right or both sides.
|
||||
// Note that padding string can be unicode and more then one character
|
||||
func buildPadStr(str string, padStr string, padLen int, padLeft bool, padRight bool) string {
|
||||
|
||||
// When padded length is less then the current string size
|
||||
if padLen < utf8.RuneCountInString(str) {
|
||||
return str
|
||||
}
|
||||
|
||||
padLen -= utf8.RuneCountInString(str)
|
||||
|
||||
targetLen := padLen
|
||||
|
||||
targetLenLeft := targetLen
|
||||
targetLenRight := targetLen
|
||||
if padLeft && padRight {
|
||||
targetLenLeft = padLen / 2
|
||||
targetLenRight = padLen - targetLenLeft
|
||||
}
|
||||
|
||||
strToRepeatLen := utf8.RuneCountInString(padStr)
|
||||
|
||||
repeatTimes := int(math.Ceil(float64(targetLen) / float64(strToRepeatLen)))
|
||||
repeatedString := strings.Repeat(padStr, repeatTimes)
|
||||
|
||||
leftSide := ""
|
||||
if padLeft {
|
||||
leftSide = repeatedString[0:targetLenLeft]
|
||||
}
|
||||
|
||||
rightSide := ""
|
||||
if padRight {
|
||||
rightSide = repeatedString[0:targetLenRight]
|
||||
}
|
||||
|
||||
return leftSide + str + rightSide
|
||||
}
|
||||
|
||||
// TruncatingErrorf removes extra args from fmt.Errorf if not formatted in the str object
|
||||
func TruncatingErrorf(str string, args ...interface{}) error {
|
||||
n := strings.Count(str, "%s")
|
||||
return fmt.Errorf(str, args[:n]...)
|
||||
}
|
||||
1768
extra/server-discovery/vendor/github.com/asaskevich/govalidator/validator.go
generated
vendored
Normal file
1768
extra/server-discovery/vendor/github.com/asaskevich/govalidator/validator.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
15
extra/server-discovery/vendor/github.com/asaskevich/govalidator/wercker.yml
generated
vendored
Normal file
15
extra/server-discovery/vendor/github.com/asaskevich/govalidator/wercker.yml
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
box: golang
|
||||
build:
|
||||
steps:
|
||||
- setup-go-workspace
|
||||
|
||||
- script:
|
||||
name: go get
|
||||
code: |
|
||||
go version
|
||||
go get -t ./...
|
||||
|
||||
- script:
|
||||
name: go test
|
||||
code: |
|
||||
go test -race -v ./...
|
||||
22
extra/server-discovery/vendor/github.com/aymerick/douceur/LICENSE
generated
vendored
Normal file
22
extra/server-discovery/vendor/github.com/aymerick/douceur/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Aymerick JEHANNE
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
60
extra/server-discovery/vendor/github.com/aymerick/douceur/css/declaration.go
generated
vendored
Normal file
60
extra/server-discovery/vendor/github.com/aymerick/douceur/css/declaration.go
generated
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
package css
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Declaration represents a parsed style property
|
||||
type Declaration struct {
|
||||
Property string
|
||||
Value string
|
||||
Important bool
|
||||
}
|
||||
|
||||
// NewDeclaration instanciates a new Declaration
|
||||
func NewDeclaration() *Declaration {
|
||||
return &Declaration{}
|
||||
}
|
||||
|
||||
// Returns string representation of the Declaration
|
||||
func (decl *Declaration) String() string {
|
||||
return decl.StringWithImportant(true)
|
||||
}
|
||||
|
||||
// StringWithImportant returns string representation with optional !important part
|
||||
func (decl *Declaration) StringWithImportant(option bool) string {
|
||||
result := fmt.Sprintf("%s: %s", decl.Property, decl.Value)
|
||||
|
||||
if option && decl.Important {
|
||||
result += " !important"
|
||||
}
|
||||
|
||||
result += ";"
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Equal returns true if both Declarations are equals
|
||||
func (decl *Declaration) Equal(other *Declaration) bool {
|
||||
return (decl.Property == other.Property) && (decl.Value == other.Value) && (decl.Important == other.Important)
|
||||
}
|
||||
|
||||
//
|
||||
// DeclarationsByProperty
|
||||
//
|
||||
|
||||
// DeclarationsByProperty represents sortable style declarations
|
||||
type DeclarationsByProperty []*Declaration
|
||||
|
||||
// Implements sort.Interface
|
||||
func (declarations DeclarationsByProperty) Len() int {
|
||||
return len(declarations)
|
||||
}
|
||||
|
||||
// Implements sort.Interface
|
||||
func (declarations DeclarationsByProperty) Swap(i, j int) {
|
||||
declarations[i], declarations[j] = declarations[j], declarations[i]
|
||||
}
|
||||
|
||||
// Implements sort.Interface
|
||||
func (declarations DeclarationsByProperty) Less(i, j int) bool {
|
||||
return declarations[i].Property < declarations[j].Property
|
||||
}
|
||||
230
extra/server-discovery/vendor/github.com/aymerick/douceur/css/rule.go
generated
vendored
Normal file
230
extra/server-discovery/vendor/github.com/aymerick/douceur/css/rule.go
generated
vendored
Normal file
@@ -0,0 +1,230 @@
|
||||
package css
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
indentSpace = 2
|
||||
)
|
||||
|
||||
// RuleKind represents a Rule kind
|
||||
type RuleKind int
|
||||
|
||||
// Rule kinds
|
||||
const (
|
||||
QualifiedRule RuleKind = iota
|
||||
AtRule
|
||||
)
|
||||
|
||||
// At Rules than have Rules inside their block instead of Declarations
|
||||
var atRulesWithRulesBlock = []string{
|
||||
"@document", "@font-feature-values", "@keyframes", "@media", "@supports",
|
||||
}
|
||||
|
||||
// Rule represents a parsed CSS rule
|
||||
type Rule struct {
|
||||
Kind RuleKind
|
||||
|
||||
// At Rule name (eg: "@media")
|
||||
Name string
|
||||
|
||||
// Raw prelude
|
||||
Prelude string
|
||||
|
||||
// Qualified Rule selectors parsed from prelude
|
||||
Selectors []string
|
||||
|
||||
// Style properties
|
||||
Declarations []*Declaration
|
||||
|
||||
// At Rule embedded rules
|
||||
Rules []*Rule
|
||||
|
||||
// Current rule embedding level
|
||||
EmbedLevel int
|
||||
}
|
||||
|
||||
// NewRule instanciates a new Rule
|
||||
func NewRule(kind RuleKind) *Rule {
|
||||
return &Rule{
|
||||
Kind: kind,
|
||||
}
|
||||
}
|
||||
|
||||
// Returns string representation of rule kind
|
||||
func (kind RuleKind) String() string {
|
||||
switch kind {
|
||||
case QualifiedRule:
|
||||
return "Qualified Rule"
|
||||
case AtRule:
|
||||
return "At Rule"
|
||||
default:
|
||||
return "WAT"
|
||||
}
|
||||
}
|
||||
|
||||
// EmbedsRules returns true if this rule embeds another rules
|
||||
func (rule *Rule) EmbedsRules() bool {
|
||||
if rule.Kind == AtRule {
|
||||
for _, atRuleName := range atRulesWithRulesBlock {
|
||||
if rule.Name == atRuleName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Equal returns true if both rules are equals
|
||||
func (rule *Rule) Equal(other *Rule) bool {
|
||||
if (rule.Kind != other.Kind) ||
|
||||
(rule.Prelude != other.Prelude) ||
|
||||
(rule.Name != other.Name) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (len(rule.Selectors) != len(other.Selectors)) ||
|
||||
(len(rule.Declarations) != len(other.Declarations)) ||
|
||||
(len(rule.Rules) != len(other.Rules)) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i, sel := range rule.Selectors {
|
||||
if sel != other.Selectors[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for i, decl := range rule.Declarations {
|
||||
if !decl.Equal(other.Declarations[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for i, rule := range rule.Rules {
|
||||
if !rule.Equal(other.Rules[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Diff returns a string representation of rules differences
|
||||
func (rule *Rule) Diff(other *Rule) []string {
|
||||
result := []string{}
|
||||
|
||||
if rule.Kind != other.Kind {
|
||||
result = append(result, fmt.Sprintf("Kind: %s | %s", rule.Kind.String(), other.Kind.String()))
|
||||
}
|
||||
|
||||
if rule.Prelude != other.Prelude {
|
||||
result = append(result, fmt.Sprintf("Prelude: \"%s\" | \"%s\"", rule.Prelude, other.Prelude))
|
||||
}
|
||||
|
||||
if rule.Name != other.Name {
|
||||
result = append(result, fmt.Sprintf("Name: \"%s\" | \"%s\"", rule.Name, other.Name))
|
||||
}
|
||||
|
||||
if len(rule.Selectors) != len(other.Selectors) {
|
||||
result = append(result, fmt.Sprintf("Selectors: %v | %v", strings.Join(rule.Selectors, ", "), strings.Join(other.Selectors, ", ")))
|
||||
} else {
|
||||
for i, sel := range rule.Selectors {
|
||||
if sel != other.Selectors[i] {
|
||||
result = append(result, fmt.Sprintf("Selector: \"%s\" | \"%s\"", sel, other.Selectors[i]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(rule.Declarations) != len(other.Declarations) {
|
||||
result = append(result, fmt.Sprintf("Declarations Nb: %d | %d", len(rule.Declarations), len(other.Declarations)))
|
||||
} else {
|
||||
for i, decl := range rule.Declarations {
|
||||
if !decl.Equal(other.Declarations[i]) {
|
||||
result = append(result, fmt.Sprintf("Declaration: \"%s\" | \"%s\"", decl.String(), other.Declarations[i].String()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(rule.Rules) != len(other.Rules) {
|
||||
result = append(result, fmt.Sprintf("Rules Nb: %d | %d", len(rule.Rules), len(other.Rules)))
|
||||
} else {
|
||||
|
||||
for i, rule := range rule.Rules {
|
||||
if !rule.Equal(other.Rules[i]) {
|
||||
result = append(result, fmt.Sprintf("Rule: \"%s\" | \"%s\"", rule.String(), other.Rules[i].String()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Returns the string representation of a rule
|
||||
func (rule *Rule) String() string {
|
||||
result := ""
|
||||
|
||||
if rule.Kind == QualifiedRule {
|
||||
for i, sel := range rule.Selectors {
|
||||
if i != 0 {
|
||||
result += ", "
|
||||
}
|
||||
result += sel
|
||||
}
|
||||
} else {
|
||||
// AtRule
|
||||
result += fmt.Sprintf("%s", rule.Name)
|
||||
|
||||
if rule.Prelude != "" {
|
||||
if result != "" {
|
||||
result += " "
|
||||
}
|
||||
result += fmt.Sprintf("%s", rule.Prelude)
|
||||
}
|
||||
}
|
||||
|
||||
if (len(rule.Declarations) == 0) && (len(rule.Rules) == 0) {
|
||||
result += ";"
|
||||
} else {
|
||||
result += " {\n"
|
||||
|
||||
if rule.EmbedsRules() {
|
||||
for _, subRule := range rule.Rules {
|
||||
result += fmt.Sprintf("%s%s\n", rule.indent(), subRule.String())
|
||||
}
|
||||
} else {
|
||||
for _, decl := range rule.Declarations {
|
||||
result += fmt.Sprintf("%s%s\n", rule.indent(), decl.String())
|
||||
}
|
||||
}
|
||||
|
||||
result += fmt.Sprintf("%s}", rule.indentEndBlock())
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Returns identation spaces for declarations and rules
|
||||
func (rule *Rule) indent() string {
|
||||
result := ""
|
||||
|
||||
for i := 0; i < ((rule.EmbedLevel + 1) * indentSpace); i++ {
|
||||
result += " "
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Returns identation spaces for end of block character
|
||||
func (rule *Rule) indentEndBlock() string {
|
||||
result := ""
|
||||
|
||||
for i := 0; i < (rule.EmbedLevel * indentSpace); i++ {
|
||||
result += " "
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
25
extra/server-discovery/vendor/github.com/aymerick/douceur/css/stylesheet.go
generated
vendored
Normal file
25
extra/server-discovery/vendor/github.com/aymerick/douceur/css/stylesheet.go
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
package css
|
||||
|
||||
// Stylesheet represents a parsed stylesheet
|
||||
type Stylesheet struct {
|
||||
Rules []*Rule
|
||||
}
|
||||
|
||||
// NewStylesheet instanciate a new Stylesheet
|
||||
func NewStylesheet() *Stylesheet {
|
||||
return &Stylesheet{}
|
||||
}
|
||||
|
||||
// Returns string representation of the Stylesheet
|
||||
func (sheet *Stylesheet) String() string {
|
||||
result := ""
|
||||
|
||||
for _, rule := range sheet.Rules {
|
||||
if result != "" {
|
||||
result += "\n"
|
||||
}
|
||||
result += rule.String()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
409
extra/server-discovery/vendor/github.com/aymerick/douceur/parser/parser.go
generated
vendored
Normal file
409
extra/server-discovery/vendor/github.com/aymerick/douceur/parser/parser.go
generated
vendored
Normal file
@@ -0,0 +1,409 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/css/scanner"
|
||||
|
||||
"github.com/aymerick/douceur/css"
|
||||
)
|
||||
|
||||
const (
|
||||
importantSuffixRegexp = `(?i)\s*!important\s*$`
|
||||
)
|
||||
|
||||
var (
|
||||
importantRegexp *regexp.Regexp
|
||||
)
|
||||
|
||||
// Parser represents a CSS parser
|
||||
type Parser struct {
|
||||
scan *scanner.Scanner // Tokenizer
|
||||
|
||||
// Tokens parsed but not consumed yet
|
||||
tokens []*scanner.Token
|
||||
|
||||
// Rule embedding level
|
||||
embedLevel int
|
||||
}
|
||||
|
||||
func init() {
|
||||
importantRegexp = regexp.MustCompile(importantSuffixRegexp)
|
||||
}
|
||||
|
||||
// NewParser instanciates a new parser
|
||||
func NewParser(txt string) *Parser {
|
||||
return &Parser{
|
||||
scan: scanner.New(txt),
|
||||
}
|
||||
}
|
||||
|
||||
// Parse parses a whole stylesheet
|
||||
func Parse(text string) (*css.Stylesheet, error) {
|
||||
result, err := NewParser(text).ParseStylesheet()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ParseDeclarations parses CSS declarations
|
||||
func ParseDeclarations(text string) ([]*css.Declaration, error) {
|
||||
result, err := NewParser(text).ParseDeclarations()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ParseStylesheet parses a stylesheet
|
||||
func (parser *Parser) ParseStylesheet() (*css.Stylesheet, error) {
|
||||
result := css.NewStylesheet()
|
||||
|
||||
// Parse BOM
|
||||
if _, err := parser.parseBOM(); err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
// Parse list of rules
|
||||
rules, err := parser.ParseRules()
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
result.Rules = rules
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ParseRules parses a list of rules
|
||||
func (parser *Parser) ParseRules() ([]*css.Rule, error) {
|
||||
result := []*css.Rule{}
|
||||
|
||||
inBlock := false
|
||||
if parser.tokenChar("{") {
|
||||
// parsing a block of rules
|
||||
inBlock = true
|
||||
parser.embedLevel++
|
||||
|
||||
parser.shiftToken()
|
||||
}
|
||||
|
||||
for parser.tokenParsable() {
|
||||
if parser.tokenIgnorable() {
|
||||
parser.shiftToken()
|
||||
} else if parser.tokenChar("}") {
|
||||
if !inBlock {
|
||||
errMsg := fmt.Sprintf("Unexpected } character: %s", parser.nextToken().String())
|
||||
return result, errors.New(errMsg)
|
||||
}
|
||||
|
||||
parser.shiftToken()
|
||||
parser.embedLevel--
|
||||
|
||||
// finished
|
||||
break
|
||||
} else {
|
||||
rule, err := parser.ParseRule()
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
rule.EmbedLevel = parser.embedLevel
|
||||
result = append(result, rule)
|
||||
}
|
||||
}
|
||||
|
||||
return result, parser.err()
|
||||
}
|
||||
|
||||
// ParseRule parses a rule
|
||||
func (parser *Parser) ParseRule() (*css.Rule, error) {
|
||||
if parser.tokenAtKeyword() {
|
||||
return parser.parseAtRule()
|
||||
}
|
||||
|
||||
return parser.parseQualifiedRule()
|
||||
}
|
||||
|
||||
// ParseDeclarations parses a list of declarations
|
||||
func (parser *Parser) ParseDeclarations() ([]*css.Declaration, error) {
|
||||
result := []*css.Declaration{}
|
||||
|
||||
if parser.tokenChar("{") {
|
||||
parser.shiftToken()
|
||||
}
|
||||
|
||||
for parser.tokenParsable() {
|
||||
if parser.tokenIgnorable() {
|
||||
parser.shiftToken()
|
||||
} else if parser.tokenChar("}") {
|
||||
// end of block
|
||||
parser.shiftToken()
|
||||
break
|
||||
} else {
|
||||
declaration, err := parser.ParseDeclaration()
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
result = append(result, declaration)
|
||||
}
|
||||
}
|
||||
|
||||
return result, parser.err()
|
||||
}
|
||||
|
||||
// ParseDeclaration parses a declaration
|
||||
func (parser *Parser) ParseDeclaration() (*css.Declaration, error) {
|
||||
result := css.NewDeclaration()
|
||||
curValue := ""
|
||||
|
||||
for parser.tokenParsable() {
|
||||
if parser.tokenChar(":") {
|
||||
result.Property = strings.TrimSpace(curValue)
|
||||
curValue = ""
|
||||
|
||||
parser.shiftToken()
|
||||
} else if parser.tokenChar(";") || parser.tokenChar("}") {
|
||||
if result.Property == "" {
|
||||
errMsg := fmt.Sprintf("Unexpected ; character: %s", parser.nextToken().String())
|
||||
return result, errors.New(errMsg)
|
||||
}
|
||||
|
||||
if importantRegexp.MatchString(curValue) {
|
||||
result.Important = true
|
||||
curValue = importantRegexp.ReplaceAllString(curValue, "")
|
||||
}
|
||||
|
||||
result.Value = strings.TrimSpace(curValue)
|
||||
|
||||
if parser.tokenChar(";") {
|
||||
parser.shiftToken()
|
||||
}
|
||||
|
||||
// finished
|
||||
break
|
||||
} else {
|
||||
token := parser.shiftToken()
|
||||
curValue += token.Value
|
||||
}
|
||||
}
|
||||
|
||||
// log.Printf("[parsed] Declaration: %s", result.String())
|
||||
|
||||
return result, parser.err()
|
||||
}
|
||||
|
||||
// Parse an At Rule
|
||||
func (parser *Parser) parseAtRule() (*css.Rule, error) {
|
||||
// parse rule name (eg: "@import")
|
||||
token := parser.shiftToken()
|
||||
|
||||
result := css.NewRule(css.AtRule)
|
||||
result.Name = token.Value
|
||||
|
||||
for parser.tokenParsable() {
|
||||
if parser.tokenChar(";") {
|
||||
parser.shiftToken()
|
||||
|
||||
// finished
|
||||
break
|
||||
} else if parser.tokenChar("{") {
|
||||
if result.EmbedsRules() {
|
||||
// parse rules block
|
||||
rules, err := parser.ParseRules()
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
result.Rules = rules
|
||||
} else {
|
||||
// parse declarations block
|
||||
declarations, err := parser.ParseDeclarations()
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
result.Declarations = declarations
|
||||
}
|
||||
|
||||
// finished
|
||||
break
|
||||
} else {
|
||||
// parse prelude
|
||||
prelude, err := parser.parsePrelude()
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
result.Prelude = prelude
|
||||
}
|
||||
}
|
||||
|
||||
// log.Printf("[parsed] Rule: %s", result.String())
|
||||
|
||||
return result, parser.err()
|
||||
}
|
||||
|
||||
// Parse a Qualified Rule
|
||||
func (parser *Parser) parseQualifiedRule() (*css.Rule, error) {
|
||||
result := css.NewRule(css.QualifiedRule)
|
||||
|
||||
for parser.tokenParsable() {
|
||||
if parser.tokenChar("{") {
|
||||
if result.Prelude == "" {
|
||||
errMsg := fmt.Sprintf("Unexpected { character: %s", parser.nextToken().String())
|
||||
return result, errors.New(errMsg)
|
||||
}
|
||||
|
||||
// parse declarations block
|
||||
declarations, err := parser.ParseDeclarations()
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
result.Declarations = declarations
|
||||
|
||||
// finished
|
||||
break
|
||||
} else {
|
||||
// parse prelude
|
||||
prelude, err := parser.parsePrelude()
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
result.Prelude = prelude
|
||||
}
|
||||
}
|
||||
|
||||
result.Selectors = strings.Split(result.Prelude, ",")
|
||||
for i, sel := range result.Selectors {
|
||||
result.Selectors[i] = strings.TrimSpace(sel)
|
||||
}
|
||||
|
||||
// log.Printf("[parsed] Rule: %s", result.String())
|
||||
|
||||
return result, parser.err()
|
||||
}
|
||||
|
||||
// Parse Rule prelude
|
||||
func (parser *Parser) parsePrelude() (string, error) {
|
||||
result := ""
|
||||
|
||||
for parser.tokenParsable() && !parser.tokenEndOfPrelude() {
|
||||
token := parser.shiftToken()
|
||||
result += token.Value
|
||||
}
|
||||
|
||||
result = strings.TrimSpace(result)
|
||||
|
||||
// log.Printf("[parsed] prelude: %s", result)
|
||||
|
||||
return result, parser.err()
|
||||
}
|
||||
|
||||
// Parse BOM
|
||||
func (parser *Parser) parseBOM() (bool, error) {
|
||||
if parser.nextToken().Type == scanner.TokenBOM {
|
||||
parser.shiftToken()
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, parser.err()
|
||||
}
|
||||
|
||||
// Returns next token without removing it from tokens buffer
|
||||
func (parser *Parser) nextToken() *scanner.Token {
|
||||
if len(parser.tokens) == 0 {
|
||||
// fetch next token
|
||||
nextToken := parser.scan.Next()
|
||||
|
||||
// log.Printf("[token] %s => %v", nextToken.Type.String(), nextToken.Value)
|
||||
|
||||
// queue it
|
||||
parser.tokens = append(parser.tokens, nextToken)
|
||||
}
|
||||
|
||||
return parser.tokens[0]
|
||||
}
|
||||
|
||||
// Returns next token and remove it from the tokens buffer
|
||||
func (parser *Parser) shiftToken() *scanner.Token {
|
||||
var result *scanner.Token
|
||||
|
||||
result, parser.tokens = parser.tokens[0], parser.tokens[1:]
|
||||
return result
|
||||
}
|
||||
|
||||
// Returns tokenizer error, or nil if no error
|
||||
func (parser *Parser) err() error {
|
||||
if parser.tokenError() {
|
||||
token := parser.nextToken()
|
||||
return fmt.Errorf("Tokenizer error: %s", token.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Returns true if next token is Error
|
||||
func (parser *Parser) tokenError() bool {
|
||||
return parser.nextToken().Type == scanner.TokenError
|
||||
}
|
||||
|
||||
// Returns true if next token is EOF
|
||||
func (parser *Parser) tokenEOF() bool {
|
||||
return parser.nextToken().Type == scanner.TokenEOF
|
||||
}
|
||||
|
||||
// Returns true if next token is a whitespace
|
||||
func (parser *Parser) tokenWS() bool {
|
||||
return parser.nextToken().Type == scanner.TokenS
|
||||
}
|
||||
|
||||
// Returns true if next token is a comment
|
||||
func (parser *Parser) tokenComment() bool {
|
||||
return parser.nextToken().Type == scanner.TokenComment
|
||||
}
|
||||
|
||||
// Returns true if next token is a CDO or a CDC
|
||||
func (parser *Parser) tokenCDOorCDC() bool {
|
||||
switch parser.nextToken().Type {
|
||||
case scanner.TokenCDO, scanner.TokenCDC:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if next token is ignorable
|
||||
func (parser *Parser) tokenIgnorable() bool {
|
||||
return parser.tokenWS() || parser.tokenComment() || parser.tokenCDOorCDC()
|
||||
}
|
||||
|
||||
// Returns true if next token is parsable
|
||||
func (parser *Parser) tokenParsable() bool {
|
||||
return !parser.tokenEOF() && !parser.tokenError()
|
||||
}
|
||||
|
||||
// Returns true if next token is an At Rule keyword
|
||||
func (parser *Parser) tokenAtKeyword() bool {
|
||||
return parser.nextToken().Type == scanner.TokenAtKeyword
|
||||
}
|
||||
|
||||
// Returns true if next token is given character
|
||||
func (parser *Parser) tokenChar(value string) bool {
|
||||
token := parser.nextToken()
|
||||
return (token.Type == scanner.TokenChar) && (token.Value == value)
|
||||
}
|
||||
|
||||
// Returns true if next token marks the end of a prelude
|
||||
func (parser *Parser) tokenEndOfPrelude() bool {
|
||||
return parser.tokenChar(";") || parser.tokenChar("{")
|
||||
}
|
||||
117
extra/server-discovery/vendor/github.com/cortezaproject/corteza/server/compose/types/attachment.go
generated
vendored
Normal file
117
extra/server-discovery/vendor/github.com/cortezaproject/corteza/server/compose/types/attachment.go
generated
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"github.com/cortezaproject/corteza/server/pkg/sql"
|
||||
"time"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/pkg/filter"
|
||||
)
|
||||
|
||||
type (
|
||||
Attachment struct {
|
||||
ID uint64 `json:"attachmentID,string"`
|
||||
OwnerID uint64 `json:"ownerID,string"`
|
||||
Kind string `json:"-"`
|
||||
Url string `json:"url,omitempty"`
|
||||
PreviewUrl string `json:"previewUrl,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Meta AttachmentMeta `json:"meta"`
|
||||
|
||||
NamespaceID uint64 `json:"namespaceID,string"`
|
||||
|
||||
CreatedAt time.Time `json:"createdAt,omitempty"`
|
||||
UpdatedAt *time.Time `json:"updatedAt,omitempty"`
|
||||
DeletedAt *time.Time `json:"deletedAt,omitempty"`
|
||||
}
|
||||
|
||||
// AttachmentFilter is used for filtering and as a return value from Find
|
||||
AttachmentFilter struct {
|
||||
NamespaceID uint64 `json:"namespaceID,string"`
|
||||
Kind string `json:"kind,omitempty"`
|
||||
PageID uint64 `json:"pageID,string,omitempty"`
|
||||
RecordID uint64 `json:"recordID,string,omitempty"`
|
||||
ModuleID uint64 `json:"moduleID,string,omitempty"`
|
||||
FieldName string `json:"fieldName,omitempty"`
|
||||
Filter string `json:"filter"`
|
||||
|
||||
Deleted filter.State `json:"deleted"`
|
||||
|
||||
// Check fn is called by store backend for each resource found function can
|
||||
// modify the resource and return false if store should not return it
|
||||
//
|
||||
// Store then loads additional resources to satisfy the paging parameters
|
||||
Check func(*Attachment) (bool, error)
|
||||
|
||||
// Standard helpers for paging and sorting
|
||||
filter.Sorting
|
||||
filter.Paging
|
||||
}
|
||||
|
||||
AttachmentImageMeta struct {
|
||||
Width int `json:"width,omitempty"`
|
||||
Height int `json:"height,omitempty"`
|
||||
Animated bool `json:"animated"`
|
||||
}
|
||||
|
||||
AttachmentFileMeta struct {
|
||||
Size int64 `json:"size"`
|
||||
Extension string `json:"ext"`
|
||||
Mimetype string `json:"mimetype"`
|
||||
Image *AttachmentImageMeta `json:"image,omitempty"`
|
||||
}
|
||||
|
||||
AttachmentIconMeta struct {
|
||||
Name string `json:"name"`
|
||||
Library string `json:"library"`
|
||||
}
|
||||
|
||||
AttachmentIconSvgMeta struct {
|
||||
Src string `json:"src"`
|
||||
}
|
||||
|
||||
AttachmentMeta struct {
|
||||
Original AttachmentFileMeta `json:"original"`
|
||||
Preview *AttachmentFileMeta `json:"preview,omitempty"`
|
||||
|
||||
Icon *AttachmentIconMeta `json:"icon,omitempty"`
|
||||
IconSvg *AttachmentIconSvgMeta `json:"iconSvg,omitempty"`
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
PageAttachment string = "page"
|
||||
IconAttachment string = "icon"
|
||||
RecordAttachment string = "record"
|
||||
NamespaceAttachment string = "namespace"
|
||||
)
|
||||
|
||||
func (a *Attachment) SetOriginalImageMeta(width, height int, animated bool) *AttachmentFileMeta {
|
||||
a.imageMeta(&a.Meta.Original, width, height, animated)
|
||||
return &a.Meta.Original
|
||||
}
|
||||
|
||||
func (a *Attachment) SetPreviewImageMeta(width, height int, animated bool) *AttachmentFileMeta {
|
||||
if a.Meta.Preview == nil {
|
||||
a.Meta.Preview = &AttachmentFileMeta{}
|
||||
}
|
||||
|
||||
a.imageMeta(a.Meta.Preview, width, height, animated)
|
||||
return a.Meta.Preview
|
||||
}
|
||||
|
||||
func (a *Attachment) imageMeta(in *AttachmentFileMeta, width, height int, animated bool) {
|
||||
if in.Image == nil {
|
||||
in.Image = &AttachmentImageMeta{}
|
||||
}
|
||||
|
||||
if width > 0 && height > 0 {
|
||||
in.Image.Animated = animated
|
||||
in.Image.Width = width
|
||||
in.Image.Height = height
|
||||
}
|
||||
}
|
||||
|
||||
func (meta *AttachmentMeta) Scan(src any) error { return sql.ParseJSON(src, meta) }
|
||||
func (meta AttachmentMeta) Value() (driver.Value, error) { return json.Marshal(meta) }
|
||||
312
extra/server-discovery/vendor/github.com/cortezaproject/corteza/server/compose/types/chart.go
generated
vendored
Normal file
312
extra/server-discovery/vendor/github.com/cortezaproject/corteza/server/compose/types/chart.go
generated
vendored
Normal file
@@ -0,0 +1,312 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/pkg/locale"
|
||||
"github.com/cortezaproject/corteza/server/pkg/sql"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/pkg/filter"
|
||||
)
|
||||
|
||||
type (
|
||||
Chart struct {
|
||||
ID uint64 `json:"chartID,string"`
|
||||
Handle string `json:"handle"`
|
||||
Name string `json:"name"`
|
||||
Config ChartConfig `json:"config"`
|
||||
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
|
||||
NamespaceID uint64 `json:"namespaceID,string"`
|
||||
|
||||
CreatedAt time.Time `json:"createdAt,omitempty"`
|
||||
UpdatedAt *time.Time `json:"updatedAt,omitempty"`
|
||||
DeletedAt *time.Time `json:"deletedAt,omitempty"`
|
||||
}
|
||||
|
||||
ChartConfig struct {
|
||||
Reports []*ChartConfigReport `json:"reports,omitempty"`
|
||||
ColorScheme string `json:"colorScheme,omitempty"`
|
||||
NoAnimation bool `json:"noAnimation,omitempty"`
|
||||
Toolbox map[string]interface{} `json:"toolbox,omitempty"`
|
||||
}
|
||||
|
||||
ChartConfigReport struct {
|
||||
ReportID uint64 `json:"reportID,string,omitempty"`
|
||||
Filter string `json:"filter"`
|
||||
ModuleID uint64 `json:"moduleID,string,omitempty"`
|
||||
Metrics []map[string]interface{} `json:"metrics,omitempty"`
|
||||
Dimensions []map[string]interface{} `json:"dimensions,omitempty"`
|
||||
YAxis map[string]interface{} `json:"yAxis,omitempty"`
|
||||
Legend map[string]interface{} `json:"legend,omitempty"`
|
||||
Tooltip map[string]interface{} `json:"tooltip,omitempty"`
|
||||
Offset map[string]interface{} `json:"offset,omitempty"`
|
||||
Renderer struct {
|
||||
Version string `json:"version,omitempty" `
|
||||
} `json:"renderer,omitempty"`
|
||||
}
|
||||
|
||||
ChartFilter struct {
|
||||
NamespaceID uint64 `json:"namespaceID,string"`
|
||||
ChartID []string `json:"chartID"`
|
||||
Handle string `json:"handle"`
|
||||
Name string `json:"name"`
|
||||
Query string `json:"query"`
|
||||
|
||||
LabeledIDs []uint64 `json:"-"`
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
|
||||
Deleted filter.State `json:"deleted"`
|
||||
|
||||
// Check fn is called by store backend for each resource found function can
|
||||
// modify the resource and return false if store should not return it
|
||||
//
|
||||
// Store then loads additional resources to satisfy the paging parameters
|
||||
Check func(*Chart) (bool, error) `json:"-"`
|
||||
|
||||
// Standard helpers for paging and sorting
|
||||
filter.Sorting
|
||||
filter.Paging
|
||||
}
|
||||
)
|
||||
|
||||
func (c Chart) decodeTranslations(tt locale.ResourceTranslationIndex) {
|
||||
var aux *locale.ResourceTranslation
|
||||
|
||||
for i, report := range c.Config.Reports {
|
||||
if report == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// apply translated label for YAxis
|
||||
if aux = tt.FindByKey(LocaleKeyChartYAxisLabel.Path); aux != nil {
|
||||
if c.Config.Reports[i].YAxis == nil {
|
||||
c.Config.Reports[i].YAxis = make(map[string]interface{})
|
||||
}
|
||||
|
||||
c.Config.Reports[i].YAxis["label"] = aux.Msg
|
||||
}
|
||||
|
||||
// apply translated labels for metrics
|
||||
report.WalkMetrics(func(metricID string, metric map[string]interface{}) {
|
||||
mpl := strings.NewReplacer("{{metricID}}", metricID)
|
||||
|
||||
aux = tt.FindByKey(mpl.Replace(LocaleKeyChartMetricsMetricIDLabel.Path))
|
||||
if aux == nil {
|
||||
return
|
||||
}
|
||||
|
||||
metric["label"] = aux.Msg
|
||||
})
|
||||
|
||||
// apply translated labels for each dimension/step
|
||||
report.WalkDimensionSteps(func(dimensionID, stepID string, step map[string]interface{}) {
|
||||
mpl := strings.NewReplacer(
|
||||
"{{dimensionID}}", dimensionID,
|
||||
"{{stepID}}", stepID,
|
||||
)
|
||||
|
||||
aux = tt.FindByKey(mpl.Replace(LocaleKeyChartDimensionsDimensionIDMetaStepsStepIDLabel.Path))
|
||||
if aux == nil {
|
||||
return
|
||||
}
|
||||
|
||||
step["label"] = aux.Msg
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (c Chart) encodeTranslations() (out locale.ResourceTranslationSet) {
|
||||
out = make(locale.ResourceTranslationSet, 0, 12)
|
||||
|
||||
for _, report := range c.Config.Reports {
|
||||
// collect labels from chart config: YAxis
|
||||
if _, ok := report.YAxis["label"]; ok {
|
||||
out = append(out, &locale.ResourceTranslation{
|
||||
Resource: c.ResourceTranslation(),
|
||||
Key: LocaleKeyChartYAxisLabel.Path,
|
||||
Msg: cast.ToString(report.YAxis["label"]),
|
||||
})
|
||||
}
|
||||
|
||||
// collect labels from chart config: metrics
|
||||
report.WalkMetrics(func(metricID string, m map[string]interface{}) {
|
||||
mpl := strings.NewReplacer(
|
||||
"{{metricID}}", metricID,
|
||||
)
|
||||
|
||||
out = append(out, &locale.ResourceTranslation{
|
||||
Resource: c.ResourceTranslation(),
|
||||
Key: mpl.Replace(LocaleKeyChartMetricsMetricIDLabel.Path),
|
||||
Msg: cast.ToString(m["label"]),
|
||||
})
|
||||
})
|
||||
|
||||
// collect labels from chart config: dimensions/steps
|
||||
report.WalkDimensionSteps(func(dimID, stepID string, step map[string]interface{}) {
|
||||
mpl := strings.NewReplacer(
|
||||
"{{dimensionID}}", dimID,
|
||||
"{{stepID}}", stepID,
|
||||
)
|
||||
|
||||
out = append(out, &locale.ResourceTranslation{
|
||||
Resource: c.ResourceTranslation(),
|
||||
Key: mpl.Replace(LocaleKeyChartDimensionsDimensionIDMetaStepsStepIDLabel.Path),
|
||||
Msg: cast.ToString(step["label"]),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Chart) setValue(name string, pos uint, value any) (err error) {
|
||||
pp := strings.Split(name, ".")
|
||||
|
||||
switch pp[0] {
|
||||
case "Config":
|
||||
if pp[1] == "Reports" {
|
||||
return c.Config.Reports[cast.ToInt(pp[2])].setValue(pp[3], 0, value)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (r *ChartConfigReport) setValue(name string, pos uint, value any) (err error) {
|
||||
switch name {
|
||||
case "moduleID", "ModuleID":
|
||||
r.ModuleID = cast.ToUint64(value)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// FindByHandle finds chart by it's handle
|
||||
func (set ChartSet) FindByHandle(handle string) *Chart {
|
||||
for i := range set {
|
||||
if set[i].Handle == handle {
|
||||
return set[i]
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cc *ChartConfig) Scan(src any) error { return sql.ParseJSON(src, cc) }
|
||||
func (cc ChartConfig) Value() (driver.Value, error) { return json.Marshal(cc) }
|
||||
|
||||
func (r *ChartConfigReport) WalkMetrics(fn func(string, map[string]interface{})) {
|
||||
for m := range r.Metrics {
|
||||
metricID, ok := r.Metrics[m]["metricID"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(r.Metrics[m]) == 0 {
|
||||
// avoid problems with nil maps
|
||||
r.Metrics[m] = make(map[string]interface{})
|
||||
}
|
||||
|
||||
fn(metricID.(string), r.Metrics[m])
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ChartConfigReport) WalkDimensionSteps(fn func(string, string, map[string]interface{})) {
|
||||
for d := range r.Dimensions {
|
||||
dimensionID, ok := r.Dimensions[d]["dimensionID"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
meta, is := r.Dimensions[d]["meta"].(map[string]interface{})
|
||||
if !is {
|
||||
continue
|
||||
}
|
||||
|
||||
var steps []map[string]interface{}
|
||||
|
||||
switch aux := meta["steps"].(type) {
|
||||
case []interface{}:
|
||||
for _, i := range aux {
|
||||
if kv, is := i.(map[string]interface{}); is {
|
||||
steps = append(steps, kv)
|
||||
}
|
||||
}
|
||||
case []map[string]interface{}:
|
||||
steps = aux
|
||||
}
|
||||
|
||||
for s := range steps {
|
||||
stepID, has := steps[s]["stepID"]
|
||||
if !has {
|
||||
return
|
||||
}
|
||||
|
||||
fn(dimensionID.(string), stepID.(string), steps[s])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ChartConfig) GenerateIDs(nextID func() uint64) {
|
||||
// Ensure chart report IDs
|
||||
for r := range c.Reports {
|
||||
c.Reports[r].ReportID = nextID()
|
||||
|
||||
// Ensure chart report metric IDs
|
||||
for m := range c.Reports[r].Metrics {
|
||||
met := c.Reports[r].Metrics[m]
|
||||
if _, has := met["metricID"]; has {
|
||||
continue
|
||||
}
|
||||
|
||||
met["metricID"] = strconv.FormatUint(nextID(), 10)
|
||||
}
|
||||
|
||||
for d := range c.Reports[r].Dimensions {
|
||||
dim := c.Reports[r].Dimensions[d]
|
||||
|
||||
if _, has := dim["dimensionID"]; !has {
|
||||
dim["dimensionID"] = strconv.FormatUint(nextID(), 10)
|
||||
}
|
||||
|
||||
meta, is := dim["meta"].(map[string]interface{})
|
||||
if !is {
|
||||
// no meta, no steps
|
||||
continue
|
||||
}
|
||||
|
||||
var steps []map[string]interface{}
|
||||
|
||||
switch aux := meta["steps"].(type) {
|
||||
case []interface{}:
|
||||
for _, i := range aux {
|
||||
if kv, is := i.(map[string]interface{}); is {
|
||||
steps = append(steps, kv)
|
||||
}
|
||||
}
|
||||
case []map[string]interface{}:
|
||||
steps = aux
|
||||
}
|
||||
|
||||
for s := range steps {
|
||||
_, has := steps[s]["stepID"]
|
||||
if has {
|
||||
continue
|
||||
}
|
||||
|
||||
steps[s]["stepID"] = strconv.FormatUint(nextID(), 10)
|
||||
}
|
||||
|
||||
meta["steps"] = steps
|
||||
dim["meta"] = meta
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
38
extra/server-discovery/vendor/github.com/cortezaproject/corteza/server/compose/types/data_privacy.go
generated
vendored
Normal file
38
extra/server-discovery/vendor/github.com/cortezaproject/corteza/server/compose/types/data_privacy.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"github.com/cortezaproject/corteza/server/pkg/filter"
|
||||
sysTypes "github.com/cortezaproject/corteza/server/system/types"
|
||||
)
|
||||
|
||||
type (
|
||||
PrivacyModule struct {
|
||||
Module PrivacyModuleMeta `json:"module"`
|
||||
Namespace PrivacyNamespaceMeta `json:"namespace"`
|
||||
|
||||
ConnectionID uint64 `json:"-"`
|
||||
Connection *sysTypes.DalConnection `json:"connection"`
|
||||
}
|
||||
|
||||
PrivacyModuleMeta struct {
|
||||
ID uint64 `json:"moduleID,string"`
|
||||
Name string `json:"name"`
|
||||
Handle string `json:"handle"`
|
||||
Fields ModuleFieldSet `json:"fields"`
|
||||
}
|
||||
|
||||
PrivacyNamespaceMeta struct {
|
||||
ID uint64 `json:"namespaceID,string"`
|
||||
Slug string `json:"slug"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
PrivacyModuleFilter struct {
|
||||
NamespaceID uint64 `json:"-"`
|
||||
ConnectionID []string `json:"connectionID"`
|
||||
|
||||
// Standard helpers for paging and sorting
|
||||
filter.Sorting
|
||||
filter.Paging
|
||||
}
|
||||
)
|
||||
528
extra/server-discovery/vendor/github.com/cortezaproject/corteza/server/compose/types/getters_setters.gen.go
generated
vendored
Normal file
528
extra/server-discovery/vendor/github.com/cortezaproject/corteza/server/compose/types/getters_setters.gen.go
generated
vendored
Normal file
@@ -0,0 +1,528 @@
|
||||
package types
|
||||
|
||||
// This file is auto-generated.
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
//
|
||||
|
||||
import (
|
||||
"github.com/cortezaproject/corteza/server/pkg/cast2"
|
||||
)
|
||||
|
||||
func (r Attachment) GetID() uint64 { return r.ID }
|
||||
|
||||
func (r *Attachment) GetValue(name string, pos uint) (any, error) {
|
||||
if r == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
switch name {
|
||||
case "createdAt", "CreatedAt":
|
||||
return r.CreatedAt, nil
|
||||
case "deletedAt", "DeletedAt":
|
||||
return r.DeletedAt, nil
|
||||
case "id", "ID":
|
||||
return r.ID, nil
|
||||
case "kind", "Kind":
|
||||
return r.Kind, nil
|
||||
case "name", "Name":
|
||||
return r.Name, nil
|
||||
case "namespaceID", "NamespaceID":
|
||||
return r.NamespaceID, nil
|
||||
case "ownerID", "OwnerID":
|
||||
return r.OwnerID, nil
|
||||
case "previewUrl", "PreviewUrl":
|
||||
return r.PreviewUrl, nil
|
||||
case "updatedAt", "UpdatedAt":
|
||||
return r.UpdatedAt, nil
|
||||
case "url", "Url":
|
||||
return r.Url, nil
|
||||
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *Attachment) SetValue(name string, pos uint, value any) (err error) {
|
||||
if r == nil {
|
||||
r = &Attachment{}
|
||||
}
|
||||
|
||||
switch name {
|
||||
case "createdAt", "CreatedAt":
|
||||
return cast2.Time(value, &r.CreatedAt)
|
||||
case "deletedAt", "DeletedAt":
|
||||
return cast2.TimePtr(value, &r.DeletedAt)
|
||||
case "id", "ID":
|
||||
return cast2.Uint64(value, &r.ID)
|
||||
case "kind", "Kind":
|
||||
return cast2.String(value, &r.Kind)
|
||||
case "name", "Name":
|
||||
return cast2.String(value, &r.Name)
|
||||
case "namespaceID", "NamespaceID":
|
||||
return cast2.Uint64(value, &r.NamespaceID)
|
||||
case "ownerID", "OwnerID":
|
||||
return cast2.Uint64(value, &r.OwnerID)
|
||||
case "previewUrl", "PreviewUrl":
|
||||
return cast2.String(value, &r.PreviewUrl)
|
||||
case "updatedAt", "UpdatedAt":
|
||||
return cast2.TimePtr(value, &r.UpdatedAt)
|
||||
case "url", "Url":
|
||||
return cast2.String(value, &r.Url)
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r Chart) GetID() uint64 { return r.ID }
|
||||
|
||||
func (r *Chart) GetValue(name string, pos uint) (any, error) {
|
||||
if r == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
switch name {
|
||||
case "createdAt", "CreatedAt":
|
||||
return r.CreatedAt, nil
|
||||
case "deletedAt", "DeletedAt":
|
||||
return r.DeletedAt, nil
|
||||
case "handle", "Handle":
|
||||
return r.Handle, nil
|
||||
case "id", "ID":
|
||||
return r.ID, nil
|
||||
case "name", "Name":
|
||||
return r.Name, nil
|
||||
case "namespaceID", "NamespaceID":
|
||||
return r.NamespaceID, nil
|
||||
case "updatedAt", "UpdatedAt":
|
||||
return r.UpdatedAt, nil
|
||||
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *Chart) SetValue(name string, pos uint, value any) (err error) {
|
||||
if r == nil {
|
||||
r = &Chart{}
|
||||
}
|
||||
|
||||
switch name {
|
||||
case "createdAt", "CreatedAt":
|
||||
return cast2.Time(value, &r.CreatedAt)
|
||||
case "deletedAt", "DeletedAt":
|
||||
return cast2.TimePtr(value, &r.DeletedAt)
|
||||
case "handle", "Handle":
|
||||
return cast2.String(value, &r.Handle)
|
||||
case "id", "ID":
|
||||
return cast2.Uint64(value, &r.ID)
|
||||
case "name", "Name":
|
||||
return cast2.String(value, &r.Name)
|
||||
case "namespaceID", "NamespaceID":
|
||||
return cast2.Uint64(value, &r.NamespaceID)
|
||||
case "updatedAt", "UpdatedAt":
|
||||
return cast2.TimePtr(value, &r.UpdatedAt)
|
||||
|
||||
default:
|
||||
return r.setValue(name, pos, value)
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r Module) GetID() uint64 { return r.ID }
|
||||
|
||||
func (r *Module) GetValue(name string, pos uint) (any, error) {
|
||||
if r == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
switch name {
|
||||
case "createdAt", "CreatedAt":
|
||||
return r.CreatedAt, nil
|
||||
case "deletedAt", "DeletedAt":
|
||||
return r.DeletedAt, nil
|
||||
case "handle", "Handle":
|
||||
return r.Handle, nil
|
||||
case "id", "ID":
|
||||
return r.ID, nil
|
||||
case "name", "Name":
|
||||
return r.Name, nil
|
||||
case "namespaceID", "NamespaceID":
|
||||
return r.NamespaceID, nil
|
||||
case "updatedAt", "UpdatedAt":
|
||||
return r.UpdatedAt, nil
|
||||
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *Module) SetValue(name string, pos uint, value any) (err error) {
|
||||
if r == nil {
|
||||
r = &Module{}
|
||||
}
|
||||
|
||||
switch name {
|
||||
case "createdAt", "CreatedAt":
|
||||
return cast2.Time(value, &r.CreatedAt)
|
||||
case "deletedAt", "DeletedAt":
|
||||
return cast2.TimePtr(value, &r.DeletedAt)
|
||||
case "handle", "Handle":
|
||||
return cast2.String(value, &r.Handle)
|
||||
case "id", "ID":
|
||||
return cast2.Uint64(value, &r.ID)
|
||||
case "name", "Name":
|
||||
return cast2.String(value, &r.Name)
|
||||
case "namespaceID", "NamespaceID":
|
||||
return cast2.Uint64(value, &r.NamespaceID)
|
||||
case "updatedAt", "UpdatedAt":
|
||||
return cast2.TimePtr(value, &r.UpdatedAt)
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r ModuleField) GetID() uint64 { return r.ID }
|
||||
|
||||
func (r *ModuleField) GetValue(name string, pos uint) (any, error) {
|
||||
if r == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
switch name {
|
||||
case "createdAt", "CreatedAt":
|
||||
return r.CreatedAt, nil
|
||||
case "deletedAt", "DeletedAt":
|
||||
return r.DeletedAt, nil
|
||||
case "id", "ID":
|
||||
return r.ID, nil
|
||||
case "kind", "Kind":
|
||||
return r.Kind, nil
|
||||
case "label", "Label":
|
||||
return r.Label, nil
|
||||
case "moduleID", "ModuleID":
|
||||
return r.ModuleID, nil
|
||||
case "multi", "Multi":
|
||||
return r.Multi, nil
|
||||
case "name", "Name":
|
||||
return r.Name, nil
|
||||
case "place", "Place":
|
||||
return r.Place, nil
|
||||
case "required", "Required":
|
||||
return r.Required, nil
|
||||
case "updatedAt", "UpdatedAt":
|
||||
return r.UpdatedAt, nil
|
||||
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *ModuleField) SetValue(name string, pos uint, value any) (err error) {
|
||||
if r == nil {
|
||||
r = &ModuleField{}
|
||||
}
|
||||
|
||||
switch name {
|
||||
case "createdAt", "CreatedAt":
|
||||
return cast2.Time(value, &r.CreatedAt)
|
||||
case "deletedAt", "DeletedAt":
|
||||
return cast2.TimePtr(value, &r.DeletedAt)
|
||||
case "id", "ID":
|
||||
return cast2.Uint64(value, &r.ID)
|
||||
case "kind", "Kind":
|
||||
return cast2.String(value, &r.Kind)
|
||||
case "label", "Label":
|
||||
return cast2.String(value, &r.Label)
|
||||
case "moduleID", "ModuleID":
|
||||
return cast2.Uint64(value, &r.ModuleID)
|
||||
case "multi", "Multi":
|
||||
return cast2.Bool(value, &r.Multi)
|
||||
case "name", "Name":
|
||||
return cast2.String(value, &r.Name)
|
||||
case "place", "Place":
|
||||
return cast2.Int(value, &r.Place)
|
||||
case "required", "Required":
|
||||
return cast2.Bool(value, &r.Required)
|
||||
case "updatedAt", "UpdatedAt":
|
||||
return cast2.TimePtr(value, &r.UpdatedAt)
|
||||
|
||||
default:
|
||||
return r.setValue(name, pos, value)
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r Namespace) GetID() uint64 { return r.ID }
|
||||
|
||||
func (r *Namespace) GetValue(name string, pos uint) (any, error) {
|
||||
if r == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
switch name {
|
||||
case "createdAt", "CreatedAt":
|
||||
return r.CreatedAt, nil
|
||||
case "deletedAt", "DeletedAt":
|
||||
return r.DeletedAt, nil
|
||||
case "enabled", "Enabled":
|
||||
return r.Enabled, nil
|
||||
case "id", "ID":
|
||||
return r.ID, nil
|
||||
case "name", "Name":
|
||||
return r.Name, nil
|
||||
case "slug", "Slug":
|
||||
return r.Slug, nil
|
||||
case "updatedAt", "UpdatedAt":
|
||||
return r.UpdatedAt, nil
|
||||
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *Namespace) SetValue(name string, pos uint, value any) (err error) {
|
||||
if r == nil {
|
||||
r = &Namespace{}
|
||||
}
|
||||
|
||||
switch name {
|
||||
case "createdAt", "CreatedAt":
|
||||
return cast2.Time(value, &r.CreatedAt)
|
||||
case "deletedAt", "DeletedAt":
|
||||
return cast2.TimePtr(value, &r.DeletedAt)
|
||||
case "enabled", "Enabled":
|
||||
return cast2.Bool(value, &r.Enabled)
|
||||
case "id", "ID":
|
||||
return cast2.Uint64(value, &r.ID)
|
||||
case "name", "Name":
|
||||
return cast2.String(value, &r.Name)
|
||||
case "slug", "Slug":
|
||||
return cast2.String(value, &r.Slug)
|
||||
case "updatedAt", "UpdatedAt":
|
||||
return cast2.TimePtr(value, &r.UpdatedAt)
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r Page) GetID() uint64 { return r.ID }
|
||||
|
||||
func (r *Page) GetValue(name string, pos uint) (any, error) {
|
||||
if r == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
switch name {
|
||||
case "createdAt", "CreatedAt":
|
||||
return r.CreatedAt, nil
|
||||
case "deletedAt", "DeletedAt":
|
||||
return r.DeletedAt, nil
|
||||
case "description", "Description":
|
||||
return r.Description, nil
|
||||
case "handle", "Handle":
|
||||
return r.Handle, nil
|
||||
case "id", "ID":
|
||||
return r.ID, nil
|
||||
case "moduleID", "ModuleID":
|
||||
return r.ModuleID, nil
|
||||
case "namespaceID", "NamespaceID":
|
||||
return r.NamespaceID, nil
|
||||
case "selfID", "SelfID":
|
||||
return r.SelfID, nil
|
||||
case "title", "Title":
|
||||
return r.Title, nil
|
||||
case "updatedAt", "UpdatedAt":
|
||||
return r.UpdatedAt, nil
|
||||
case "visible", "Visible":
|
||||
return r.Visible, nil
|
||||
case "weight", "Weight":
|
||||
return r.Weight, nil
|
||||
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *Page) SetValue(name string, pos uint, value any) (err error) {
|
||||
if r == nil {
|
||||
r = &Page{}
|
||||
}
|
||||
|
||||
switch name {
|
||||
case "createdAt", "CreatedAt":
|
||||
return cast2.Time(value, &r.CreatedAt)
|
||||
case "deletedAt", "DeletedAt":
|
||||
return cast2.TimePtr(value, &r.DeletedAt)
|
||||
case "description", "Description":
|
||||
return cast2.String(value, &r.Description)
|
||||
case "handle", "Handle":
|
||||
return cast2.String(value, &r.Handle)
|
||||
case "id", "ID":
|
||||
return cast2.Uint64(value, &r.ID)
|
||||
case "moduleID", "ModuleID":
|
||||
return cast2.Uint64(value, &r.ModuleID)
|
||||
case "namespaceID", "NamespaceID":
|
||||
return cast2.Uint64(value, &r.NamespaceID)
|
||||
case "selfID", "SelfID":
|
||||
return cast2.Uint64(value, &r.SelfID)
|
||||
case "title", "Title":
|
||||
return cast2.String(value, &r.Title)
|
||||
case "updatedAt", "UpdatedAt":
|
||||
return cast2.TimePtr(value, &r.UpdatedAt)
|
||||
case "visible", "Visible":
|
||||
return cast2.Bool(value, &r.Visible)
|
||||
case "weight", "Weight":
|
||||
return cast2.Int(value, &r.Weight)
|
||||
|
||||
default:
|
||||
return r.setValue(name, pos, value)
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r PageLayout) GetID() uint64 { return r.ID }
|
||||
|
||||
func (r *PageLayout) GetValue(name string, pos uint) (any, error) {
|
||||
if r == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
switch name {
|
||||
case "createdAt", "CreatedAt":
|
||||
return r.CreatedAt, nil
|
||||
case "deletedAt", "DeletedAt":
|
||||
return r.DeletedAt, nil
|
||||
case "handle", "Handle":
|
||||
return r.Handle, nil
|
||||
case "id", "ID":
|
||||
return r.ID, nil
|
||||
case "namespaceID", "NamespaceID":
|
||||
return r.NamespaceID, nil
|
||||
case "ownedBy", "OwnedBy":
|
||||
return r.OwnedBy, nil
|
||||
case "pageID", "PageID":
|
||||
return r.PageID, nil
|
||||
case "parentID", "ParentID":
|
||||
return r.ParentID, nil
|
||||
case "updatedAt", "UpdatedAt":
|
||||
return r.UpdatedAt, nil
|
||||
case "weight", "Weight":
|
||||
return r.Weight, nil
|
||||
|
||||
default:
|
||||
return r.getValue(name, pos)
|
||||
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *PageLayout) SetValue(name string, pos uint, value any) (err error) {
|
||||
if r == nil {
|
||||
r = &PageLayout{}
|
||||
}
|
||||
|
||||
switch name {
|
||||
case "createdAt", "CreatedAt":
|
||||
return cast2.Time(value, &r.CreatedAt)
|
||||
case "deletedAt", "DeletedAt":
|
||||
return cast2.TimePtr(value, &r.DeletedAt)
|
||||
case "handle", "Handle":
|
||||
return cast2.String(value, &r.Handle)
|
||||
case "id", "ID":
|
||||
return cast2.Uint64(value, &r.ID)
|
||||
case "namespaceID", "NamespaceID":
|
||||
return cast2.Uint64(value, &r.NamespaceID)
|
||||
case "ownedBy", "OwnedBy":
|
||||
return cast2.Uint64(value, &r.OwnedBy)
|
||||
case "pageID", "PageID":
|
||||
return cast2.Uint64(value, &r.PageID)
|
||||
case "parentID", "ParentID":
|
||||
return cast2.Uint64(value, &r.ParentID)
|
||||
case "updatedAt", "UpdatedAt":
|
||||
return cast2.TimePtr(value, &r.UpdatedAt)
|
||||
case "weight", "Weight":
|
||||
return cast2.Int(value, &r.Weight)
|
||||
|
||||
default:
|
||||
return r.setValue(name, pos, value)
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r Record) GetID() uint64 { return r.ID }
|
||||
|
||||
func (r *Record) GetValue(name string, pos uint) (any, error) {
|
||||
if r == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
switch name {
|
||||
case "createdAt", "CreatedAt":
|
||||
return r.CreatedAt, nil
|
||||
case "createdBy", "CreatedBy", "created_by":
|
||||
return r.CreatedBy, nil
|
||||
case "deletedAt", "DeletedAt":
|
||||
return r.DeletedAt, nil
|
||||
case "deletedBy", "DeletedBy", "deleted_by":
|
||||
return r.DeletedBy, nil
|
||||
case "id", "ID":
|
||||
return r.ID, nil
|
||||
case "meta", "Meta":
|
||||
return r.Meta, nil
|
||||
case "moduleID", "ModuleID":
|
||||
return r.ModuleID, nil
|
||||
case "namespaceID", "NamespaceID":
|
||||
return r.NamespaceID, nil
|
||||
case "ownedBy", "OwnedBy", "owned_by":
|
||||
return r.OwnedBy, nil
|
||||
case "revision", "Revision":
|
||||
return r.Revision, nil
|
||||
case "updatedAt", "UpdatedAt":
|
||||
return r.UpdatedAt, nil
|
||||
case "updatedBy", "UpdatedBy", "updated_by":
|
||||
return r.UpdatedBy, nil
|
||||
|
||||
default:
|
||||
return r.getValue(name, pos)
|
||||
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *Record) SetValue(name string, pos uint, value any) (err error) {
|
||||
if r == nil {
|
||||
r = &Record{}
|
||||
}
|
||||
|
||||
switch name {
|
||||
case "createdAt", "CreatedAt":
|
||||
return cast2.Time(value, &r.CreatedAt)
|
||||
case "createdBy", "CreatedBy", "created_by":
|
||||
return cast2.Uint64(value, &r.CreatedBy)
|
||||
case "deletedAt", "DeletedAt":
|
||||
return cast2.TimePtr(value, &r.DeletedAt)
|
||||
case "deletedBy", "DeletedBy", "deleted_by":
|
||||
return cast2.Uint64(value, &r.DeletedBy)
|
||||
case "id", "ID":
|
||||
return cast2.Uint64(value, &r.ID)
|
||||
case "meta", "Meta":
|
||||
return cast2.Meta(value, &r.Meta)
|
||||
case "moduleID", "ModuleID":
|
||||
return cast2.Uint64(value, &r.ModuleID)
|
||||
case "namespaceID", "NamespaceID":
|
||||
return cast2.Uint64(value, &r.NamespaceID)
|
||||
case "ownedBy", "OwnedBy", "owned_by":
|
||||
return cast2.Uint64(value, &r.OwnedBy)
|
||||
case "revision", "Revision":
|
||||
return cast2.Int(value, &r.Revision)
|
||||
case "updatedAt", "UpdatedAt":
|
||||
return cast2.TimePtr(value, &r.UpdatedAt)
|
||||
case "updatedBy", "UpdatedBy", "updated_by":
|
||||
return cast2.Uint64(value, &r.UpdatedBy)
|
||||
|
||||
default:
|
||||
return r.setValue(name, pos, value)
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
39
extra/server-discovery/vendor/github.com/cortezaproject/corteza/server/compose/types/getters_setters.go
generated
vendored
Normal file
39
extra/server-discovery/vendor/github.com/cortezaproject/corteza/server/compose/types/getters_setters.go
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
package types
|
||||
|
||||
func (r *Record) GetValues(name string) ([]any, error) {
|
||||
if r == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
switch name {
|
||||
case "createdAt", "CreatedAt":
|
||||
return []any{r.CreatedAt}, nil
|
||||
case "createdBy", "CreatedBy", "created_by":
|
||||
return []any{r.CreatedBy}, nil
|
||||
case "deletedAt", "DeletedAt":
|
||||
return []any{r.DeletedAt}, nil
|
||||
case "deletedBy", "DeletedBy", "deleted_by":
|
||||
return []any{r.DeletedBy}, nil
|
||||
case "id", "ID":
|
||||
return []any{r.ID}, nil
|
||||
case "meta", "Meta":
|
||||
return []any{r.Meta}, nil
|
||||
case "moduleID", "ModuleID":
|
||||
return []any{r.ModuleID}, nil
|
||||
case "namespaceID", "NamespaceID":
|
||||
return []any{r.NamespaceID}, nil
|
||||
case "ownedBy", "OwnedBy", "owned_by":
|
||||
return []any{r.OwnedBy}, nil
|
||||
case "revision", "Revision":
|
||||
return []any{r.Revision}, nil
|
||||
case "updatedAt", "UpdatedAt":
|
||||
return []any{r.UpdatedAt}, nil
|
||||
case "updatedBy", "UpdatedBy", "updated_by":
|
||||
return []any{r.UpdatedBy}, nil
|
||||
|
||||
default:
|
||||
return r.getValues(name)
|
||||
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
44
extra/server-discovery/vendor/github.com/cortezaproject/corteza/server/compose/types/icon.go
generated
vendored
Normal file
44
extra/server-discovery/vendor/github.com/cortezaproject/corteza/server/compose/types/icon.go
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"github.com/cortezaproject/corteza/server/pkg/filter"
|
||||
)
|
||||
|
||||
type (
|
||||
Icon struct {
|
||||
}
|
||||
|
||||
IconFilter struct {
|
||||
// Check fn is called by store backend for each resource found function can
|
||||
// modify the resource and return false if store should not return it
|
||||
//
|
||||
// Store then loads additional resources to satisfy the paging parameters
|
||||
Check func(icon *Icon) (bool, error) `json:"-"`
|
||||
|
||||
// Standard helpers for paging and sorting
|
||||
filter.Sorting
|
||||
filter.Paging
|
||||
}
|
||||
|
||||
IconType string
|
||||
)
|
||||
|
||||
const (
|
||||
// IconTypeLink empty or "link" (default):
|
||||
// Indicate that src will contain an absolute or relative link to an icon.
|
||||
// Can also be used for inline images (storing "base64:" prefixed string in source).
|
||||
// This type and reference is not validated by the backend.
|
||||
IconTypeLink IconType = "link" // type, source
|
||||
// IconTypeLibrary "library"
|
||||
// Source references an icon from a library. Ref's value should be in the following
|
||||
// notation: "font-awesome://<icon-identifier>".
|
||||
// This type and source is not validated by the backend.
|
||||
IconTypeLibrary IconType = "library" // type, library(font-awesome), icon name
|
||||
// IconTypeInlineSvg "svg"
|
||||
// SRC contains raw SVG document
|
||||
IconTypeInlineSvg IconType = "inline-svg" // source
|
||||
// IconTypeAttachment "attachment"
|
||||
// Reference (ID) to an existing attachment in local Corteza instance is expected
|
||||
// This type and reference must be validated by the backend.
|
||||
IconTypeAttachment IconType = "attachment" // type, Icon, name
|
||||
)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user