diff --git a/.github/workflows/e2e-test.yaml b/.github/workflows/e2e-test.yaml index fe645f15..809e37c7 100644 --- a/.github/workflows/e2e-test.yaml +++ b/.github/workflows/e2e-test.yaml @@ -95,6 +95,10 @@ jobs: echo "********************************************************************************" bin/kubecm s kind-2nd-kind --config merge.config echo "********************************************************************************" + echo "Running kubecm namespace..." + echo "********************************************************************************" + bin/kubecm ns kube-system + echo "********************************************************************************" echo "Running kubecm list from env KUBECONFIG..." echo "********************************************************************************" echo "default config" diff --git a/cmd/namespace.go b/cmd/namespace.go index 1b43e3cf..e3eecace 100644 --- a/cmd/namespace.go +++ b/cmd/namespace.go @@ -1,8 +1,11 @@ package cmd import ( + "context" "errors" "fmt" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" "log" "os" "strings" @@ -10,7 +13,6 @@ import ( "github.com/manifoldco/promptui" "github.com/spf13/cobra" "k8s.io/client-go/tools/clientcmd" - clientcmdapi "k8s.io/client-go/tools/clientcmd/api" ) // NamespaceCommand namespace cmd struct @@ -46,21 +48,31 @@ func (nc *NamespaceCommand) runNamespace(command *cobra.Command, args []string) } currentContext := config.CurrentContext - contNs := config.Contexts[currentContext].Namespace - namespaceList, err := GetNamespaceList(contNs) + currentNamespace := config.Contexts[currentContext].Namespace + clientset, err := GetClientSet(cfgFile) if err != nil { return err } if len(args) == 0 { + namespaceList, err := GetNamespaceList(currentNamespace, clientset) + if err != nil { + return err + } // exit option namespaceList = append(namespaceList, Namespaces{Name: "", Default: false}) num := selectNamespace(namespaceList) config.Contexts[currentContext].Namespace = namespaceList[num].Name } else { - err := changeNamespace(args, namespaceList, currentContext, config) + exist, err := CheckNamespaceExist(args[0], clientset) if err != nil { - return err + return errors.New("Can not find namespace: " + args[0]) + } + if exist { + config.Contexts[currentContext].Namespace = args[0] + fmt.Printf("Namespace: 「%s」 is selected.\n", args[0]) + } else { + return errors.New("Can not find namespace: " + args[0]) } } err = WriteConfig(true, cfgFile, config) @@ -70,17 +82,6 @@ func (nc *NamespaceCommand) runNamespace(command *cobra.Command, args []string) return MacNotifier(fmt.Sprintf("Switch to the [%s] namespace\n", config.Contexts[currentContext].Namespace)) } -func changeNamespace(args []string, namespaceList []Namespaces, currentContext string, config *clientcmdapi.Config) error { - for _, ns := range namespaceList { - if ns.Name == args[0] { - config.Contexts[currentContext].Namespace = args[0] - fmt.Printf("Namespace: 「%s」 is selected.\n", args[0]) - return nil - } - } - return errors.New("Can not find namespace: " + args[0]) -} - func selectNamespace(namespaces []Namespaces) int { ns, err := selectNamespaceWithRunner(namespaces, nil) if err != nil { @@ -128,6 +129,53 @@ func selectNamespaceWithRunner(namespaces []Namespaces, runner SelectRunner) (in return i, err } +// GetClientSet return clientset +func GetClientSet(configFile string) (kubernetes.Interface, error) { + config, err := clientcmd.BuildConfigFromFlags("", configFile) + if err != nil { + return nil, fmt.Errorf(err.Error()) + } + return kubernetes.NewForConfig(config) +} + +// GetNamespaceList return namespace list +func GetNamespaceList(currentNamespace string, clientset kubernetes.Interface) ([]Namespaces, error) { + var nss []Namespaces + namespaceList, err := clientset.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return nil, fmt.Errorf(err.Error()) + } + for _, specItem := range namespaceList.Items { + switch currentNamespace { + case "": + if specItem.Name == "default" { + nss = append(nss, Namespaces{Name: specItem.Name, Default: true}) + } else { + nss = append(nss, Namespaces{Name: specItem.Name, Default: false}) + } + default: + if specItem.Name == currentNamespace { + nss = append(nss, Namespaces{Name: specItem.Name, Default: true}) + } else { + nss = append(nss, Namespaces{Name: specItem.Name, Default: false}) + } + } + } + return nss, nil +} + +// CheckNamespaceExist check namespace exist +func CheckNamespaceExist(namespace string, clientset kubernetes.Interface) (bool, error) { + ns, err := clientset.CoreV1().Namespaces().Get(context.TODO(), namespace, metav1.GetOptions{}) + if err != nil { + return false, fmt.Errorf(err.Error()) + } + if ns.Name == namespace { + return true, nil + } + return false, errors.New("namespace not found") +} + func namespaceExample() string { return ` # Switch Namespace interactively diff --git a/cmd/namespace_test.go b/cmd/namespace_test.go index aabbbb7b..e2fe8931 100644 --- a/cmd/namespace_test.go +++ b/cmd/namespace_test.go @@ -1,77 +1,17 @@ package cmd import ( + "context" "errors" - "fmt" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/fake" "testing" "github.com/stretchr/testify/assert" - - clientcmdapi "k8s.io/client-go/tools/clientcmd/api" ) -var ( - testNsConfig = 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"}, - }, - } -) - -func Test_changeNamespace(t *testing.T) { - type args struct { - args []string - namespaceList []Namespaces - currentContext string - config *clientcmdapi.Config - } - tests := []struct { - name string - args args - wantErr bool - }{ - // TODO: Add test cases. - { - "ns", - args{args: []string{"test"}, - namespaceList: []Namespaces{ - {"test", false}, - {"hammer-ns", true}}, - currentContext: "root-context", - config: &testNsConfig}, - false, - }, - { - "ns-not-exit", - args{args: []string{"a"}, - namespaceList: []Namespaces{ - {"test", false}, - {"hammer-ns", true}}, - currentContext: "root-context", - config: &testNsConfig}, - true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := changeNamespace(tt.args.args, tt.args.namespaceList, tt.args.currentContext, tt.args.config); (err != nil) != tt.wantErr { - t.Errorf("changeNamespace() error = %v, wantErr %v", err, tt.wantErr) - } else { - fmt.Printf("Catch ERROR: %v\n", err) - } - }) - } -} - type testSelectNamespacePrompt struct { index int err error @@ -136,3 +76,150 @@ func TestSelectNamespace(t *testing.T) { }) } } + +func TestCheckNamespaceExist(t *testing.T) { + // Define test cases + testCases := []struct { + name string + namespace string + namespaces []string + expectExist bool + expectError bool + }{ + { + name: "Namespace exists", + namespace: "test", + namespaces: []string{"default", "test"}, + expectExist: true, + expectError: false, + }, + { + name: "Namespace does not exist", + namespace: "nonexistent", + namespaces: []string{"default", "test"}, + expectExist: false, + expectError: true, + }, + { + name: "Error case", + namespace: "", + namespaces: nil, // Assuming this simulates an error condition + expectExist: false, + expectError: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + clientset := mockKubernetesClientSet(tc.namespaces) + exist, err := CheckNamespaceExist(tc.namespace, clientset) + if tc.expectError { + if err == nil { + t.Errorf("Expected an error, but got none") + } + } else { + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if exist != tc.expectExist { + t.Errorf("Expected existence to be %v, got %v", tc.expectExist, exist) + } + } + }) + } +} + +func TestGetNamespaceList(t *testing.T) { + testCases := []struct { + name string + currentNamespace string + namespaces []string + expected []Namespaces + expectError bool + }{ + { + name: "Success with default namespace", + currentNamespace: "default", + namespaces: []string{"default", "test"}, + expected: []Namespaces{ + {Name: "default", Default: true}, + {Name: "test", Default: false}, + }, + expectError: false, + }, + { + name: "Success with specified namespace", + currentNamespace: "test", + namespaces: []string{"default", "test"}, + expected: []Namespaces{ + {Name: "default", Default: false}, + {Name: "test", Default: true}, + }, + expectError: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + clientset := mockKubernetesClientSet(tc.namespaces) + nss, err := GetNamespaceList(tc.currentNamespace, clientset) + if tc.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Equal(t, tc.expected, nss) + } + }) + } +} + +// mockKubernetesClientSet creates a mock clientset that contains the provided namespaces. +func mockKubernetesClientSet(namespaces []string) kubernetes.Interface { + clientset := fake.NewSimpleClientset() + + for _, ns := range namespaces { + namespace := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: ns, + }, + } + _, err := clientset.CoreV1().Namespaces().Create(context.TODO(), namespace, metav1.CreateOptions{}) + if err != nil { + return nil + } + } + + return clientset +} + +func TestGetClientSet(t *testing.T) { + // Create a fake clientset + clientset := fake.NewSimpleClientset() + + // Create a namespace object + namespace := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "default", + }, + } + + // Add the namespace to the fake clientset + _, err := clientset.CoreV1().Namespaces().Create(context.Background(), namespace, metav1.CreateOptions{}) + if err != nil { + t.Fatalf("Could not create namespace: %v", err) + } + + // Use the clientset to get the list of namespaces + namespaces, err := clientset.CoreV1().Namespaces().List(context.Background(), metav1.ListOptions{}) + if err != nil { + t.Fatalf("Could not list namespaces: %v", err) + } + + // Check that the "default" namespace exists + for _, ns := range namespaces.Items { + if ns.Name == "default" { + return + } + } + t.Fatal("Did not find 'default' namespace") +} diff --git a/cmd/utils.go b/cmd/utils.go index 546a729f..18000548 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -354,41 +354,6 @@ func ExitOption(kubeItems []Needle) ([]Needle, error) { return kubeItems, nil } -// GetNamespaceList return namespace list -func GetNamespaceList(cont string) ([]Namespaces, error) { - var nss []Namespaces - config, err := clientcmd.BuildConfigFromFlags("", cfgFile) - if err != nil { - return nil, fmt.Errorf(err.Error()) - } - clientset, err := kubernetes.NewForConfig(config) - if err != nil { - return nil, fmt.Errorf(err.Error()) - } - ctx := context.TODO() - namespaceList, err := clientset.CoreV1().Namespaces().List(ctx, metav1.ListOptions{}) - if err != nil { - return nil, fmt.Errorf(err.Error()) - } - for _, specItem := range namespaceList.Items { - switch cont { - case "": - if specItem.Name == "default" { - nss = append(nss, Namespaces{Name: specItem.Name, Default: true}) - } else { - nss = append(nss, Namespaces{Name: specItem.Name, Default: false}) - } - default: - if specItem.Name == cont { - nss = append(nss, Namespaces{Name: specItem.Name, Default: true}) - } else { - nss = append(nss, Namespaces{Name: specItem.Name, Default: false}) - } - } - } - return nss, nil -} - func printService(out io.Writer, name, link string) { ct.ChangeColor(ct.Green, false, ct.None, false) fmt.Fprint(out, name)