From 052be17472a8d3f903a161848db7669b4569d8bd Mon Sep 17 00:00:00 2001 From: gjbae1212 Date: Thu, 4 Feb 2021 10:52:04 +0900 Subject: [PATCH] Add delete command. --- cmd/delete.go | 59 ++++++++++++++++++++++++++ cmd/delete_test.go | 1 + cmd/rename.go | 1 - cmd/root.go | 11 ++++- internal/kubeconfig.go | 84 +++++++++++++++++++++++++++++++++++++ internal/kubeconfig_test.go | 43 +++++++++++++++++++ 6 files changed, 197 insertions(+), 2 deletions(-) create mode 100644 cmd/delete.go create mode 100644 cmd/delete_test.go diff --git a/cmd/delete.go b/cmd/delete.go new file mode 100644 index 0000000..4fa8e44 --- /dev/null +++ b/cmd/delete.go @@ -0,0 +1,59 @@ +package cmd + +import ( + "fmt" + + "github.com/fatih/color" + "github.com/gjbae1212/kubectl-cred/internal" + "github.com/spf13/cobra" +) + +var ( + deleteCmd = &cobra.Command{ + Use: "delete", + Short: "Delete context in k8s config, using an interactive CLI.", + Long: "Delete context in k8s config, using an interactive CLI.", + PreRun: deletePreRun(), + Run: deleteRun(), + } +) + +func deletePreRun() commandRun { + return func(cmd *cobra.Command, args []string) {} +} + +func deleteRun() commandRun { + return func(cmd *cobra.Command, args []string) { + contexts, err := kubeConfig.GetContexts() + if err != nil { + internal.PanicWithRed(fmt.Errorf("[err] failed getting current kubernetes context. %w", err)) + } + + deleteContextName, err := askContext(contexts) + if err != nil { + internal.PanicWithRed(fmt.Errorf("[err] failed context selecting")) + } + + if ok := askConfirmDeletingContextName(deleteContextName); !ok { + return + } + + if err := kubeConfig.DeleteContext(deleteContextName); err != nil { + internal.PanicWithRed(fmt.Errorf("[err] failed delete context")) + } + + if err := kubeConfig.Sync(); err != nil { + internal.PanicWithRed(fmt.Errorf("[err] config sync error")) + } + + fmt.Printf("%s %s %s\n", + color.GreenString("[success]"), + color.YellowString("delete context:"), + color.CyanString(deleteContextName), + ) + } +} + +func init() { + rootCmd.AddCommand(deleteCmd) +} diff --git a/cmd/delete_test.go b/cmd/delete_test.go new file mode 100644 index 0000000..1d619dd --- /dev/null +++ b/cmd/delete_test.go @@ -0,0 +1 @@ +package cmd diff --git a/cmd/rename.go b/cmd/rename.go index 931e2ab..e953358 100644 --- a/cmd/rename.go +++ b/cmd/rename.go @@ -4,7 +4,6 @@ import ( "fmt" "github.com/fatih/color" - "github.com/gjbae1212/kubectl-cred/internal" "github.com/spf13/cobra" ) diff --git a/cmd/root.go b/cmd/root.go index 7b66140..fdf330e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -126,7 +126,16 @@ func askWantedContextName() string { func askConfirmChangingContextName(currentContextName, changeContextName string) bool { ok := false prompt := &survey.Confirm{ - Message: fmt.Sprintf("Do you really want to change %s to %s", currentContextName, changeContextName), + Message: fmt.Sprintf("Do you really want to change %s to %s?", currentContextName, changeContextName), + } + survey.AskOne(prompt, &ok) + return ok +} + +func askConfirmDeletingContextName(contextName string) bool { + ok := false + prompt := &survey.Confirm{ + Message: fmt.Sprintf("Do you really want to delete %s?", contextName), } survey.AskOne(prompt, &ok) return ok diff --git a/internal/kubeconfig.go b/internal/kubeconfig.go index b580962..e69d6e3 100644 --- a/internal/kubeconfig.go +++ b/internal/kubeconfig.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "os" "path/filepath" + "reflect" "github.com/mitchellh/go-homedir" "github.com/tomwright/dasel" @@ -185,6 +186,62 @@ func (k *KubeConfig) GetContexts() ([]*KubeContext, error) { return kubectxs, nil } +// DeleteContext deletes context. +func (k *KubeConfig) DeleteContext(ctxName string) error { + contexts, err := k.GetContexts() + if err != nil { + return err + } + + var deleteContext *KubeContext + for _, c := range contexts { + if c.Name == ctxName { + deleteContext = c + break + } + } + if deleteContext == nil { + return nil + } + + unmarshalYaml := k.rootNode.OriginalValue + if yamlMap, ok := unmarshalYaml.(map[interface{}]interface{}); ok { + for name, value := range yamlMap { + switch name.(string) { + case "clusters": + changed, err := k.deleteByName(value.([]interface{}), deleteContext.Cluster) + if err != nil { + return err + } + yamlMap[name] = changed + case "contexts": + changed, err := k.deleteByName(value.([]interface{}), deleteContext.Name) + if err != nil { + return err + } + yamlMap[name] = changed + case "current-context": + valueString, err := InterfaceToString(value) + if err != nil { + return err + } + if valueString == deleteContext.Name { + yamlMap[name] = "" + } + case "users": + changed, err := k.deleteByName(value.([]interface{}), deleteContext.User) + if err != nil { + return err + } + yamlMap[name] = changed + } + } + k.rootNode.Value = reflect.ValueOf(yamlMap) + k.rootNode.OriginalValue = yamlMap + } + return nil +} + // Sync syncs rootNode to file. func (k *KubeConfig) Sync() error { // copy origin file to prev file. @@ -225,6 +282,33 @@ func (k *KubeConfig) RawBytes() ([]byte, error) { return bys, err } +func (k *KubeConfig) deleteByName(arr []interface{}, name string) ([]interface{}, error) { + deleteIndex := -1 +Loop: + for index, data := range arr { + if m, ok := data.(map[interface{}]interface{}); ok { + for k, v := range m { + if k.(string) == "name" { + vStr, err := InterfaceToString(v) + if err != nil { + return nil, err + } + if vStr == name { + deleteIndex = index + break Loop + } + } + } + } + } + + if deleteIndex != -1 { + arr = append(arr[:deleteIndex], arr[deleteIndex+1:]...) + } + + return arr, nil +} + // NewKubeConfig returns struct for kubernetes config file. func NewKubeConfig(path string) (*KubeConfig, error) { if path == "" { diff --git a/internal/kubeconfig_test.go b/internal/kubeconfig_test.go index ec8904e..a0d249a 100644 --- a/internal/kubeconfig_test.go +++ b/internal/kubeconfig_test.go @@ -192,7 +192,50 @@ func TestKubeConfig_GetContexts(t *testing.T) { assert.NotEmpty(ctx.Server) } } + } +} + +func TestKubeConfig_DeleteContext(t *testing.T) { + assert := assert.New(t) + cfgPath, err := KubeConfigPath() + if err != nil { + return + } + + kubeConfig, err := NewKubeConfig(cfgPath) + assert.NoError(err) + + tests := map[string]struct { + input string + isErr bool + }{ + "success": {input: "minikube"}, + } + + for _, t := range tests { + ctxs, err := kubeConfig.GetContexts() + assert.NoError(err) + var deleteContxt *KubeContext + for _, ctx := range ctxs { + if ctx.Name == t.input { + deleteContxt = ctx + break + } + } + if deleteContxt != nil { + err := kubeConfig.DeleteContext(t.input) + assert.Equal(t.isErr, err != nil) + if err == nil { + ctxs, err := kubeConfig.GetContexts() + assert.NoError(err) + for _, ctx := range ctxs { + assert.NotEqual(deleteContxt.Name, ctx.Name) + assert.NotEmpty(deleteContxt.Cluster, ctx.Cluster) + assert.NotEmpty(deleteContxt.User, ctx.User) + } + } + } } }