diff --git a/cli/cmd/context/delete.go b/cli/cmd/context/delete.go new file mode 100644 index 00000000..ec6df905 --- /dev/null +++ b/cli/cmd/context/delete.go @@ -0,0 +1,21 @@ +package context + +import ( + "github.com/gravitl/netmaker/cli/config" + "github.com/spf13/cobra" +) + +// contextDeleteCmd deletes a contex +var contextDeleteCmd = &cobra.Command{ + Use: "delete [NAME]", + Args: cobra.ExactArgs(1), + Short: "Delete a context", + Long: `Delete a context`, + Run: func(cmd *cobra.Command, args []string) { + config.DeleteContext(args[0]) + }, +} + +func init() { + rootCmd.AddCommand(contextDeleteCmd) +} diff --git a/cli/cmd/context/list.go b/cli/cmd/context/list.go new file mode 100644 index 00000000..e6dc7ad1 --- /dev/null +++ b/cli/cmd/context/list.go @@ -0,0 +1,21 @@ +package context + +import ( + "github.com/gravitl/netmaker/cli/config" + "github.com/spf13/cobra" +) + +// contextListCmd lists all contexts +var contextListCmd = &cobra.Command{ + Use: "list", + Args: cobra.NoArgs, + Short: "List all contexts", + Long: `List all contexts`, + Run: func(cmd *cobra.Command, args []string) { + config.ListAll() + }, +} + +func init() { + rootCmd.AddCommand(contextListCmd) +} diff --git a/cli/cmd/context/root.go b/cli/cmd/context/root.go new file mode 100644 index 00000000..43498aed --- /dev/null +++ b/cli/cmd/context/root.go @@ -0,0 +1,37 @@ +package context + +import ( + "os" + + "github.com/spf13/cobra" +) + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "context", + Short: "Manage various netmaker server configurations", + Long: `Manage various netmaker server configurations`, + // Run: func(cmd *cobra.Command, args []string) { }, +} + +func GetRoot() *cobra.Command { + return rootCmd +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } +} + +func init() { + // Here you will define your flags and configuration settings. + // Cobra supports persistent flags, which, if defined here, + // will be global for your application. + // Cobra also supports local flags, which will only run + // when this action is called directly. + rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} diff --git a/cli/cmd/context/set.go b/cli/cmd/context/set.go new file mode 100644 index 00000000..670169ee --- /dev/null +++ b/cli/cmd/context/set.go @@ -0,0 +1,55 @@ +package context + +import ( + "fmt" + "log" + + "github.com/gravitl/netmaker/cli/config" + "github.com/spf13/cobra" +) + +const ( + FlagEndpoint = "endpoint" + FlagUsername = "username" + FlagPassword = "password" + FlagMasterKey = "master_key" +) + +var ( + endpoint string + username string + password string + masterKey string +) + +// contextSetCmd creates/updates a context +var contextSetCmd = &cobra.Command{ + Use: fmt.Sprintf("set [NAME] [--%s=https://api.netmaker.io] [--%s=admin] [--%s=pass] [--%s=secret]", FlagEndpoint, FlagUsername, FlagPassword, FlagMasterKey), + Args: cobra.ExactArgs(1), + Short: "Create a context or update an existing one", + Long: `Create a context or update an existing one`, + Run: func(cmd *cobra.Command, args []string) { + ctx := config.Context{ + Endpoint: endpoint, + Username: username, + Password: password, + MasterKey: masterKey, + } + if ctx.Username == "" && ctx.MasterKey == "" { + cmd.Usage() + log.Fatal("Either username/password or master key is required") + } + config.SetContext(args[0], ctx) + }, +} + +func init() { + contextSetCmd.Flags().StringVar(&endpoint, FlagEndpoint, "", "Endpoint of the API Server (Required)") + contextSetCmd.MarkFlagRequired(FlagEndpoint) + contextSetCmd.Flags().StringVar(&username, FlagUsername, "", "Username") + contextSetCmd.Flags().StringVar(&password, FlagPassword, "", "Password") + contextSetCmd.MarkFlagsRequiredTogether(FlagUsername, FlagPassword) + contextSetCmd.Flags().StringVar(&masterKey, FlagMasterKey, "", "Master Key") + + rootCmd.AddCommand(contextSetCmd) +} diff --git a/cli/cmd/context/use.go b/cli/cmd/context/use.go new file mode 100644 index 00000000..bdb688f9 --- /dev/null +++ b/cli/cmd/context/use.go @@ -0,0 +1,21 @@ +package context + +import ( + "github.com/gravitl/netmaker/cli/config" + "github.com/spf13/cobra" +) + +// contextUseCmd sets the current context +var contextUseCmd = &cobra.Command{ + Use: "use [NAME]", + Args: cobra.ExactArgs(1), + Short: "Set the current context", + Long: `Set the current context`, + Run: func(cmd *cobra.Command, args []string) { + config.SetCurrentContext(args[0]) + }, +} + +func init() { + rootCmd.AddCommand(contextUseCmd) +} diff --git a/cli/cmd/network/create.go b/cli/cmd/network/create.go new file mode 100644 index 00000000..c302bacc --- /dev/null +++ b/cli/cmd/network/create.go @@ -0,0 +1,27 @@ +package network + +import ( + "fmt" + "os" + + "github.com/gravitl/netmaker/cli/functions" + "github.com/gravitl/netmaker/models" + "github.com/spf13/cobra" +) + +// networkCreateCmd represents the networkCreate command +var networkCreateCmd = &cobra.Command{ + Use: "create [network_definition.json]", + Short: "Create a Network", + Long: `Create a Network`, + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + network := &models.Network{} + resp := functions.CreateNetwork(network) + fmt.Fprintf(os.Stdout, "Response from `NetworksApi.CreateNetwork`: %v\n", resp) + }, +} + +func init() { + rootCmd.AddCommand(networkCreateCmd) +} diff --git a/cli/cmd/network/root.go b/cli/cmd/network/root.go new file mode 100644 index 00000000..b3b6e5df --- /dev/null +++ b/cli/cmd/network/root.go @@ -0,0 +1,39 @@ +package network + +import ( + "os" + + "github.com/spf13/cobra" +) + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "network", + Short: "Manage Netmaker Networks", + Long: `Manage Netmaker Networks`, + // Uncomment the following line if your bare application + // has an action associated with it: + // Run: func(cmd *cobra.Command, args []string) { }, +} + +func GetRoot() *cobra.Command { + return rootCmd +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } +} + +func init() { + // Here you will define your flags and configuration settings. + // Cobra supports persistent flags, which, if defined here, + // will be global for your application. + // Cobra also supports local flags, which will only run + // when this action is called directly. + rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} diff --git a/cli/cmd/root.go b/cli/cmd/root.go new file mode 100644 index 00000000..d1cc930d --- /dev/null +++ b/cli/cmd/root.go @@ -0,0 +1,48 @@ +package cmd + +import ( + "os" + + "github.com/gravitl/netmaker/cli/cmd/context" + "github.com/gravitl/netmaker/cli/cmd/network" + "github.com/spf13/cobra" +) + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "netmaker", + Short: "CLI for interacting with Netmaker Server", + Long: `CLI for interacting with Netmaker Server`, + // Uncomment the following line if your bare application + // has an action associated with it: + // Run: func(cmd *cobra.Command, args []string) { }, +} + +func GetRoot() *cobra.Command { + return rootCmd +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } +} + +func init() { + // Here you will define your flags and configuration settings. + // Cobra supports persistent flags, which, if defined here, + // will be global for your application. + + // rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.tctl.yaml)") + + // Cobra also supports local flags, which will only run + // when this action is called directly. + rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") + + // IMP: Bind subcommands here + rootCmd.AddCommand(network.GetRoot()) + rootCmd.AddCommand(context.GetRoot()) +} diff --git a/cli/config/config.go b/cli/config/config.go new file mode 100644 index 00000000..f3349ae8 --- /dev/null +++ b/cli/config/config.go @@ -0,0 +1,130 @@ +package config + +import ( + "fmt" + "log" + "os" + "path/filepath" + + "github.com/spf13/viper" + "gopkg.in/yaml.v3" +) + +type Context struct { + Endpoint string + Username string + Password string + MasterKey string + Current bool `yaml:"current,omitempty"` +} + +var ( + contextMap = map[string]Context{} + configFilePath string + filename string +) + +func createConfigPathIfNotExists() { + homeDir, err := os.UserHomeDir() + if err != nil { + log.Fatal(err) + } + configFilePath = filepath.Join(homeDir, ".netmaker") + // create directory if not exists + if err := os.MkdirAll(configFilePath, os.ModePerm); err != nil { + log.Fatal(err) + } + filename = filepath.Join(configFilePath, "config.yml") + // create file if not exists + if _, err := os.Stat(filename); err != nil { + if os.IsNotExist(err) { + if _, err := os.Create(filename); err != nil { + log.Fatalf("Unable to create file filename: %s", err) + } + } else { + log.Fatal(err) + } + } +} + +func loadConfig() { + viper.SetConfigName("config") + viper.AddConfigPath(configFilePath) + viper.SetConfigType("yml") + if err := viper.ReadInConfig(); err != nil { + log.Fatalf("Error reading config file: %s", err) + } + if err := viper.Unmarshal(&contextMap); err != nil { + log.Fatalf("Unable to decode into struct: %s", err) + } +} + +func GetCurrentContext() (ret Context) { + for _, ctx := range contextMap { + if ctx.Current { + ret = ctx + return + } + } + log.Fatalf("No current context set, do so via `netmaker context use `") + return +} + +func SaveContext() { + bodyBytes, err := yaml.Marshal(&contextMap) + if err != nil { + log.Fatalf("Error marshalling into YAML %s", err) + } + file, err := os.Create(filename) + if err != nil { + log.Fatal(err) + } + if _, err := file.Write(bodyBytes); err != nil { + log.Fatal(err) + } + if err := file.Close(); err != nil { + log.Fatal(err) + } +} + +func SetCurrentContext(ctxName string) { + if _, ok := contextMap[ctxName]; !ok { + log.Fatalf("No such context %s", ctxName) + } + for key, ctx := range contextMap { + ctx.Current = key == ctxName + contextMap[key] = ctx + } + SaveContext() +} + +func SetContext(ctxName string, ctx Context) { + if oldCtx, ok := contextMap[ctxName]; ok && oldCtx.Current { + ctx.Current = true + } + contextMap[ctxName] = ctx + SaveContext() +} + +func DeleteContext(ctxName string) { + if _, ok := contextMap[ctxName]; ok { + delete(contextMap, ctxName) + SaveContext() + } else { + log.Fatalf("No such context %s", ctxName) + } +} + +func ListAll() { + for key, ctx := range contextMap { + fmt.Print("\n", key, " -> ", ctx.Endpoint) + if ctx.Current { + fmt.Print(" (current)") + } + } +} + +func init() { + createConfigPathIfNotExists() + loadConfig() +} diff --git a/cli/functions/auth.go b/cli/functions/auth.go new file mode 100644 index 00000000..5af97a40 --- /dev/null +++ b/cli/functions/auth.go @@ -0,0 +1,12 @@ +package functions + +import ( + "net/http" + + "github.com/gravitl/netmaker/models" +) + +func LoginWithUserAndPassword(username, password string) *models.SuccessResponse { + authParams := &models.UserAuthParams{UserName: username, Password: password} + return Request[models.SuccessResponse](http.MethodPost, "/api/users/adm/authenticate", authParams) +} diff --git a/cli/functions/http_client.go b/cli/functions/http_client.go new file mode 100644 index 00000000..05fabe13 --- /dev/null +++ b/cli/functions/http_client.go @@ -0,0 +1,43 @@ +package functions + +import ( + "bytes" + "encoding/json" + "io/ioutil" + "log" + "net/http" +) + +func Request[T any](method, route string, payload any) *T { + requestURL := "http://localhost:3000" + var ( + req *http.Request + err error + ) + if payload == nil { + req, err = http.NewRequest(method, requestURL+route, nil) + } else { + payloadBytes, jsonErr := json.Marshal(payload) + if jsonErr != nil { + log.Fatalf("Error in request JSON marshalling: %s", err) + } + req, err = http.NewRequest(method, requestURL+route, bytes.NewReader(payloadBytes)) + } + if err != nil { + log.Fatalf("Client could not create request: %s", err) + } + res, err := http.DefaultClient.Do(req) + if err != nil { + log.Fatalf("Client error making http request: %s", err) + } + + resBodyBytes, err := ioutil.ReadAll(res.Body) + if err != nil { + log.Fatalf("Client could not read response body: %s", err) + } + body := new(T) + if err := json.Unmarshal(resBodyBytes, body); err != nil { + log.Fatalf("Error unmarshalling JSON: %s", err) + } + return body +} diff --git a/cli/functions/network.go b/cli/functions/network.go new file mode 100644 index 00000000..b8001f10 --- /dev/null +++ b/cli/functions/network.go @@ -0,0 +1,17 @@ +package functions + +import ( + "net/http" + + "github.com/gravitl/netmaker/models" +) + +// CreateNetwork - creates a network +func CreateNetwork(payload *models.Network) *models.Network { + return Request[models.Network](http.MethodPost, "/api/networks", payload) +} + +// GetNetworks - fetch all networks +func GetNetworks() *models.Network { + return Request[models.Network](http.MethodGet, "/api/networks", nil) +} diff --git a/cli/main.go b/cli/main.go new file mode 100644 index 00000000..3a9415be --- /dev/null +++ b/cli/main.go @@ -0,0 +1,11 @@ +package main + +import ( + "github.com/gravitl/netmaker/cli/cmd" + _ "github.com/gravitl/netmaker/cli/config" +) + +func main() { + + cmd.Execute() +} diff --git a/go.mod b/go.mod index 02cd7986..fe16cf2d 100644 --- a/go.mod +++ b/go.mod @@ -43,6 +43,8 @@ require ( github.com/agnivade/levenshtein v1.1.1 github.com/coreos/go-oidc/v3 v3.4.0 github.com/gorilla/websocket v1.5.0 + github.com/spf13/cobra v1.5.0 + github.com/spf13/viper v1.8.1 golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 ) @@ -74,23 +76,32 @@ require ( github.com/golang/protobuf v1.5.2 // indirect github.com/google/go-cmp v0.5.8 // indirect github.com/gopherjs/gopherjs v1.17.2 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/josharian/native v1.0.0 // indirect github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.2.1 // indirect + github.com/magiconair/properties v1.8.5 // indirect github.com/mdlayher/genetlink v1.2.0 // indirect github.com/mdlayher/netlink v1.6.0 // indirect github.com/mdlayher/socket v0.1.1 // indirect + github.com/mitchellh/mapstructure v1.4.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect + github.com/pelletier/go-toml v1.9.3 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sirupsen/logrus v1.9.0 // indirect github.com/spf13/afero v1.9.2 // indirect + github.com/spf13/cast v1.3.1 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 // indirect github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 // indirect + github.com/subosito/gotenv v1.2.0 // indirect github.com/tevino/abool v1.2.0 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect diff --git a/go.sum b/go.sum index 0bc4b3b9..73ba3a69 100644 --- a/go.sum +++ b/go.sum @@ -304,6 +304,7 @@ github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.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/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= @@ -311,6 +312,7 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= 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/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jackmordaunt/icns/v2 v2.2.1/go.mod h1:6aYIB9eSzyfHHMKqDf17Xrs1zetQPReAkiUSHzdw4cI= github.com/josephspurrier/goversioninfo v1.4.0/go.mod h1:JWzv5rKQr+MmW+LvM412ToT/IkYDZjaclF2pKDss8IY= @@ -340,6 +342,7 @@ github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lucor/goinfo v0.0.0-20210802170112-c078a2b0f08b/go.mod h1:PRq09yoB+Q2OJReAmwzKivcYyremnibWGbK7WfftHzc= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= @@ -364,6 +367,7 @@ github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS4 github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae h1:O4SWKdcHVCvYqyDV+9CJA1fcDN2L11Bule0iFy3YlAI= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -380,6 +384,7 @@ github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrB github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= 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= @@ -426,14 +431,20 @@ github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw= github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= +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.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +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.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 h1:HunZiaEKNGVdhTRQOVpMmj5MQnGnv+e8uZNu3xFLgyM= github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564/go.mod h1:afMbS0qvv1m5tfENCwnOdZGOF8RGR/FsZ7bvBxQGZG4= @@ -453,6 +464,7 @@ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1F github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tevino/abool v1.2.0 h1:heAkClL8H6w+mK5md9dzsuohKeXHUpY7Vw0ZCKW+huA= github.com/tevino/abool v1.2.0/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg=