Skip to content

Commit

Permalink
add cmd update (#32)
Browse files Browse the repository at this point in the history
* update add cmd

* update add cmd

* add utest

* fix & add docs
  • Loading branch information
sunny0826 authored Jan 6, 2021
1 parent ef716ad commit 2a4b743
Show file tree
Hide file tree
Showing 10 changed files with 230 additions and 73 deletions.
112 changes: 55 additions & 57 deletions cmd/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"errors"
"fmt"
"strconv"
"strings"

"github.com/spf13/cobra"
"k8s.io/client-go/tools/clientcmd"
Expand All @@ -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)
},
Expand All @@ -33,25 +36,33 @@ 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
}
oldConfig, err := clientcmd.LoadFromFile(cfgFile)
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
Expand All @@ -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 {
Expand Down
152 changes: 152 additions & 0 deletions cmd/add_test.go
Original file line number Diff line number Diff line change
@@ -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)
//}
})
}
}
9 changes: 0 additions & 9 deletions cmd/delete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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)
}
}
5 changes: 5 additions & 0 deletions cmd/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions cmd/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
4 changes: 2 additions & 2 deletions docs/en-us/cli/kubecm_add.md
Original file line number Diff line number Diff line change
@@ -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]
Expand Down
4 changes: 2 additions & 2 deletions docs/en-us/cli/kubecm_ls.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
## kubecm ls

List kubeconfig
List KubeConfig

### Synopsis

List kubeconfig
List KubeConfig

```
kubecm ls
Expand Down
2 changes: 2 additions & 0 deletions docs/en-us/cli/kubecm_switch.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ kubecm switch [flags]
# Switch Kube Context interactively
kubecm switch
# Quick switch Kube Context
kubecm switch dev
```

Expand Down
Loading

0 comments on commit 2a4b743

Please sign in to comment.