diff --git a/Makefile b/Makefile index 07d7c15..f77cec0 100644 --- a/Makefile +++ b/Makefile @@ -47,13 +47,23 @@ split : build ## Split a sample Kubernetes configuration file list : build ## List a set of sample Kubernetes konfigurations $(KONF_PREFIX) konf-sh list --single-konfigs ./examples/konfigs +# TODO refactor after shell wrapper implementation set-local : build ## Set local Kubernetes context (current shell) @echo "It's useless to run an 'eval' command from the Makefile as each line is executed in a new shell instance" - @echo "Please manually execute 'eval $(konf-sh set local context_b --single-konfigs ./examples/konfigs)'" + @echo "Please manually execute 'eval $(konf-sh set local --single-konfigs ./examples/konfigs context_b)'" + @echo "" + +# TODO refactor after shell wrapper implementation +set-last-local : build ## Set last Kubernetes context as local (current shell) + @echo "It's useless to run an 'eval' command from the Makefile as each line is executed in a new shell instance" + @echo "Please manually execute 'eval $(konf-sh set local --single-konfigs ./examples/konfigs context_b)'" @echo "" set-global : build ## Set global Kubernetes context - $(KONF_PREFIX) konf-sh set global context_b --kube-config ./examples/config + $(KONF_PREFIX) konf-sh set global --kube-config ./examples/config context_b + +set-last-global : build ## Set last Kubernetes context as global + $(KONF_PREFIX) konf-sh set global --kube-config ./examples/config --single-konfigs ./examples/konfigs - view : build ## View local and global Kubernetes contexts $(KONF_PREFIX) konf-sh view --kube-config ./examples/config @@ -70,6 +80,7 @@ delete : build ## Remove context list from Kubernetes configuration rename : build ## Rename specified context in Kubernetes configuration $(KONF_PREFIX) konf-sh rename --kube-config ./examples/config --single-konfigs ./examples/konfigs context_a context_x +# TODO refactor after shell wrapper implementation reset-local : build ## Reset local Kubernetes configuration (current shell) @echo "It's useless to run an 'eval' command from the Makefile as each line is executed in a new shell instance" @echo "Please manually execute 'eval $(konf-sh reset local)'" diff --git a/README.md b/README.md index 8c4585c..99951f2 100644 --- a/README.md +++ b/README.md @@ -62,10 +62,16 @@ This project was inspired by [kubectx+kubens](https://github.com/ahmetb/kubectx) ### 1. Install binary -`๐Ÿ— work in progress` +**_โš ๏ธ A more automated installation process is under development_** + +1. Download compressed binary from the [release page](https://github.com/bygui86/konf-sh/releases) +2. Extract binary wherever you prefer +3. Add binary to your PATH ### 2. Install shell wrapper +**_โš ๏ธ Shell wrapper won't work if binary is not in PATH_** + #### zsh Add the following to your `.zshrc` and restart the shell: @@ -108,16 +114,10 @@ source <(konf completion bash) Here following some optional customisations to make your life even easier. -#### Open last konf used - -Add the following to your `.zshrc` or `.bashrc` and restart the shell: - -```sh -export KUBECONFIG=$(konf --silent set -) -``` - #### Aliases +**_โš ๏ธ Aliases won't work if shell wrapper is not installed_** + Add the following to your `.zshrc` or `.bashrc` and restart the shell: ```sh @@ -191,6 +191,8 @@ konf view `konf set global ` sets the global Kubernetes context (by default `currentContext` in `~/.kube/config`) to the specified one (by default `~/.kube/konfigs`). +`konf set [local | global] -` switches back the local or global Kubernetes context to the previous one. + ### View `konf view` shows both local (current shell) and global Kubernetes context. @@ -236,6 +238,14 @@ konf view | --kube-config | split, view, view global, set global, delete, rename, reset global | - | ~/.kube/config | KONF_KUBE_CONFIG_PATH | Specify a custom Kubernetes configuration file path | | --single-konfigs | split, list, set local, delete, rename | - | ~/.kube/konfigs/ | KONF_SINGLE_KUBE_KONFIGS_PATH | Specify the single Kubernetes konfigurations path | +> **_โš ๏ธ Please note_** +> +> All flags must be specified BEFORE arguments +> +> This works: `konf set global --kube-config ./examples/config context_b` +> +> This doesn't work: `konf set global context_b --kube-config ./examples/config` + ### Environment variables | Key | Command list | Available values | Default | Corresponding flag | Description | @@ -257,11 +267,13 @@ konf view | 11 | split | Error checking existence of Kubernetes konfigurations path | | 12 | split, set global, delete, rename | Error validating Kubernetes configuration (single, global, cleaned) | | 13 | split, set global, delete, rename | Error writing Kubernetes configuration (single, global, cleaned) to file | +| 14 | set local, set global | Error checking existence of last Kubernetes context path | | 21 | list | Error listing single Kubernetes konfigurations | | 31 | set local | Error checking existence of Kubernetes konfigurations path | | 32 | set local, set global | Error getting Kubernetes context: context argument not specified | | 33 | set local | Error checking existence of Kubernetes context | | 34 | set global, rename | Error checking existence of context in Kubernetes configuration | +| 35 | set global | Error retrieving last Kubernetes context | | 41 | delete | Error getting Kubernetes context list: 'context list' argument not specified | | 42 | delete | Error validating Kubernetes context list: 'context list' argument not valid. Context list must be a comma-separated list | | 43 | delete | Error removing Kubernetes context list | diff --git a/cmd/completion/action.go b/cmd/completion/action.go index 425db21..1d7cb54 100644 --- a/cmd/completion/action.go +++ b/cmd/completion/action.go @@ -44,18 +44,14 @@ compdef _cli_zsh_autocomplete konf` ) func bashCompletion(ctx *cli.Context) error { - zap.L().Debug("") zap.L().Debug("๐Ÿ› Executing BASH-COMPLETION command") - zap.L().Debug("") zap.L().Info(bashScript) return nil } func zshCompletion(ctx *cli.Context) error { - zap.L().Debug("") zap.L().Debug("๐Ÿ› Executing ZSH-COMPLETION command") - zap.L().Debug("") zap.L().Info(zshScript) return nil diff --git a/cmd/delete/action.go b/cmd/delete/action.go index 302b003..37b138f 100644 --- a/cmd/delete/action.go +++ b/cmd/delete/action.go @@ -17,9 +17,7 @@ import ( // INFO: deleteCtx cannot be named delete because of name collision func deleteCtx(ctx *cli.Context) error { - zap.L().Info("") zap.L().Debug("๐Ÿ› Executing DELETE command") - zap.L().Debug("") zap.L().Debug("๐Ÿ› Get Kubernetes configuration file path") kCfgFilePath := ctx.String(commons.KubeConfigFlagName) @@ -180,6 +178,7 @@ func deleteContextList(kCfg *clientcmdapi.Config, contextSlice []string) error { kCfg.AuthInfos = authMap if rmCtx == kCfg.CurrentContext { + zap.L().Debug("๐Ÿ› Resetting current context in Kubernetes configuration") kCfg.CurrentContext = "" } } diff --git a/cmd/delete/command.go b/cmd/delete/command.go index 7d6918e..c7c3c22 100644 --- a/cmd/delete/command.go +++ b/cmd/delete/command.go @@ -20,7 +20,7 @@ func BuildCommand() *cli.Command { Name: commons.GetUrfaveFlagName(commons.KubeConfigFlagName, commons.KubeConfigFlagShort), Usage: commons.KubeConfigFlagDescription, EnvVars: []string{commons.KubeConfigPathEnvVar}, - Value: kubeconfig.GetCustomKubeConfigPathDefault(home), + Value: kubeconfig.GetKubeConfigPathDefault(home), Required: false, }, &cli.StringFlag{ diff --git a/cmd/list/action.go b/cmd/list/action.go index 53bbaae..34f06f2 100644 --- a/cmd/list/action.go +++ b/cmd/list/action.go @@ -13,9 +13,7 @@ import ( ) func list(ctx *cli.Context) error { - zap.L().Info("") zap.L().Debug("๐Ÿ› Executing LIST command") - zap.L().Debug("") zap.L().Debug("๐Ÿ› Get single Kubernetes konfigurations path") singleKfgsPath := ctx.String(commons.SingleKonfigsFlagName) diff --git a/cmd/rename/action.go b/cmd/rename/action.go index 7c51808..9f57301 100644 --- a/cmd/rename/action.go +++ b/cmd/rename/action.go @@ -15,9 +15,7 @@ import ( ) func rename(ctx *cli.Context) error { - zap.L().Info("") zap.L().Debug("๐Ÿ› Executing RENAME command") - zap.L().Debug("") zap.L().Debug("๐Ÿ› Get Kubernetes configuration file path") kCfgFilePath := ctx.String(commons.KubeConfigFlagName) diff --git a/cmd/rename/command.go b/cmd/rename/command.go index 91793bb..4249b51 100644 --- a/cmd/rename/command.go +++ b/cmd/rename/command.go @@ -20,7 +20,7 @@ func BuildCommand() *cli.Command { Name: commons.GetUrfaveFlagName(commons.KubeConfigFlagName, commons.KubeConfigFlagShort), Usage: commons.KubeConfigFlagDescription, EnvVars: []string{commons.KubeConfigPathEnvVar}, - Value: kubeconfig.GetCustomKubeConfigPathDefault(home), + Value: kubeconfig.GetKubeConfigPathDefault(home), Required: false, }, &cli.StringFlag{ diff --git a/cmd/reset/action.go b/cmd/reset/action.go index 2faca72..d6787fa 100644 --- a/cmd/reset/action.go +++ b/cmd/reset/action.go @@ -10,18 +10,15 @@ import ( ) func resetLocal(ctx *cli.Context) error { - zap.L().Debug("") zap.L().Debug("๐Ÿ› Executing RESET-LOCAL command") - zap.L().Debug("") - zap.L().Info(fmt.Sprintf("unset %s", commons.KubeConfigEnvVar)) + zap.L().Info(fmt.Sprintf("unset %s", commons.KubeConfigEnvVar)) // TODO to be replaced by following line + //zap.L().Info() // TODO enable when shell wrapper is available return nil } func resetGlobal(ctx *cli.Context) error { - zap.L().Info("") zap.L().Debug("๐Ÿ› Executing RESET-GLOBAL command") - zap.L().Debug("") zap.L().Debug("๐Ÿ› Get Kubernetes configuration file path") kubeConfigFilePath := ctx.String(commons.KubeConfigFlagName) @@ -35,18 +32,18 @@ func resetGlobal(ctx *cli.Context) error { newValErr := kubeconfig.Validate(kubeConfig) if newValErr != nil { return cli.Exit( - fmt.Sprintf("โŒ Error validating Kubernetes configuration from '%s': %s", kubeConfigFilePath, newValErr.Error()), - 12) + fmt.Sprintf("โŒ Error validating Kubernetes configuration from '%s': %s", + kubeConfigFilePath, newValErr.Error()), 12) } newWriteErr := kubeconfig.Write(kubeConfig, kubeConfigFilePath) if newWriteErr != nil { return cli.Exit( - fmt.Sprintf("โŒ Error writing Kubernetes configuration '%s' to file: %s", kubeConfigFilePath, newWriteErr.Error()), - 13) + fmt.Sprintf("โŒ Error writing Kubernetes configuration '%s' to file: %s", + kubeConfigFilePath, newWriteErr.Error()), 13) } - zap.S().Infof("โœ… Completed! Kubernetes configuration reset") + zap.S().Infof("โœ… Global Kubernetes configuration reset") zap.L().Info("") return nil } diff --git a/cmd/reset/command.go b/cmd/reset/command.go index bda8267..ee5407b 100644 --- a/cmd/reset/command.go +++ b/cmd/reset/command.go @@ -30,7 +30,7 @@ func BuildCommand() *cli.Command { Name: commons.GetUrfaveFlagName(commons.KubeConfigFlagName, commons.KubeConfigFlagShort), Usage: commons.KubeConfigFlagDescription, EnvVars: []string{commons.KubeConfigPathEnvVar}, - Value: kubeconfig.GetCustomKubeConfigPathDefault(home), + Value: kubeconfig.GetKubeConfigPathDefault(home), Required: false, }, }, diff --git a/cmd/set/action.go b/cmd/set/action.go index 720e3d6..3e360e3 100644 --- a/cmd/set/action.go +++ b/cmd/set/action.go @@ -2,6 +2,8 @@ package set import ( "fmt" + "io/ioutil" + "os" "path/filepath" "strings" @@ -11,93 +13,211 @@ import ( "go.uber.org/zap" ) -// INFO: it seems that is not possible to run a command like "source ./set-local-script.sh" :( func setLocal(ctx *cli.Context) error { zap.L().Debug("๐Ÿ› Executing SET-LOCAL command") - zap.L().Debug("") + + zap.L().Debug("๐Ÿ› Get selected Kubernetes context") + args := ctx.Args() + if args.Len() == 0 || strings.Compare(args.Get(0), "") == 0 { + return cli.Exit("โŒ Error getting Kubernetes context: context argument not specified", 32) + } + context := args.Get(0) zap.L().Debug("๐Ÿ› Get single Kubernetes konfigurations path") - singleConfigsPath := ctx.String(commons.SingleKonfigsFlagName) + singleKcfgsPath := ctx.String(commons.SingleKonfigsFlagName) - zap.S().Debugf("๐Ÿ› Check existence of single Kubernetes konfigurations path '%s'", singleConfigsPath) - checkFolderErr := commons.CheckIfFolderExist(singleConfigsPath, true) - if checkFolderErr != nil { - return cli.Exit( - fmt.Sprintf("โŒ Error checking existence of Kubernetes konfigurations path '%s': %s", singleConfigsPath, checkFolderErr.Error()), - 31) + if context == "-" { + return setLastLocal(singleKcfgsPath) } - zap.S().Debugf("๐Ÿ“š Single Kubernetes konfigurations path: '%s'", singleConfigsPath) - zap.L().Debug("๐Ÿ› Get selected Kubernetes context") - args := ctx.Args() - if args.Len() == 0 || strings.Compare(args.Get(0), "") == 0 { + return setSelectedLocal(singleKcfgsPath, context) +} + +func setLastLocal(singleKcfgsPath string) error { + zap.L().Debug("๐Ÿ› Set last Kubernetes context as local") + + return setSelectedLocal( + singleKcfgsPath, + readLastCtx(singleKcfgsPath, "local"), + ) +} + +func setSelectedLocal(singleKcfgsPath, context string) error { + zap.S().Debugf("๐Ÿ› Set selected Kubernetes context '%s' as local", context) + + zap.S().Debugf("๐Ÿ› Check existence of single Kubernetes konfigurations path '%s'", singleKcfgsPath) + dirErr := commons.CheckIfFolderExist(singleKcfgsPath, true) + if dirErr != nil { return cli.Exit( - "โŒ Error getting Kubernetes context: context argument not specified", - 32) + fmt.Sprintf("โŒ Error checking existence of Kubernetes konfigurations path '%s': %s", + singleKcfgsPath, dirErr.Error()), 31) } - context := args.Get(0) + zap.S().Debugf("๐Ÿ› Single Kubernetes konfigurations path: '%s'", singleKcfgsPath) zap.S().Debugf("๐Ÿ› Check existence of single Kubernetes konfigurations file for context '%s'", context) - localKubeConfig := filepath.Join(singleConfigsPath, context) - checkFileErr := commons.CheckIfFileExist(localKubeConfig) - if checkFileErr != nil { + localKCfg := filepath.Join(singleKcfgsPath, context) + fileErr := commons.CheckIfFileExist(localKCfg) + if fileErr != nil { return cli.Exit( - fmt.Sprintf("โŒ Error checking existence of Kubernetes context '%s' configuration file: %s", localKubeConfig, checkFileErr.Error()), - 33) + fmt.Sprintf("โŒ Error checking existence of Kubernetes context '%s' configuration file: %s", + localKCfg, fileErr.Error()), 33) + } + zap.S().Debugf("๐Ÿ› Selected Kubernetes context: '%s'", context) + + zap.S().Debugf("๐Ÿ› Get '%s' environment variable value", commons.KubeConfigEnvVar) + lastCtxFull := kubeconfig.GetKubeConfigEnvVar() + singleKcfgsPathTemp := strings.Replace(singleKcfgsPath, "./", "", 1) + lastCtx := strings.Replace(lastCtxFull, fmt.Sprintf("%s/", singleKcfgsPathTemp), "", 1) + zap.S().Debugf("๐Ÿ› Last Kubernetes context: '%s'", lastCtx) + if lastCtx != "" { + lastErr := saveLastCtx( + singleKcfgsPath, + lastCtx, + "local", + ) + if lastErr != nil { + zap.S().Errorf("โŒ Error saving last Kubernetes context '%s': %s - 'konf set local -' might not work", + context, lastErr.Error()) + } } - zap.S().Debugf("๐Ÿงฉ Selected Kubernetes context: '%s'", context) - zap.L().Info(fmt.Sprintf("export %s='%s'", commons.KubeConfigEnvVar, localKubeConfig)) + zap.L().Info(fmt.Sprintf("export %s='%s'", commons.KubeConfigEnvVar, localKCfg)) // TODO to be replaced by following line + //zap.L().Info(fmt.Sprintf("%s", localKCfg)) // TODO enable when shell wrapper is available return nil } func setGlobal(ctx *cli.Context) error { - zap.L().Info("") zap.L().Debug("๐Ÿ› Executing SET-GLOBAL command") - zap.L().Debug("") - - zap.L().Debug("๐Ÿ› Get Kubernetes configuration file path") - kubeConfigFilePath := ctx.String(commons.KubeConfigFlagName) - zap.S().Infof("๐Ÿ“– Load Kubernetes configuration from '%s'", kubeConfigFilePath) - kubeConfig := kubeconfig.Load(kubeConfigFilePath) - // INFO: no need to check if kubeConfig is nil, because the inner method called will exit if it does not find the configuration file zap.L().Debug("๐Ÿ› Get selected Kubernetes context") args := ctx.Args() if args.Len() == 0 || strings.Compare(args.Get(0), "") == 0 { - return cli.Exit( - "โŒ Error getting Kubernetes context: context argument not specified", - 32) + return cli.Exit("โŒ Error getting Kubernetes context: context argument not specified", 32) } context := args.Get(0) - zap.S().Debugf("๐Ÿ› Check existence of context '%s' in Kubernetes configuration '%s'", context, kubeConfigFilePath) - checkCtxErr := kubeconfig.CheckIfContextExist(kubeConfig, context) - if checkCtxErr != nil { + zap.L().Debug("๐Ÿ› Get single Kubernetes konfigurations path") + singleKcfgsPath := ctx.String(commons.SingleKonfigsFlagName) + + if context == "-" { + return setLastGlobal(ctx, singleKcfgsPath) + } + + return setSelectedGlobal(ctx, singleKcfgsPath, context) +} + +func setLastGlobal(ctx *cli.Context, singleKcfgsPath string) error { + zap.L().Debug("๐Ÿ› Set last Kubernetes context as global") + + lastCtx := readLastCtx(singleKcfgsPath, "global") + if lastCtx == "" { + return cli.Exit("โŒ Error retrieving last Kubernetes context: no last global found", 35) + } + + zap.S().Infof("โฎ Set last Kubernetes context '%s' as global", lastCtx) + return setSelectedGlobal(ctx, singleKcfgsPath, lastCtx) +} + +func setSelectedGlobal(ctx *cli.Context, singleKcfgsPath, context string) error { + zap.S().Debugf("๐Ÿ› Set selected Kubernetes context '%s' as global", context) + + zap.L().Debug("๐Ÿ› Get Kubernetes configuration file path") + kCfgFilePath := ctx.String(commons.KubeConfigFlagName) + zap.S().Infof("๐Ÿ“– Load Kubernetes configuration from '%s'", kCfgFilePath) + kCfg := kubeconfig.Load(kCfgFilePath) + // INFO: no need to check if kubeConfig is nil, because the inner method called will exit if it does not find the configuration file + + zap.S().Debugf("๐Ÿ› Check existence of context '%s' in Kubernetes configuration '%s'", context, kCfgFilePath) + ctxErr := kubeconfig.CheckIfContextExist(kCfg, context) + if ctxErr != nil { return cli.Exit( - fmt.Sprintf("โŒ Error checking existence of context '%s' in Kubernetes configuration '%s': %s", context, kubeConfigFilePath, checkCtxErr.Error()), - 34) + fmt.Sprintf("โŒ Error checking existence of context '%s' in Kubernetes configuration '%s': %s", + context, kCfgFilePath, ctxErr.Error()), 34) } zap.S().Infof("๐Ÿงฉ Selected Kubernetes context: '%s'", context) - zap.S().Debugf("๐Ÿ› Set new context '%s' in Kubernetes configuration '%s'", context, kubeConfigFilePath) - kubeConfig.CurrentContext = context + zap.S().Debugf("๐Ÿ› Set new context '%s' in Kubernetes configuration '%s'", context, kCfgFilePath) + lastCtx := kCfg.CurrentContext + kCfg.CurrentContext = context - newValErr := kubeconfig.Validate(kubeConfig) + newValErr := kubeconfig.Validate(kCfg) if newValErr != nil { return cli.Exit( - fmt.Sprintf("โŒ Error validating Kubernetes configuration from '%s': %s", kubeConfigFilePath, newValErr.Error()), - 12) + fmt.Sprintf("โŒ Error validating Kubernetes configuration from '%s': %s", + kCfgFilePath, newValErr.Error()), 12) } - newWriteErr := kubeconfig.Write(kubeConfig, kubeConfigFilePath) + newWriteErr := kubeconfig.Write(kCfg, kCfgFilePath) if newWriteErr != nil { return cli.Exit( - fmt.Sprintf("โŒ Error writing Kubernetes configuration '%s' to file: %s", kubeConfigFilePath, newWriteErr.Error()), - 13) + fmt.Sprintf("โŒ Error writing Kubernetes configuration '%s' to file: %s", + kCfgFilePath, newWriteErr.Error()), 13) + } + + zap.S().Debugf("๐Ÿ› Last Kubernetes context: '%s'", lastCtx) + if lastCtx != "" { + lastErr := saveLastCtx( + strings.Replace(singleKcfgsPath, "/config", "", 1), + lastCtx, + "global", + ) + if lastErr != nil { + zap.S().Errorf("โŒ Error saving last Kubernetes context '%s': %s - 'konf set local -' might not work", + lastCtx, lastErr.Error()) + } } - zap.S().Infof("โœ… Completed! Kubernetes global configuration '%s' successfully updated with current context '%s'", kubeConfigFilePath, context) + zap.S().Infof("โœ… Kubernetes global configuration '%s' successfully updated with current context '%s'", kCfgFilePath, context) zap.L().Info("") return nil } + +func saveLastCtx(singleKcfgsPath, context, command string) error { + lastDirPath := fmt.Sprintf("%s/last-ctx", singleKcfgsPath) + lastFilePath := fmt.Sprintf("%s/%s", lastDirPath, command) + zap.S().Debugf("๐Ÿ› Saving last %s Kubernetes context '%s' to file '%s'", command, context, lastFilePath) + + zap.S().Debugf("๐Ÿ› Check existence of last Kubernetes context path '%s'", lastDirPath) + checkErr := commons.CheckIfFolderExist(lastDirPath, true) + if checkErr != nil { + return cli.Exit( + fmt.Sprintf("โŒ Error checking existence of last Kubernetes context path '%s': %s", + lastDirPath, checkErr.Error()), 14) + } + + file, openErr := os.Create(lastFilePath) + if openErr != nil { + return openErr + } + defer file.Close() + + _, wrErr := file.WriteString(context) + if wrErr != nil { + return wrErr + } + + return nil +} + +func readLastCtx(singleKcfgsPath, command string) string { + lastDirPath := fmt.Sprintf("%s/last-ctx", singleKcfgsPath) + lastFilePath := fmt.Sprintf("%s/%s", lastDirPath, command) + zap.S().Debugf("๐Ÿ› Reading last %s Kubernetes context from file '%s'", command, lastFilePath) + + zap.S().Debugf("๐Ÿ› Check existence of last Kubernetes context path '%s'", lastDirPath) + checkErr := commons.CheckIfFolderExist(lastDirPath, true) + if checkErr != nil { + zap.S().Debugf("โŒ Error checking existence of last Kubernetes context path '%s': %s", + lastDirPath, checkErr.Error()) + return "" + } + + bytes, readErr := ioutil.ReadFile(lastFilePath) + if readErr != nil { + zap.S().Debugf("โŒ Error reading last Kubernetes context from file '%s': %s", + lastFilePath, readErr.Error()) + return "" + } + + return string(bytes) +} diff --git a/cmd/set/command.go b/cmd/set/command.go index d91d160..28eb611 100644 --- a/cmd/set/command.go +++ b/cmd/set/command.go @@ -39,7 +39,14 @@ func BuildCommand() *cli.Command { Name: commons.GetUrfaveFlagName(commons.KubeConfigFlagName, commons.KubeConfigFlagShort), Usage: commons.KubeConfigFlagDescription, EnvVars: []string{commons.KubeConfigPathEnvVar}, - Value: kubeconfig.GetCustomKubeConfigPathDefault(home), + Value: kubeconfig.GetKubeConfigPathDefault(home), + Required: false, + }, + &cli.StringFlag{ + Name: commons.GetUrfaveFlagName(commons.SingleKonfigsFlagName, commons.SingleKonfigsFlagShort), + Usage: commons.SingleKonfigsFlagDescription, + EnvVars: []string{commons.SingleKonfigsPathEnvVar}, + Value: kubeconfig.GetSingleConfigsPathDefault(home), Required: false, }, }, diff --git a/cmd/split/action.go b/cmd/split/action.go index 27de8ba..9f70a90 100644 --- a/cmd/split/action.go +++ b/cmd/split/action.go @@ -11,9 +11,7 @@ import ( ) func split(ctx *cli.Context) error { - zap.L().Info("") zap.L().Debug("๐Ÿ› Executing SPLIT command") - zap.L().Debug("") zap.L().Debug("๐Ÿ› Get Kubernetes configuration file path") kCfgFilePath := ctx.String(commons.KubeConfigFlagName) @@ -42,7 +40,7 @@ func split(ctx *cli.Context) error { if checkErr != nil { return cli.Exit( fmt.Sprintf("โŒ Error checking existence of Kubernetes konfigurations path '%s': %s", - checkErr.Error(), singleKfgsPath), 11) + singleKfgsPath, checkErr.Error()), 11) } validKfgs := make([]string, 0) diff --git a/cmd/split/command.go b/cmd/split/command.go index b912bcf..8fa66b3 100644 --- a/cmd/split/command.go +++ b/cmd/split/command.go @@ -19,7 +19,7 @@ func BuildCommand() *cli.Command { Name: commons.GetUrfaveFlagName(commons.KubeConfigFlagName, commons.KubeConfigFlagShort), Usage: commons.KubeConfigFlagDescription, EnvVars: []string{commons.KubeConfigPathEnvVar}, - Value: kubeconfig.GetCustomKubeConfigPathDefault(home), + Value: kubeconfig.GetKubeConfigPathDefault(home), Required: false, }, &cli.StringFlag{ diff --git a/cmd/view/action.go b/cmd/view/action.go index 2606e16..48a8256 100644 --- a/cmd/view/action.go +++ b/cmd/view/action.go @@ -19,16 +19,14 @@ func view(ctx *cli.Context) error { // INFO: viewLocal function returns error even if always nil as used to build cli command func viewLocal(ctx *cli.Context) error { - zap.L().Info("") zap.L().Debug("๐Ÿ› Executing VIEW-LOCAL command") - zap.L().Debug("") - zap.S().Debugf("๐Ÿ› Get '%s' environment variable", commons.KubeConfigEnvVar) - kCfg := kubeconfig.GetKubeConfigEnvVar() - if kCfg != "" { - zap.S().Infof("๐Ÿ’ป Local Kubernetes context: '%s'", kCfg) + zap.S().Debugf("๐Ÿ› Get '%s' environment variable value", commons.KubeConfigEnvVar) + kCfgEnvVarValue := kubeconfig.GetKubeConfigEnvVar() + if kCfgEnvVarValue != "" { + zap.S().Infof("๐Ÿ’ป Local Kubernetes context: '%s'", kCfgEnvVarValue) } else { - zap.S().Infof("๐Ÿ’ป No local Kubernetes context set or default to '%s/%s'", commons.GetHomeDirOrExit("view-local"), commons.KubeConfigPathDefault) + zap.L().Info("๐Ÿ’ป No local Kubernetes context set") } zap.L().Info("") @@ -37,14 +35,12 @@ func viewLocal(ctx *cli.Context) error { // INFO: viewGlobal function returns error even if always nil as used to build cli command func viewGlobal(ctx *cli.Context) error { - zap.L().Info("") zap.L().Debug("๐Ÿ› Executing VIEW-GLOBAL command") - zap.L().Debug("") zap.L().Debug("๐Ÿ› Get Kubernetes configuration file path") - kubeConfigFilePath := ctx.String(commons.KubeConfigFlagName) - zap.S().Infof("๐Ÿ“– Load Kubernetes configuration from '%s'", kubeConfigFilePath) - kCfg := kubeconfig.Load(kubeConfigFilePath) + kCfgFilePath := ctx.String(commons.KubeConfigFlagName) + zap.S().Infof("๐Ÿ“– Load Kubernetes configuration from '%s'", kCfgFilePath) + kCfg := kubeconfig.Load(kCfgFilePath) // INFO: no need to check if kubeConfig is nil, because the inner method called will exit if it does not find the configuration file zap.S().Infof("๐ŸŒ Global Kubernetes context: '%s'", kCfg.CurrentContext) diff --git a/cmd/view/command.go b/cmd/view/command.go index 4d0e4ff..ea8d2c1 100644 --- a/cmd/view/command.go +++ b/cmd/view/command.go @@ -19,7 +19,7 @@ func BuildCommand() *cli.Command { Name: commons.GetUrfaveFlagName(commons.KubeConfigFlagName, commons.KubeConfigFlagShort), Usage: commons.KubeConfigFlagDescription, EnvVars: []string{commons.KubeConfigPathEnvVar}, - Value: kubeconfig.GetCustomKubeConfigPathDefault(home), + Value: kubeconfig.GetKubeConfigPathDefault(home), Required: false, }, }, @@ -38,7 +38,7 @@ func BuildCommand() *cli.Command { Name: commons.GetUrfaveFlagName(commons.KubeConfigFlagName, commons.KubeConfigFlagShort), Usage: commons.KubeConfigFlagDescription, EnvVars: []string{commons.KubeConfigPathEnvVar}, - Value: kubeconfig.GetCustomKubeConfigPathDefault(home), + Value: kubeconfig.GetKubeConfigPathDefault(home), Required: false, }, }, diff --git a/docs/emoji.md b/docs/emoji.md index 0ef715d..2b16f68 100644 --- a/docs/emoji.md +++ b/docs/emoji.md @@ -9,7 +9,7 @@ ๐Ÿ—ก๏ธ ๐Ÿชš ๐Ÿช“ ๐Ÿ“‹ ๐Ÿงน ๐Ÿงฉ -๐Ÿ’ป ๐ŸŒ +๐Ÿ’ป ๐ŸŒ ๐Ÿ”™ โฎ ## Not working diff --git a/docs/roadmap.md b/docs/roadmap.md index 674fd81..0f2f208 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,6 +1,17 @@ # konf-sh - Roadmap +## Legend + +| Emoji | Description | +|:------|:--------------------------| +| โœ… | Released | +| ๐Ÿ— | In development | +| ๐Ÿ—“ | Scheduled for development | +| ๐Ÿง | Evaluating | + +--- + ## v1.0.0 ๐Ÿ—“ - [ ] create go install @@ -17,6 +28,7 @@ - [ ] `TBD` add test badge (maybe https://github.com/bygui86/konf-sh/actions/workflows/test.yaml/badge.svg) - [ ] code-coverage check - [ ] `TBD` add code-coverage badge (see https://codecov.io/gh/etherlabsio/healthcheck) +- [ ] `TBD` introduce https://github.com/spf13/afero (A FileSystem Abstraction System for Go) ## v0.7.0 ๐Ÿ—“ @@ -25,9 +37,12 @@ - [ ] list all namespaces - [ ] set local (current shell only) namespace - [ ] set global namespace + - [ ] set "-" to switch back namespace to previous + - [ ] save last set namespace to "~/.kube/konfigs/last-ns" ## v0.6.0 ๐Ÿ—“ +- [ ] `TBD` put "global" as implicit - [ ] implement "--silent" flag - [ ] shellwrapper - [ ] implement command @@ -36,7 +51,7 @@ - [ ] prompting - [ ] make it interactive with https://github.com/manifoldco/promptui -## v0.5.0 ๐Ÿ— +## v0.5.0 โœ… - [x] improve README and overall documentation - [x] update codebase @@ -64,8 +79,9 @@ - [x] improve "split" command (see TODO in commands/set/action.go) - [x] improve "completion" command - [x] fix "completion zsh" (replace "PROG" with "konf") -- [ ] improve "set" command - - [ ] add "-" to set "last konf used" in new shell +- [x] improve "set" command + - [x] save last set ctx to "~/.kube/konfigs/last-ctx/" + - [x] add "set -" to switch back ctx to previous - [x] review logging - [x] refactor logger embracing better and more standard approach with zap library - [x] review logs in all commands diff --git a/pkg/commons/constants.go b/pkg/commons/constants.go index cd6991c..716ac5f 100644 --- a/pkg/commons/constants.go +++ b/pkg/commons/constants.go @@ -3,10 +3,10 @@ package commons const ( // Flags KubeConfigFlagName = "kube-config" - KubeConfigFlagShort = "k" + KubeConfigFlagShort = "c" KubeConfigFlagDescription = "Kubernetes configuration custom (`PATH`)" SingleKonfigsFlagName = "single-konfigs" - SingleKonfigsFlagShort = "c" + SingleKonfigsFlagShort = "k" SingleKonfigsFlagDescription = "Single Kubernetes konfigurations custom (`PATH`)" // Environment variables diff --git a/pkg/kubeconfig/kubeconfig.go b/pkg/kubeconfig/kubeconfig.go index 937bb03..21cef66 100644 --- a/pkg/kubeconfig/kubeconfig.go +++ b/pkg/kubeconfig/kubeconfig.go @@ -2,7 +2,6 @@ package kubeconfig import ( "errors" - "strings" "go.uber.org/zap" "k8s.io/client-go/tools/clientcmd" @@ -50,39 +49,6 @@ func Write(kubeConfig *clientcmdApi.Config, filepath string) error { return clientcmd.WriteToFile(*kubeConfig, filepath) } -func CheckIfContextExist(kubeConfig *clientcmdApi.Config, context string) error { - zap.S().Debugf("๐Ÿ› Check context '%s' existence in Kubernetes configuration '%s'", context, kubeConfig.CurrentContext) - ctxValue := kubeConfig.Contexts[context] - if ctxValue == nil { - zap.S().Debugf("๐Ÿ› Context '%s' not found", context) - return errors.New("context not found") - } - zap.S().Debugf("๐Ÿ› Context '%s' is valid", context) - return nil -} - -func CheckIfClusterExist(kubeConfig *clientcmdApi.Config, cluster string) error { - zap.S().Debugf("๐Ÿ› Check cluster '%s' existence in Kubernetes configuration '%s'", cluster, kubeConfig.CurrentContext) - clValue := kubeConfig.Clusters[cluster] - if clValue == nil { - zap.S().Debugf("๐Ÿ› Cluster '%s' not found", cluster) - return errors.New("cluster not found") - } - zap.S().Debugf("๐Ÿ› Cluster '%s' is valid", cluster) - return nil -} - -func CheckIfAuthInfoExist(kubeConfig *clientcmdApi.Config, authInfo string) error { - zap.S().Debugf("๐Ÿ› Check user '%s' existence in Kubernetes configuration '%s'", authInfo, kubeConfig.CurrentContext) - authValue := kubeConfig.AuthInfos[authInfo] - if authValue == nil { - zap.S().Debugf("๐Ÿ› User '%s' not found", authInfo) - return errors.New("user not found") - } - zap.S().Debugf("๐Ÿ› User '%s' is valid", authInfo) - return nil -} - func RemoveContext(ctxMap map[string]*clientcmdApi.Context, context string) (map[string]*clientcmdApi.Context, error) { ctxValue := ctxMap[context] if ctxValue == nil { @@ -130,46 +96,3 @@ func RemoveAuthInfo(authMap map[string]*clientcmdApi.AuthInfo, authInfo string) } return newAuthMap, nil } - -func GetContextsKeys(contexts map[string]*clientcmdApi.Context) []string { - keys := make([]string, 0, len(contexts)) - for k := range contexts { - keys = append(keys, k) - } - return keys -} - -func GetClustersKeys(clusters map[string]*clientcmdApi.Cluster) []string { - keys := make([]string, 0, len(clusters)) - for k := range clusters { - keys = append(keys, k) - } - return keys -} - -func GetAuthInfosKeys(auths map[string]*clientcmdApi.AuthInfo) []string { - keys := make([]string, 0, len(auths)) - for k := range auths { - keys = append(keys, k) - } - return keys -} - -// PrintOnLogs is for development and debugging purposes only -func PrintOnLogs(kCfg *clientcmdApi.Config, isDebug bool) { - if isDebug { - // zap.S().Debugf("Api version: %s", kCfg.APIVersion) - // zap.S().Debugf("Kind: %s", kCfg.Kind) - zap.S().Debugf("Current context: %s", kCfg.CurrentContext) - zap.S().Debugf("Contexts: %s", strings.Join(GetContextsKeys(kCfg.Contexts), ", ")) - zap.S().Debugf("Clusters: %s", strings.Join(GetClustersKeys(kCfg.Clusters), ", ")) - zap.S().Debugf("Users: %s", strings.Join(GetAuthInfosKeys(kCfg.AuthInfos), ", ")) - } else { - // logger.SugaredLogger.Infof("Api version: %s", kCfg.APIVersion) - // logger.SugaredLogger.Infof("Kind: %s", kCfg.Kind) - zap.S().Infof("Current context: %s", kCfg.CurrentContext) - zap.S().Infof("Contexts: %s", strings.Join(GetContextsKeys(kCfg.Contexts), ", ")) - zap.S().Infof("Clusters: %s", strings.Join(GetClustersKeys(kCfg.Clusters), ", ")) - zap.S().Infof("Users: %s", strings.Join(GetAuthInfosKeys(kCfg.AuthInfos), ", ")) - } -} diff --git a/pkg/kubeconfig/utils.go b/pkg/kubeconfig/utils.go index 436b8bb..5aea462 100644 --- a/pkg/kubeconfig/utils.go +++ b/pkg/kubeconfig/utils.go @@ -1,19 +1,99 @@ package kubeconfig import ( + "errors" "path/filepath" + "strings" "github.com/bygui86/konf-sh/pkg/commons" + "go.uber.org/zap" + clientcmdApi "k8s.io/client-go/tools/clientcmd/api" ) func GetKubeConfigEnvVar() string { return commons.GetString(commons.KubeConfigEnvVar, commons.KubeConfigEnvVarDefault) } -func GetCustomKubeConfigPathDefault(home string) string { +func GetKubeConfigPathDefault(home string) string { return filepath.Join(home, commons.KubeConfigPathDefault) } func GetSingleConfigsPathDefault(home string) string { return filepath.Join(home, commons.SingleKonfigsPathDefault) } + +func CheckIfContextExist(kubeConfig *clientcmdApi.Config, context string) error { + zap.S().Debugf("๐Ÿ› Check context '%s' existence in Kubernetes configuration '%s'", context, kubeConfig.CurrentContext) + ctxValue := kubeConfig.Contexts[context] + if ctxValue == nil { + zap.S().Debugf("๐Ÿ› Context '%s' not found", context) + return errors.New("context not found") + } + zap.S().Debugf("๐Ÿ› Context '%s' is valid", context) + return nil +} + +func CheckIfClusterExist(kubeConfig *clientcmdApi.Config, cluster string) error { + zap.S().Debugf("๐Ÿ› Check cluster '%s' existence in Kubernetes configuration '%s'", cluster, kubeConfig.CurrentContext) + clValue := kubeConfig.Clusters[cluster] + if clValue == nil { + zap.S().Debugf("๐Ÿ› Cluster '%s' not found", cluster) + return errors.New("cluster not found") + } + zap.S().Debugf("๐Ÿ› Cluster '%s' is valid", cluster) + return nil +} + +func CheckIfAuthInfoExist(kubeConfig *clientcmdApi.Config, authInfo string) error { + zap.S().Debugf("๐Ÿ› Check user '%s' existence in Kubernetes configuration '%s'", authInfo, kubeConfig.CurrentContext) + authValue := kubeConfig.AuthInfos[authInfo] + if authValue == nil { + zap.S().Debugf("๐Ÿ› User '%s' not found", authInfo) + return errors.New("user not found") + } + zap.S().Debugf("๐Ÿ› User '%s' is valid", authInfo) + return nil +} + +func GetContextsKeys(contexts map[string]*clientcmdApi.Context) []string { + keys := make([]string, 0, len(contexts)) + for k := range contexts { + keys = append(keys, k) + } + return keys +} + +func GetClustersKeys(clusters map[string]*clientcmdApi.Cluster) []string { + keys := make([]string, 0, len(clusters)) + for k := range clusters { + keys = append(keys, k) + } + return keys +} + +func GetAuthInfosKeys(auths map[string]*clientcmdApi.AuthInfo) []string { + keys := make([]string, 0, len(auths)) + for k := range auths { + keys = append(keys, k) + } + return keys +} + +// PrintOnLogs is for development and debugging purposes only +func PrintOnLogs(kCfg *clientcmdApi.Config, isDebug bool) { + if isDebug { + // zap.S().Debugf("Api version: %s", kCfg.APIVersion) + // zap.S().Debugf("Kind: %s", kCfg.Kind) + zap.S().Debugf("Current context: %s", kCfg.CurrentContext) + zap.S().Debugf("Contexts: %s", strings.Join(GetContextsKeys(kCfg.Contexts), ", ")) + zap.S().Debugf("Clusters: %s", strings.Join(GetClustersKeys(kCfg.Clusters), ", ")) + zap.S().Debugf("Users: %s", strings.Join(GetAuthInfosKeys(kCfg.AuthInfos), ", ")) + } else { + // logger.SugaredLogger.Infof("Api version: %s", kCfg.APIVersion) + // logger.SugaredLogger.Infof("Kind: %s", kCfg.Kind) + zap.S().Infof("Current context: %s", kCfg.CurrentContext) + zap.S().Infof("Contexts: %s", strings.Join(GetContextsKeys(kCfg.Contexts), ", ")) + zap.S().Infof("Clusters: %s", strings.Join(GetClustersKeys(kCfg.Clusters), ", ")) + zap.S().Infof("Users: %s", strings.Join(GetAuthInfosKeys(kCfg.AuthInfos), ", ")) + } +}