mirror of
https://github.com/ahmetb/kubectx.git
synced 2025-07-04 02:56:12 +00:00
Move all yaml logic to pkg/kubeconfig
Signed-off-by: Ahmet Alp Balkan <ahmetb@google.com>
This commit is contained in:
parent
492e3e7053
commit
1f4eed962a
@ -15,15 +15,14 @@ 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()
|
||||||
_, err := kc.ParseRaw()
|
if err := kc.Parse(); err != nil {
|
||||||
if err != nil {
|
return errors.Wrap(err, "failed to parse kubeconfig")
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
v := kc.GetCurrentContext()
|
v := kc.GetCurrentContext()
|
||||||
if v == "" {
|
if v == "" {
|
||||||
return errors.New("current-context is not set")
|
return errors.New("current-context is not set")
|
||||||
}
|
}
|
||||||
_, err = fmt.Fprintln(stdout, v)
|
_, err := fmt.Fprintln(stdout, v)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,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"
|
||||||
)
|
)
|
||||||
@ -37,9 +36,8 @@ func (op DeleteOp) Run(_, stderr io.Writer) error {
|
|||||||
func deleteContext(name string) (deleteName string, wasActiveContext bool, err error) {
|
func deleteContext(name string) (deleteName string, wasActiveContext bool, err error) {
|
||||||
kc := new(kubeconfig.Kubeconfig).WithLoader(defaultLoader)
|
kc := new(kubeconfig.Kubeconfig).WithLoader(defaultLoader)
|
||||||
defer kc.Close()
|
defer kc.Close()
|
||||||
rootNode, err := kc.ParseRaw()
|
if err := kc.Parse(); err != nil {
|
||||||
if err != nil {
|
return "", false, errors.Wrap(err, "failed to parse kubeconfig")
|
||||||
return "", false, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cur := kc.GetCurrentContext()
|
cur := kc.GetCurrentContext()
|
||||||
@ -54,36 +52,9 @@ func deleteContext(name string) (deleteName string, wasActiveContext bool, err e
|
|||||||
return "", false, errors.New("context does not exist")
|
return "", false, errors.New("context does not exist")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := modifyDocToDeleteContext(rootNode, name); err != nil {
|
if err := kc.DeleteContextEntry(name); err != nil {
|
||||||
return "", false, errors.Wrap(err, "failed to modify yaml doc")
|
return "", false, errors.Wrap(err, "failed to modify yaml doc")
|
||||||
}
|
}
|
||||||
return name, wasActiveContext, errors.Wrap(kc.Save(), "failed to save kubeconfig file")
|
return name, wasActiveContext, errors.Wrap(kc.Save(), "failed to save kubeconfig file")
|
||||||
}
|
}
|
||||||
|
|
||||||
func modifyDocToDeleteContext(rootNode *yaml.Node, deleteName string) error {
|
|
||||||
if rootNode.Kind != yaml.MappingNode {
|
|
||||||
return errors.New("root node was not a mapping node")
|
|
||||||
}
|
|
||||||
contexts := valueOf(rootNode, "contexts")
|
|
||||||
if contexts == nil {
|
|
||||||
return errors.New("there are no contexts in kubeconfig")
|
|
||||||
}
|
|
||||||
if contexts.Kind != yaml.SequenceNode {
|
|
||||||
return errors.New("'contexts' key is not a sequence")
|
|
||||||
}
|
|
||||||
|
|
||||||
i := -1
|
|
||||||
for j, ctxNode := range contexts.Content {
|
|
||||||
nameNode := valueOf(ctxNode, "name")
|
|
||||||
if nameNode != nil && nameNode.Kind == yaml.ScalarNode && nameNode.Value == deleteName {
|
|
||||||
i = j
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if i >= 0 {
|
|
||||||
copy(contexts.Content[i:], contexts.Content[i+1:])
|
|
||||||
contexts.Content[len(contexts.Content)-1] = nil
|
|
||||||
contexts.Content = contexts.Content[:len(contexts.Content)-1]
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
@ -5,7 +5,6 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"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"
|
||||||
)
|
)
|
||||||
@ -69,19 +68,3 @@ func homeDir() string {
|
|||||||
}
|
}
|
||||||
return home
|
return home
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO parseKubeconfig doesn't seem necessary when there's a raw version that returns what's needed
|
|
||||||
func parseKubeconfig(path string) (kubeconfigContents, error) {
|
|
||||||
// TODO refactor to accept io.Reader instead of file
|
|
||||||
var v kubeconfigContents
|
|
||||||
|
|
||||||
f, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return v, nil
|
|
||||||
}
|
|
||||||
return v, errors.Wrap(err, "file open error")
|
|
||||||
}
|
|
||||||
err = yaml.NewDecoder(f).Decode(&v)
|
|
||||||
return v, errors.Wrap(err, "yaml parse error")
|
|
||||||
}
|
|
||||||
|
74
cmd/kubectx/kubeconfig/contextmodify.go
Normal file
74
cmd/kubectx/kubeconfig/contextmodify.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
package kubeconfig
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (k *Kubeconfig) DeleteContextEntry(deleteName string) error {
|
||||||
|
contexts := valueOf(k.rootNode, "contexts")
|
||||||
|
if contexts == nil {
|
||||||
|
return errors.New("there are no contexts in kubeconfig")
|
||||||
|
}
|
||||||
|
if contexts.Kind != yaml.SequenceNode {
|
||||||
|
return errors.New("'contexts' key is not a sequence")
|
||||||
|
}
|
||||||
|
|
||||||
|
i := -1
|
||||||
|
for j, ctxNode := range contexts.Content {
|
||||||
|
nameNode := valueOf(ctxNode, "name")
|
||||||
|
if nameNode != nil && nameNode.Kind == yaml.ScalarNode && nameNode.Value == deleteName {
|
||||||
|
i = j
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if i >= 0 {
|
||||||
|
copy(contexts.Content[i:], contexts.Content[i+1:])
|
||||||
|
contexts.Content[len(contexts.Content)-1] = nil
|
||||||
|
contexts.Content = contexts.Content[:len(contexts.Content)-1]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Kubeconfig) ModifyCurrentContext(name string) error {
|
||||||
|
currentCtxNode := valueOf(k.rootNode, "current-context")
|
||||||
|
if currentCtxNode != nil {
|
||||||
|
currentCtxNode.Value = name
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// if current-context field doesn't exist, create new field
|
||||||
|
keyNode := &yaml.Node{
|
||||||
|
Kind: yaml.ScalarNode,
|
||||||
|
Value: "current-context",
|
||||||
|
Tag: "!!str"}
|
||||||
|
valueNode := &yaml.Node{
|
||||||
|
Kind: yaml.ScalarNode,
|
||||||
|
Value: name,
|
||||||
|
Tag: "!!str"}
|
||||||
|
k.rootNode.Content = append(k.rootNode.Content, keyNode, valueNode)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Kubeconfig) ModifyContextName(old, new string) error {
|
||||||
|
contexts := valueOf(k.rootNode, "contexts")
|
||||||
|
if contexts == nil {
|
||||||
|
return errors.New("\"contexts\" entry is nil")
|
||||||
|
} else if contexts.Kind != yaml.SequenceNode {
|
||||||
|
return errors.New("\"contexts\" is not a sequence node")
|
||||||
|
}
|
||||||
|
|
||||||
|
var changed bool
|
||||||
|
for _, contextNode := range contexts.Content {
|
||||||
|
nameNode := valueOf(contextNode, "name")
|
||||||
|
if nameNode.Kind == yaml.ScalarNode && nameNode.Value == old {
|
||||||
|
nameNode.Value = new
|
||||||
|
changed = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !changed {
|
||||||
|
return errors.New("no changes were made")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
129
cmd/kubectx/kubeconfig/contextmodify_test.go
Normal file
129
cmd/kubectx/kubeconfig/contextmodify_test.go
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
package kubeconfig
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestKubeconfig_DeleteContextEntry_errors(t *testing.T) {
|
||||||
|
kc := new(Kubeconfig).WithLoader(&testLoader{in: strings.NewReader(`[1, 2, 3]`)})
|
||||||
|
_ = kc.Parse()
|
||||||
|
err := kc.DeleteContextEntry("foo")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("supposed to fail on non-mapping nodes")
|
||||||
|
}
|
||||||
|
|
||||||
|
kc = new(Kubeconfig).WithLoader(&testLoader{in: strings.NewReader(`a: b`)})
|
||||||
|
_ = kc.Parse()
|
||||||
|
err = kc.DeleteContextEntry("foo")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("supposed to fail if contexts key does not exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
kc = new(Kubeconfig).WithLoader(&testLoader{in: strings.NewReader(`contexts: "some string"`)})
|
||||||
|
_ = kc.Parse()
|
||||||
|
err = kc.DeleteContextEntry("foo")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("supposed to fail if contexts key is not an array")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKubeconfig_DeleteContextEntry(t *testing.T) {
|
||||||
|
test := &testLoader{in: strings.NewReader(
|
||||||
|
`contexts: [{name: c1}, {name: c2}, {name: c3}]`)}
|
||||||
|
kc := new(Kubeconfig).WithLoader(test)
|
||||||
|
if err := kc.Parse(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := kc.DeleteContextEntry("c1"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := kc.Save(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := "contexts: [{name: c2}, {name: c3}]\n"
|
||||||
|
out := test.out.String()
|
||||||
|
if diff := cmp.Diff(expected,out); diff != "" {
|
||||||
|
t.Fatalf("diff: %s", diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKubeconfig_ModifyCurrentContext_fieldExists(t *testing.T) {
|
||||||
|
test := &testLoader{in: strings.NewReader(
|
||||||
|
`current-context: abc
|
||||||
|
field1: value1`)}
|
||||||
|
kc := new(Kubeconfig).WithLoader(test)
|
||||||
|
if err := kc.Parse(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := kc.ModifyCurrentContext("foo"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := kc.Save(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := `current-context: foo
|
||||||
|
field1: value1`+"\n"
|
||||||
|
out := test.out.String()
|
||||||
|
if diff := cmp.Diff(expected,out); diff != "" {
|
||||||
|
t.Fatalf("diff: %s", diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKubeconfig_ModifyCurrentContext_fieldMissing(t *testing.T) {
|
||||||
|
test := &testLoader{in: strings.NewReader(
|
||||||
|
`field1: value1`)}
|
||||||
|
kc := new(Kubeconfig).WithLoader(test)
|
||||||
|
if err := kc.Parse(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := kc.ModifyCurrentContext("foo"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := kc.Save(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := `field1: value1` + "\n" + "current-context: foo\n"
|
||||||
|
out := test.out.String()
|
||||||
|
if diff := cmp.Diff(expected,out); diff != "" {
|
||||||
|
t.Fatalf("diff: %s", diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKubeconfig_ModifyContextName_noChange(t *testing.T) {
|
||||||
|
test := &testLoader{in: strings.NewReader(
|
||||||
|
`contexts: [{name: c1}, {name: c2}, {name: c3}]`)}
|
||||||
|
kc := new(Kubeconfig).WithLoader(test)
|
||||||
|
if err := kc.Parse(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := kc.ModifyContextName("c5", "c6"); err == nil {
|
||||||
|
t.Fatal("was expecting error for 'no changes made'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKubeconfig_ModifyContextName(t *testing.T) {
|
||||||
|
test := &testLoader{in: strings.NewReader(
|
||||||
|
`contexts: [{name: c1}, {name: c2}, {name: c3}]`)}
|
||||||
|
kc := new(Kubeconfig).WithLoader(test)
|
||||||
|
if err := kc.Parse(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := kc.ModifyContextName("c1", "ccc"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := kc.Save(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := "contexts: [{name: ccc}, {name: c2}, {name: c3}]\n"
|
||||||
|
out := test.out.String()
|
||||||
|
if diff := cmp.Diff(expected,out); diff != "" {
|
||||||
|
t.Fatalf("diff: %s", diff)
|
||||||
|
}
|
||||||
|
}
|
@ -18,10 +18,10 @@ contexts:
|
|||||||
bar: zoo`)}
|
bar: zoo`)}
|
||||||
|
|
||||||
kc := new(Kubeconfig).WithLoader(tl)
|
kc := new(Kubeconfig).WithLoader(tl)
|
||||||
_, err := kc.ParseRaw()
|
if err := kc.Parse(); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := kc.ContextNames()
|
ctx := kc.ContextNames()
|
||||||
expected := []string{"abc", "def", "ghi"}
|
expected := []string{"abc", "def", "ghi"}
|
||||||
if diff := cmp.Diff(expected, ctx); diff != "" {
|
if diff := cmp.Diff(expected, ctx); diff != "" {
|
||||||
@ -35,8 +35,7 @@ func TestKubeconfig_CheckContextExists(t *testing.T) {
|
|||||||
- name: c2`)}
|
- name: c2`)}
|
||||||
|
|
||||||
kc := new(Kubeconfig).WithLoader(tl)
|
kc := new(Kubeconfig).WithLoader(tl)
|
||||||
_, err := kc.ParseRaw()
|
if err := kc.Parse(); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,16 +1,8 @@
|
|||||||
package kubeconfig
|
package kubeconfig
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetCurrentContext returns "current-context" value in given
|
// GetCurrentContext returns "current-context" value in given
|
||||||
// kubeconfig object Node, or returns "" if not found.
|
// kubeconfig object Node, or returns "" if not found.
|
||||||
func (k *Kubeconfig) GetCurrentContext() string {
|
func (k *Kubeconfig) GetCurrentContext() string {
|
||||||
if k.rootNode.Kind != yaml.MappingNode {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
v := valueOf(k.rootNode, "current-context")
|
v := valueOf(k.rootNode, "current-context")
|
||||||
if v == nil {
|
if v == nil {
|
||||||
return ""
|
return ""
|
||||||
@ -19,9 +11,6 @@ func (k *Kubeconfig) GetCurrentContext() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (k *Kubeconfig) UnsetCurrentContext() error {
|
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 := valueOf(k.rootNode, "current-context")
|
||||||
curCtxValNode.Value = ""
|
curCtxValNode.Value = ""
|
||||||
return nil
|
return nil
|
||||||
|
@ -8,8 +8,7 @@ import (
|
|||||||
func TestKubeconfig_GetCurrentContext(t *testing.T) {
|
func TestKubeconfig_GetCurrentContext(t *testing.T) {
|
||||||
tl := &testLoader{in: strings.NewReader(`current-context: foo`)}
|
tl := &testLoader{in: strings.NewReader(`current-context: foo`)}
|
||||||
kc := new(Kubeconfig).WithLoader(tl)
|
kc := new(Kubeconfig).WithLoader(tl)
|
||||||
_, err := kc.ParseRaw()
|
if err := kc.Parse(); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
v := kc.GetCurrentContext()
|
v := kc.GetCurrentContext()
|
||||||
@ -23,8 +22,7 @@ func TestKubeconfig_GetCurrentContext(t *testing.T) {
|
|||||||
func TestKubeconfig_GetCurrentContext_missingField(t *testing.T) {
|
func TestKubeconfig_GetCurrentContext_missingField(t *testing.T) {
|
||||||
tl := &testLoader{in: strings.NewReader(`abc: def`)}
|
tl := &testLoader{in: strings.NewReader(`abc: def`)}
|
||||||
kc := new(Kubeconfig).WithLoader(tl)
|
kc := new(Kubeconfig).WithLoader(tl)
|
||||||
_, err := kc.ParseRaw()
|
if err := kc.Parse(); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
v := kc.GetCurrentContext()
|
v := kc.GetCurrentContext()
|
||||||
@ -38,8 +36,7 @@ func TestKubeconfig_GetCurrentContext_missingField(t *testing.T) {
|
|||||||
func TestKubeconfig_UnsetCurrentContext(t *testing.T) {
|
func TestKubeconfig_UnsetCurrentContext(t *testing.T) {
|
||||||
tl := &testLoader{in: strings.NewReader(`current-context: foo`)}
|
tl := &testLoader{in: strings.NewReader(`current-context: foo`)}
|
||||||
kc := new(Kubeconfig).WithLoader(tl)
|
kc := new(Kubeconfig).WithLoader(tl)
|
||||||
_, err := kc.ParseRaw()
|
if err := kc.Parse(); err != nil {
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := kc.UnsetCurrentContext(); err != nil {
|
if err := kc.UnsetCurrentContext(); err != nil {
|
||||||
|
@ -37,20 +37,23 @@ func (k *Kubeconfig) Close() error {
|
|||||||
return k.f.Close()
|
return k.f.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *Kubeconfig) ParseRaw() (*yaml.Node, error) {
|
func (k *Kubeconfig) Parse() error {
|
||||||
f, err := k.loader.Load()
|
f, err := k.loader.Load()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to load kubeconfig")
|
return errors.Wrap(err, "failed to load")
|
||||||
}
|
}
|
||||||
|
|
||||||
k.f = f
|
k.f = f
|
||||||
|
|
||||||
var v yaml.Node
|
var v yaml.Node
|
||||||
if err := yaml.NewDecoder(f).Decode(&v); err != nil {
|
if err := yaml.NewDecoder(f).Decode(&v); err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to decode kubeconfig")
|
return errors.Wrap(err, "failed to decode")
|
||||||
}
|
}
|
||||||
k.rootNode = v.Content[0]
|
k.rootNode = v.Content[0]
|
||||||
return k. rootNode, nil
|
if k.rootNode.Kind != yaml.MappingNode {
|
||||||
|
return errors.New("kubeconfig file is not a map document")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *Kubeconfig) Save() error {
|
func (k *Kubeconfig) Save() error {
|
||||||
|
23
cmd/kubectx/kubeconfig/kubeconfig_test.go
Normal file
23
cmd/kubectx/kubeconfig/kubeconfig_test.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package kubeconfig
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParse(t *testing.T) {
|
||||||
|
err := new(Kubeconfig).WithLoader(&testLoader{in: strings.NewReader(`a:b`)}).Parse()
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error from bad yaml")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = new(Kubeconfig).WithLoader(&testLoader{in: strings.NewReader(`[1, 2, 3]`)}).Parse()
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error from not-mapping root node")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = new(Kubeconfig).WithLoader(&testLoader{in: strings.NewReader(`current-context: foo`)}).Parse()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
@ -6,8 +6,6 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_kubeconfigPath_homePath(t *testing.T) {
|
func Test_kubeconfigPath_homePath(t *testing.T) {
|
||||||
@ -77,63 +75,6 @@ func Test_kubeconfigPath_envOvverideDoesNotSupportPathSeparator(t *testing.T) {
|
|||||||
if err == nil { t.Fatal("expected error")}
|
if err == nil { t.Fatal("expected error")}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func Test_parseKubeconfig_openError(t *testing.T) {
|
|
||||||
_, err := parseKubeconfig("/non/existing/path")
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("expected error")
|
|
||||||
}
|
|
||||||
msg := err.Error()
|
|
||||||
expected := `file open error`
|
|
||||||
if !strings.Contains(msg, expected) {
|
|
||||||
t.Fatalf("expected=%q, got=%q", expected, msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_parseKubeconfig_yamlFormatError(t *testing.T) {
|
|
||||||
file, cleanup := testfile(t, `a: [1,2 `) // supposed to be invalid yaml
|
|
||||||
defer cleanup()
|
|
||||||
|
|
||||||
_, err := parseKubeconfig(file)
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("expected error")
|
|
||||||
}
|
|
||||||
expected := "yaml parse error"
|
|
||||||
if got := err.Error(); !strings.Contains(got, expected) {
|
|
||||||
t.Fatalf("expected=%q; got=%q", expected, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_parseKubeconfig(t *testing.T) {
|
|
||||||
file, cleanup := testfile(t,
|
|
||||||
`
|
|
||||||
apiVersion: v1
|
|
||||||
current-context: foo
|
|
||||||
contexts:
|
|
||||||
- name: c3
|
|
||||||
- name: c2
|
|
||||||
- name: c1`)
|
|
||||||
defer cleanup()
|
|
||||||
|
|
||||||
got, err := parseKubeconfig(file)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := kubeconfig{
|
|
||||||
APIVersion: "v1",
|
|
||||||
CurrentContext: "foo",
|
|
||||||
Contexts: []context{
|
|
||||||
{Name:"c3"},{Name:"c2"},{Name:"c1"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
diff := cmp.Diff(expected, got)
|
|
||||||
if diff != "" {
|
|
||||||
t.Fatalf("got wrong object:\n%s", diff)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testfile(t *testing.T, contents string) (path string, cleanup func()) {
|
func testfile(t *testing.T, contents string) (path string, cleanup func()) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"facette.io/natsort"
|
"facette.io/natsort"
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/ahmetb/kubectx/cmd/kubectx/kubeconfig"
|
"github.com/ahmetb/kubectx/cmd/kubectx/kubeconfig"
|
||||||
)
|
)
|
||||||
@ -26,9 +27,8 @@ 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()
|
||||||
_, err := kc.ParseRaw()
|
if err := kc.Parse(); err != nil {
|
||||||
if err != nil {
|
return errors.Wrap(err, "failed to parse kubeconfig")
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctxs := kc.ContextNames()
|
ctxs := kc.ContextNames()
|
||||||
|
@ -5,7 +5,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"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"
|
||||||
)
|
)
|
||||||
@ -36,10 +35,8 @@ func parseRenameSyntax(v string) (string, string, bool) {
|
|||||||
func (op RenameOp) Run(_, stderr io.Writer) error {
|
func (op RenameOp) Run(_, stderr io.Writer) error {
|
||||||
kc := new(kubeconfig.Kubeconfig).WithLoader(defaultLoader)
|
kc := new(kubeconfig.Kubeconfig).WithLoader(defaultLoader)
|
||||||
defer kc.Close()
|
defer kc.Close()
|
||||||
|
if err := kc.Parse(); err != nil {
|
||||||
rootNode, err := kc.ParseRaw()
|
return errors.Wrap(err, "failed to parse kubeconfig")
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cur := kc.GetCurrentContext()
|
cur := kc.GetCurrentContext()
|
||||||
@ -53,16 +50,16 @@ func (op RenameOp) Run(_, stderr io.Writer) error {
|
|||||||
|
|
||||||
if kc.ContextExists( 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 := kc.DeleteContextEntry(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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := modifyContextName(rootNode, op.Old, op.New); err != nil {
|
if err := kc.ModifyContextName(op.Old, op.New); err != nil {
|
||||||
return errors.Wrap(err, "failed to change context name")
|
return errors.Wrap(err, "failed to change context name")
|
||||||
}
|
}
|
||||||
if op.New == cur {
|
if op.New == cur {
|
||||||
if err := modifyCurrentContext(rootNode, op.New); err != nil {
|
if err := kc.ModifyCurrentContext( op.New); err != nil {
|
||||||
return errors.Wrap(err, "failed to set current-context to new name")
|
return errors.Wrap(err, "failed to set current-context to new name")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -73,28 +70,3 @@ func (op RenameOp) Run(_, stderr io.Writer) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func modifyContextName(rootNode *yaml.Node, old, new string) error {
|
|
||||||
if rootNode.Kind != yaml.MappingNode {
|
|
||||||
return errors.New("root doc is not a mapping node")
|
|
||||||
}
|
|
||||||
contexts := valueOf(rootNode, "contexts")
|
|
||||||
if contexts == nil {
|
|
||||||
return errors.New("\"contexts\" entry is nil")
|
|
||||||
} else if contexts.Kind != yaml.SequenceNode {
|
|
||||||
return errors.New("\"contexts\" is not a sequence node")
|
|
||||||
}
|
|
||||||
|
|
||||||
var changed bool
|
|
||||||
for _, contextNode := range contexts.Content {
|
|
||||||
nameNode := valueOf(contextNode, "name")
|
|
||||||
if nameNode.Kind == yaml.ScalarNode && nameNode.Value == old {
|
|
||||||
nameNode.Value = new
|
|
||||||
changed = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !changed {
|
|
||||||
return errors.New("no changes were made")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
@ -4,7 +4,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"
|
||||||
)
|
)
|
||||||
@ -14,7 +13,7 @@ type SwitchOp struct {
|
|||||||
Target string // '-' for back and forth, or NAME
|
Target string // '-' for back and forth, or NAME
|
||||||
}
|
}
|
||||||
|
|
||||||
func (op SwitchOp) Run(stdout, stderr io.Writer) error {
|
func (op SwitchOp) Run(_, stderr io.Writer) error {
|
||||||
var newCtx string
|
var newCtx string
|
||||||
var err error
|
var err error
|
||||||
if op.Target == "-" {
|
if op.Target == "-" {
|
||||||
@ -38,17 +37,15 @@ func switchContext(name string) (string, error) {
|
|||||||
|
|
||||||
kc := new(kubeconfig.Kubeconfig).WithLoader(defaultLoader)
|
kc := new(kubeconfig.Kubeconfig).WithLoader(defaultLoader)
|
||||||
defer kc.Close()
|
defer kc.Close()
|
||||||
|
if err := kc.Parse(); err != nil {
|
||||||
rootNode, err := kc.ParseRaw()
|
return "", errors.Wrap(err, "failed to parse kubeconfig")
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
prev := kc.GetCurrentContext()
|
prev := kc.GetCurrentContext()
|
||||||
if !kc.ContextExists(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 := kc.ModifyCurrentContext(name); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if err := kc.Save(); err != nil {
|
if err := kc.Save(); err != nil {
|
||||||
@ -79,45 +76,3 @@ func swapContext() (string, error) {
|
|||||||
}
|
}
|
||||||
return switchContext(prev)
|
return switchContext(prev)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// TODO delete
|
|
||||||
func valueOf(mapNode *yaml.Node, key string) *yaml.Node {
|
|
||||||
if mapNode.Kind != yaml.MappingNode {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
for i, ch := range mapNode.Content {
|
|
||||||
if i%2 == 0 && ch.Kind == yaml.ScalarNode && ch.Value == key {
|
|
||||||
return mapNode.Content[i+1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func modifyCurrentContext(rootNode *yaml.Node, name string) error {
|
|
||||||
if rootNode.Kind != yaml.MappingNode {
|
|
||||||
return errors.New("document is not a map")
|
|
||||||
}
|
|
||||||
|
|
||||||
// find current-context field => modify value (next children)
|
|
||||||
for i, ch := range rootNode.Content {
|
|
||||||
if i%2 == 0 && ch.Value == "current-context" {
|
|
||||||
rootNode.Content[i+1].Value = name
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if current-context ==> create New field
|
|
||||||
keyNode := &yaml.Node{
|
|
||||||
Kind: yaml.ScalarNode,
|
|
||||||
Value: "current-context",
|
|
||||||
Tag: "!!str"}
|
|
||||||
valueNode := &yaml.Node{
|
|
||||||
Kind: yaml.ScalarNode,
|
|
||||||
Value: name,
|
|
||||||
Tag: "!!str"}
|
|
||||||
rootNode.Content = append(rootNode.Content, keyNode, valueNode)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
@ -15,10 +15,8 @@ type UnsetOp struct{}
|
|||||||
func (_ UnsetOp) Run(_, stderr io.Writer) error {
|
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()
|
||||||
|
if err := kc.Parse(); err != nil {
|
||||||
_, err := kc.ParseRaw()
|
return errors.Wrap(err, "failed to parse kubeconfig")
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := kc.UnsetCurrentContext(); err != nil {
|
if err := kc.UnsetCurrentContext(); err != nil {
|
||||||
@ -28,7 +26,7 @@ func (_ UnsetOp) Run(_, stderr io.Writer) error {
|
|||||||
return errors.Wrap(err, "failed to save kubeconfig file after modification")
|
return errors.Wrap(err, "failed to save kubeconfig file after modification")
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = fmt.Fprintln(stderr, "Successfully unset the current context")
|
_, err := fmt.Fprintln(stderr, "Successfully unset the current context")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user