diff --git a/Makefile b/Makefile index e593553..07d7c15 100644 --- a/Makefile +++ b/Makefile @@ -44,7 +44,7 @@ simulate-release : ## Simulate a release with goreleaser split : build ## Split a sample Kubernetes configuration file $(KONF_PREFIX) konf-sh split --kube-config ./examples/config --single-konfigs ./examples/konfigs -list : build ## List a set of sample Kubernetes konfigurations files +list : build ## List a set of sample Kubernetes konfigurations $(KONF_PREFIX) konf-sh list --single-konfigs ./examples/konfigs set-local : build ## Set local Kubernetes context (current shell) @@ -65,10 +65,10 @@ view-global : build ## View global Kubernetes context $(KONF_PREFIX) konf-sh view global --kube-config ./examples/config delete : build ## Remove context list from Kubernetes configuration - $(KONF_PREFIX) konf-sh delete --kube-config ./examples/config context_a,context_b + $(KONF_PREFIX) konf-sh delete --kube-config ./examples/config --single-konfigs ./examples/konfigs context_a,context_b rename : build ## Rename specified context in Kubernetes configuration - $(KONF_PREFIX) konf-sh rename --kube-config ./examples/config context_a NEW_context_a + $(KONF_PREFIX) konf-sh rename --kube-config ./examples/config --single-konfigs ./examples/konfigs context_a context_x 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" diff --git a/README.md b/README.md index 0f06275..8405c09 100644 --- a/README.md +++ b/README.md @@ -177,11 +177,11 @@ konf view ### Split -`konf split` separates the Kubernetes configuration (by default `~/.kube/config`) into single Kubernetes konfigurations files (by default in `~/.kube/konfigs`), one for each context. +`konf split` separates the Kubernetes configuration (by default `~/.kube/config`) into single Kubernetes konfigurations (by default in `~/.kube/konfigs`), one for each context. ### List -`konf list` lists all single Kubernetes konfigurations files separated by `konf split` (by default in `~/.kube/konfigs`). +`konf list` lists all single Kubernetes konfigurations separated by `konf split` (by default in `~/.kube/konfigs`). ### Set @@ -229,19 +229,19 @@ konf set local sets the local (current shell) Kubernetes context (sett ### Flags -| Flag | Command list | Available values | Default | Corresponding env-var | Description | -|:-----------------|:-------------------------------------|:-----------------|:---------------------|:------------------------------|:--------------------------------------------------------| -| --kube-config | split, view, view global, set global | - | $HOME/.kube/config | KONF_KUBE_CONFIG_PATH | Specify a custom Kubernetes configuration file path | -| --single-konfigs | split, list, set local, set global | - | $HOME/.kube/konfigs/ | KONF_SINGLE_KUBE_KONFIGS_PATH | Specify the single Kubernetes konfigurations files path | +| Flag | Command list | Available values | Default | Corresponding env-var | Description | +|:-----------------|:-------------------------------------|:-----------------|:---------------------|:------------------------------|:----------------------------------------------------| +| --kube-config | split, view, view global, set global | - | $HOME/.kube/config | KONF_KUBE_CONFIG_PATH | Specify a custom Kubernetes configuration file path | +| --single-konfigs | split, list, set local, set global | - | $HOME/.kube/konfigs/ | KONF_SINGLE_KUBE_KONFIGS_PATH | Specify the single Kubernetes konfigurations path | ### Environment variables -| Key | Command list | Available values | Default | Corresponding flag | Description | -|:------------------------------|:-------------------------------------|:--------------------------------|:---------------------|:-------------------|:--------------------------------------------------------| -| KONF_LOG_ENCODING | (global) | console, json | console | - | Set logger encoding | -| KONF_LOG_LEVEL | (global) | debug, info, warn, error, fatal | info | - | Set logger level | -| KONF_KUBE_CONFIG_PATH | split, view, view global, set global | - | $HOME/.kube/config | --kube-config | Specify a custom Kubernetes configuration file path | -| KONF_SINGLE_KUBE_KONFIGS_PATH | split, list, set local, set global | - | $HOME/.kube/konfigs/ | --single-konfigs | Specify the single Kubernetes konfigurations files path | +| Key | Command list | Available values | Default | Corresponding flag | Description | +|:------------------------------|:-------------------------------------|:--------------------------------|:---------------------|:-------------------|:----------------------------------------------------| +| KONF_LOG_ENCODING | (global) | console, json | console | - | Set logger encoding | +| KONF_LOG_LEVEL | (global) | debug, info, warn, error, fatal | info | - | Set logger level | +| KONF_KUBE_CONFIG_PATH | split, view, view global, set global | - | $HOME/.kube/config | --kube-config | Specify a custom Kubernetes configuration file path | +| KONF_SINGLE_KUBE_KONFIGS_PATH | split, list, set local, set global | - | $HOME/.kube/konfigs/ | --single-konfigs | Specify the single Kubernetes konfigurations path | ### Error codes @@ -250,17 +250,17 @@ konf set local sets the local (current shell) Kubernetes context (sett | 1 | (all) | Error initializing zap logger | | 2 | (all) | Error starting application | | 3 | (all) | Error creating specific application command | -| 11 | split | Error checking existence of Kubernetes konfigurations files path | +| 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 | | 21 | list | Error listing single Kubernetes konfigurations | -| 31 | set local | Error checking existence of Kubernetes konfigurations files path | +| 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 | | 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 cleaning Kubernetes context list | +| 43 | delete | Error removing Kubernetes context list | | 51 | rename | Error getting Kubernetes context to rename: 'context to rename' and 'new context name' arguments not specified | | 52 | rename | Error getting Kubernetes context to rename: 'context to rename' argument not specified | | 53 | rename | Error getting Kubernetes context to rename: 'new context name' argument not specified | diff --git a/cmd/delete/action.go b/cmd/delete/action.go index e7fbf7c..54c19c3 100644 --- a/cmd/delete/action.go +++ b/cmd/delete/action.go @@ -5,37 +5,56 @@ import ( "errors" "fmt" "os" + "path/filepath" "strings" "github.com/bygui86/konf-sh/pkg/commons" "github.com/bygui86/konf-sh/pkg/kubeconfig" "github.com/bygui86/konf-sh/pkg/logger" + "github.com/bygui86/konf-sh/pkg/utils" "github.com/urfave/cli/v2" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" ) -func clean(ctx *cli.Context) error { +// INFO: deleteCtx cannot be named delete because of name collision +func deleteCtx(ctx *cli.Context) error { logger.Logger.Info("") logger.Logger.Debug("๐Ÿ› Executing DELETE command") logger.Logger.Debug("") + logger.Logger.Debug("๐Ÿ› Get Kubernetes configuration file path") + kCfgFilePath := ctx.String(commons.KubeConfigFlagName) + logger.SugaredLogger.Debugf("๐Ÿ› Kubernetes configuration file path: '%s'", kCfgFilePath) + + logger.Logger.Debug("๐Ÿ› Get single Kubernetes konfigurations path") + singleKonfigsPath := ctx.String(commons.SingleKonfigsFlagName) + logger.SugaredLogger.Debugf("๐Ÿ› Single Kubernetes konfigurations path: '%s'", singleKonfigsPath) + + logger.Logger.Debug("๐Ÿ› Get contexts to delete") contextSlice, ctxErr := getContextList(ctx) if ctxErr != nil { return ctxErr } + logger.SugaredLogger.Infof("๐Ÿ“‹ Contexts to delete: '%s'", strings.Join(contextSlice, ", ")) - logger.Logger.Debug("๐Ÿ› Ask for user confirmation to clean context list") + logger.Logger.Debug("๐Ÿ› Ask for user confirmation to delete contexts") if userDeletionConfirm() { - kubeConfigFilePath, cleanErr := cleanInternal(ctx, contextSlice) - if cleanErr != nil { - return cleanErr + kCfgErr := deleteFromKubeConfig(kCfgFilePath, contextSlice) + if kCfgErr != nil { + return kCfgErr } - logger.SugaredLogger.Infof("โœ… Completed! Context list '%s' removed from Kubernetes configuration '%s'", - strings.Join(contextSlice, ", "), kubeConfigFilePath) + + kfgsErr := deleteFromKubeKonfigs(singleKonfigsPath, contextSlice) + if kfgsErr != nil { + return kfgsErr + } + + logger.SugaredLogger.Infof("โœ… Removing contexts '%s' completed", strings.Join(contextSlice, ", ")) + logger.Logger.Info("") } else { - logger.Logger.Info("โŒ User didn't confirm to proceed, aborting...") + logger.Logger.Info("โŒ User didn't confirm to proceed, aborting...") logger.Logger.Info("") } @@ -43,11 +62,10 @@ func clean(ctx *cli.Context) error { } func getContextList(ctx *cli.Context) ([]string, error) { - logger.Logger.Debug("๐Ÿ› Get Kubernetes context list to clean") args := ctx.Args() if args.Len() == 0 || strings.Compare(args.Get(0), "") == 0 { return nil, cli.Exit( - "โŒ Error getting Kubernetes context list: 'context list' argument not specified", + "โŒ Error getting Kubernetes contexts: argument not specified", 41) } @@ -55,11 +73,10 @@ func getContextList(ctx *cli.Context) ([]string, error) { contextSlice, ctxValErr := validateContextListArgument(contextList) if ctxValErr != nil { return nil, cli.Exit( - "โŒ Error validating Kubernetes context list: 'context list' argument not valid. Context list must be a comma-separated list.", + "โŒ Error validating Kubernetes contexts: argument not valid, it must be a comma-separated list.", 42) } - logger.SugaredLogger.Infof("๐Ÿ“‹ Context list to clean: '%s'", strings.Join(contextSlice, ", ")) return contextSlice, nil } @@ -68,101 +85,159 @@ func validateContextListArgument(contextList string) ([]string, error) { if len(contextSlice) > 0 { return contextSlice, nil } - return nil, errors.New("error validating context list") + return nil, errors.New("error validating contexts") } func userDeletionConfirm() bool { reader := bufio.NewReader(os.Stdin) - logger.Logger.Warn("โš ๏ธ Deleting a context from Kubernetes configuration can't be undone") - logger.Logger.Info("โ“ are you sure you want to proceed? [y | n]") + logger.Logger.Warn("โš ๏ธ Deleting a context from Kubernetes configuration can't be undone") + logger.Logger.Info("โ“ are you sure you want to proceed? [y | n]") for { confirm, _ := reader.ReadString('\n') confirm = strings.Replace(confirm, "\n", "", -1) // convert CRLF to LF logger.SugaredLogger.Debugf("๐Ÿ› User confirmation answer: %s", confirm) if strings.Compare(confirm, "y") == 0 { + logger.Logger.Info("") return true } else if strings.Compare(confirm, "n") == 0 { break } else { - logger.Logger.Error("โŒ Wrong input, please answer with 'y' or 'n'") + logger.Logger.Error("โŒ Wrong input, please answer with 'y' or 'n'") } } return false } -func cleanInternal(ctx *cli.Context, contextSlice []string) (string, error) { - logger.Logger.Debug("๐Ÿ› Get Kubernetes configuration file path") - kubeConfigFilePath := ctx.String(commons.KubeConfigFlagName) - logger.SugaredLogger.Infof("๐Ÿ“– Load Kubernetes configuration from '%s'", kubeConfigFilePath) - kubeConfig := kubeconfig.Load(kubeConfigFilePath) +func deleteFromKubeConfig(kCfgFilePath string, contextSlice []string) error { + logger.SugaredLogger.Infof("๐Ÿ“– Load Kubernetes configuration from '%s'", kCfgFilePath) + kubeConfig := 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 - logger.SugaredLogger.Debugf("๐Ÿ› Validate Kubernetes configuration from '%s'", kubeConfigFilePath) + logger.SugaredLogger.Debugf("๐Ÿ› Validate Kubernetes configuration from '%s'", kCfgFilePath) valErr := kubeconfig.Validate(kubeConfig) if valErr != nil { - return "", cli.Exit( - fmt.Sprintf("โŒ Error validating Kubernetes configuration from '%s': %s", kubeConfigFilePath, valErr.Error()), + return cli.Exit( + fmt.Sprintf("โŒ Error validating Kubernetes configuration from '%s': %s", kCfgFilePath, valErr.Error()), 12) } - logger.Logger.Info("๐Ÿงน Removing selected contexts from Kubernetes configuration") - cleanErr := cleanContextList(kubeConfig, contextSlice) + logger.Logger.Info("๐Ÿงน Removing selected context from Kubernetes configuration") + cleanErr := deleteContextList(kubeConfig, contextSlice) if cleanErr != nil { - return "", cli.Exit( - fmt.Sprintf("โŒ Error cleaning Kubernetes context list: %s", cleanErr.Error()), + return cli.Exit( + fmt.Sprintf("โŒ Error cleaning Kubernetes contexts: %s", cleanErr.Error()), 43) } newValErr := kubeconfig.Validate(kubeConfig) if newValErr != nil { - return "", cli.Exit( - fmt.Sprintf("โŒ Error validating Kubernetes configuration from '%s': %s", kubeConfigFilePath, newValErr.Error()), + return cli.Exit( + fmt.Sprintf("โŒ Error validating Kubernetes configuration from '%s': %s", kCfgFilePath, newValErr.Error()), 12) } - newWriteErr := kubeconfig.Write(kubeConfig, kubeConfigFilePath) + newWriteErr := kubeconfig.Write(kubeConfig, kCfgFilePath) if newWriteErr != nil { - return "", cli.Exit( - fmt.Sprintf("โŒ Error writing Kubernetes configuration '%s' to file: %s", kubeConfigFilePath, newWriteErr.Error()), + return cli.Exit( + fmt.Sprintf("โŒ Error writing Kubernetes configuration '%s' to file: %s", kCfgFilePath, newWriteErr.Error()), 13) } - return kubeConfigFilePath, nil + logger.SugaredLogger.Infof("โœ… Contexts '%s' removed from Kubernetes configuration '%s'", + strings.Join(contextSlice, ", "), kCfgFilePath) + return nil } -func cleanContextList(kubeConfig *clientcmdapi.Config, contextSlice []string) error { +func deleteContextList(kCfg *clientcmdapi.Config, contextSlice []string) error { for _, rmCtx := range contextSlice { - err := kubeconfig.CheckIfContextExist(kubeConfig, rmCtx) - if err != nil { - logger.SugaredLogger.Infof("โ“ Context '%s' to clean not found, skipping...", rmCtx) + checkErr := kubeconfig.CheckIfContextExist(kCfg, rmCtx) + if checkErr != nil { + logger.SugaredLogger.Debugf("๐Ÿ› Context '%s' to delete not found, skipping...", rmCtx) continue } logger.SugaredLogger.Debugf("๐Ÿ› Removing context '%s' from Kubernetes configuration", rmCtx) - tempCtx := kubeConfig.Contexts[rmCtx] - ctxMap, ctxErr := kubeconfig.RemoveContext(kubeConfig.Contexts, rmCtx) + tempCtx := kCfg.Contexts[rmCtx] + ctxMap, ctxErr := kubeconfig.RemoveContext(kCfg.Contexts, rmCtx) if ctxErr != nil { return ctxErr } - kubeConfig.Contexts = ctxMap + kCfg.Contexts = ctxMap rmCluster := tempCtx.Cluster logger.SugaredLogger.Debugf("๐Ÿ› Removing cluster '%s' from Kubernetes configuration", rmCluster) - clMap, clErr := kubeconfig.RemoveCluster(kubeConfig.Clusters, rmCluster) + clMap, clErr := kubeconfig.RemoveCluster(kCfg.Clusters, rmCluster) if clErr != nil { return clErr } - kubeConfig.Clusters = clMap + kCfg.Clusters = clMap rmAuthInfo := tempCtx.AuthInfo // user logger.SugaredLogger.Debugf("๐Ÿ› Removing user '%s' from Kubernetes configuration", rmAuthInfo) - authMap, authErr := kubeconfig.RemoveAuthInfo(kubeConfig.AuthInfos, rmAuthInfo) + authMap, authErr := kubeconfig.RemoveAuthInfo(kCfg.AuthInfos, rmAuthInfo) if authErr != nil { return authErr } - kubeConfig.AuthInfos = authMap + kCfg.AuthInfos = authMap + + if rmCtx == kCfg.CurrentContext { + kCfg.CurrentContext = "" + } + } + + return nil +} + +func deleteFromKubeKonfigs(singleKonfigsPath string, contextSlice []string) error { + checkErr := utils.CheckIfFolderExist(singleKonfigsPath, false) + if checkErr != nil { + logger.SugaredLogger.Warnf("โš ๏ธ Single Kubernetes konfigurations path not found ('%s')", singleKonfigsPath) + logger.Logger.Warn("โ„น๏ธ Tip: run 'konf split' before anything else") + } else { + cfgToDel := make([]string, 0) + walkErr := filepath.Walk( + singleKonfigsPath, + func(path string, info os.FileInfo, err error) error { + if info.IsDir() { + return nil + } + + if strings.HasPrefix(info.Name(), ".") { + return nil + } + + kCfg := kubeconfig.Load(path) + // INFO: no need to check if kubeConfig is nil, because the inner method called will exit if it does not find the configuration file + for name := range kCfg.Contexts { + for _, ctxDel := range contextSlice { + if name == ctxDel { + cfgToDel = append(cfgToDel, path) + } + } + } + + return nil + }, + ) + if walkErr != nil { + return cli.Exit( + fmt.Sprintf("โŒ Error removing single Kubernetes konfigurations from '%s': %s", + singleKonfigsPath, walkErr.Error()), 43) + } + + logger.Logger.Info("๐Ÿงน Removing selected contexts from single Kubernetes konfigurations") + for _, delPath := range cfgToDel { + delErr := os.Remove(delPath) + if delErr != nil { + return cli.Exit( + fmt.Sprintf("โŒ Error removing '%s' single Kubernetes konfigurations from '%s': %s", + delPath, singleKonfigsPath, delErr.Error()), 43) + } + } + + logger.SugaredLogger.Infof("โœ… Contexts '%s' removed from single Kubernetes konfigurations '%s'", + strings.Join(contextSlice, ", "), singleKonfigsPath) } - kubeConfig.CurrentContext = "" return nil } diff --git a/cmd/delete/command.go b/cmd/delete/command.go index 122a57d..aebffd6 100644 --- a/cmd/delete/command.go +++ b/cmd/delete/command.go @@ -13,9 +13,9 @@ func BuildCommand() *cli.Command { home := utils.GetHomeDirOrExit("delete") return &cli.Command{ Name: "delete", - Usage: "Remove specified context (and relative user and cluster) from Kubernetes configuration", + Usage: "Remove all specified contexts from Kubernetes configuration and single Kubernetes konfigurations", ArgsUsage: "", - Action: clean, + Action: deleteCtx, Flags: []cli.Flag{ &cli.StringFlag{ Name: utils.GetUrfaveFlagName(commons.KubeConfigFlagName, commons.KubeConfigFlagShort), @@ -24,6 +24,13 @@ func BuildCommand() *cli.Command { Value: kubeconfig.GetCustomKubeConfigPathDefault(home), Required: false, }, + &cli.StringFlag{ + Name: utils.GetUrfaveFlagName(commons.SingleKonfigsFlagName, commons.SingleKonfigsFlagShort), + Usage: commons.SingleKonfigsFlagDescription, + EnvVars: []string{commons.SingleKonfigsPathEnvVar}, + Value: kubeconfig.GetSingleConfigsPathDefault(home), + Required: false, + }, }, } } diff --git a/cmd/list/action.go b/cmd/list/action.go index e6c9f9c..8dbc413 100644 --- a/cmd/list/action.go +++ b/cmd/list/action.go @@ -18,19 +18,19 @@ func list(ctx *cli.Context) error { logger.Logger.Debug("๐Ÿ› Executing LIST command") logger.Logger.Debug("") - logger.Logger.Debug("๐Ÿ› Get single Kubernetes konfigurations files path") - singleConfigsPath := ctx.String(commons.SingleKonfigsFlagName) - logger.SugaredLogger.Debugf("๐Ÿ› Single Kubernetes konfigurations files path: '%s'", singleConfigsPath) + logger.Logger.Debug("๐Ÿ› Get single Kubernetes konfigurations path") + singleKonfigsPath := ctx.String(commons.SingleKonfigsFlagName) + logger.SugaredLogger.Debugf("๐Ÿ› Single Kubernetes konfigurations path: '%s'", singleKonfigsPath) - checkErr := utils.CheckIfFolderExist(singleConfigsPath, false) + checkErr := utils.CheckIfFolderExist(singleKonfigsPath, false) if checkErr != nil { - logger.SugaredLogger.Warn("โš ๏ธ Single Kubernetes konfigurations files path not found") - logger.SugaredLogger.Warn("โ„น๏ธ Tip: run 'konf split' before 'konf list'") + logger.SugaredLogger.Warnf("โš ๏ธ Single Kubernetes konfigurations path not found ('%s')", singleKonfigsPath) + logger.Logger.Warn("โ„น๏ธ Tip: run 'konf split' before anything else") } else { validCfg := make([]string, 0) invalidCfg := make([]string, 0) err := filepath.Walk( - singleConfigsPath, + singleKonfigsPath, func(path string, info os.FileInfo, err error) error { if info.IsDir() { return nil @@ -54,16 +54,16 @@ func list(ctx *cli.Context) error { ) if err != nil { return cli.Exit( - fmt.Sprintf("โŒ Error listing single Kubernetes konfigurations in '%s': %s", singleConfigsPath, err.Error()), + fmt.Sprintf("โŒ Error listing single Kubernetes konfigurations in '%s': %s", singleKonfigsPath, err.Error()), 21) } - logger.SugaredLogger.Infof("๐Ÿ“š Available Kubernetes konfigurations in '%s':", singleConfigsPath) + logger.SugaredLogger.Infof("๐Ÿ“š Available Kubernetes konfigurations in '%s':", singleKonfigsPath) for _, v := range validCfg { logger.SugaredLogger.Infof("\t%s", v) } logger.Logger.Info("") - logger.SugaredLogger.Infof("โ“๏ธ Invalid Kubernetes konfigurations in '%s':", singleConfigsPath) + logger.SugaredLogger.Infof("โ“๏ธ Invalid Kubernetes konfigurations in '%s':", singleKonfigsPath) for _, iv := range invalidCfg { logger.SugaredLogger.Infof("\t%s", iv) } diff --git a/cmd/rename/action.go b/cmd/rename/action.go index 4c5c662..74ab32e 100644 --- a/cmd/rename/action.go +++ b/cmd/rename/action.go @@ -1,109 +1,194 @@ package rename import ( + "errors" "fmt" + "os" + "path/filepath" "strings" "github.com/bygui86/konf-sh/pkg/commons" "github.com/bygui86/konf-sh/pkg/kubeconfig" "github.com/bygui86/konf-sh/pkg/logger" + "github.com/bygui86/konf-sh/pkg/utils" "github.com/urfave/cli/v2" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" ) +const ( + stopWalkingErrMsg = "STOP WALKING" +) + func rename(ctx *cli.Context) error { logger.Logger.Info("") logger.Logger.Debug("๐Ÿ› Executing RENAME command") logger.Logger.Debug("") logger.Logger.Debug("๐Ÿ› Get Kubernetes configuration file path") - kubeConfigFilePath := ctx.String(commons.KubeConfigFlagName) - logger.SugaredLogger.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 + kCfgFilePath := ctx.String(commons.KubeConfigFlagName) + logger.SugaredLogger.Debugf("๐Ÿ› Kubernetes configuration file path: '%s'", kCfgFilePath) - contextInfo, ctxErr := getContextInfo(ctx) + logger.Logger.Debug("๐Ÿ› Get single Kubernetes konfigurations path") + singleKonfigsPath := ctx.String(commons.SingleKonfigsFlagName) + logger.SugaredLogger.Debugf("๐Ÿ› Single Kubernetes konfigurations path: '%s'", singleKonfigsPath) + + logger.Logger.Debug("๐Ÿ› Get context to rename") + contextToRename, newContextName, ctxErr := getContextInfo(ctx) if ctxErr != nil { return ctxErr } - contextToRename := contextInfo.Get(0) - newContextName := contextInfo.Get(1) - logger.SugaredLogger.Debugf("๐Ÿ› Check existence of context '%s' in Kubernetes configuration '%s'", contextToRename, kubeConfigFilePath) - checkCtxErr := kubeconfig.CheckIfContextExist(kubeConfig, contextToRename) - if checkCtxErr != nil { - return cli.Exit( - fmt.Sprintf("โŒ Error checking existence of context '%s' in Kubernetes configuration '%s': %s", contextToRename, kubeConfigFilePath, checkCtxErr.Error()), - 34) + kCfgErr := renameInKubeConfig(kCfgFilePath, contextToRename, newContextName) + if kCfgErr != nil { + return kCfgErr } - renameErr := renameContext(kubeConfig, kubeConfigFilePath, contextToRename, newContextName) - if renameErr != nil { - return renameErr + kfgsErr := renameInKubeKonfigs(singleKonfigsPath, contextToRename, newContextName) + if kfgsErr != nil { + return kfgsErr } - newValErr := kubeconfig.Validate(kubeConfig) - if newValErr != nil { - return cli.Exit( - 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) - } - - logger.SugaredLogger.Infof("โœ… Completed! Context successfully renamed from '%s' to '%s' in Kubernetes configuration '%s'", - contextToRename, newContextName, kubeConfigFilePath) + logger.SugaredLogger.Infof("โœ… Context '%s' renamed to '%s'", contextToRename, newContextName) logger.Logger.Info("") return nil } -func getContextInfo(ctx *cli.Context) (cli.Args, error) { +func getContextInfo(ctx *cli.Context) (string, string, error) { logger.Logger.Debug("๐Ÿ› Get Kubernetes context to rename") args := ctx.Args() if args.Len() == 0 { - return nil, cli.Exit( - "โŒ Error getting Kubernetes context to rename: 'context to rename' and 'new context name' arguments not specified", + return "", "", cli.Exit( + "โŒ Error getting Kubernetes context to rename: 'context to rename' and 'new context name' arguments not specified", 51) } if strings.Compare(args.Get(0), "") == 0 { - return nil, cli.Exit( - "โŒ Error getting Kubernetes context to rename: 'context to rename' argument not specified", + return "", "", cli.Exit( + "โŒ Error getting Kubernetes context to rename: 'context to rename' argument not specified", 52) } if strings.Compare(args.Get(1), "") == 0 { - return nil, cli.Exit( - "โŒ Error getting Kubernetes context to rename: 'new context name' argument not specified", + return "", "", cli.Exit( + "โŒ Error getting Kubernetes context to rename: 'new context name' argument not specified", 53) } - return args, nil + return args.Get(0), args.Get(1), nil +} + +func renameInKubeConfig(kCfgFilePath string, contextToRename, newContextName string) error { + logger.SugaredLogger.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 + + checkCtxErr := kubeconfig.CheckIfContextExist(kCfg, contextToRename) + if checkCtxErr != nil { + return cli.Exit( + fmt.Sprintf("โŒ Error checking existence of context '%s' in Kubernetes configuration '%s': %s", + contextToRename, kCfgFilePath, checkCtxErr.Error()), 34) + } + + renameErr := renameContext(kCfg, kCfgFilePath, contextToRename, newContextName) + if renameErr != nil { + return renameErr + } + + newValErr := kubeconfig.Validate(kCfg) + if newValErr != nil { + return cli.Exit( + fmt.Sprintf("โŒ Error validating Kubernetes configuration from '%s': %s", kCfgFilePath, newValErr.Error()), + 12) + } + + newWriteErr := kubeconfig.Write(kCfg, kCfgFilePath) + if newWriteErr != nil { + return cli.Exit( + fmt.Sprintf("โŒ Error writing Kubernetes configuration '%s' to file: %s", kCfgFilePath, newWriteErr.Error()), + 13) + } + + logger.SugaredLogger.Infof("โœ… Context renamed from '%s' to '%s' in Kubernetes configuration '%s'", + contextToRename, newContextName, kCfgFilePath) + return nil } -func renameContext(kubeConfig *clientcmdapi.Config, kubeConfigFilePath, contextToRename, newContextName string) error { +func renameContext(kCfg *clientcmdapi.Config, kCfgFilePath, contextToRename, newContextName string) error { logger.SugaredLogger.Infof("๐Ÿ”€ Renaming Kubernetes context '%s' as '%s'", contextToRename, newContextName) - if strings.Compare(kubeConfig.CurrentContext, contextToRename) == 0 { - logger.SugaredLogger.Debugf("๐Ÿ› Set new current context '%s' in Kubernetes configuration '%s'", newContextName, kubeConfigFilePath) - kubeConfig.CurrentContext = newContextName + if strings.Compare(kCfg.CurrentContext, contextToRename) == 0 { + logger.SugaredLogger.Debugf("๐Ÿ› Set new current context '%s' in Kubernetes configuration '%s'", newContextName, kCfgFilePath) + kCfg.CurrentContext = newContextName } - logger.SugaredLogger.Debugf("๐Ÿ› Remove context '%s' from Kubernetes configuration '%s'", contextToRename, kubeConfigFilePath) - ctx := kubeConfig.Contexts[contextToRename] - newContexts, remCtxErr := kubeconfig.RemoveContext(kubeConfig.Contexts, contextToRename) + logger.SugaredLogger.Debugf("๐Ÿ› Remove context '%s' from Kubernetes configuration '%s'", contextToRename, kCfgFilePath) + ctx := kCfg.Contexts[contextToRename] + newContexts, remCtxErr := kubeconfig.RemoveContext(kCfg.Contexts, contextToRename) if remCtxErr != nil { return cli.Exit( - fmt.Sprintf("โŒ Error removing context '%s' from Kubernetes configuration '%s': %s", contextToRename, kubeConfigFilePath, remCtxErr.Error()), + fmt.Sprintf("โŒ Error removing context '%s' from Kubernetes configuration '%s': %s", contextToRename, kCfgFilePath, remCtxErr.Error()), 54) } - logger.SugaredLogger.Debugf("๐Ÿ› Insert context '%s' in Kubernetes configuration '%s'", newContextName, kubeConfigFilePath) + logger.SugaredLogger.Debugf("๐Ÿ› Insert context '%s' in Kubernetes configuration '%s'", newContextName, kCfgFilePath) newContexts[newContextName] = ctx - kubeConfig.Contexts = newContexts + kCfg.Contexts = newContexts + + return nil +} + +func renameInKubeKonfigs(singleKonfigsPath, contextToRename, newContextName string) error { + checkErr := utils.CheckIfFolderExist(singleKonfigsPath, false) + if checkErr != nil { + logger.SugaredLogger.Warnf("โš ๏ธ Single Kubernetes konfigurations path not found ('%s')", singleKonfigsPath) + logger.Logger.Warn("โ„น๏ธ Tip: run 'konf split' before anything else") + } else { + var cfgToRename string + walkErr := filepath.Walk( + singleKonfigsPath, + func(path string, info os.FileInfo, err error) error { + if info.IsDir() { + return nil + } + + if strings.HasPrefix(info.Name(), ".") { + return nil + } + + kCfg := kubeconfig.Load(path) + // INFO: no need to check if kubeConfig is nil, because the inner method called will exit if it does not find the configuration file + for name := range kCfg.Contexts { + if name == contextToRename { + cfgToRename = path + return errors.New(stopWalkingErrMsg) + } + } + + return nil + }, + ) + if walkErr != nil { + if walkErr.Error() != stopWalkingErrMsg { + return cli.Exit( + fmt.Sprintf("โŒ Error removing single Kubernetes konfigurations from '%s': %s", + singleKonfigsPath, walkErr.Error()), 43) + } + } + + logger.SugaredLogger.Infof("๐Ÿ”€ Renaming context '%s' to '%s' in single Kubernetes konfigurations", contextToRename, newContextName) + renErr := os.Rename( + cfgToRename, + strings.Replace(cfgToRename, contextToRename, newContextName, 1), + ) + if renErr != nil { + return cli.Exit( + fmt.Sprintf("โŒ Error renaming '%s' single Kubernetes konfigurations from '%s': %s", + cfgToRename, singleKonfigsPath, renErr.Error()), 43) + } + + // TODO rename also content of file! + + logger.SugaredLogger.Infof("โœ… Context '%s' renamed to '%s' in single Kubernetes konfigurations '%s'", + contextToRename, newContextName, singleKonfigsPath) + } return nil } diff --git a/cmd/rename/command.go b/cmd/rename/command.go index 20bb0a1..8231a2c 100644 --- a/cmd/rename/command.go +++ b/cmd/rename/command.go @@ -13,7 +13,7 @@ func BuildCommand() *cli.Command { home := utils.GetHomeDirOrExit("rename") return &cli.Command{ Name: "rename", - Usage: "Rename specified context with a new name", + Usage: "Rename specified context in Kubernetes configuration and single Kubernetes konfiguration", ArgsUsage: " ", Action: rename, Flags: []cli.Flag{ @@ -24,6 +24,13 @@ func BuildCommand() *cli.Command { Value: kubeconfig.GetCustomKubeConfigPathDefault(home), Required: false, }, + &cli.StringFlag{ + Name: utils.GetUrfaveFlagName(commons.SingleKonfigsFlagName, commons.SingleKonfigsFlagShort), + Usage: commons.SingleKonfigsFlagDescription, + EnvVars: []string{commons.SingleKonfigsPathEnvVar}, + Value: kubeconfig.GetSingleConfigsPathDefault(home), + Required: false, + }, }, } } diff --git a/cmd/reset/action.go b/cmd/reset/action.go index 0045f16..32fb490 100644 --- a/cmd/reset/action.go +++ b/cmd/reset/action.go @@ -35,14 +35,14 @@ 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()), + 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()), + fmt.Sprintf("โŒ Error writing Kubernetes configuration '%s' to file: %s", kubeConfigFilePath, newWriteErr.Error()), 13) } diff --git a/cmd/set/action.go b/cmd/set/action.go index cb07522..c71c254 100644 --- a/cmd/set/action.go +++ b/cmd/set/action.go @@ -17,23 +17,23 @@ func setLocal(ctx *cli.Context) error { logger.Logger.Debug("๐Ÿ› Executing SET-LOCAL command") logger.Logger.Debug("") - logger.Logger.Debug("๐Ÿ› Get single Kubernetes konfigurations files path") + logger.Logger.Debug("๐Ÿ› Get single Kubernetes konfigurations path") singleConfigsPath := ctx.String(commons.SingleKonfigsFlagName) - logger.SugaredLogger.Debugf("๐Ÿ› Check existence of single Kubernetes konfigurations files path '%s'", singleConfigsPath) + logger.SugaredLogger.Debugf("๐Ÿ› Check existence of single Kubernetes konfigurations path '%s'", singleConfigsPath) checkFolderErr := utils.CheckIfFolderExist(singleConfigsPath, true) if checkFolderErr != nil { return cli.Exit( - fmt.Sprintf("โŒ Error checking existence of Kubernetes konfigurations files path '%s': %s", singleConfigsPath, checkFolderErr.Error()), + fmt.Sprintf("โŒ Error checking existence of Kubernetes konfigurations path '%s': %s", singleConfigsPath, checkFolderErr.Error()), 31) } - logger.SugaredLogger.Debugf("๐Ÿ“š Single Kubernetes konfigurations files path: '%s'", singleConfigsPath) + logger.SugaredLogger.Debugf("๐Ÿ“š Single Kubernetes konfigurations path: '%s'", singleConfigsPath) logger.Logger.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", + "โŒ Error getting Kubernetes context: context argument not specified", 32) } context := args.Get(0) @@ -43,7 +43,7 @@ func setLocal(ctx *cli.Context) error { checkFileErr := utils.CheckIfFileExist(localKubeConfig) if checkFileErr != nil { return cli.Exit( - fmt.Sprintf("โŒ Error checking existence of Kubernetes context '%s' configuration file: %s", localKubeConfig, checkFileErr.Error()), + fmt.Sprintf("โŒ Error checking existence of Kubernetes context '%s' configuration file: %s", localKubeConfig, checkFileErr.Error()), 33) } logger.SugaredLogger.Debugf("๐Ÿงฉ Selected Kubernetes context: '%s'", context) @@ -67,7 +67,7 @@ func setGlobal(ctx *cli.Context) error { args := ctx.Args() if args.Len() == 0 || strings.Compare(args.Get(0), "") == 0 { return cli.Exit( - "โŒ Error getting Kubernetes context: context argument not specified", + "โŒ Error getting Kubernetes context: context argument not specified", 32) } context := args.Get(0) @@ -76,7 +76,7 @@ func setGlobal(ctx *cli.Context) error { checkCtxErr := kubeconfig.CheckIfContextExist(kubeConfig, context) if checkCtxErr != nil { return cli.Exit( - fmt.Sprintf("โŒ Error checking existence of context '%s' in Kubernetes configuration '%s': %s", context, kubeConfigFilePath, checkCtxErr.Error()), + fmt.Sprintf("โŒ Error checking existence of context '%s' in Kubernetes configuration '%s': %s", context, kubeConfigFilePath, checkCtxErr.Error()), 34) } logger.SugaredLogger.Infof("๐Ÿงฉ Selected Kubernetes context: '%s'", context) @@ -87,14 +87,14 @@ func setGlobal(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()), + 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()), + fmt.Sprintf("โŒ Error writing Kubernetes configuration '%s' to file: %s", kubeConfigFilePath, newWriteErr.Error()), 13) } diff --git a/cmd/split/action.go b/cmd/split/action.go index 71b9f3c..dee9d2a 100644 --- a/cmd/split/action.go +++ b/cmd/split/action.go @@ -27,23 +27,23 @@ func split(ctx *cli.Context) error { valErr := kubeconfig.Validate(kubeConfig) if valErr != nil { return cli.Exit( - fmt.Sprintf("โŒ Error validating Kubernetes configuration from '%s': %s", kubeConfigFilePath, valErr.Error()), + fmt.Sprintf("โŒ Error validating Kubernetes configuration from '%s': %s", kubeConfigFilePath, valErr.Error()), 12) } logger.SugaredLogger.Infof("โœ‚๏ธ Split Kubernetes configuration from %s", kubeConfigFilePath) singleConfigs := kubeconfig.Split(kubeConfig, kubeConfigFilePath) - logger.Logger.Info("๐Ÿ’พ Save single Kubernetes konfigurations files") - logger.Logger.Debug("๐Ÿ› Get single Kubernetes konfigurations files path") + logger.Logger.Info("๐Ÿ’พ Save single Kubernetes konfigurations") + logger.Logger.Debug("๐Ÿ› Get single Kubernetes konfigurations path") singleConfigsPath := ctx.String(commons.SingleKonfigsFlagName) - logger.SugaredLogger.Debugf("๐Ÿ› Single Kubernetes konfigurations files path: '%s'", singleConfigsPath) + logger.SugaredLogger.Debugf("๐Ÿ› Single Kubernetes konfigurations path: '%s'", singleConfigsPath) - logger.SugaredLogger.Debugf("๐Ÿ› Check existence of single Kubernetes konfigurations files path '%s'", singleConfigsPath) + logger.SugaredLogger.Debugf("๐Ÿ› Check existence of single Kubernetes konfigurations path '%s'", singleConfigsPath) checkErr := utils.CheckIfFolderExist(singleConfigsPath, true) if checkErr != nil { return cli.Exit( - fmt.Sprintf("โŒ Error checking existence of Kubernetes konfigurations files path '%s': %s", checkErr.Error(), singleConfigsPath), + fmt.Sprintf("โŒ Error checking existence of Kubernetes konfigurations path '%s': %s", checkErr.Error(), singleConfigsPath), 11) } @@ -52,7 +52,7 @@ func split(ctx *cli.Context) error { return valWrErr } - logger.SugaredLogger.Infof("โœ… Completed! Single Kubernetes konfigurations files saved in '%s'", singleConfigsPath) + logger.SugaredLogger.Infof("โœ… Completed! Single Kubernetes konfigurations saved in '%s'", singleConfigsPath) logger.Logger.Info("") return nil } @@ -65,14 +65,14 @@ func validateAndWrite(singleConfigs map[string]*api.Config, singleConfigsPath st newValErr := kubeconfig.Validate(cfg) if newValErr != nil { return cli.Exit( - fmt.Sprintf("โŒ Error validating Kubernetes configuration from '%s': %s", cfgFilePath, newValErr.Error()), + fmt.Sprintf("โŒ Error validating Kubernetes configuration from '%s': %s", cfgFilePath, newValErr.Error()), 12) } newWriteErr := kubeconfig.Write(cfg, cfgFilePath) if newWriteErr != nil { return cli.Exit( - fmt.Sprintf("โŒ Error writing Kubernetes configuration '%s' to file: %s", cfgFilePath, newWriteErr.Error()), + fmt.Sprintf("โŒ Error writing Kubernetes configuration '%s' to file: %s", cfgFilePath, newWriteErr.Error()), 13) } } diff --git a/docs/makefile.md b/docs/makefile.md index 654eb4f..2069437 100644 --- a/docs/makefile.md +++ b/docs/makefile.md @@ -29,7 +29,7 @@ make clean-bin make split ``` -### List a set of sample Kubernetes konfigurations files +### List a set of sample Kubernetes konfigurations ```sh make list diff --git a/docs/roadmap.md b/docs/roadmap.md index f589838..bd25e41 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -46,9 +46,9 @@ - [x] fix "list konfigs" - [x] hide .DS_store and other files - [x] show only valid kubeconfig -- [ ] improve "clean" command +- [x] improve "clean/delete" command - [x] rename to "delete" - - [ ] remove ctx from both "~/.kube/config" and "~/.kube/konfigs" + - [x] remove ctx from both "~/.kube/config" and "~/.kube/konfigs" - [ ] improve "rename" command - [ ] rename ctx in both "~/.kube/config" and "~/.kube/konfigs" - [ ] improve split command (see TODO in commands/set/action.go) @@ -59,7 +59,9 @@ - [ ] shellwrapper - [ ] implement command - [ ] fix "konf set local " -- [ ] refactor logger (replace logger.Logger with zap.L()) +- [ ] review logging + - [ ] review all logs + - [ ] refactor logger (replace logger.Logger with zap.L()) ### v0.4.0 โœ… diff --git a/examples/context_invalid b/examples/context_invalid index fe5879a..f0e5ee4 100644 --- a/examples/context_invalid +++ b/examples/context_invalid @@ -12,15 +12,3 @@ contexts: name: context_c kind: Config preferences: {} -users: -- name: clint - user: - auth-provider: - config: - access-token: access-token_sample - cmd-args: config config-helper --format=json - cmd-path: /Users/clint/tools/google-cloud-sdk/bin/gcloud - expiry: "2022-01-01T12:00:00Z" - expiry-key: '{.credential.token_expiry}' - token-key: '{.credential.access_token}' - name: gcp diff --git a/pkg/commons/constants.go b/pkg/commons/constants.go index 1ece622..cd6991c 100644 --- a/pkg/commons/constants.go +++ b/pkg/commons/constants.go @@ -7,7 +7,7 @@ const ( KubeConfigFlagDescription = "Kubernetes configuration custom (`PATH`)" SingleKonfigsFlagName = "single-konfigs" SingleKonfigsFlagShort = "c" - SingleKonfigsFlagDescription = "Single Kubernetes konfigurations files custom (`PATH`)" + SingleKonfigsFlagDescription = "Single Kubernetes konfigurations custom (`PATH`)" // Environment variables KubeConfigPathEnvVar = "KONF_KUBE_CONFIG_PATH"