From 30aeb06d43b451b37720e8fa40328644c35322ee Mon Sep 17 00:00:00 2001 From: guoxudong Date: Thu, 4 Feb 2021 15:50:24 +0800 Subject: [PATCH] add create cmd (#39) --- README.md | 2 + cmd/cmd.go | 1 + cmd/create.go | 255 ++++++++++++++++++++++++++++++++ docs/en-us/README.md | 1 + docs/en-us/_sidebar.md | 23 +-- docs/en-us/cli/kubecm_create.md | 38 +++++ docs/zh-cn/README.md | 1 + docs/zh-cn/_sidebar.md | 23 +-- docs/zh-cn/cli/kubecm_create.md | 38 +++++ go.mod | 1 + 10 files changed, 361 insertions(+), 22 deletions(-) create mode 100644 cmd/create.go create mode 100644 docs/en-us/cli/kubecm_create.md create mode 100644 docs/zh-cn/cli/kubecm_create.md diff --git a/README.md b/README.md index 9851ec65..69172d51 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,9 @@ Usage: Available Commands: add Merge configuration file with $HOME/.kube/config alias Generate alias for all contexts + clear Clear lapsed context, cluster and user completion Generates bash/zsh completion scripts + create Create new KubeConfig(experiment) delete Delete the specified context from the kubeconfig help Help about any command ls List kubeconfig diff --git a/cmd/cmd.go b/cmd/cmd.go index fe8c9f06..cd9ce57c 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -18,6 +18,7 @@ func NewBaseCommand() *BaseCommand { &ListCommand{}, // list command &AliasCommand{}, // alias command &ClearCommand{}, // clear command + &CreateCommand{}, // create command ) return baseCmd diff --git a/cmd/create.go b/cmd/create.go new file mode 100644 index 00000000..185b1a11 --- /dev/null +++ b/cmd/create.go @@ -0,0 +1,255 @@ +package cmd + +import ( + "context" + "encoding/base64" + "fmt" + "os" + + "github.com/manifoldco/promptui" + "github.com/spf13/cobra" + coreV1 "k8s.io/api/core/v1" + rbacV1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" +) + +// ClearCommand clean command struct +type CreateCommand struct { + BaseCommand +} + +type CreateOptions struct { + config *clientcmdapi.Config + clientSet *kubernetes.Clientset + role string + token string + contextName string + userName string + namespace string +} + +// Init AliasCommand +func (ce *CreateCommand) Init() { + ce.command = &cobra.Command{ + Use: "create", + Short: "Create new KubeConfig(experiment)", + Long: "Create new KubeConfig(experiment)", + RunE: func(cmd *cobra.Command, args []string) error { + return ce.runCreate(cmd, args) + }, + Example: createExample(), + } + ce.command.DisableFlagsInUseLine = true +} + +func (ce *CreateCommand) runCreate(cmd *cobra.Command, args []string) error { + config, err := clientcmd.LoadFromFile(cfgFile) + if err != nil { + return err + } + userName := PromptUI("user name", "") + co := CreateOptions{ + config: config, + userName: userName, + } + err = co.chooseContext() + if err != nil { + return err + } + err = co.chooseNamespace() + if err != nil { + return err + } + err = co.createServiceAccounts() + if err != nil { + return err + } + err = co.selectClusterRole() + if err != nil { + return err + } + err = co.createRoleBinding() + if err != nil { + return err + } + err = co.getToken() + if err != nil { + return err + } + newConfig := co.putOutKubeConfig() + fileName := fmt.Sprintf("%s.kubeconfig", co.userName) + err = clientcmd.WriteToFile(*newConfig, fileName) + if err != nil { + return err + } + printString(os.Stdout, "kubeconfig: "+fileName+" create success\n") + return nil +} + +func (co *CreateOptions) chooseContext() error { + var kubeItems []Needle + current := co.config.CurrentContext + for key, obj := range co.config.Contexts { + if key != current { + kubeItems = append(kubeItems, Needle{Name: key, Cluster: obj.Cluster, User: obj.AuthInfo}) + } else { + kubeItems = append([]Needle{{Name: key, Cluster: obj.Cluster, User: obj.AuthInfo, Center: "(*)"}}, kubeItems...) + } + } + num := SelectUI(kubeItems, "Select Kube Context") + co.contextName = kubeItems[num].Name + co.config.CurrentContext = co.contextName + clientConfig := clientcmd.NewDefaultClientConfig( + *co.config, + &clientcmd.ConfigOverrides{}, + ) + c, _ := clientConfig.ClientConfig() + clientSet, err := kubernetes.NewForConfig(c) + if err != nil { + return err + } + co.clientSet = clientSet + return nil +} + +func (co *CreateOptions) chooseNamespace() error { + var nss []Namespaces + ctx := context.TODO() + namespaceList, err := co.clientSet.CoreV1().Namespaces().List(ctx, metav1.ListOptions{}) + if err != nil { + return err + } + for _, specItem := range namespaceList.Items { + nss = append(nss, Namespaces{Name: specItem.Name, Default: false}) + } + num := selectNamespace(nss) + co.namespace = nss[num].Name + return nil +} + +func (co *CreateOptions) createServiceAccounts() error { + saName := co.userName + // TODO 判断 sa 是否存在 + userServiceAccount, err := co.clientSet.CoreV1().ServiceAccounts(co.namespace).Get(context.TODO(), saName, metav1.GetOptions{}) + if err != nil { + saObj := &coreV1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: saName, + }, + } + userServiceAccount, err = co.clientSet.CoreV1().ServiceAccounts(co.namespace).Create(context.TODO(), saObj, metav1.CreateOptions{}) + if err != nil { + return err + } + printString(os.Stdout, "ServiceAccount") + fmt.Printf(" : %s create success\n", userServiceAccount.Name) + } else { + printYellow(os.Stdout, "ServiceAccount") + fmt.Printf(" : %s already exists\n", userServiceAccount.Name) + } + return nil +} + +func (co *CreateOptions) selectClusterRole() error { + clusterRoleList := []string{ + "view", "edit", "admin", "cluster-admin", "custom", + } + templates := &promptui.SelectTemplates{ + Label: "{{ . }}", + Active: "\U0001F63C {{ . | red }}", + Inactive: " {{ . | cyan }}", + Selected: "\U0001F638 Select:{{ . | green }}", + } + prompt := promptui.Select{ + Label: "please select the cluster role of the user:", + Items: clusterRoleList, + Templates: templates, + Size: 4, + } + i, _, err := prompt.Run() + if err != nil { + return err + } + if clusterRoleList[i] == "custom" { + customClusterRole := PromptUI("custom cluster role", "") + fmt.Println(customClusterRole) + _, err := co.clientSet.RbacV1().ClusterRoles().Get(context.TODO(), customClusterRole, metav1.GetOptions{}) + if err != nil { + return err + } + co.role = customClusterRole + } else { + co.role = clusterRoleList[i] + } + return nil +} + +func (co *CreateOptions) createRoleBinding() error { + co.role = "view" + rb := &rbacV1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-%s", co.userName, co.role), + Namespace: co.namespace, + }, + Subjects: []rbacV1.Subject{ + { + Kind: "ServiceAccount", + Name: co.userName, + Namespace: co.namespace, + }, + }, + RoleRef: rbacV1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "ClusterRole", + Name: co.role, + }, + } + newRoleBinding, err := co.clientSet.RbacV1().RoleBindings(co.namespace).Create(context.TODO(), rb, metav1.CreateOptions{}) + if err != nil { + return err + } + printString(os.Stdout, "RoleBinding") + fmt.Printf(" : %s create success\n", newRoleBinding.Name) + return nil +} + +func (co *CreateOptions) getToken() error { + sa, err := co.clientSet.CoreV1().ServiceAccounts(co.namespace).Get(context.TODO(), co.userName, metav1.GetOptions{}) + if err != nil { + return err + } + secretName := sa.Secrets[0].Name + secretToken, _ := co.clientSet.CoreV1().Secrets(co.namespace).Get(context.TODO(), secretName, metav1.GetOptions{}) + sEnc := base64.StdEncoding.EncodeToString(secretToken.Data["token"]) + sDec, err := base64.StdEncoding.DecodeString(sEnc) + if err != nil { + return err + } + co.token = string(sDec) + return nil +} + +func (co *CreateOptions) putOutKubeConfig() *clientcmdapi.Config { + coContext := co.config.Contexts[co.contextName] + coCluster := co.config.Clusters[coContext.Cluster] + coAuthInfo := clientcmdapi.NewAuthInfo() + coAuthInfo.Token = co.token + coContext.AuthInfo = co.userName + + newConfig := clientcmdapi.NewConfig() + newConfig.Clusters[coContext.Cluster] = coCluster + newConfig.AuthInfos[coContext.AuthInfo] = coAuthInfo + newConfig.Contexts[co.userName] = coContext + newConfig.CurrentContext = co.userName + return newConfig +} + +func createExample() string { + return ` +# Create new KubeConfig(experiment) +kubecm create +` +} diff --git a/docs/en-us/README.md b/docs/en-us/README.md index a91fbf16..a59fb2ed 100644 --- a/docs/en-us/README.md +++ b/docs/en-us/README.md @@ -25,6 +25,7 @@ Available Commands: alias Generate alias for all contexts clear Clear lapsed context, cluster and user completion Generates bash/zsh completion scripts + create Create new KubeConfig(experiment) delete Delete the specified context from the kubeconfig help Help about any command ls List kubeconfig diff --git a/docs/en-us/_sidebar.md b/docs/en-us/_sidebar.md index 7ef46ef8..a53fe455 100644 --- a/docs/en-us/_sidebar.md +++ b/docs/en-us/_sidebar.md @@ -1,15 +1,16 @@ * [Install](/en-us/install.md) * [Interactive operation](/en-us/interactive.md) * CLI References - * [kubecm add](/en-us/cli/kubecm_add.md) - * [kubecm alias](/en-us/cli/kubecm_alias.md) - * [kubecm completion](/en-us/cli/kubecm_completion.md) - * [kubecm delete](/en-us/cli/kubecm_delete.md) - * [kubecm ls](/en-us/cli/kubecm_ls.md) - * [kubecm merge](/en-us/cli/kubecm_merge.md) - * [kubecm namespace](/en-us/cli/kubecm_namespace.md) - * [kubecm rename](/en-us/cli/kubecm_rename.md) - * [kubecm switch](/en-us/cli/kubecm_switch.md) - * [kubecm clear](/en-us/cli/kubecm_clear.md) - * [kubecm version](/en-us/cli/kubecm_version.md) + * [add](/en-us/cli/kubecm_add.md) + * [create(experiment)](/en-us/cli/kubecm_create.md) + * [alias](/en-us/cli/kubecm_alias.md) + * [completion](/en-us/cli/kubecm_completion.md) + * [delete](/en-us/cli/kubecm_delete.md) + * [ls](/en-us/cli/kubecm_ls.md) + * [merge](/en-us/cli/kubecm_merge.md) + * [namespace](/en-us/cli/kubecm_namespace.md) + * [rename](/en-us/cli/kubecm_rename.md) + * [switch](/en-us/cli/kubecm_switch.md) + * [clear](/en-us/cli/kubecm_clear.md) + * [version](/en-us/cli/kubecm_version.md) * [Contribute](/en-us/contribute.md) \ No newline at end of file diff --git a/docs/en-us/cli/kubecm_create.md b/docs/en-us/cli/kubecm_create.md new file mode 100644 index 00000000..2f0d3112 --- /dev/null +++ b/docs/en-us/cli/kubecm_create.md @@ -0,0 +1,38 @@ +## kubecm create + +Create new KubeConfig(experiment) + +### Synopsis + +Create new KubeConfig(experiment) + +``` +kubecm create +``` + +### Examples + +``` + +# Create new KubeConfig(experiment) +kubecm create + +``` + +### Options + +``` + -h, --help help for create +``` + +### Options inherited from parent commands + +``` + --config string path of kubeconfig (default "/Users/saybot/.kube/config") +``` + +### SEE ALSO + +* [kubecm](../../../tmp/cli/kubecm.md) - KubeConfig Manager. + +###### Auto generated by spf13/cobra on 4-Feb-2021 diff --git a/docs/zh-cn/README.md b/docs/zh-cn/README.md index ca425626..99ec73c0 100644 --- a/docs/zh-cn/README.md +++ b/docs/zh-cn/README.md @@ -25,6 +25,7 @@ Available Commands: alias Generate alias for all contexts clear Clear lapsed context, cluster and user completion Generates bash/zsh completion scripts + create Create new KubeConfig(experiment) delete Delete the specified context from the kubeconfig help Help about any command ls List kubeconfig diff --git a/docs/zh-cn/_sidebar.md b/docs/zh-cn/_sidebar.md index 2a0be61e..267a020b 100644 --- a/docs/zh-cn/_sidebar.md +++ b/docs/zh-cn/_sidebar.md @@ -1,15 +1,16 @@ * [安装](/zh-cn/install.md) * [交互式操作](/zh-cn/interactive.md) * CLI 参考 - * [kubecm add](/zh-cn/cli/kubecm_add.md) - * [kubecm alias](/zh-cn/cli/kubecm_alias.md) - * [kubecm completion](/zh-cn/cli/kubecm_completion.md) - * [kubecm delete](/zh-cn/cli/kubecm_delete.md) - * [kubecm ls](/zh-cn/cli/kubecm_ls.md) - * [kubecm merge](/zh-cn/cli/kubecm_merge.md) - * [kubecm namespace](/zh-cn/cli/kubecm_namespace.md) - * [kubecm rename](/zh-cn/cli/kubecm_rename.md) - * [kubecm switch](/zh-cn/cli/kubecm_switch.md) - * [kubecm clear](/en-us/cli/kubecm_clear.md) - * [kubecm version](/zh-cn/cli/kubecm_version.md) + * [add](/zh-cn/cli/kubecm_add.md) + * [create(实验性)](/zh-cn/cli/kubecm_create.md) + * [alias](/zh-cn/cli/kubecm_alias.md) + * [completion](/zh-cn/cli/kubecm_completion.md) + * [delete](/zh-cn/cli/kubecm_delete.md) + * [ls](/zh-cn/cli/kubecm_ls.md) + * [merge](/zh-cn/cli/kubecm_merge.md) + * [namespace](/zh-cn/cli/kubecm_namespace.md) + * [rename](/zh-cn/cli/kubecm_rename.md) + * [switch](/zh-cn/cli/kubecm_switch.md) + * [clear](/en-us/cli/kubecm_clear.md) + * [version](/zh-cn/cli/kubecm_version.md) * [贡献](/zh-cn/contribute.md) \ No newline at end of file diff --git a/docs/zh-cn/cli/kubecm_create.md b/docs/zh-cn/cli/kubecm_create.md new file mode 100644 index 00000000..f01217c3 --- /dev/null +++ b/docs/zh-cn/cli/kubecm_create.md @@ -0,0 +1,38 @@ +## kubecm create(实验性) + +生成 KubeConfig(实验性) + +### Synopsis + +交互式生成 KubeConfig(实验性) + +``` +kubecm create +``` + +### Examples + +``` + +# Create new KubeConfig(experiment) +kubecm create + +``` + +### Options + +``` + -h, --help help for create +``` + +### Options inherited from parent commands + +``` + --config string path of kubeconfig (default "/Users/saybot/.kube/config") +``` + +### SEE ALSO + +* [kubecm](../../../tmp/cli/kubecm.md) - KubeConfig Manager. + +###### Auto generated by spf13/cobra on 4-Feb-2021 diff --git a/go.mod b/go.mod index 65723a2a..91eabfc2 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/rsteube/cobra-zsh-gen v1.1.0 github.com/spf13/cobra v1.0.0 + k8s.io/api v0.19.3 k8s.io/apimachinery v0.19.3 k8s.io/client-go v0.19.3 k8s.io/utils v0.0.0-20201015054608-420da100c033