add config management commands

This commit is contained in:
Anish Mukherjee 2022-11-18 02:52:44 +05:30
parent 2a69df0979
commit fa9b7643cb
15 changed files with 505 additions and 0 deletions

21
cli/cmd/context/delete.go Normal file
View file

@ -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)
}

21
cli/cmd/context/list.go Normal file
View file

@ -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)
}

37
cli/cmd/context/root.go Normal file
View file

@ -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")
}

55
cli/cmd/context/set.go Normal file
View file

@ -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)
}

21
cli/cmd/context/use.go Normal file
View file

@ -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)
}

27
cli/cmd/network/create.go Normal file
View file

@ -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)
}

39
cli/cmd/network/root.go Normal file
View file

@ -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")
}

48
cli/cmd/root.go Normal file
View file

@ -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())
}

130
cli/config/config.go Normal file
View file

@ -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 <name>`")
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()
}

12
cli/functions/auth.go Normal file
View file

@ -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)
}

View file

@ -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
}

17
cli/functions/network.go Normal file
View file

@ -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)
}

11
cli/main.go Normal file
View file

@ -0,0 +1,11 @@
package main
import (
"github.com/gravitl/netmaker/cli/cmd"
_ "github.com/gravitl/netmaker/cli/config"
)
func main() {
cmd.Execute()
}

11
go.mod
View file

@ -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

12
go.sum
View file

@ -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=