mirror of
https://github.com/ahmetb/kubectx.git
synced 2025-08-09 11:17:44 +00:00
define Run(stdout,stderr) method on **Ops
Signed-off-by: Ahmet Alp Balkan <ahmetb@google.com>
This commit is contained in:
parent
5b3796ba1c
commit
28051b1fd7
@ -7,7 +7,10 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func printCurrentContext(w io.Writer) error {
|
// CurrentOp prints the current context
|
||||||
|
type CurrentOp struct{}
|
||||||
|
|
||||||
|
func (_op CurrentOp) Run(stdout, _ io.Writer) error {
|
||||||
cfgPath, err := kubeconfigPath()
|
cfgPath, err := kubeconfigPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to determine kubeconfig path")
|
return errors.Wrap(err, "failed to determine kubeconfig path")
|
||||||
@ -22,6 +25,6 @@ func printCurrentContext(w io.Writer) error {
|
|||||||
if v == "" {
|
if v == "" {
|
||||||
return errors.New("current-context is not set")
|
return errors.New("current-context is not set")
|
||||||
}
|
}
|
||||||
_, err = fmt.Fprintln(w, v)
|
_, err = fmt.Fprintln(stdout, v)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -8,9 +8,14 @@ import (
|
|||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// DeleteOp indicates intention to delete contexts.
|
||||||
|
type DeleteOp struct {
|
||||||
|
Contexts []string // NAME or '.' to indicate current-context.
|
||||||
|
}
|
||||||
|
|
||||||
// deleteContexts deletes context entries one by one.
|
// deleteContexts deletes context entries one by one.
|
||||||
func deleteContexts(w io.Writer, ctxs []string) error {
|
func (op DeleteOp) Run(_, stderr io.Writer) error {
|
||||||
for _, ctx := range ctxs {
|
for _, ctx := range op.Contexts {
|
||||||
// TODO inefficency here. we open/write/close the same file many times.
|
// TODO inefficency here. we open/write/close the same file many times.
|
||||||
deletedName, wasActiveContext, err := deleteContext(ctx)
|
deletedName, wasActiveContext, err := deleteContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -20,7 +25,7 @@ func deleteContexts(w io.Writer, ctxs []string) error {
|
|||||||
// TODO we don't always run as kubectx (sometimes "kubectl ctx")
|
// TODO we don't always run as kubectx (sometimes "kubectl ctx")
|
||||||
printWarning("You deleted the current context. use \"kubectx\" to select a different one.")
|
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)
|
fmt.Fprintf(stderr, "deleted context %q\n", deletedName) // TODO write with printSuccess (i.e. green)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -49,10 +54,6 @@ func deleteContext(name string) (deleteName string, wasActiveContext bool, err e
|
|||||||
if err := modifyDocToDeleteContext(rootNode, name); err != nil {
|
if err := modifyDocToDeleteContext(rootNode, name); err != nil {
|
||||||
return "", false, errors.Wrap(err, "failed to modify yaml doc")
|
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")
|
return name, wasActiveContext, errors.Wrap(saveKubeconfigRaw(f, rootNode), "failed to save kubeconfig file")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,40 +1,23 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "strings"
|
import (
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
type Op interface{}
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
// HelpOp describes printing help.
|
type Op interface {
|
||||||
type HelpOp struct{}
|
Run(stdout, stderr io.Writer) error
|
||||||
|
|
||||||
// ListOp describes listing contexts.
|
|
||||||
type ListOp struct{}
|
|
||||||
|
|
||||||
// CurrentOp prints the current context
|
|
||||||
type CurrentOp struct{}
|
|
||||||
|
|
||||||
// SwitchOp indicates intention to switch contexts.
|
|
||||||
type SwitchOp struct {
|
|
||||||
Target string // '-' for back and forth, or NAME
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnsetOp indicates intention to remove current-context preference.
|
// UnsupportedOp indicates an unsupported flag.
|
||||||
type UnsetOp struct{}
|
type UnsupportedOp struct{ Err error }
|
||||||
|
|
||||||
// DeleteOp indicates intention to delete contexts.
|
func (op UnsupportedOp) Run(_, _ io.Writer) error {
|
||||||
type DeleteOp struct {
|
return op.Err
|
||||||
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 }
|
|
||||||
|
|
||||||
// parseArgs looks at flags (excl. executable name, i.e. argv[0])
|
// parseArgs looks at flags (excl. executable name, i.e. argv[0])
|
||||||
// and decides which operation should be taken.
|
// and decides which operation should be taken.
|
||||||
func parseArgs(argv []string) Op {
|
func parseArgs(argv []string) Op {
|
||||||
@ -43,8 +26,7 @@ func parseArgs(argv []string) Op {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if argv[0] == "-d" {
|
if argv[0] == "-d" {
|
||||||
ctxs := argv[1:]
|
return DeleteOp{Contexts: argv[1:]}
|
||||||
return DeleteOp{ctxs}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(argv) == 1 {
|
if len(argv) == 1 {
|
||||||
@ -59,21 +41,16 @@ func parseArgs(argv []string) Op {
|
|||||||
return UnsetOp{}
|
return UnsetOp{}
|
||||||
}
|
}
|
||||||
|
|
||||||
new, old, ok := parseRenameSyntax(v) // a=b a=.
|
if new, old, ok := parseRenameSyntax(v); ok {
|
||||||
if ok {
|
return RenameOp{New: new, Old: old}
|
||||||
return RenameOp{new, old}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(v, "-") && v != "-" {
|
if strings.HasPrefix(v, "-") && v != "-" {
|
||||||
return UnknownOp{argv}
|
return UnsupportedOp{Err: errors.Errorf("unsupported option %s", v)}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO handle -d
|
|
||||||
// TODO handle -u/--unset
|
|
||||||
// TODO handle -c/--current
|
|
||||||
return SwitchOp{Target: argv[0]}
|
return SwitchOp{Target: argv[0]}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO handle too many arguments e.g. "kubectx a b c"
|
// TODO handle too many arguments e.g. "kubectx a b c"
|
||||||
return UnknownOp{}
|
return UnsupportedOp{Err: errors.New("too many arguments")}
|
||||||
}
|
}
|
||||||
|
@ -59,8 +59,8 @@ func Test_parseArgs_new(t *testing.T) {
|
|||||||
want: RenameOp{"a", "."}},
|
want: RenameOp{"a", "."}},
|
||||||
{name: "unrecognized flag",
|
{name: "unrecognized flag",
|
||||||
args: []string{"-x"},
|
args: []string{"-x"},
|
||||||
want: UnknownOp{Args: []string{"-x"}}},
|
want: UnsupportedOp{Args: []string{"-x"}}},
|
||||||
// TODO add more UnknownOp cases
|
// TODO add more UnsupportedOp cases
|
||||||
|
|
||||||
// TODO consider these cases
|
// TODO consider these cases
|
||||||
// - kubectx foo --help
|
// - kubectx foo --help
|
||||||
|
@ -5,7 +5,14 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
func printHelp(out io.Writer) {
|
// HelpOp describes printing help.
|
||||||
|
type HelpOp struct{}
|
||||||
|
|
||||||
|
func (_ HelpOp) Run(stdout, _ io.Writer) error {
|
||||||
|
return printUsage(stdout)
|
||||||
|
}
|
||||||
|
|
||||||
|
func printUsage(out io.Writer) error {
|
||||||
help := `USAGE:
|
help := `USAGE:
|
||||||
kubectx : list the contexts
|
kubectx : list the contexts
|
||||||
kubectx <NAME> : switch to context <NAME>
|
kubectx <NAME> : switch to context <NAME>
|
||||||
@ -20,5 +27,6 @@ func printHelp(out io.Writer) {
|
|||||||
|
|
||||||
kubectx -h,--help : show this message`
|
kubectx -h,--help : show this message`
|
||||||
|
|
||||||
fmt.Fprintf(out, "%s\n", help)
|
_, err := fmt.Fprintf(out, "%s\n", help)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,10 @@ func kubeconfigPath() (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func homeDir() string {
|
func homeDir() string {
|
||||||
// TODO move tests out of kubeconfigPath to TestHomeDir()
|
// TODO move tests for this out of kubeconfigPath to TestHomeDir()
|
||||||
|
if v := os.Getenv("XDG_CACHE_HOME"); v != "" {
|
||||||
|
return v
|
||||||
|
}
|
||||||
home := os.Getenv("HOME")
|
home := os.Getenv("HOME")
|
||||||
if home == "" {
|
if home == "" {
|
||||||
home = os.Getenv("USERPROFILE") // windows
|
home = os.Getenv("USERPROFILE") // windows
|
||||||
@ -37,6 +40,8 @@ func homeDir() string {
|
|||||||
return home
|
return home
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO parseKubeconfig doesn't seem necessary when there's a raw version that returns what's needed
|
||||||
func parseKubeconfig(path string) (kubeconfig, error) {
|
func parseKubeconfig(path string) (kubeconfig, error) {
|
||||||
// TODO refactor to accept io.Reader instead of file
|
// TODO refactor to accept io.Reader instead of file
|
||||||
var v kubeconfig
|
var v kubeconfig
|
||||||
|
@ -17,6 +17,14 @@ func parseKubeconfigRaw(r io.Reader) (*yaml.Node, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func saveKubeconfigRaw(w io.Writer, rootNode *yaml.Node) error {
|
func saveKubeconfigRaw(w io.Writer, rootNode *yaml.Node) error {
|
||||||
|
if f, ok := w.(*os.File); ok {
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
enc := yaml.NewEncoder(w)
|
enc := yaml.NewEncoder(w)
|
||||||
enc.SetIndent(2)
|
enc.SetIndent(2)
|
||||||
return enc.Encode(rootNode)
|
return enc.Encode(rootNode)
|
||||||
@ -39,14 +47,3 @@ func openKubeconfig() (f *os.File, rootNode *yaml.Node, err error) {
|
|||||||
}
|
}
|
||||||
return f, kc, nil
|
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")
|
|
||||||
}
|
|
||||||
|
@ -19,7 +19,10 @@ type kubeconfig struct {
|
|||||||
Contexts []context `yaml:"contexts"`
|
Contexts []context `yaml:"contexts"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func printListContexts(out io.Writer) error {
|
// ListOp describes listing contexts.
|
||||||
|
type ListOp struct{}
|
||||||
|
|
||||||
|
func (_ ListOp) Run(stdout, stderr io.Writer) error {
|
||||||
// TODO extract printing and sorting into a function that's testable
|
// TODO extract printing and sorting into a function that's testable
|
||||||
|
|
||||||
cfgPath, err := kubeconfigPath()
|
cfgPath, err := kubeconfigPath()
|
||||||
@ -41,12 +44,11 @@ func printListContexts(out io.Writer) error {
|
|||||||
// TODO support KUBECTX_CURRENT_FGCOLOR
|
// TODO support KUBECTX_CURRENT_FGCOLOR
|
||||||
// TODO support KUBECTX_CURRENT_BGCOLOR
|
// TODO support KUBECTX_CURRENT_BGCOLOR
|
||||||
for _, c := range ctxs {
|
for _, c := range ctxs {
|
||||||
out := c
|
s := c
|
||||||
if c == cfg.CurrentContext {
|
if c == cfg.CurrentContext {
|
||||||
out = color.New(color.FgYellow, color.Bold).Sprint(c)
|
s = color.New(color.FgGreen, color.Bold).Sprint(c)
|
||||||
}
|
}
|
||||||
fmt.Println(out)
|
fmt.Fprintf(stdout, "%s\n", s)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
)
|
)
|
||||||
@ -13,56 +12,9 @@ func main() {
|
|||||||
var op Op
|
var op Op
|
||||||
op = parseArgs(os.Args[1:])
|
op = parseArgs(os.Args[1:])
|
||||||
|
|
||||||
// TODO consider addin Run() operation to each operation type
|
if err := op.Run(os.Stdout, os.Stderr); err != nil {
|
||||||
switch v := op.(type) {
|
printError(err.Error())
|
||||||
case HelpOp:
|
|
||||||
printHelp(os.Stdout)
|
|
||||||
case CurrentOp:
|
|
||||||
if err := printCurrentContext(os.Stdout); err != nil {
|
|
||||||
printError(err.Error())
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
case UnsetOp:
|
|
||||||
if err := unsetContext(); err != nil {
|
|
||||||
printError(err.Error())
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
case ListOp:
|
|
||||||
// TODO fzf installed show interactive selection
|
|
||||||
|
|
||||||
if err := printListContexts(os.Stdout); err != nil {
|
|
||||||
printError("%v", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
case DeleteOp:
|
|
||||||
if err := deleteContexts(os.Stderr, v.Contexts); err != nil {
|
|
||||||
printError("%v", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
case RenameOp:
|
|
||||||
if err := renameContexts(v.Old, v.New); err != nil {
|
|
||||||
printError("failed to rename: %v", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
case SwitchOp:
|
|
||||||
var newCtx string
|
|
||||||
var err error
|
|
||||||
if v.Target == "-" {
|
|
||||||
newCtx, err = swapContext()
|
|
||||||
} else {
|
|
||||||
newCtx, err = switchContext(v.Target)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
printError("failed to switch context: %v", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
fmt.Fprintf(os.Stderr, "Switched to context %q.\n", newCtx)
|
|
||||||
case UnknownOp:
|
|
||||||
printError("unsupported operation: %s", strings.Join(v.Args, " "))
|
|
||||||
printHelp(os.Stdout)
|
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
default:
|
|
||||||
fmt.Printf("internal error: operation type %T not handled", op)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -9,6 +10,12 @@ import (
|
|||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
// parseRenameSyntax parses A=B form into [A,B] and returns
|
// parseRenameSyntax parses A=B form into [A,B] and returns
|
||||||
// whether it is parsed correctly.
|
// whether it is parsed correctly.
|
||||||
func parseRenameSyntax(v string) (string, string, bool) {
|
func parseRenameSyntax(v string) (string, string, bool) {
|
||||||
@ -26,7 +33,7 @@ func parseRenameSyntax(v string) (string, string, bool) {
|
|||||||
// rename changes the old (NAME or '.' for current-context)
|
// rename changes the old (NAME or '.' for current-context)
|
||||||
// to the "new" value. If the old refers to the current-context,
|
// to the "new" value. If the old refers to the current-context,
|
||||||
// current-context preference is also updated.
|
// current-context preference is also updated.
|
||||||
func renameContexts(old, new string) error {
|
func (op RenameOp) Run(_, _ io.Writer) error {
|
||||||
f, rootNode, err := openKubeconfig()
|
f, rootNode, err := openKubeconfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
@ -34,35 +41,29 @@ func renameContexts(old, new string) error {
|
|||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
cur := getCurrentContext(rootNode)
|
cur := getCurrentContext(rootNode)
|
||||||
if old == "." {
|
if op.Old == "." {
|
||||||
old = cur
|
op.Old = cur
|
||||||
}
|
}
|
||||||
|
|
||||||
if !checkContextExists(rootNode, old) {
|
if !checkContextExists(rootNode, op.Old) {
|
||||||
return errors.Errorf("context %q not found, can't rename it", old)
|
return errors.Errorf("context %q not found, can't rename it", op.Old)
|
||||||
}
|
}
|
||||||
|
|
||||||
if checkContextExists(rootNode, new) {
|
if checkContextExists(rootNode, op.New) {
|
||||||
printWarning("context %q exists, overwriting it.", new)
|
printWarning("context %q exists, overwriting it.", op.New)
|
||||||
if err := modifyDocToDeleteContext(rootNode, new); err != nil {
|
if err := modifyDocToDeleteContext(rootNode, op.New); err != nil {
|
||||||
return errors.Wrap(err, "failed to delete new context to overwrite it")
|
return errors.Wrap(err, "failed to delete new context to overwrite it")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := modifyContextName(rootNode, old, new); err != nil {
|
if err := modifyContextName(rootNode, op.Old, op.New); err != nil {
|
||||||
return errors.Wrap(err, "failed to change context name")
|
return errors.Wrap(err, "failed to change context name")
|
||||||
}
|
}
|
||||||
|
if op.New == cur {
|
||||||
if old == cur {
|
if err := modifyCurrentContext(rootNode, op.New); err != nil {
|
||||||
if err := modifyCurrentContext(rootNode, new); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to set current-context to new name")
|
return errors.Wrap(err, "failed to set current-context to new name")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO the next two functions are always repeated.
|
|
||||||
if err := resetFile(f); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := saveKubeconfigRaw(f, rootNode); err != nil {
|
if err := saveKubeconfigRaw(f, rootNode); err != nil {
|
||||||
return errors.Wrap(err, "failed to save modified kubeconfig")
|
return errors.Wrap(err, "failed to save modified kubeconfig")
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func kubectxFilePath() (string, error) {
|
func kubectxPrevCtxFile() (string, error) {
|
||||||
home := homeDir()
|
home := homeDir()
|
||||||
if home == "" {
|
if home == "" {
|
||||||
return "", errors.New("HOME or USERPROFILE environment variable not set")
|
return "", errors.New("HOME or USERPROFILE environment variable not set")
|
||||||
|
@ -64,7 +64,7 @@ func Test_kubectxFilePath(t *testing.T) {
|
|||||||
defer os.Setenv("HOME", origHome)
|
defer os.Setenv("HOME", origHome)
|
||||||
|
|
||||||
expected := filepath.Join(filepath.FromSlash("/foo/bar"), ".kube", "kubectx")
|
expected := filepath.Join(filepath.FromSlash("/foo/bar"), ".kube", "kubectx")
|
||||||
v, err := kubectxFilePath()
|
v, err := kubectxPrevCtxFile()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -81,7 +81,7 @@ func Test_kubectxFilePath_error(t *testing.T) {
|
|||||||
defer os.Setenv("HOME", origHome)
|
defer os.Setenv("HOME", origHome)
|
||||||
defer os.Setenv("USERPROFILE", origUserprofile)
|
defer os.Setenv("USERPROFILE", origUserprofile)
|
||||||
|
|
||||||
_, err := kubectxFilePath()
|
_, err := kubectxPrevCtxFile()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,40 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// SwitchOp indicates intention to switch contexts.
|
||||||
|
type SwitchOp struct {
|
||||||
|
Target string // '-' for back and forth, or NAME
|
||||||
|
}
|
||||||
|
|
||||||
|
func (op SwitchOp) Run(stdout, stderr io.Writer) error {
|
||||||
|
var newCtx string
|
||||||
|
var err error
|
||||||
|
if op.Target == "-" {
|
||||||
|
newCtx, err = swapContext()
|
||||||
|
} else {
|
||||||
|
newCtx, err = switchContext(op.Target)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to switch context")
|
||||||
|
}
|
||||||
|
// TODO use printSuccess when available.
|
||||||
|
fmt.Fprintf(stderr, "Switched to context %q.\n", newCtx)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// switchContext switches to specified context name.
|
// switchContext switches to specified context name.
|
||||||
func switchContext(name string) (string, error) {
|
func switchContext(name string) (string, error) {
|
||||||
stateFile, err := kubectxFilePath()
|
prevCtxFile, err := kubectxPrevCtxFile()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.Wrap(err, "failed to determine state file")
|
return "", errors.Wrap(err, "failed to determine state file")
|
||||||
}
|
}
|
||||||
|
|
||||||
f, kc, err := openKubeconfig()
|
f, kc, err := openKubeconfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@ -19,33 +42,42 @@ func switchContext(name string) (string, error) {
|
|||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
prev := getCurrentContext(kc)
|
prev := getCurrentContext(kc)
|
||||||
|
if !checkContextExists(kc, name) {
|
||||||
// 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)
|
return "", errors.Errorf("no context exists with the name: %q", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := modifyCurrentContext(kc, name); err != nil {
|
if err := modifyCurrentContext(kc, name); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := resetFile(f); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := saveKubeconfigRaw(f, kc); err != nil {
|
if err := saveKubeconfigRaw(f, kc); err != nil {
|
||||||
return "", errors.Wrap(err, "failed to save kubeconfig")
|
return "", errors.Wrap(err, "failed to save kubeconfig")
|
||||||
}
|
}
|
||||||
|
|
||||||
if prev != name {
|
if prev != name {
|
||||||
if err := writeLastContext(stateFile, prev); err != nil {
|
if err := writeLastContext(prevCtxFile, prev); err != nil {
|
||||||
return "", errors.Wrap(err, "failed to save previous context name")
|
return "", errors.Wrap(err, "failed to save previous context name")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return name, nil
|
return name, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// swapContext switches to previously switch context.
|
||||||
|
func swapContext() (string, error) {
|
||||||
|
prevCtxFile, err := kubectxPrevCtxFile()
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.Wrap(err, "failed to determine state file")
|
||||||
|
}
|
||||||
|
prev, err := readLastContext(prevCtxFile)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
func checkContextExists(rootNode *yaml.Node, name string) bool {
|
func checkContextExists(rootNode *yaml.Node, name string) bool {
|
||||||
contexts := valueOf(rootNode, "contexts")
|
contexts := valueOf(rootNode, "contexts")
|
||||||
if contexts == nil {
|
if contexts == nil {
|
||||||
@ -83,22 +115,6 @@ func valueOf(mapNode *yaml.Node, key string) *yaml.Node {
|
|||||||
return nil
|
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
|
// getCurrentContext returns "current-context" value in given
|
||||||
// kubeconfig object Node, or returns "" if not found.
|
// kubeconfig object Node, or returns "" if not found.
|
||||||
func getCurrentContext(rootNode *yaml.Node) string {
|
func getCurrentContext(rootNode *yaml.Node) string {
|
||||||
|
@ -1,11 +1,17 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func unsetContext() error {
|
// UnsetOp indicates intention to remove current-context preference.
|
||||||
|
type UnsetOp struct{}
|
||||||
|
|
||||||
|
func (_ UnsetOp) Run(_, stderr io.Writer) error {
|
||||||
f, rootNode, err := openKubeconfig()
|
f, rootNode, err := openKubeconfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -15,13 +21,12 @@ func unsetContext() error {
|
|||||||
if err := modifyDocToUnsetContext(rootNode); err != nil {
|
if err := modifyDocToUnsetContext(rootNode); err != nil {
|
||||||
return errors.Wrap(err, "error while modifying current-context")
|
return errors.Wrap(err, "error while modifying current-context")
|
||||||
}
|
}
|
||||||
if err := resetFile(f); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := saveKubeconfigRaw(f, rootNode); err != nil {
|
if err := saveKubeconfigRaw(f, rootNode); err != nil {
|
||||||
return errors.Wrap(err, "failed to save kubeconfig file after modification")
|
return errors.Wrap(err, "failed to save kubeconfig file after modification")
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
|
_, err = fmt.Fprintln(stderr, "Successfully unset the current context")
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func modifyDocToUnsetContext(rootNode *yaml.Node) error {
|
func modifyDocToUnsetContext(rootNode *yaml.Node) error {
|
||||||
|
Loading…
Reference in New Issue
Block a user