Skip to content

Commit

Permalink
fix: 🐛 Unable to switch to a namespace (#856)
Browse files Browse the repository at this point in the history
  • Loading branch information
sunny0826 authored Jan 15, 2024
1 parent 750debc commit 5cd1b02
Show file tree
Hide file tree
Showing 4 changed files with 220 additions and 116 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/e2e-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
80 changes: 64 additions & 16 deletions cmd/namespace.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
package cmd

import (
"context"
"errors"
"fmt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"log"
"os"
"strings"

"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
Expand Down Expand Up @@ -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: "<Exit>", 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)
Expand All @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
217 changes: 152 additions & 65 deletions cmd/namespace_test.go
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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")
}
Loading

0 comments on commit 5cd1b02

Please sign in to comment.