diff --git a/cmd/kubectx/main.go b/cmd/kubectx/main.go index f21e9f4..bd89c04 100644 --- a/cmd/kubectx/main.go +++ b/cmd/kubectx/main.go @@ -20,13 +20,15 @@ func main() { case ListOp: printListContexts(os.Stdout) case SwitchOp: + var newCtx string + var err error if v.Target == "-" { - // TODO implement swap - panic("not implemented") - } - newCtx, err := switchContext(v.Target) + newCtx, err = swapContext() + } else { + newCtx, err = switchContext(v.Target) + } if err != nil { - printError("faield to switch context: %v", err) + printError("failed to switch context: %v", err) os.Exit(1) } fmt.Fprintf(os.Stderr, "Switched to context %q.\n", newCtx) @@ -40,5 +42,5 @@ func main() { } func printError(format string, args ...interface{}) { - fmt.Fprintf(os.Stderr, color.RedString("error: "+format+"\n"), args...) + fmt.Fprintf(os.Stderr, color.RedString("error: ")+format+"\n", args...) } diff --git a/cmd/kubectx/switch.go b/cmd/kubectx/switch.go index 8093d14..0cc9304 100644 --- a/cmd/kubectx/switch.go +++ b/cmd/kubectx/switch.go @@ -32,11 +32,16 @@ func switchContext(name string) (string, error) { prev := getCurrentContext(kc) + // TODO: add a check to ensure user can't switch to non-existing context. + if !checkContextExists(kc, name) { + return "", errors.Errorf("no context exists with the name: %q", name) + } + if err := modifyCurrentContext(kc, name); err != nil { return "", err } - if err := f.Truncate(0); err != nil { + if err := f.Truncate(0); err != nil { return "", errors.Wrap(err, "failed to truncate") } @@ -57,6 +62,58 @@ func switchContext(name string) (string, error) { return name, nil } +func checkContextExists(rootNode *yaml.Node, name string) bool { + contexts := valueOf(rootNode, "contexts") + if contexts == nil { + return false + } + if contexts.Kind != yaml.SequenceNode { + return false + } + + var ctxNames []string + for _, ctx := range contexts.Content { + nameVal := valueOf(ctx, "name") + if nameVal != nil { + ctxNames = append(ctxNames, nameVal.Value) + } + } + + for _, v := range ctxNames { + if v == name { + return true + } + } + return false +} + +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 +} + +// swapContext switches to previously switch context. +func swapContext() (string, error) { + stateFile, err := kubectxFilePath() + if err != nil { + return "", errors.Wrap(err, "failed to determine state file") + } + prev, err := readLastContext(stateFile) + if err != nil { + return "", errors.Wrap(err, "failed to read previous context file") + } + if prev == "" { + return "", errors.New("no previous context found") + } + return switchContext(prev) +} // getCurrentContext returns "current-context" value in given // kubeconfig object Node, or returns "" if not found. @@ -64,12 +121,11 @@ func getCurrentContext(rootNode *yaml.Node) string { if rootNode.Kind != yaml.MappingNode { return "" } - for i, ch := range rootNode.Content { - if i%2 == 0 && ch.Value == "current-context" { - return rootNode.Content[i+1].Value - } + v := valueOf(rootNode, "current-context") + if v == nil { + return "" } - return "" + return v.Value } func modifyCurrentContext(rootNode *yaml.Node, name string) error {