From 5ec2f4f032d41f3625f58e9b8171e9fbe184e0c7 Mon Sep 17 00:00:00 2001 From: Ahmet Alp Balkan Date: Fri, 10 Apr 2020 15:38:50 -0700 Subject: [PATCH] Support for -d (deleting contexts) Signed-off-by: Ahmet Alp Balkan --- cmd/kubectx/delete.go | 85 +++++++++++++++++++++++++++++++++++++++ cmd/kubectx/flags.go | 10 +++++ cmd/kubectx/flags_test.go | 9 +++++ cmd/kubectx/main.go | 9 +++++ 4 files changed, 113 insertions(+) create mode 100644 cmd/kubectx/delete.go diff --git a/cmd/kubectx/delete.go b/cmd/kubectx/delete.go new file mode 100644 index 0000000..47f47c7 --- /dev/null +++ b/cmd/kubectx/delete.go @@ -0,0 +1,85 @@ +package main + +import ( + "fmt" + "io" + + "github.com/pkg/errors" + "gopkg.in/yaml.v3" +) + +// deleteContexts deletes context entries one by one. +func deleteContexts(w io.Writer, ctxs []string) error { + for _, ctx := range ctxs { + // TODO inefficency here. we open/write/close the same file many times. + deletedName, wasActiveContext, err := deleteContext(ctx) + if err != nil { + return errors.Wrapf(err, "error deleting context %q", ctx) + } + if wasActiveContext { + // TODO we don't always run as kubectx (sometimes "kubectl ctx") + printWarning("You deleted the current context. use \"kubectx\" to select a different one.") + } + fmt.Fprintf(w, "deleted context %q\n", deletedName) // TODO write with printSuccess (i.e. green) + } + return nil +} + +// deleteContext deletes a context entry by NAME or current-context +// indicated by ".". +func deleteContext(name string) (deleteName string, wasActiveContext bool, err error) { + f, rootNode, err := openKubeconfig() + if err != nil { + return "", false, err + } + defer f.Close() + + cur := getCurrentContext(rootNode) + + // resolve "." to a real name + if name == "." { + wasActiveContext = true + name = cur + } + + if !checkContextExists(rootNode, name) { + return "", false, errors.New("context does not exist") + } + + if err := modifyDocToDeleteContext(rootNode, name); err != nil { + return "", false, errors.Wrap(err, "failed to modify yaml doc") + } + + if err := resetFile(f); err != nil { + return "", false, err + } + return name, wasActiveContext, errors.Wrap(saveKubeconfigRaw(f, rootNode), "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 +} diff --git a/cmd/kubectx/flags.go b/cmd/kubectx/flags.go index 7b6099b..644e708 100644 --- a/cmd/kubectx/flags.go +++ b/cmd/kubectx/flags.go @@ -21,6 +21,11 @@ type SwitchOp struct { // UnsetOp indicates intention to remove current-context preference. type UnsetOp struct{} +// DeleteOp indicates intention to delete contexts. +type DeleteOp struct { + Contexts []string // NAME or '.' to indicate current-context. +} + // UnknownOp indicates an unsupported flag. type UnknownOp struct{ Args []string } @@ -31,6 +36,11 @@ func parseArgs(argv []string) Op { return ListOp{} } + if argv[0] == "-d" { + ctxs := argv[1:] + return DeleteOp{ctxs} + } + if len(argv) == 1 { v := argv[0] if v == "--help" || v == "-h" { diff --git a/cmd/kubectx/flags_test.go b/cmd/kubectx/flags_test.go index 3a6919d..f2b94ac 100644 --- a/cmd/kubectx/flags_test.go +++ b/cmd/kubectx/flags_test.go @@ -42,6 +42,15 @@ func Test_parseArgs_new(t *testing.T) { {name: "switch by swap", args: []string{"-"}, want: SwitchOp{Target: "-"}}, + {name: "delete - without contexts", + args: []string{"-d"}, + want: DeleteOp{[]string{}}}, + {name: "delete - current context", + args: []string{"-d", "."}, + want: DeleteOp{[]string{"."}}}, + {name: "delete - multiple contexts", + args: []string{"-d", ".", "a", "b"}, + want: DeleteOp{[]string{".", "a", "b"}}}, {name: "unrecognized flag", args: []string{"-x"}, want: UnknownOp{Args: []string{"-x"}}}, diff --git a/cmd/kubectx/main.go b/cmd/kubectx/main.go index e5a649e..9693d40 100644 --- a/cmd/kubectx/main.go +++ b/cmd/kubectx/main.go @@ -32,6 +32,11 @@ func main() { printError(err.Error()) os.Exit(1) } + case DeleteOp: + if err := deleteContexts(os.Stderr, v.Contexts); err != nil { + printError(err.Error()) + os.Exit(1) + } case SwitchOp: var newCtx string var err error @@ -57,3 +62,7 @@ func main() { func printError(format string, args ...interface{}) { fmt.Fprintf(os.Stderr, color.RedString("error: ")+format+"\n", args...) } + +func printWarning(format string, args ...interface{}) { + fmt.Fprintf(os.Stderr, color.YellowString("warning: ")+format+"\n", args...) +}