diff --git a/Dockerfile b/Dockerfile
index 5838d12..c60e84f 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -8,7 +8,7 @@ WORKDIR /go/src/sablier
ARG TARGETOS
ARG TARGETARCH
-RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -buildvcs=false -o /go/bin/sablier
+RUN GIN_MODE=release GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -buildvcs=false -o /go/bin/sablier
FROM alpine
EXPOSE 10000
diff --git a/Makefile b/Makefile
index 0c4a5f9..552ceed 100644
--- a/Makefile
+++ b/Makefile
@@ -5,9 +5,16 @@ os = $(word 1, $(temp))
arch = $(word 2, $(temp))
version = draft
-release: $(PLATFORMS)
+# Version info for binaries
+GIT_REVISION := $(shell git rev-parse --short HEAD)
+GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD)
+
+VPREFIX := github.com/acouvreur/sablier/version
+GO_LDFLAGS := -X $(VPREFIX).Branch=$(GIT_BRANCH) -X $(VPREFIX).Version=$(version) -X $(VPREFIX).Revision=$(GIT_REVISION) -X $(VPREFIX).BuildUser=$(shell whoami)@$(shell hostname) -X $(VPREFIX).BuildDate=$(shell date -u +"%Y-%m-%dT%H:%M:%SZ")
$(PLATFORMS):
- GOOS=$(os) GOARCH=$(arch) go build -o 'sablier_$(version)_$(os)-$(arch)' .
+ GIN_MODE=release GOOS=$(os) GOARCH=$(arch) go build -ldflags="${GO_LDFLAGS}" -o 'sablier_$(version)_$(os)-$(arch)' .
+
+release: $(PLATFORMS)
.PHONY: release $(PLATFORMS)
\ No newline at end of file
diff --git a/README.md b/README.md
index b507a92..db9b3c6 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,33 @@
-# Sablier    
+# Sablier
+
+   
+
+Sablier is an API that start containers on demand.
+It provides an integrations with multiple reverse proxies and different loading strategies.
+
+- [Sablier](#sablier)
+ - [Getting started](#getting-started)
+ - [Features](#features)
+ - [CLI Usage](#cli-usage)
+ - [Configuration](#configuration)
+ - [Reverse proxies integration plugins](#reverse-proxies-integration-plugins)
+ - [Traefik Integration](#traefik-integration)
+ - [Kubernetes](#kubernetes)
+ - [API](#api)
## Getting started
+Binary
+
+```bash
+docker run -d --name nginx nginx
+docker stop nginx
+./sablier start
+curl 'http://localhost:10000/?name=nginx&timeout=1m'
+```
+
+Docker
+
```bash
docker run -d --name nginx nginx
docker stop nginx
@@ -9,8 +35,6 @@ docker run -v /var/run/docker.sock:/var/run/docker.sock -p 10000:10000 ghcr.io/a
curl 'http://localhost:10000/?name=nginx&timeout=1m'
```
-## Plugins
-
## Features
- Support for **Docker** containers
@@ -21,32 +45,70 @@ curl 'http://localhost:10000/?name=nginx&timeout=1m'
- Dynamic loading page (cloudflare or grafana cloud style)
- Customize dynamic and loading pages
-## Usage
+## CLI Usage
-`docker run -v /var/run/docker.sock:/var/run/docker.sock -p 10000:10000 ghcr.io/acouvreur/sablier:latest --swarmode=true`
+```
+Usage:
+ sablier [command]
-### CLI
+Available Commands:
+ completion Generate the autocompletion script for the specified shell
+ help Help about any command
+ start Start the Sablier server
+ version Print the version Sablier
-`./sablier --swarmMode=true --kubernetesMode=false`
+Flags:
+ -h, --help help for sablier
-| Argument | Value | Description |
-| ---------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| `swarmMode` | true,false (default true) | Enable/Disable swarm mode. Used to determine the scaler implementation. |
-| `kubernetesMode` | true,false (default false) | Enable/Disable Kubernetes mode. Used to determine the scaler implementation. |
-| `storagePath` | path/to/storage/file (default nil) | Enables persistent storage, file will be used to load previous state upon starting and will sync the current content to memory into the file every 5 seconds |
+Use "sablier [command] --help" for more information about a command.
+```
-### Docker
+Start options
-- Docker Hub `acouvreur/sablier`
-- Ghcr `ghcr.io/acouvreur/sablier`
+```
+Start the Sablier server
-`docker run -v /var/run/docker.sock:/var/run/docker.sock -p 10000:10000 ghcr.io/acouvreur/sablier:latest --swarmode=true`
+Usage:
+ sablier start [flags]
-### Kubernetes
+Flags:
+ -h, --help help for start
+ --provider.name string Provider to use to manage containers [docker swarm kubernetes] (default "docker")
+ --server.base-path string The base path for the API (default "/")
+ --server.port int The server port to use (default 10000)
+ --storage.file string File path to save the state
+```
-see KUBERNETES.md
+## Configuration
-### API
+Sablier can be configured in that order:
+
+1. command line arguments
+2. environment variable
+3. config.yaml file
+
+```yaml
+server:
+ port: 10000
+ basePath: /
+storage:
+ file:
+provider:
+ name: docker # available providers are docker, swarm and kubernetes
+```
+
+## Reverse proxies integration plugins
+
+### Traefik Integration
+
+see [Traefik Integration](./plugins/traefik/README.md)
+
+
+## Kubernetes
+
+see [KUBERNETES.md](https://github.com/acouvreur/sablier/blob/main/KUBERNETES.md)
+
+## API
```
GET :10000/?name=&timeout=
diff --git a/app/middleware/logging.go b/app/middleware/logging.go
new file mode 100644
index 0000000..e8e0ae4
--- /dev/null
+++ b/app/middleware/logging.go
@@ -0,0 +1,78 @@
+package middleware
+
+import (
+ "fmt"
+ "math"
+ "net/http"
+ "os"
+ "time"
+
+ "github.com/gin-gonic/gin"
+ "github.com/sirupsen/logrus"
+)
+
+var timeFormat = "02/Jan/2006:15:04:05 -0700"
+
+// Logger is the logrus logger handler
+func Logger(logger logrus.FieldLogger, notLogged ...string) gin.HandlerFunc {
+ hostname, err := os.Hostname()
+ if err != nil {
+ hostname = "unknow"
+ }
+
+ var skip map[string]struct{}
+
+ if length := len(notLogged); length > 0 {
+ skip = make(map[string]struct{}, length)
+
+ for _, p := range notLogged {
+ skip[p] = struct{}{}
+ }
+ }
+
+ return func(c *gin.Context) {
+ // other handler can change c.Path so:
+ path := c.Request.URL.Path
+ start := time.Now()
+ c.Next()
+ stop := time.Since(start)
+ latency := int(math.Ceil(float64(stop.Nanoseconds()) / 1000000.0))
+ statusCode := c.Writer.Status()
+ clientIP := c.ClientIP()
+ clientUserAgent := c.Request.UserAgent()
+ referer := c.Request.Referer()
+ dataLength := c.Writer.Size()
+ if dataLength < 0 {
+ dataLength = 0
+ }
+
+ if _, ok := skip[path]; ok {
+ return
+ }
+
+ entry := logger.WithFields(logrus.Fields{
+ "hostname": hostname,
+ "statusCode": statusCode,
+ "latency": latency, // time to process
+ "clientIP": clientIP,
+ "method": c.Request.Method,
+ "path": path,
+ "referer": referer,
+ "dataLength": dataLength,
+ "userAgent": clientUserAgent,
+ })
+
+ if len(c.Errors) > 0 {
+ entry.Error(c.Errors.ByType(gin.ErrorTypePrivate).String())
+ } else {
+ msg := fmt.Sprintf("%s - %s [%s] \"%s %s\" %d %d \"%s\" \"%s\" (%dms)", clientIP, hostname, time.Now().Format(timeFormat), c.Request.Method, path, statusCode, dataLength, referer, clientUserAgent, latency)
+ if statusCode >= http.StatusInternalServerError {
+ entry.Error(msg)
+ } else if statusCode >= http.StatusBadRequest {
+ entry.Warn(msg)
+ } else {
+ entry.Info(msg)
+ }
+ }
+ }
+}
diff --git a/app/sablier.go b/app/sablier.go
new file mode 100644
index 0000000..a546cac
--- /dev/null
+++ b/app/sablier.go
@@ -0,0 +1,190 @@
+package app
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "os"
+ "time"
+
+ "github.com/acouvreur/sablier/app/middleware"
+ "github.com/acouvreur/sablier/config"
+ "github.com/acouvreur/sablier/pkg/scaler"
+ "github.com/acouvreur/sablier/pkg/storage"
+ "github.com/acouvreur/tinykv"
+ "github.com/docker/docker/client"
+ "github.com/gin-gonic/gin"
+ log "github.com/sirupsen/logrus"
+ "k8s.io/client-go/kubernetes"
+ "k8s.io/client-go/rest"
+)
+
+type OnDemandRequestState struct {
+ State string `json:"state"`
+ Name string `json:"name"`
+}
+
+func Start(conf config.Config) error {
+
+ scaler, err := initScaler(conf.Provider)
+ if err != nil {
+ return (err)
+ }
+ log.Infof("using provider \"%s\"", conf.Provider.Name)
+
+ store, err := initRuntimeStorage(scaler)
+ if err != nil {
+ return (err)
+ }
+
+ err = initPersistentStorage(conf.Storage, store)
+ if err != nil {
+ return (err)
+ }
+
+ err = initServer(conf.Server, scaler, store)
+ if err != nil {
+ return (err)
+ }
+
+ return nil
+}
+
+func initScaler(conf config.Provider) (scaler.Scaler, error) {
+
+ err := conf.IsValid()
+ if err != nil {
+ return nil, err
+ }
+
+ switch {
+ case conf.Name == "swarm":
+ cli, err := client.NewClientWithOpts()
+ if err != nil {
+ log.Fatal(fmt.Errorf("%+v", "Could not connect to docker API"))
+ }
+ return &scaler.DockerSwarmScaler{
+ Client: cli,
+ }, nil
+ case conf.Name == "docker":
+ cli, err := client.NewClientWithOpts()
+ if err != nil {
+ log.Fatal(fmt.Errorf("%+v", "Could not connect to docker API"))
+ }
+ return &scaler.DockerClassicScaler{
+ Client: cli,
+ }, nil
+ case conf.Name == "kubernetes":
+ config, err := rest.InClusterConfig()
+ if err != nil {
+ log.Fatal(err)
+ }
+ client, err := kubernetes.NewForConfig(config)
+ if err != nil {
+ log.Fatal(err)
+ }
+ return scaler.NewKubernetesScaler(client), nil
+ }
+
+ return nil, fmt.Errorf("unimplemented provider %s", conf.Name)
+}
+
+func initRuntimeStorage(scaler scaler.Scaler) (tinykv.KV[OnDemandRequestState], error) {
+ // TODO: Add some checks
+ return tinykv.New(time.Second*20, func(key string, _ OnDemandRequestState) {
+ // Auto scale down after timeout
+ err := scaler.ScaleDown(key)
+
+ if err != nil {
+ log.Warnf("error scaling down %s: %s", key, err.Error())
+ }
+ }), nil
+}
+
+func initPersistentStorage(config config.Storage, store tinykv.KV[OnDemandRequestState]) error {
+ if len(config.File) > 0 {
+ file, err := os.OpenFile(config.File, os.O_RDWR|os.O_CREATE, 0755)
+
+ if err != nil {
+ return err
+ }
+
+ // TODO: Add data check
+ json.NewDecoder(file).Decode(store)
+ storage.New(file, time.Second*5, store)
+ log.Infof("initialized storage to %s", config.File)
+ } else {
+ log.Infof("no storage configuration provided. all states will be lost upon exit")
+ }
+ return nil
+}
+
+func initServer(conf config.Server, scaler scaler.Scaler, store tinykv.KV[OnDemandRequestState]) error {
+ r := gin.Default()
+
+ r.Use(middleware.Logger(log.New()), gin.Recovery())
+
+ base := r.Group(conf.BasePath)
+ {
+ base.GET("/", onDemand(scaler, store))
+ }
+
+ r.Run(fmt.Sprintf(":%d", conf.Port))
+ return nil
+}
+
+func onDemand(scaler scaler.Scaler, store tinykv.KV[OnDemandRequestState]) func(c *gin.Context) {
+ return func(c *gin.Context) {
+
+ name := c.Query("name")
+ to := c.Query("timeout")
+
+ if name == "" || to == "" {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "name or timeout empty"})
+ return
+ }
+
+ timeout, err := time.ParseDuration(to)
+
+ if err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+ return
+ }
+
+ requestState, exists := store.Get(name)
+
+ // 1. Check against the current state
+ if !exists || requestState.State != "started" {
+ if scaler.IsUp(name) {
+ requestState = OnDemandRequestState{
+ State: "started",
+ Name: name,
+ }
+ } else {
+ requestState = OnDemandRequestState{
+ State: "starting",
+ Name: name,
+ }
+ err := scaler.ScaleUp(name)
+
+ if err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+ return
+ }
+ }
+ }
+
+ // 2. Store the updated state
+ store.Put(name, requestState, tinykv.ExpiresAfter(timeout))
+
+ // 3. Serve depending on the current state
+ switch requestState.State {
+ case "starting":
+ c.JSON(http.StatusAccepted, gin.H{"state": requestState.State})
+ case "started":
+ c.JSON(http.StatusCreated, gin.H{"state": requestState.State})
+ default:
+ c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Errorf("unknown state %s", requestState.State)})
+ }
+ }
+}
diff --git a/cmd/root.go b/cmd/root.go
new file mode 100644
index 0000000..3ecba4c
--- /dev/null
+++ b/cmd/root.go
@@ -0,0 +1,100 @@
+package cmd
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/acouvreur/sablier/config"
+ "github.com/spf13/cobra"
+ "github.com/spf13/pflag"
+ "github.com/spf13/viper"
+)
+
+const (
+ // The name of our config file, without the file extension because viper supports many different config file languages.
+ defaultConfigFilename = "config"
+)
+
+var (
+ rootCmd = &cobra.Command{
+ Use: "sablier",
+ Short: "A webserver to start container on demand",
+ Long: `Sablier is an API that start containers on demand.
+It provides an integrations with multiple reverse proxies and different loading strategies.`,
+ PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
+ // You can bind cobra and viper in a few locations, but PersistencePreRunE on the root command works well
+ return initializeConfig(cmd)
+ },
+ }
+)
+
+// Execute executes the root command.
+func Execute() error {
+ return rootCmd.Execute()
+}
+
+var conf = config.NewConfig()
+
+func init() {
+
+ rootCmd.AddCommand(startCmd)
+ // Provider flags
+ startCmd.Flags().StringVar(&conf.Provider.Name, "provider.name", "docker", fmt.Sprintf("Provider to use to manage containers %v", config.GetProviders()))
+ viper.BindPFlag("provider.name", startCmd.Flags().Lookup("provider.name"))
+ // Server flags
+ startCmd.Flags().IntVar(&conf.Server.Port, "server.port", 10000, "The server port to use")
+ viper.BindPFlag("server.port", startCmd.Flags().Lookup("server.port"))
+ startCmd.Flags().StringVar(&conf.Server.BasePath, "server.base-path", "/", "The base path for the API")
+ viper.BindPFlag("server.base-path", startCmd.Flags().Lookup("server.base-path"))
+ // Storage flags
+ startCmd.Flags().StringVar(&conf.Storage.File, "storage.file", "", "File path to save the state")
+ viper.BindPFlag("storage.file", startCmd.Flags().Lookup("storage.file"))
+
+ rootCmd.AddCommand(versionCmd)
+}
+
+func initializeConfig(cmd *cobra.Command) error {
+ v := viper.New()
+
+ // Set the base name of the config file, without the file extension.
+ v.SetConfigName(defaultConfigFilename)
+
+ // Set as many paths as you like where viper should look for the
+ // config file. We are only looking in the current working directory.
+ v.AddConfigPath(".")
+
+ // Attempt to read the config file, gracefully ignoring errors
+ // caused by a config file not being found. Return an error
+ // if we cannot parse the config file.
+ if err := v.ReadInConfig(); err != nil {
+ // It's okay if there isn't a config file
+ if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
+ return err
+ }
+ }
+
+ // Bind to environment variables
+ // Works great for simple config names, but needs help for names
+ // like --favorite-color which we fix in the bindFlags function
+ v.AutomaticEnv()
+
+ // Bind the current command's flags to viper
+ bindFlags(cmd, v)
+
+ return nil
+}
+
+// Bind each cobra flag to its associated viper configuration (config file and environment variable)
+func bindFlags(cmd *cobra.Command, v *viper.Viper) {
+ cmd.Flags().VisitAll(func(f *pflag.Flag) {
+ envVarSuffix := strings.ToUpper(strings.ReplaceAll(f.Name, "-", "_"))
+ envVarSuffix = strings.ToUpper(strings.ReplaceAll(envVarSuffix, ".", "_"))
+ v.BindEnv(f.Name, envVarSuffix)
+
+ // Apply the viper config value to the flag when the flag is not set and viper has a value
+ if !f.Changed && v.IsSet(f.Name) {
+ val := v.Get(f.Name)
+ cmd.Flags().Set(f.Name, fmt.Sprintf("%v", val))
+ }
+ })
+}
diff --git a/cmd/start.go b/cmd/start.go
new file mode 100644
index 0000000..2323319
--- /dev/null
+++ b/cmd/start.go
@@ -0,0 +1,22 @@
+package cmd
+
+import (
+ "github.com/acouvreur/sablier/app"
+ "github.com/acouvreur/sablier/config"
+ "github.com/spf13/cobra"
+ "github.com/spf13/viper"
+)
+
+var startCmd = &cobra.Command{
+ Use: "start",
+ Short: "Start the Sablier server",
+ Run: func(cmd *cobra.Command, args []string) {
+ conf := config.NewConfig()
+ viper.Unmarshal(&conf)
+
+ err := app.Start(conf)
+ if err != nil {
+ panic(err)
+ }
+ },
+}
diff --git a/cmd/version.go b/cmd/version.go
new file mode 100644
index 0000000..b32a9de
--- /dev/null
+++ b/cmd/version.go
@@ -0,0 +1,16 @@
+package cmd
+
+import (
+ "fmt"
+
+ "github.com/acouvreur/sablier/version"
+ "github.com/spf13/cobra"
+)
+
+var versionCmd = &cobra.Command{
+ Use: "version",
+ Short: "Print the version Sablier",
+ Run: func(cmd *cobra.Command, args []string) {
+ fmt.Println(version.Info())
+ },
+}
diff --git a/config/configuration.go b/config/configuration.go
new file mode 100644
index 0000000..ab0d038
--- /dev/null
+++ b/config/configuration.go
@@ -0,0 +1,15 @@
+package config
+
+type Config struct {
+ Server Server
+ Storage Storage
+ Provider Provider
+}
+
+func NewConfig() Config {
+ return Config{
+ Server: NewServerConfig(),
+ Storage: NewStorageConfig(),
+ Provider: NewProviderConfig(),
+ }
+}
diff --git a/config/provider.go b/config/provider.go
new file mode 100644
index 0000000..4ad3eb8
--- /dev/null
+++ b/config/provider.go
@@ -0,0 +1,32 @@
+package config
+
+import (
+ "fmt"
+)
+
+// Provider holds the provider description
+// It can be either docker, swarm or kubernetes
+type Provider struct {
+ Name string `mapstructure:"NAME" yaml:"provider,omitempty"`
+}
+
+var providers = []string{"docker", "swarm", "kubernetes"}
+
+func NewProviderConfig() Provider {
+ return Provider{
+ Name: "docker",
+ }
+}
+
+func (provider Provider) IsValid() error {
+ for _, p := range providers {
+ if p == provider.Name {
+ return nil
+ }
+ }
+ return fmt.Errorf("unrecognized provider %s. providers available: %v", provider.Name, providers)
+}
+
+func GetProviders() []string {
+ return providers
+}
diff --git a/config/server.go b/config/server.go
new file mode 100644
index 0000000..d972109
--- /dev/null
+++ b/config/server.go
@@ -0,0 +1,13 @@
+package config
+
+type Server struct {
+ Port int `mapstructure:"PORT" yaml:"port" default:"10000"`
+ BasePath string `mapstructure:"BASEPATH" yaml:"basePath" default:"/"`
+}
+
+func NewServerConfig() Server {
+ return Server{
+ Port: 10000,
+ BasePath: "/",
+ }
+}
diff --git a/config/storage.go b/config/storage.go
new file mode 100644
index 0000000..35401a1
--- /dev/null
+++ b/config/storage.go
@@ -0,0 +1,11 @@
+package config
+
+type Storage struct {
+ File string `mapstructure:"FILE" yaml:"file" default:""`
+}
+
+func NewStorageConfig() Storage {
+ return Storage{
+ File: "",
+ }
+}
diff --git a/go.mod b/go.mod
index 90619ef..21c227b 100644
--- a/go.mod
+++ b/go.mod
@@ -8,11 +8,11 @@ require (
github.com/docker/go-connections v0.4.0 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/sirupsen/logrus v1.8.1
- github.com/stretchr/testify v1.7.1
- golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect
- golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 // indirect
+ github.com/stretchr/testify v1.8.0
+ golang.org/x/net v0.0.0-20220930213112-107f3e3c3b0b // indirect
+ golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect
golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect
- gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apimachinery v0.24.0
k8s.io/client-go v0.24.0
)
@@ -27,33 +27,58 @@ require (
github.com/docker/distribution v2.8.1+incompatible // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/emicklei/go-restful v2.15.0+incompatible // indirect
+ github.com/fsnotify/fsnotify v1.5.4 // indirect
+ github.com/gin-contrib/sse v0.1.0 // indirect
+ github.com/gin-gonic/gin v1.8.1 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.20.0 // indirect
github.com/go-openapi/swag v0.21.1 // indirect
+ github.com/go-playground/locales v0.14.0 // indirect
+ github.com/go-playground/universal-translator v0.18.0 // indirect
+ github.com/go-playground/validator/v10 v10.11.1 // indirect
+ github.com/goccy/go-json v0.9.11 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/gnostic v0.6.9 // indirect
github.com/google/go-cmp v0.5.8 // indirect
github.com/google/gofuzz v1.2.0 // indirect
+ github.com/hashicorp/hcl v1.0.0 // indirect
+ github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
+ github.com/leodido/go-urn v1.2.1 // indirect
+ github.com/magiconair/properties v1.8.6 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
+ github.com/mattn/go-isatty v0.0.16 // indirect
+ github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect
+ github.com/pelletier/go-toml v1.9.5 // indirect
+ github.com/pelletier/go-toml/v2 v2.0.5 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
- github.com/stretchr/objx v0.3.0 // indirect
+ github.com/spf13/afero v1.8.2 // indirect
+ github.com/spf13/cast v1.5.0 // indirect
+ github.com/spf13/cobra v1.5.0 // indirect
+ github.com/spf13/jwalterweatherman v1.1.0 // indirect
+ github.com/spf13/pflag v1.0.5 // indirect
+ github.com/spf13/viper v1.13.0 // indirect
+ github.com/stretchr/objx v0.4.0 // indirect
+ github.com/subosito/gotenv v1.4.1 // indirect
+ github.com/ugorji/go/codec v1.2.7 // indirect
+ golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be // indirect
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 // indirect
golang.org/x/text v0.3.7 // indirect
google.golang.org/appengine v1.6.7 // indirect
- google.golang.org/protobuf v1.28.0 // indirect
+ google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
+ gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gotest.tools/v3 v3.0.3 // indirect
k8s.io/klog/v2 v2.60.1 // indirect
diff --git a/go.sum b/go.sum
index fd07d94..ba37f1f 100644
--- a/go.sum
+++ b/go.sum
@@ -3,6 +3,7 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
+cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
@@ -15,6 +16,7 @@ cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOY
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
+cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
@@ -35,6 +37,7 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
+cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
@@ -69,6 +72,7 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -102,8 +106,14 @@ github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoD
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
+github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
+github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
+github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8=
+github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
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=
@@ -123,6 +133,15 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh
github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU=
github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
+github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
+github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
+github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
+github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
+github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
+github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ=
+github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
+github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
+github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@@ -191,22 +210,29 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
+github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
+github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
+github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
+github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
@@ -216,18 +242,29 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
+github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
+github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
+github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
+github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
+github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc=
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw=
@@ -260,27 +297,48 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec=
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
+github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
+github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
+github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg=
+github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
+github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
+github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
+github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
+github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo=
+github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo=
+github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
+github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
+github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
+github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=
+github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
+github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/viper v1.13.0 h1:BWSJ/M+f+3nmdz9bxB+bWX28kkALN2ok11D0rSo8EJU=
+github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw=
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As=
github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
+github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
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=
@@ -289,6 +347,14 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs=
+github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
+github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo=
+github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
+github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
+github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
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=
@@ -311,7 +377,12 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
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-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
+golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be h1:fmw3UbQh+nxngCAHrDCCztao/kbYFnWjoqop8dHx05A=
+golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
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=
@@ -379,6 +450,7 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
@@ -388,6 +460,10 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 h1:NWy5+hlRbC7HK+PmcXVUmW1IMyFce7to56IUvhUFm7Y=
+golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220930213112-107f3e3c3b0b h1:uKO3Js8lXGjpjdc4J3rqs0/Ex5yDKUGfk43tTYWVLas=
+golang.org/x/net v0.0.0-20220930213112-107f3e3c3b0b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
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=
@@ -452,18 +528,27 @@ golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/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-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec h1:BkDtF2Ih9xZ7le9ndzTA7KJow28VbQW3odyk/8drmuI=
+golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 h1:EH1Deb8WZJ0xc0WK//leUHXcX9aLE5SymusoTmMZye8=
@@ -533,6 +618,7 @@ golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -540,6 +626,7 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df h1:5Pf6pFKu98ODmgnpvkJ3kFUOQGGLIzLIkbzUHp47618=
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=
@@ -605,7 +692,9 @@ google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
@@ -646,15 +735,21 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
+google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
+gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
+gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -669,6 +764,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
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=
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
diff --git a/main.go b/main.go
index 0cc6f1a..4cb9b51 100644
--- a/main.go
+++ b/main.go
@@ -1,178 +1,9 @@
package main
import (
- "encoding/json"
- "flag"
- "fmt"
- "net/http"
- "net/url"
- "os"
- "time"
-
- "github.com/acouvreur/tinykv"
- "github.com/acouvreur/sablier/pkg/scaler"
- "github.com/acouvreur/sablier/pkg/storage"
- "github.com/docker/docker/client"
- log "github.com/sirupsen/logrus"
- "k8s.io/client-go/kubernetes"
- "k8s.io/client-go/rest"
+ "github.com/acouvreur/sablier/cmd"
)
-type OnDemandRequestState struct {
- State string `json:"state"`
- Name string `json:"name"`
-}
-
func main() {
-
- swarmMode := flag.Bool("swarmMode", true, "Enable swarm mode")
- kubernetesMode := flag.Bool("kubernetesMode", false, "Enable Kubernetes mode")
- storagePath := flag.String("storagePath", "", "Enable persistent storage")
-
- flag.Parse()
-
- dockerScaler := getDockerScaler(*swarmMode, *kubernetesMode)
-
- store := tinykv.New[OnDemandRequestState](time.Second*20, func(key string, _ OnDemandRequestState) {
- // Auto scale down after timeout
- err := dockerScaler.ScaleDown(key)
-
- if err != nil {
- log.Warnf("error scaling down %s: %s", key, err.Error())
- }
- })
-
- if storagePath != nil && len(*storagePath) > 0 {
- file, err := os.OpenFile(*storagePath, os.O_RDWR|os.O_CREATE, 0755)
-
- if err != nil {
- panic(err)
- }
-
- json.NewDecoder(file).Decode(store)
- storage.New(file, time.Second*5, store)
- }
-
- fmt.Printf("Server listening on port 10000, swarmMode: %t, kubernetesMode: %t\n", *swarmMode, *kubernetesMode)
- http.HandleFunc("/", onDemand(dockerScaler, store))
- log.Fatal(http.ListenAndServe(":10000", nil))
-}
-
-func getDockerScaler(swarmMode, kubernetesMode bool) scaler.Scaler {
- cliMode := !swarmMode && !kubernetesMode
-
- switch {
- case swarmMode:
- cli, err := client.NewClientWithOpts()
- if err != nil {
- log.Fatal(fmt.Errorf("%+v", "Could not connect to docker API"))
- }
- return &scaler.DockerSwarmScaler{
- Client: cli,
- }
- case cliMode:
- cli, err := client.NewClientWithOpts()
- if err != nil {
- log.Fatal(fmt.Errorf("%+v", "Could not connect to docker API"))
- }
- return &scaler.DockerClassicScaler{
- Client: cli,
- }
- case kubernetesMode:
- config, err := rest.InClusterConfig()
- if err != nil {
- log.Fatal(err)
- }
- client, err := kubernetes.NewForConfig(config)
- if err != nil {
- log.Fatal(err)
- }
- return scaler.NewKubernetesScaler(client)
- }
-
- panic("invalid mode")
-}
-
-func onDemand(scaler scaler.Scaler, store tinykv.KV[OnDemandRequestState]) func(w http.ResponseWriter, r *http.Request) {
- return func(rw http.ResponseWriter, r *http.Request) {
-
- name, err := getParam(r.URL.Query(), "name")
-
- if err != nil {
- ServeHTTPInternalError(rw, err)
- return
- }
-
- to, err := getParam(r.URL.Query(), "timeout")
-
- if err != nil {
- ServeHTTPInternalError(rw, err)
- return
- }
-
- timeout, err := time.ParseDuration(to)
-
- if err != nil {
- ServeHTTPInternalError(rw, err)
- return
- }
-
- requestState, exists := store.Get(name)
-
- // 1. Check against the current state
- if !exists || requestState.State != "started" {
- if scaler.IsUp(name) {
- requestState = OnDemandRequestState{
- State: "started",
- Name: name,
- }
- } else {
- requestState = OnDemandRequestState{
- State: "starting",
- Name: name,
- }
- err := scaler.ScaleUp(name)
-
- if err != nil {
- ServeHTTPInternalError(rw, err)
- return
- }
- }
- }
-
- // 2. Store the updated state
- store.Put(name, requestState, tinykv.ExpiresAfter(timeout))
-
- // 3. Serve depending on the current state
- switch requestState.State {
- case "starting":
- ServeHTTPRequestState(rw, requestState)
- case "started":
- ServeHTTPRequestState(rw, requestState)
- default:
- ServeHTTPInternalError(rw, fmt.Errorf("unknown state %s", requestState.State))
- }
- }
-}
-
-func getParam(queryParams url.Values, paramName string) (string, error) {
- if queryParams[paramName] == nil {
- return "", fmt.Errorf("%s is required", paramName)
- }
- return queryParams[paramName][0], nil
-}
-
-func ServeHTTPInternalError(rw http.ResponseWriter, err error) {
- rw.WriteHeader(http.StatusInternalServerError)
- rw.Write([]byte(err.Error()))
-}
-
-func ServeHTTPRequestState(rw http.ResponseWriter, requestState OnDemandRequestState) {
- rw.Header().Set("Content-Type", "text/plain")
- if requestState.State == "started" {
- rw.WriteHeader(http.StatusCreated)
- } else {
- rw.WriteHeader(http.StatusAccepted)
- }
- rw.Write([]byte(requestState.State))
+ cmd.Execute()
}
diff --git a/main_test.go b/main_test.go
deleted file mode 100644
index 089d994..0000000
--- a/main_test.go
+++ /dev/null
@@ -1,76 +0,0 @@
-package main
-
-import (
- "net/http"
- "net/http/httptest"
- "testing"
- "time"
-
- "github.com/acouvreur/tinykv"
-)
-
-type ScalerMock struct {
- isUp bool
-}
-
-func (s ScalerMock) IsUp(name string) bool {
- return s.isUp
-}
-
-func (ScalerMock) ScaleUp(name string) error { return nil }
-
-func (ScalerMock) ScaleDown(name string) error { return nil }
-
-func TestOndemand_ServeHTTP(t *testing.T) {
- testCases := []struct {
- desc string
- scaler ScalerMock
- status string
- statusCode int
- contentType string
- }{
- {
- desc: "service is starting",
- status: "starting",
- scaler: ScalerMock{isUp: false},
- statusCode: http.StatusAccepted,
- contentType: "text/plain",
- },
- {
- desc: "service is started",
- status: "started",
- scaler: ScalerMock{isUp: true},
- statusCode: http.StatusCreated,
- contentType: "text/plain",
- },
- }
-
- for _, test := range testCases {
- test := test
- t.Run(test.desc, func(t *testing.T) {
-
- t.Logf("IsUp: %t", test.scaler.isUp)
- request := httptest.NewRequest("GET", "/?name=whoami&timeout=5m", nil)
- responseRecorder := httptest.NewRecorder()
-
- store := tinykv.New[OnDemandRequestState](time.Second * 20)
-
- onDemandHandler := onDemand(test.scaler, store)
- onDemandHandler(responseRecorder, request)
-
- body := responseRecorder.Body.String()
-
- if responseRecorder.Code != test.statusCode {
- t.Errorf("Want status '%d', got '%d'", test.statusCode, responseRecorder.Code)
- }
-
- if responseRecorder.Body.String() != test.status {
- t.Errorf("Want body '%s', got '%s'", test.status, body)
- }
-
- if responseRecorder.Header().Get("Content-Type") != test.contentType {
- t.Errorf("Want content type '%s', got '%s'", test.contentType, responseRecorder.Header().Get("Content-Type"))
- }
- })
- }
-}
diff --git a/version/info.go b/version/info.go
new file mode 100644
index 0000000..beb84cc
--- /dev/null
+++ b/version/info.go
@@ -0,0 +1,59 @@
+package version
+
+import (
+ "bytes"
+ "fmt"
+ "runtime"
+ "strings"
+ "text/template"
+)
+
+// Build information. Populated at build-time.
+var (
+ Version string
+ Revision string
+ Branch string
+ BuildUser string
+ BuildDate string
+ GoVersion = runtime.Version()
+)
+
+// versionInfoTmpl contains the template used by Info.
+var versionInfoTmpl = `
+{{.program}}, version {{.version}} (branch: {{.branch}}, revision: {{.revision}})
+ build user: {{.buildUser}}
+ build date: {{.buildDate}}
+ go version: {{.goVersion}}
+ platform: {{.platform}}
+`
+
+// Print returns version information.
+func Print(program string) string {
+ m := map[string]string{
+ "program": program,
+ "version": Version,
+ "revision": Revision,
+ "branch": Branch,
+ "buildUser": BuildUser,
+ "buildDate": BuildDate,
+ "goVersion": GoVersion,
+ "platform": runtime.GOOS + "/" + runtime.GOARCH,
+ }
+ t := template.Must(template.New("version").Parse(versionInfoTmpl))
+
+ var buf bytes.Buffer
+ if err := t.ExecuteTemplate(&buf, "version", m); err != nil {
+ panic(err)
+ }
+ return strings.TrimSpace(buf.String())
+}
+
+// Info returns version, branch and revision information.
+func Info() string {
+ return fmt.Sprintf("(version=%s, branch=%s, revision=%s)", Version, Branch, Revision)
+}
+
+// BuildContext returns goVersion, buildUser and buildDate information.
+func BuildContext() string {
+ return fmt.Sprintf("(go=%s, user=%s, date=%s)", GoVersion, BuildUser, BuildDate)
+}