diff --git a/cmd/kubectx/current.go b/cmd/kubectx/current.go index c426189..c164ef5 100644 --- a/cmd/kubectx/current.go +++ b/cmd/kubectx/current.go @@ -15,12 +15,12 @@ type CurrentOp struct{} func (_op CurrentOp) Run(stdout, _ io.Writer) error { kc := new(kubeconfig.Kubeconfig).WithLoader(defaultLoader) defer kc.Close() - rootNode, err := kc.ParseRaw() + _, err := kc.ParseRaw() if err != nil { return err } - v := kubeconfig.GetCurrentContext(rootNode) + v := kc.GetCurrentContext() if v == "" { return errors.New("current-context is not set") } diff --git a/cmd/kubectx/delete.go b/cmd/kubectx/delete.go index 48c7aee..ece176b 100644 --- a/cmd/kubectx/delete.go +++ b/cmd/kubectx/delete.go @@ -42,7 +42,7 @@ func deleteContext(name string) (deleteName string, wasActiveContext bool, err e return "", false, err } - cur := kubeconfig.GetCurrentContext(rootNode) + cur := kc.GetCurrentContext() // resolve "." to a real name if name == "." { @@ -50,7 +50,7 @@ func deleteContext(name string) (deleteName string, wasActiveContext bool, err e name = cur } - if !checkContextExists(rootNode, name) { + if !kc.ContextExists(name) { return "", false, errors.New("context does not exist") } diff --git a/cmd/kubectx/kubeconfig/helpers.go b/cmd/kubectx/kubeconfig/contexts.go similarity index 57% rename from cmd/kubectx/kubeconfig/helpers.go rename to cmd/kubectx/kubeconfig/contexts.go index a3b41b7..d0d3f4b 100644 --- a/cmd/kubectx/kubeconfig/helpers.go +++ b/cmd/kubectx/kubeconfig/contexts.go @@ -1,9 +1,11 @@ package kubeconfig -import "gopkg.in/yaml.v3" +import ( + "gopkg.in/yaml.v3" +) -func ContextNames(rootNode *yaml.Node) []string { - contexts := valueOf(rootNode, "contexts") +func (k *Kubeconfig) ContextNames() []string { + contexts := valueOf(k.rootNode, "contexts") if contexts == nil { return nil } @@ -21,17 +23,14 @@ func ContextNames(rootNode *yaml.Node) []string { return ctxNames } -// GetCurrentContext returns "current-context" value in given -// kubeconfig object Node, or returns "" if not found. -func GetCurrentContext(rootNode *yaml.Node) string { - if rootNode.Kind != yaml.MappingNode { - return "" +func (k *Kubeconfig) ContextExists(name string) bool { + ctxNames := k.ContextNames() + for _, v := range ctxNames { + if v == name { + return true + } } - v := valueOf(rootNode, "current-context") - if v == nil { - return "" - } - return v.Value + return false } func valueOf(mapNode *yaml.Node, key string) *yaml.Node { diff --git a/cmd/kubectx/kubeconfig/contexts_test.go b/cmd/kubectx/kubeconfig/contexts_test.go new file mode 100644 index 0000000..3708405 --- /dev/null +++ b/cmd/kubectx/kubeconfig/contexts_test.go @@ -0,0 +1,52 @@ +package kubeconfig + +import ( + "strings" + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestKubeconfig_ContextNames(t *testing.T) { + tl := &testLoader{in: strings.NewReader(` +contexts: +- name: abc +- name: def + field1: value1 +- name: ghi + foo: + bar: zoo`)} + + kc := new(Kubeconfig).WithLoader(tl) + _, err := kc.ParseRaw() + if err != nil { + t.Fatal(err) + } + ctx := kc.ContextNames() + expected := []string{"abc", "def", "ghi"} + if diff := cmp.Diff(expected, ctx); diff != "" { + t.Fatalf("%s", diff) + } +} + +func TestKubeconfig_CheckContextExists(t *testing.T) { + tl := &testLoader{in: strings.NewReader(`contexts: +- name: c1 +- name: c2`)} + + kc := new(Kubeconfig).WithLoader(tl) + _, err := kc.ParseRaw() + if err != nil { + t.Fatal(err) + } + + if !kc.ContextExists("c1") { + t.Fatal("c1 actually exists; reported false") + } + if !kc.ContextExists("c2") { + t.Fatal("c2 actually exists; reported false") + } + if kc.ContextExists("c3") { + t.Fatal("c3 does not exist; but reported true") + } +} diff --git a/cmd/kubectx/kubeconfig/currentcontext.go b/cmd/kubectx/kubeconfig/currentcontext.go new file mode 100644 index 0000000..6662985 --- /dev/null +++ b/cmd/kubectx/kubeconfig/currentcontext.go @@ -0,0 +1,28 @@ +package kubeconfig + +import ( + "github.com/pkg/errors" + "gopkg.in/yaml.v3" +) + +// GetCurrentContext returns "current-context" value in given +// kubeconfig object Node, or returns "" if not found. +func (k *Kubeconfig) GetCurrentContext() string { + if k.rootNode.Kind != yaml.MappingNode { + return "" + } + v := valueOf(k.rootNode, "current-context") + if v == nil { + return "" + } + return v.Value +} + +func (k *Kubeconfig) UnsetCurrentContext() error { + if k.rootNode.Kind != yaml.MappingNode { + return errors.New("kubeconfig file is not a map document") + } + curCtxValNode := valueOf(k.rootNode, "current-context") + curCtxValNode.Value = "" + return nil +} diff --git a/cmd/kubectx/kubeconfig/currentcontext_test.go b/cmd/kubectx/kubeconfig/currentcontext_test.go new file mode 100644 index 0000000..8626615 --- /dev/null +++ b/cmd/kubectx/kubeconfig/currentcontext_test.go @@ -0,0 +1,58 @@ +package kubeconfig + +import ( + "strings" + "testing" +) + +func TestKubeconfig_GetCurrentContext(t *testing.T) { + tl := &testLoader{in: strings.NewReader(`current-context: foo`)} + kc := new(Kubeconfig).WithLoader(tl) + _, err := kc.ParseRaw() + if err != nil { + t.Fatal(err) + } + v := kc.GetCurrentContext() + + expected := "foo" + if v != expected { + t.Fatalf("expected=%q; got=%q", expected, v) + } +} + +func TestKubeconfig_GetCurrentContext_missingField(t *testing.T) { + tl := &testLoader{in: strings.NewReader(`abc: def`)} + kc := new(Kubeconfig).WithLoader(tl) + _, err := kc.ParseRaw() + if err != nil { + t.Fatal(err) + } + v := kc.GetCurrentContext() + + expected := "" + if v != expected { + t.Fatalf("expected=%q; got=%q", expected, v) + } +} + +func TestKubeconfig_UnsetCurrentContext(t *testing.T) { + tl := &testLoader{in: strings.NewReader(`current-context: foo`)} + kc := new(Kubeconfig).WithLoader(tl) + _, err := kc.ParseRaw() + if err != nil { + t.Fatal(err) + } + if err := kc.UnsetCurrentContext(); err != nil { + t.Fatal(err) + } + if err := kc.Save(); err != nil { + t.Fatal(err) + } + + out := tl.out.String() + expected := `current-context: "" +` + if out != expected { + t.Fatalf("expected=%q; got=%q", expected, out) + } +} diff --git a/cmd/kubectx/kubeconfig/helpers_test.go b/cmd/kubectx/kubeconfig/helpers_test.go new file mode 100644 index 0000000..001c20e --- /dev/null +++ b/cmd/kubectx/kubeconfig/helpers_test.go @@ -0,0 +1,17 @@ +package kubeconfig + +import ( + "bytes" + "strings" +) + +type testLoader struct { + in *strings.Reader + out bytes.Buffer +} + +func (t *testLoader) Read(p []byte) (n int, err error) { return t.in.Read(p) } +func (t *testLoader) Write(p []byte) (n int, err error) { return t.out.Write(p) } +func (t *testLoader) Close() error { return nil } +func (t *testLoader) Reset() error { return nil } +func (t *testLoader) Load() (ReadWriteResetCloser, error) { return t, nil } diff --git a/cmd/kubectx/list.go b/cmd/kubectx/list.go index ef41960..3d51aea 100644 --- a/cmd/kubectx/list.go +++ b/cmd/kubectx/list.go @@ -26,17 +26,17 @@ type ListOp struct{} func (_ ListOp) Run(stdout, _ io.Writer) error { kc := new(kubeconfig.Kubeconfig).WithLoader(defaultLoader) defer kc.Close() - rootNode, err := kc.ParseRaw() + _, err := kc.ParseRaw() if err != nil { return err } - ctxs := kubeconfig.ContextNames(rootNode) + ctxs := kc.ContextNames() natsort.Sort(ctxs) // TODO support KUBECTX_CURRENT_FGCOLOR // TODO support KUBECTX_CURRENT_BGCOLOR - cur := kubeconfig.GetCurrentContext(rootNode) + cur := kc.GetCurrentContext() for _, c := range ctxs { s := c if c == cur { diff --git a/cmd/kubectx/rename.go b/cmd/kubectx/rename.go index f2a38ad..97860f2 100644 --- a/cmd/kubectx/rename.go +++ b/cmd/kubectx/rename.go @@ -42,16 +42,16 @@ func (op RenameOp) Run(_, stderr io.Writer) error { return err } - cur := kubeconfig.GetCurrentContext(rootNode) + cur := kc.GetCurrentContext() if op.Old == "." { op.Old = cur } - if !checkContextExists(rootNode, op.Old) { + if !kc.ContextExists(op.Old) { return errors.Errorf("context %q not found, can't rename it", op.Old) } - if checkContextExists(rootNode, op.New) { + if kc.ContextExists( op.New) { printWarning(stderr, "context %q exists, overwriting it.", op.New) if err := modifyDocToDeleteContext(rootNode, op.New); err != nil { return errors.Wrap(err, "failed to delete new context to overwrite it") diff --git a/cmd/kubectx/switch.go b/cmd/kubectx/switch.go index 96117d7..fffcba3 100644 --- a/cmd/kubectx/switch.go +++ b/cmd/kubectx/switch.go @@ -44,8 +44,8 @@ func switchContext(name string) (string, error) { return "", err } - prev := kubeconfig.GetCurrentContext(rootNode) - if !checkContextExists(rootNode, name) { + prev := kc.GetCurrentContext() + if !kc.ContextExists(name) { return "", errors.Errorf("no context exists with the name: %q", name) } if err := modifyCurrentContext(rootNode, name); err != nil { @@ -81,16 +81,6 @@ func swapContext() (string, error) { } -func checkContextExists(rootNode *yaml.Node, name string) bool { - ctxNames := kubeconfig.ContextNames(rootNode) - for _, v := range ctxNames { - if v == name { - return true - } - } - return false -} - // TODO delete func valueOf(mapNode *yaml.Node, key string) *yaml.Node { if mapNode.Kind != yaml.MappingNode { diff --git a/cmd/kubectx/unset.go b/cmd/kubectx/unset.go index cb12259..a36075b 100644 --- a/cmd/kubectx/unset.go +++ b/cmd/kubectx/unset.go @@ -5,7 +5,6 @@ import ( "io" "github.com/pkg/errors" - "gopkg.in/yaml.v3" "github.com/ahmetb/kubectx/cmd/kubectx/kubeconfig" ) @@ -17,12 +16,12 @@ func (_ UnsetOp) Run(_, stderr io.Writer) error { kc := new(kubeconfig.Kubeconfig).WithLoader(defaultLoader) defer kc.Close() - rootNode, err := kc.ParseRaw() + _, err := kc.ParseRaw() if err != nil { return err } - if err := modifyDocToUnsetContext(rootNode); err != nil { + if err := kc.UnsetCurrentContext(); err != nil { return errors.Wrap(err, "error while modifying current-context") } if err := kc.Save(); err != nil { @@ -33,11 +32,3 @@ func (_ UnsetOp) Run(_, stderr io.Writer) error { return err } -func modifyDocToUnsetContext(rootNode *yaml.Node) error { - if rootNode.Kind != yaml.MappingNode { - return errors.New("kubeconfig file is not a map document") - } - curCtxValNode := valueOf(rootNode, "current-context") - curCtxValNode.Value = "" - return nil -}