From 32d65fc527f9d5dc7040a208551d844d39504a5d Mon Sep 17 00:00:00 2001 From: Ahmet Alp Balkan Date: Fri, 10 Apr 2020 15:04:31 -0700 Subject: [PATCH] Add support for -u/--unset Signed-off-by: Ahmet Alp Balkan --- cmd/kubectx/flags.go | 6 ++++ cmd/kubectx/flags_test.go | 6 ++++ cmd/kubectx/kubeconfig_raw.go | 52 +++++++++++++++++++++++++++++++++++ cmd/kubectx/main.go | 5 ++++ cmd/kubectx/switch.go | 38 +++---------------------- cmd/kubectx/unset.go | 34 +++++++++++++++++++++++ 6 files changed, 107 insertions(+), 34 deletions(-) create mode 100644 cmd/kubectx/kubeconfig_raw.go create mode 100644 cmd/kubectx/unset.go diff --git a/cmd/kubectx/flags.go b/cmd/kubectx/flags.go index 9f0cbed..7b6099b 100644 --- a/cmd/kubectx/flags.go +++ b/cmd/kubectx/flags.go @@ -18,6 +18,9 @@ type SwitchOp struct { Target string // '-' for back and forth, or NAME } +// UnsetOp indicates intention to remove current-context preference. +type UnsetOp struct{} + // UnknownOp indicates an unsupported flag. type UnknownOp struct{ Args []string } @@ -36,6 +39,9 @@ func parseArgs(argv []string) Op { if v == "--current" || v == "-c" { return CurrentOp{} } + if v == "--unset" || v == "-u" { + return UnsetOp{} + } if strings.HasPrefix(v, "-") && v != "-" { return UnknownOp{argv} diff --git a/cmd/kubectx/flags_test.go b/cmd/kubectx/flags_test.go index 06c7b29..3a6919d 100644 --- a/cmd/kubectx/flags_test.go +++ b/cmd/kubectx/flags_test.go @@ -30,6 +30,12 @@ func Test_parseArgs_new(t *testing.T) { {name: "current long form", args: []string{"--current"}, want: CurrentOp{}}, + {name: "unset shorthand", + args: []string{"-u"}, + want: UnsetOp{}}, + {name: "unset long form", + args: []string{"--unset"}, + want: UnsetOp{}}, {name: "switch by name", args: []string{"foo"}, want: SwitchOp{Target: "foo"}}, diff --git a/cmd/kubectx/kubeconfig_raw.go b/cmd/kubectx/kubeconfig_raw.go new file mode 100644 index 0000000..53ed227 --- /dev/null +++ b/cmd/kubectx/kubeconfig_raw.go @@ -0,0 +1,52 @@ +package main + +import ( + "io" + "os" + + "github.com/pkg/errors" + "gopkg.in/yaml.v3" +) + +func parseKubeconfigRaw(r io.Reader) (*yaml.Node, error) { + var v yaml.Node + if err := yaml.NewDecoder(r).Decode(&v); err != nil { + return nil, err + } + return v.Content[0], nil +} + +func saveKubeconfigRaw(w io.Writer, rootNode *yaml.Node) error { + enc := yaml.NewEncoder(w) + enc.SetIndent(2) + return enc.Encode(rootNode) +} + +func openKubeconfig() (f *os.File, rootNode *yaml.Node, err error) { + cfgPath, err := kubeconfigPath() + if err != nil { + return nil, nil, errors.Wrap(err, "cannot determine kubeconfig path") + } + f, err = os.OpenFile(cfgPath, os.O_RDWR, 0) + if err != nil { + return nil, nil, errors.Wrap(err, "failed to open file") + } + + kc, err := parseKubeconfigRaw(f) + if err != nil { + f.Close() + return nil, nil, errors.Wrap(err, "yaml parse error") + } + return f, kc, nil +} + +// resetFile deletes contents of a file and sets the seek +// position to 0. +func resetFile(f *os.File) error { + if err := f.Truncate(0); err != nil { + return errors.Wrap(err, "failed to truncate") + } + + _, err := f.Seek(0, 0) + return errors.Wrap(err, "failed to seek") +} diff --git a/cmd/kubectx/main.go b/cmd/kubectx/main.go index 7340132..e5a649e 100644 --- a/cmd/kubectx/main.go +++ b/cmd/kubectx/main.go @@ -22,6 +22,11 @@ func main() { printError(err.Error()) os.Exit(1) } + case UnsetOp: + if err := unsetContext(); err != nil { + printError(err.Error()) + os.Exit(1) + } case ListOp: if err := printListContexts(os.Stdout); err != nil { printError(err.Error()) diff --git a/cmd/kubectx/switch.go b/cmd/kubectx/switch.go index 0cc9304..5b6f982 100644 --- a/cmd/kubectx/switch.go +++ b/cmd/kubectx/switch.go @@ -1,9 +1,6 @@ package main import ( - "io" - "os" - "github.com/pkg/errors" "gopkg.in/yaml.v3" ) @@ -15,21 +12,12 @@ func switchContext(name string) (string, error) { return "", errors.Wrap(err, "failed to determine state file") } - cfgPath, err := kubeconfigPath() + f, kc, err := openKubeconfig() if err != nil { - return "", errors.Wrap(err, "cannot determine kubeconfig path") - } - f, err := os.OpenFile(cfgPath, os.O_RDWR, 0) - if err != nil { - return "", errors.Wrap(err, "failed to open file") + return "", err } defer f.Close() - kc, err := parseKubeconfigRaw(f) - if err != nil { - return "", errors.Wrap(err, "yaml parse error") - } - prev := getCurrentContext(kc) // TODO: add a check to ensure user can't switch to non-existing context. @@ -41,12 +29,8 @@ func switchContext(name string) (string, error) { return "", err } - if err := f.Truncate(0); err != nil { - return "", errors.Wrap(err, "failed to truncate") - } - - if _, err := f.Seek(0, 0); err != nil { - return "", errors.Wrap(err, "failed to seek") + if err := resetFile(f); err != nil { + return "", err } if err := saveKubeconfigRaw(f, kc); err != nil { @@ -153,17 +137,3 @@ func modifyCurrentContext(rootNode *yaml.Node, name string) error { rootNode.Content = append(rootNode.Content, keyNode, valueNode) return nil } - -func parseKubeconfigRaw(r io.Reader) (*yaml.Node, error) { - var v yaml.Node - if err := yaml.NewDecoder(r).Decode(&v); err != nil { - return nil, err - } - return v.Content[0], nil -} - -func saveKubeconfigRaw(w io.Writer, rootNode *yaml.Node) error { - enc := yaml.NewEncoder(w) - enc.SetIndent(2) - return enc.Encode(rootNode) -} diff --git a/cmd/kubectx/unset.go b/cmd/kubectx/unset.go new file mode 100644 index 0000000..c9002ee --- /dev/null +++ b/cmd/kubectx/unset.go @@ -0,0 +1,34 @@ +package main + +import ( + "github.com/pkg/errors" + "gopkg.in/yaml.v3" +) + +func unsetContext() error { + f, rootNode, err := openKubeconfig() + if err != nil { + return err + } + defer f.Close() + + if err := modifyDocToUnsetContext(rootNode); err != nil { + return errors.Wrap(err, "error while modifying current-context") + } + if err := resetFile(f); err != nil { + return err + } + if err := saveKubeconfigRaw(f, rootNode); err != nil { + return errors.Wrap(err, "failed to save kubeconfig file after modification") + } + return nil +} + +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 +}