Move ctx-related YAML parse methods to pkg

Signed-off-by: Ahmet Alp Balkan <ahmetb@google.com>
This commit is contained in:
Ahmet Alp Balkan 2020-04-12 13:46:14 -07:00
parent 7013899503
commit 492e3e7053
No known key found for this signature in database
GPG Key ID: 441833503E604E2C
11 changed files with 181 additions and 46 deletions

View File

@ -15,12 +15,12 @@ type CurrentOp struct{}
func (_op CurrentOp) Run(stdout, _ io.Writer) error { func (_op CurrentOp) Run(stdout, _ io.Writer) error {
kc := new(kubeconfig.Kubeconfig).WithLoader(defaultLoader) kc := new(kubeconfig.Kubeconfig).WithLoader(defaultLoader)
defer kc.Close() defer kc.Close()
rootNode, err := kc.ParseRaw() _, err := kc.ParseRaw()
if err != nil { if err != nil {
return err return err
} }
v := kubeconfig.GetCurrentContext(rootNode) v := kc.GetCurrentContext()
if v == "" { if v == "" {
return errors.New("current-context is not set") return errors.New("current-context is not set")
} }

View File

@ -42,7 +42,7 @@ func deleteContext(name string) (deleteName string, wasActiveContext bool, err e
return "", false, err return "", false, err
} }
cur := kubeconfig.GetCurrentContext(rootNode) cur := kc.GetCurrentContext()
// resolve "." to a real name // resolve "." to a real name
if name == "." { if name == "." {
@ -50,7 +50,7 @@ func deleteContext(name string) (deleteName string, wasActiveContext bool, err e
name = cur name = cur
} }
if !checkContextExists(rootNode, name) { if !kc.ContextExists(name) {
return "", false, errors.New("context does not exist") return "", false, errors.New("context does not exist")
} }

View File

@ -1,9 +1,11 @@
package kubeconfig package kubeconfig
import "gopkg.in/yaml.v3" import (
"gopkg.in/yaml.v3"
)
func ContextNames(rootNode *yaml.Node) []string { func (k *Kubeconfig) ContextNames() []string {
contexts := valueOf(rootNode, "contexts") contexts := valueOf(k.rootNode, "contexts")
if contexts == nil { if contexts == nil {
return nil return nil
} }
@ -21,17 +23,14 @@ func ContextNames(rootNode *yaml.Node) []string {
return ctxNames return ctxNames
} }
// GetCurrentContext returns "current-context" value in given func (k *Kubeconfig) ContextExists(name string) bool {
// kubeconfig object Node, or returns "" if not found. ctxNames := k.ContextNames()
func GetCurrentContext(rootNode *yaml.Node) string { for _, v := range ctxNames {
if rootNode.Kind != yaml.MappingNode { if v == name {
return "" return true
}
} }
v := valueOf(rootNode, "current-context") return false
if v == nil {
return ""
}
return v.Value
} }
func valueOf(mapNode *yaml.Node, key string) *yaml.Node { func valueOf(mapNode *yaml.Node, key string) *yaml.Node {

View File

@ -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")
}
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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 }

View File

@ -26,17 +26,17 @@ type ListOp struct{}
func (_ ListOp) Run(stdout, _ io.Writer) error { func (_ ListOp) Run(stdout, _ io.Writer) error {
kc := new(kubeconfig.Kubeconfig).WithLoader(defaultLoader) kc := new(kubeconfig.Kubeconfig).WithLoader(defaultLoader)
defer kc.Close() defer kc.Close()
rootNode, err := kc.ParseRaw() _, err := kc.ParseRaw()
if err != nil { if err != nil {
return err return err
} }
ctxs := kubeconfig.ContextNames(rootNode) ctxs := kc.ContextNames()
natsort.Sort(ctxs) natsort.Sort(ctxs)
// TODO support KUBECTX_CURRENT_FGCOLOR // TODO support KUBECTX_CURRENT_FGCOLOR
// TODO support KUBECTX_CURRENT_BGCOLOR // TODO support KUBECTX_CURRENT_BGCOLOR
cur := kubeconfig.GetCurrentContext(rootNode) cur := kc.GetCurrentContext()
for _, c := range ctxs { for _, c := range ctxs {
s := c s := c
if c == cur { if c == cur {

View File

@ -42,16 +42,16 @@ func (op RenameOp) Run(_, stderr io.Writer) error {
return err return err
} }
cur := kubeconfig.GetCurrentContext(rootNode) cur := kc.GetCurrentContext()
if op.Old == "." { if op.Old == "." {
op.Old = cur 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) 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) printWarning(stderr, "context %q exists, overwriting it.", op.New)
if err := modifyDocToDeleteContext(rootNode, op.New); err != nil { if err := modifyDocToDeleteContext(rootNode, op.New); err != nil {
return errors.Wrap(err, "failed to delete new context to overwrite it") return errors.Wrap(err, "failed to delete new context to overwrite it")

View File

@ -44,8 +44,8 @@ func switchContext(name string) (string, error) {
return "", err return "", err
} }
prev := kubeconfig.GetCurrentContext(rootNode) prev := kc.GetCurrentContext()
if !checkContextExists(rootNode, name) { if !kc.ContextExists(name) {
return "", errors.Errorf("no context exists with the name: %q", name) return "", errors.Errorf("no context exists with the name: %q", name)
} }
if err := modifyCurrentContext(rootNode, name); err != nil { 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 // TODO delete
func valueOf(mapNode *yaml.Node, key string) *yaml.Node { func valueOf(mapNode *yaml.Node, key string) *yaml.Node {
if mapNode.Kind != yaml.MappingNode { if mapNode.Kind != yaml.MappingNode {

View File

@ -5,7 +5,6 @@ import (
"io" "io"
"github.com/pkg/errors" "github.com/pkg/errors"
"gopkg.in/yaml.v3"
"github.com/ahmetb/kubectx/cmd/kubectx/kubeconfig" "github.com/ahmetb/kubectx/cmd/kubectx/kubeconfig"
) )
@ -17,12 +16,12 @@ func (_ UnsetOp) Run(_, stderr io.Writer) error {
kc := new(kubeconfig.Kubeconfig).WithLoader(defaultLoader) kc := new(kubeconfig.Kubeconfig).WithLoader(defaultLoader)
defer kc.Close() defer kc.Close()
rootNode, err := kc.ParseRaw() _, err := kc.ParseRaw()
if err != nil { if err != nil {
return err return err
} }
if err := modifyDocToUnsetContext(rootNode); err != nil { if err := kc.UnsetCurrentContext(); err != nil {
return errors.Wrap(err, "error while modifying current-context") return errors.Wrap(err, "error while modifying current-context")
} }
if err := kc.Save(); err != nil { if err := kc.Save(); err != nil {
@ -33,11 +32,3 @@ func (_ UnsetOp) Run(_, stderr io.Writer) error {
return err 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
}