From 2a4b743b18ef9cdb66d266bebbc7405c22688f82 Mon Sep 17 00:00:00 2001 From: guoxudong Date: Wed, 6 Jan 2021 16:56:11 +0800 Subject: [PATCH] add cmd update (#32) * update add cmd * update add cmd * add utest * fix & add docs --- cmd/add.go | 112 ++++++++++++----------- cmd/add_test.go | 152 ++++++++++++++++++++++++++++++++ cmd/delete_test.go | 9 -- cmd/utils.go | 5 ++ cmd/utils_test.go | 7 ++ docs/en-us/cli/kubecm_add.md | 4 +- docs/en-us/cli/kubecm_ls.md | 4 +- docs/en-us/cli/kubecm_switch.md | 2 + docs/zh-cn/cli/kubecm_add.md | 4 +- docs/zh-cn/cli/kubecm_switch.md | 4 +- 10 files changed, 230 insertions(+), 73 deletions(-) create mode 100644 cmd/add_test.go diff --git a/cmd/add.go b/cmd/add.go index e933a3b8..2ffef4c0 100644 --- a/cmd/add.go +++ b/cmd/add.go @@ -4,7 +4,6 @@ import ( "errors" "fmt" "strconv" - "strings" "github.com/spf13/cobra" "k8s.io/client-go/tools/clientcmd" @@ -16,12 +15,16 @@ type AddCommand struct { BaseCommand } +type KubeConfig struct { + config *clientcmdapi.Config +} + // Init AddCommand func (ac *AddCommand) Init() { ac.command = &cobra.Command{ Use: "add", - Short: "Add kubeconfig to $HOME/.kube/config", - Long: "Add kubeconfig to $HOME/.kube/config", + Short: "Add KubeConfig to $HOME/.kube/config", + Long: "Add KubeConfig to $HOME/.kube/config", RunE: func(cmd *cobra.Command, args []string) error { return ac.runAdd(cmd, args) }, @@ -33,11 +36,12 @@ func (ac *AddCommand) Init() { func (ac *AddCommand) runAdd(cmd *cobra.Command, args []string) error { file, _ := ac.command.Flags().GetString("file") + // check path file, err := CheckAndTransformFilePath(file) if err != nil { return err } - newConfig, newName, err := formatNewConfig(file) + newConfig, err := clientcmd.LoadFromFile(file) if err != nil { return err } @@ -45,13 +49,20 @@ func (ac *AddCommand) runAdd(cmd *cobra.Command, args []string) error { if err != nil { return err } - outConfig := appendConfig(oldConfig, newConfig) + kubeConfig := &KubeConfig{ + config: newConfig, + } + // merge context loop + outConfig, err := kubeConfig.handleContexts(oldConfig) + if err != nil { + return err + } if len(outConfig.Contexts) == 1 { for k := range outConfig.Contexts { outConfig.CurrentContext = k } } - cover := BoolUI(fmt.Sprintf("Are you sure you want to add 「%s」 to the 「%s」context?", newName, cfgFile)) + cover := BoolUI(fmt.Sprintf("Does it overwrite File 「%s」?", cfgFile)) confirm, err := strconv.ParseBool(cover) if err != nil { return err @@ -63,61 +74,48 @@ func (ac *AddCommand) runAdd(cmd *cobra.Command, args []string) error { return nil } -func formatNewConfig(file string) (*clientcmdapi.Config, string, error) { - config, err := clientcmd.LoadFromFile(file) - if err != nil { - return nil, "", err - } - if len(config.AuthInfos) != 1 { - return nil, "", errors.New("Only support add 1 context. You can use `merge` cmd") - } - name, err := formatAndCheckName(file) - config = CheckValidContext(config) - if err != nil { - return nil, "", err - } - suffix := HashSuf(config) - userName := fmt.Sprintf("user-%v", suffix) - clusterName := fmt.Sprintf("cluster-%v", suffix) - for key, obj := range config.AuthInfos { - config.AuthInfos[userName] = obj - delete(config.AuthInfos, key) - break - } - for key, obj := range config.Clusters { - config.Clusters[clusterName] = obj - delete(config.Clusters, key) - break - } - for key, obj := range config.Contexts { - obj.AuthInfo = userName - obj.Cluster = clusterName - config.Contexts[name] = obj - delete(config.Contexts, key) - break +func (kc *KubeConfig) handleContexts(oldConfig *clientcmdapi.Config) (*clientcmdapi.Config, error) { + newConfig := clientcmdapi.NewConfig() + for name, ctx := range kc.config.Contexts { + newName := name + if checkContextName(name, oldConfig) { + nameConfirm := BoolUI(fmt.Sprintf("「%s」 Name already exists, do you want to rename it. (If you select `False`, this context will not be merged)", name)) + if nameConfirm == "True" { + newName = PromptUI("Rename", name) + if newName == name { + return nil, errors.New("need to rename") + } + } else { + continue + } + } + itemConfig := kc.handleContext(newName, ctx) + newConfig = appendConfig(newConfig, itemConfig) + fmt.Printf("Add Context: %s \n", newName) } - fmt.Printf("Context Add: %s \n", name) - return config, name, nil + outConfig := appendConfig(oldConfig, newConfig) + return outConfig, nil } -func formatAndCheckName(file string) (string, error) { - n := strings.Split(file, "/") - result := strings.Split(n[len(n)-1], ".") - name := result[0] - nameConfirm := BoolUI(fmt.Sprintf("Need to rename 「%s」 context?", name)) - if nameConfirm == "True" { - name = PromptUI("Rename", name) - } - config, err := clientcmd.LoadFromFile(cfgFile) - if err != nil { - return "", err +func checkContextName(name string, oldConfig *clientcmdapi.Config) bool { + if _, ok := oldConfig.Contexts[name]; ok { + return true } - for key := range config.Contexts { - if key == name { - return key, errors.New("The name: 「" + name + "」 already exists, please select another one.") - } - } - return name, nil + return false +} + +func (kc *KubeConfig) handleContext(key string, ctx *clientcmdapi.Context) *clientcmdapi.Config { + newConfig := clientcmdapi.NewConfig() + suffix := HashSufString(key) + userName := fmt.Sprintf("user-%v", suffix) + clusterName := fmt.Sprintf("cluster-%v", suffix) + newCtx := ctx.DeepCopy() + newConfig.AuthInfos[userName] = kc.config.AuthInfos[newCtx.AuthInfo] + newConfig.Clusters[clusterName] = kc.config.Clusters[newCtx.Cluster] + newConfig.Contexts[key] = newCtx + newConfig.Contexts[key].AuthInfo = userName + newConfig.Contexts[key].Cluster = clusterName + return newConfig } func addExample() string { diff --git a/cmd/add_test.go b/cmd/add_test.go new file mode 100644 index 00000000..78059401 --- /dev/null +++ b/cmd/add_test.go @@ -0,0 +1,152 @@ +package cmd + +import ( + "testing" + + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" +) + +var ( + addTestConfig = clientcmdapi.Config{ + AuthInfos: map[string]*clientcmdapi.AuthInfo{ + "black-user": {Token: "black-token"}, + "red-user": {Token: "red-token"}}, + Clusters: map[string]*clientcmdapi.Cluster{ + "pig-cluster": {Server: "http://pig.org:8080"}, + "cow-cluster": {Server: "http://cow.org:8080"}}, + Contexts: map[string]*clientcmdapi.Context{ + "root-context": {AuthInfo: "black-user", Cluster: "pig-cluster", Namespace: "saw-ns"}, + "federal-context": {AuthInfo: "red-user", Cluster: "cow-cluster", Namespace: "hammer-ns"}, + }, + } + oldTestConfig = clientcmdapi.Config{ + AuthInfos: map[string]*clientcmdapi.AuthInfo{ + "black-user": {Token: "black-token"}, + "red-user": {Token: "red-token"}}, + Clusters: map[string]*clientcmdapi.Cluster{ + "pig-cluster": {Server: "http://pig.org:8080"}, + "cow-cluster": {Server: "http://cow.org:8080"}}, + Contexts: map[string]*clientcmdapi.Context{ + "root": {AuthInfo: "black-user", Cluster: "pig-cluster", Namespace: "saw-ns"}, + "federal": {AuthInfo: "red-user", Cluster: "cow-cluster", Namespace: "hammer-ns"}, + }, + } + handleConfig = clientcmdapi.Config{ + AuthInfos: map[string]*clientcmdapi.AuthInfo{ + "user-cbc897d6ch": {Token: "red-token"}}, + Clusters: map[string]*clientcmdapi.Cluster{ + "cluster-cbc897d6ch": {Server: "http://cow.org:8080"}}, + Contexts: map[string]*clientcmdapi.Context{ + "federal-context": {AuthInfo: "user-cbc897d6ch", Cluster: "cluster-cbc897d6ch", Namespace: "hammer-ns"}}, + } + mergedConfig = clientcmdapi.Config{ + AuthInfos: map[string]*clientcmdapi.AuthInfo{ + "black-user": {Token: "black-token"}, + "red-user": {Token: "red-token"}, + "user-cbc897d6ch": {Token: "red-token"}, + "user-d2m9fd8b7d": {Token: "black-token"}, + }, + Clusters: map[string]*clientcmdapi.Cluster{ + "pig-cluster": {Server: "http://pig.org:8080"}, + "cow-cluster": {Server: "http://cow.org:8080"}, + "cluster-cbc897d6ch": {Server: "http://cow.org:8080"}, + "cluster-d2m9fd8b7d": {Server: "http://pig.org:8080"}, + }, + Contexts: map[string]*clientcmdapi.Context{ + "root": {AuthInfo: "black-user", Cluster: "pig-cluster", Namespace: "saw-ns"}, + "federal": {AuthInfo: "red-user", Cluster: "cow-cluster", Namespace: "hammer-ns"}, + "root-context": {AuthInfo: "user-d2m9fd8b7d", Cluster: "cluster-d2m9fd8b7d", Namespace: "saw-ns"}, + "federal-context": {AuthInfo: "user-cbc897d6ch", Cluster: "cluster-cbc897d6ch", Namespace: "hammer-ns"}, + }, + } +) + +func Test_checkContextName(t *testing.T) { + type args struct { + name string + oldConfig *clientcmdapi.Config + } + tests := []struct { + name string + args args + want bool + }{ + // TODO: Add more test cases. + {"exit", args{name: "root-context", oldConfig: &addTestConfig}, true}, + {"not-exit", args{name: "test", oldConfig: &addTestConfig}, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := checkContextName(tt.args.name, tt.args.oldConfig); got != tt.want { + t.Errorf("checkContextName() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestKubeConfig_handleContext(t *testing.T) { + newConfig := addTestConfig.DeepCopy() + testCtx := clientcmdapi.Context{AuthInfo: "red-user", Cluster: "cow-cluster", Namespace: "hammer-ns"} + + type fields struct { + config *clientcmdapi.Config + } + type args struct { + key string + ctx *clientcmdapi.Context + } + tests := []struct { + name string + fields fields + args args + want *clientcmdapi.Config + }{ + // TODO: Add more test cases. + {"one", fields{config: newConfig}, args{"federal-context", &testCtx}, &handleConfig}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + kc := &KubeConfig{ + config: tt.fields.config, + } + got := kc.handleContext(tt.args.key, tt.args.ctx) + checkConfig(got, tt.want, t) + }) + } +} + +func TestKubeConfig_handleContexts(t *testing.T) { + newConfig := addTestConfig.DeepCopy() + type fields struct { + config *clientcmdapi.Config + } + type args struct { + oldConfig *clientcmdapi.Config + } + tests := []struct { + name string + fields fields + args args + want *clientcmdapi.Config + wantErr bool + }{ + // TODO: Add test cases. + {"test", fields{config: newConfig}, args{&oldTestConfig}, &mergedConfig, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + kc := &KubeConfig{ + config: tt.fields.config, + } + got, err := kc.handleContexts(tt.args.oldConfig) + if (err != nil) != tt.wantErr { + t.Errorf("handleContexts() error = %v, wantErr %v", err, tt.wantErr) + return + } + checkConfig(got, tt.want, t) + //if !reflect.DeepEqual(got, tt.want) { + // t.Errorf("handleContexts() got = %v, want %v", got, tt.want) + //} + }) + } +} diff --git a/cmd/delete_test.go b/cmd/delete_test.go index c929e4b3..b69791da 100644 --- a/cmd/delete_test.go +++ b/cmd/delete_test.go @@ -4,9 +4,7 @@ import ( "fmt" "testing" - apiequality "k8s.io/apimachinery/pkg/api/equality" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" - "k8s.io/utils/diff" ) var ( @@ -67,10 +65,3 @@ func Test_deleteContext(t *testing.T) { }) } } - -func checkConfig(want, got *clientcmdapi.Config, t *testing.T) { - if !apiequality.Semantic.DeepEqual(want, got) { - t.Errorf("diff: %v", diff.ObjectDiff(want, got)) - t.Errorf("expected: %#v\n actual: %#v", want, got) - } -} diff --git a/cmd/utils.go b/cmd/utils.go index 4d2b0518..f2e2c541 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -78,6 +78,11 @@ func HashSuf(config *clientcmdapi.Config) string { return sum } +func HashSufString(data string) string { + sum, _ := hEncode(Hash(data)) + return sum +} + // PrintTable generate table func PrintTable(config *clientcmdapi.Config) error { var table [][]string diff --git a/cmd/utils_test.go b/cmd/utils_test.go index 29f18be8..c32415f3 100644 --- a/cmd/utils_test.go +++ b/cmd/utils_test.go @@ -231,3 +231,10 @@ func TestCheckValidContext(t *testing.T) { }) } } + +func checkConfig(want, got *clientcmdapi.Config, t *testing.T) { + if !apiequality.Semantic.DeepEqual(want, got) { + t.Errorf("diff: %v", diff.ObjectDiff(want, got)) + t.Errorf("expected: %#v\n actual: %#v", want, got) + } +} diff --git a/docs/en-us/cli/kubecm_add.md b/docs/en-us/cli/kubecm_add.md index b41249ef..608f9eb5 100644 --- a/docs/en-us/cli/kubecm_add.md +++ b/docs/en-us/cli/kubecm_add.md @@ -1,10 +1,10 @@ ## kubecm add -Add kubeconfig to $HOME/.kube/config +Add KubeConfig to $HOME/.kube/config ### Synopsis -Add kubeconfig to $HOME/.kube/config +Add KubeConfig to $HOME/.kube/config ``` kubecm add [flags] diff --git a/docs/en-us/cli/kubecm_ls.md b/docs/en-us/cli/kubecm_ls.md index 63825be4..19075718 100644 --- a/docs/en-us/cli/kubecm_ls.md +++ b/docs/en-us/cli/kubecm_ls.md @@ -1,10 +1,10 @@ ## kubecm ls -List kubeconfig +List KubeConfig ### Synopsis -List kubeconfig +List KubeConfig ``` kubecm ls diff --git a/docs/en-us/cli/kubecm_switch.md b/docs/en-us/cli/kubecm_switch.md index f885f697..7eac6ac0 100644 --- a/docs/en-us/cli/kubecm_switch.md +++ b/docs/en-us/cli/kubecm_switch.md @@ -20,6 +20,8 @@ kubecm switch [flags] # Switch Kube Context interactively kubecm switch +# Quick switch Kube Context +kubecm switch dev ``` diff --git a/docs/zh-cn/cli/kubecm_add.md b/docs/zh-cn/cli/kubecm_add.md index 7559ca44..71e723ca 100644 --- a/docs/zh-cn/cli/kubecm_add.md +++ b/docs/zh-cn/cli/kubecm_add.md @@ -1,10 +1,10 @@ ## kubecm add -将 kubeconfig 加入到 `$HOME/.kube/config` +将 KubeConfig 加入到 `$HOME/.kube/config` ### 简介 -将 kubeconfig 加入到 `$HOME/.kube/config` +将 KubeConfig 加入到 `$HOME/.kube/config` ``` kubecm add [flags] diff --git a/docs/zh-cn/cli/kubecm_switch.md b/docs/zh-cn/cli/kubecm_switch.md index 17c107aa..066a5d46 100644 --- a/docs/zh-cn/cli/kubecm_switch.md +++ b/docs/zh-cn/cli/kubecm_switch.md @@ -5,7 +5,7 @@ ### 简介 -交互式切换 Kube Context +切换 Kube Context,可以直接切换或交互式切换。 **可以通过键入 `\` 来进行搜索** @@ -21,6 +21,8 @@ kubecm switch [flags] # Switch Kube Context interactively kubecm switch +# Quick switch Kube Context +kubecm switch dev ```