mirror of
https://github.com/ahmetb/kubectx.git
synced 2025-06-22 05:31:58 +00:00
Add support for renaming contexts
Signed-off-by: Ahmet Alp Balkan <ahmetb@google.com>
This commit is contained in:
parent
5eabeab47e
commit
cb103701ac
@ -26,6 +26,12 @@ type DeleteOp struct {
|
||||
Contexts []string // NAME or '.' to indicate current-context.
|
||||
}
|
||||
|
||||
// RenameOp indicates intention to rename contexts.
|
||||
type RenameOp struct {
|
||||
New string // NAME of New context
|
||||
Old string // NAME of Old context (or '.' for current-context)
|
||||
}
|
||||
|
||||
// UnknownOp indicates an unsupported flag.
|
||||
type UnknownOp struct{ Args []string }
|
||||
|
||||
@ -53,6 +59,11 @@ func parseArgs(argv []string) Op {
|
||||
return UnsetOp{}
|
||||
}
|
||||
|
||||
new, old, ok := parseRenameSyntax(v) // a=b a=.
|
||||
if ok {
|
||||
return RenameOp{new, old}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(v, "-") && v != "-" {
|
||||
return UnknownOp{argv}
|
||||
}
|
||||
|
@ -51,6 +51,12 @@ func Test_parseArgs_new(t *testing.T) {
|
||||
{name: "delete - multiple contexts",
|
||||
args: []string{"-d", ".", "a", "b"},
|
||||
want: DeleteOp{[]string{".", "a", "b"}}},
|
||||
{name: "rename context",
|
||||
args: []string{"a=b"},
|
||||
want: RenameOp{"a", "b"}},
|
||||
{name: "rename context with old=current",
|
||||
args: []string{"a=."},
|
||||
want: RenameOp{"a", "."}},
|
||||
{name: "unrecognized flag",
|
||||
args: []string{"-x"},
|
||||
want: UnknownOp{Args: []string{"-x"}}},
|
||||
|
@ -16,6 +16,6 @@ func TestPrintHelp(t *testing.T) {
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(out, "\n") {
|
||||
t.Errorf("does not end with new line; output=%q", out)
|
||||
t.Errorf("does not end with New line; output=%q", out)
|
||||
}
|
||||
}
|
||||
|
@ -29,12 +29,17 @@ func main() {
|
||||
}
|
||||
case ListOp:
|
||||
if err := printListContexts(os.Stdout); err != nil {
|
||||
printError(err.Error())
|
||||
printError("%v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
case DeleteOp:
|
||||
if err := deleteContexts(os.Stderr, v.Contexts); err != nil {
|
||||
printError(err.Error())
|
||||
printError("%v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
case RenameOp:
|
||||
if err := rename(v.Old, v.New); err != nil {
|
||||
printError("failed to rename: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
case SwitchOp:
|
||||
|
99
cmd/kubectx/rename.go
Normal file
99
cmd/kubectx/rename.go
Normal file
@ -0,0 +1,99 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// parseRenameSyntax parses A=B form into [A,B] and returns
|
||||
// whether it is parsed correctly.
|
||||
func parseRenameSyntax(v string) (string, string, bool) {
|
||||
s := strings.Split(v, "=")
|
||||
if len(s) != 2 {
|
||||
return "", "", false
|
||||
}
|
||||
new, old := s[0], s[1]
|
||||
if new == "" || old == "" {
|
||||
return "", "", false
|
||||
}
|
||||
return new, old, true
|
||||
}
|
||||
|
||||
// rename changes the old (NAME or '.' for current-context)
|
||||
// to the "new" value. If the old refers to the current-context,
|
||||
// current-context preference is also updated.
|
||||
func rename(old, new string) error {
|
||||
f, rootNode, err := openKubeconfig()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
cur := getCurrentContext(rootNode)
|
||||
if old == "." {
|
||||
old = cur
|
||||
}
|
||||
|
||||
if !checkContextExists(rootNode, old) {
|
||||
return errors.Errorf("context %q not found, can't rename it", old)
|
||||
}
|
||||
|
||||
if checkContextExists(rootNode, new) {
|
||||
printWarning("context %q exists, overwriting it.", new)
|
||||
if err := modifyDocToDeleteContext(rootNode, new); err != nil {
|
||||
return errors.Wrap(err, "failed to delete new context to overwrite it")
|
||||
}
|
||||
}
|
||||
|
||||
if err := modifyContextName(rootNode, old, new); err != nil {
|
||||
return errors.Wrap(err, "failed to change context name")
|
||||
}
|
||||
|
||||
if old == cur {
|
||||
if err := modifyCurrentContext(rootNode, new); err != nil {
|
||||
return errors.Wrap(err, "failed to set current-context to new name")
|
||||
}
|
||||
}
|
||||
|
||||
if err := resetFile(f); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := saveKubeconfigRaw(f, rootNode); err != nil {
|
||||
return errors.Wrap(err, "failed to save modified kubeconfig")
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
// TODO use printSuccess
|
||||
// TODO consider moving printing logic to main
|
||||
fmt.Fprintf(os.Stderr, "Context %q renamed to %q.\n", old, new)
|
||||
return nil
|
||||
}
|
69
cmd/kubectx/rename_test.go
Normal file
69
cmd/kubectx/rename_test.go
Normal file
@ -0,0 +1,69 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
func Test_parseRenameSyntax(t *testing.T) {
|
||||
|
||||
type out struct {
|
||||
New string
|
||||
Old string
|
||||
OK bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
in string
|
||||
want out
|
||||
}{
|
||||
{
|
||||
name: "no equals sign",
|
||||
in: "foo",
|
||||
want: out{OK: false},
|
||||
},
|
||||
{
|
||||
name: "no left side",
|
||||
in: "=a",
|
||||
want: out{OK: false},
|
||||
},
|
||||
{
|
||||
name: "no right side",
|
||||
in: "a=",
|
||||
want: out{OK: false},
|
||||
},
|
||||
{
|
||||
name: "correct format",
|
||||
in: "a=b",
|
||||
want: out{
|
||||
New: "a",
|
||||
Old: "b",
|
||||
OK: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "correct format with current context",
|
||||
in: "NEW_NAME=.",
|
||||
want: out{
|
||||
New: "NEW_NAME",
|
||||
Old: ".",
|
||||
OK: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
new, old, ok := parseRenameSyntax(tt.in)
|
||||
got := out{
|
||||
New: new,
|
||||
Old: old,
|
||||
OK: ok,
|
||||
}
|
||||
diff := cmp.Diff(tt.want, got)
|
||||
if diff != "" {
|
||||
t.Errorf("parseRenameSyntax() diff=%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -125,7 +125,7 @@ func modifyCurrentContext(rootNode *yaml.Node, name string) error {
|
||||
}
|
||||
}
|
||||
|
||||
// if current-context ==> create new field
|
||||
// if current-context ==> create New field
|
||||
keyNode := &yaml.Node{
|
||||
Kind: yaml.ScalarNode,
|
||||
Value: "current-context",
|
||||
|
Loading…
Reference in New Issue
Block a user