From 64e5a0ed1352bdcb3e8939cefe9c9bf10f301ffe Mon Sep 17 00:00:00 2001 From: Ahmet Alp Balkan Date: Wed, 29 Apr 2020 11:55:28 -0700 Subject: [PATCH] Add interactive switching to kubens Signed-off-by: Ahmet Alp Balkan --- cmd/kubens/flags.go | 9 ++++--- cmd/kubens/fzf.go | 60 ++++++++++++++++++++++++++++++++++++++++++++ cmd/kubens/switch.go | 43 ++++++++++++++++++------------- 3 files changed, 91 insertions(+), 21 deletions(-) create mode 100644 cmd/kubens/fzf.go diff --git a/cmd/kubens/flags.go b/cmd/kubens/flags.go index f86d4c3..8e0ad3d 100644 --- a/cmd/kubens/flags.go +++ b/cmd/kubens/flags.go @@ -3,7 +3,10 @@ package main import ( "fmt" "io" + "os" "strings" + + "github.com/ahmetb/kubectx/internal/cmdutil" ) // UnsupportedOp indicates an unsupported flag. @@ -17,9 +20,9 @@ func (op UnsupportedOp) Run(_, _ io.Writer) error { // and decides which operation should be taken. func parseArgs(argv []string) Op { if len(argv) == 0 { - //if env.IsInteractiveMode(os.Stdout) { - // return InteractiveSwitchOp{SelfCmd: os.Args[0]} - //} + if cmdutil.IsInteractiveMode(os.Stdout) { + return InteractiveSwitchOp{SelfCmd: os.Args[0]} + } return ListOp{} } diff --git a/cmd/kubens/fzf.go b/cmd/kubens/fzf.go new file mode 100644 index 0000000..1fd843b --- /dev/null +++ b/cmd/kubens/fzf.go @@ -0,0 +1,60 @@ +package main + +import ( + "bytes" + "fmt" + "io" + "os" + "os/exec" + "strings" + + "github.com/pkg/errors" + + "github.com/ahmetb/kubectx/internal/cmdutil" + "github.com/ahmetb/kubectx/internal/env" + "github.com/ahmetb/kubectx/internal/kubeconfig" + "github.com/ahmetb/kubectx/internal/printer" +) + +type InteractiveSwitchOp struct { + SelfCmd string +} + +// TODO(ahmetb) This method is heavily repetitive vs kubectx/fzf.go. +func (op InteractiveSwitchOp) Run(_, stderr io.Writer) error { + // parse kubeconfig just to see if it can be loaded + kc := new(kubeconfig.Kubeconfig).WithLoader(cmdutil.DefaultLoader) + if err := kc.Parse(); err != nil { + if cmdutil.IsNotFoundErr(err) { + printer.Warning(stderr, "kubeconfig file not found") + return nil + } + return errors.Wrap(err, "kubeconfig error") + } + defer kc.Close() + + cmd := exec.Command("fzf", "--ansi", "--no-preview") + var out bytes.Buffer + cmd.Stdin = os.Stdin + cmd.Stderr = stderr + cmd.Stdout = &out + + cmd.Env = append(os.Environ(), + fmt.Sprintf("FZF_DEFAULT_COMMAND=%s", op.SelfCmd), + fmt.Sprintf("%s=1", env.EnvForceColor)) + if err := cmd.Run(); err != nil { + if _, ok := err.(*exec.ExitError); !ok { + return err + } + } + choice := strings.TrimSpace(out.String()) + if choice == "" { + return errors.New("you did not choose any of the options") + } + name, err := switchNamespace(kc, choice) + if err != nil { + return errors.Wrap(err, "failed to switch context") + } + printer.Success(stderr, "Switched to context %s.", printer.SuccessColor.Sprint(name)) + return nil +} diff --git a/cmd/kubens/switch.go b/cmd/kubens/switch.go index 76f2259..61e351b 100644 --- a/cmd/kubens/switch.go +++ b/cmd/kubens/switch.go @@ -21,53 +21,60 @@ func (s SwitchOp) Run(_, stderr io.Writer) error { return errors.Wrap(err, "kubeconfig error") } + toNS, err := switchNamespace(kc, s.Target) + if err != nil { + return err + } + err = printer.Success(stderr, "Active namespace is %q", toNS) + return err +} + +func switchNamespace(kc *kubeconfig.Kubeconfig, ns string) (string, error) { ctx := kc.GetCurrentContext() if ctx == "" { - return errors.New("current-context is not set") + return "", errors.New("current-context is not set") } curNS, err := kc.NamespaceOfContext(ctx) if ctx == "" { - return errors.New("failed to get current namespace") + return "", errors.New("failed to get current namespace") } f := NewNSFile(ctx) prev, err := f.Load() if err != nil { - return errors.Wrap(err, "failed to load previous namespace from file") + return "", errors.Wrap(err, "failed to load previous namespace from file") } - toNS := s.Target - if s.Target == "-" { + if ns == "-" { if prev == "" { - return errors.Errorf("No previous namespace found for current context (%s)", ctx) + return "", errors.Errorf("No previous namespace found for current context (%s)", ctx) } - toNS = prev + ns = prev } - ok, err := namespaceExists(toNS) + ok, err := namespaceExists(ns) if err != nil { - return errors.Wrap(err, "failed to query if namespace exists (is cluster accessible?)") + return "", errors.Wrap(err, "failed to query if namespace exists (is cluster accessible?)") } if !ok { - return errors.Errorf("no namespace exists with name %q", toNS) + return "", errors.Errorf("no namespace exists with name %q", ns) } - if err := kc.SetNamespace(ctx, toNS); err != nil { - return errors.Wrapf(err, "failed to change to namespace %q", toNS) + if err := kc.SetNamespace(ctx, ns); err != nil { + return "", errors.Wrapf(err, "failed to change to namespace %q", ns) } if err := kc.Save(); err != nil { - return errors.Wrap(err, "failed to save kubeconfig file") + return "", errors.Wrap(err, "failed to save kubeconfig file") } - if curNS != toNS { + if curNS != ns { if err := f.Save(curNS); err != nil { - return errors.Wrap(err, "failed to save the previous namespace to file") + return "", errors.Wrap(err, "failed to save the previous namespace to file") } } - - err = printer.Success(stderr, "Active namespace is %q", toNS) - return err + return ns, nil } + func namespaceExists(ns string) (bool, error) { nses, err := queryNamespaces() if err != nil {