diff --git a/README.md b/README.md index 87e8f47f..051012b0 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,15 @@ Active namespace is "kube-system". $ kubens - Context "test" set. Active namespace is "default". + +# change the active namespace even if it doesn't exist +$ kubens not-found-namespace --force +Context "test" set. +Active namespace is "not-found-namespace". +--- +$ kubens not-found-namespace -f +Context "test" set. +Active namespace is "not-found-namespace". ``` If you have [`fzf`](https://github.com/junegunn/fzf) installed, you can also diff --git a/cmd/kubens/flags.go b/cmd/kubens/flags.go index fc3c64d2..030889c0 100644 --- a/cmd/kubens/flags.go +++ b/cmd/kubens/flags.go @@ -18,6 +18,7 @@ import ( "fmt" "io" "os" + "slices" "strings" "github.com/ahmetb/kubectx/internal/cmdutil" @@ -33,28 +34,51 @@ func (op UnsupportedOp) Run(_, _ io.Writer) error { // parseArgs looks at flags (excl. executable name, i.e. argv[0]) // and decides which operation should be taken. func parseArgs(argv []string) Op { - if len(argv) == 0 { + n := len(argv) + + if n == 0 { if cmdutil.IsInteractiveMode(os.Stdout) { return InteractiveSwitchOp{SelfCmd: os.Args[0]} } return ListOp{} } - if len(argv) == 1 { + if n == 1 { v := argv[0] - if v == "--help" || v == "-h" { + switch v { + case "--help", "-h": return HelpOp{} - } - if v == "--version" || v == "-V" { + case "--version", "-V": return VersionOp{} - } - if v == "--current" || v == "-c" { + case "--current", "-c": return CurrentOp{} + default: + return getSwitchOp(v, false) } - if strings.HasPrefix(v, "-") && v != "-" { - return UnsupportedOp{Err: fmt.Errorf("unsupported option '%s'", v)} + } else if n == 2 { + // {namespace} -f|--force + name := argv[0] + force := slices.Contains([]string{"-f", "--force"}, argv[1]) + + if !force { + if !slices.Contains([]string{"-f", "--force"}, argv[0]) { + return UnsupportedOp{Err: fmt.Errorf("unsupported arguments %q", argv)} + } + + // -f|--force {namespace} + force = true + name = argv[1] } - return SwitchOp{Target: argv[0]} + + return getSwitchOp(name, force) } + return UnsupportedOp{Err: fmt.Errorf("too many arguments")} } + +func getSwitchOp(v string, force bool) Op { + if strings.HasPrefix(v, "-") && v != "-" { + return UnsupportedOp{Err: fmt.Errorf("unsupported option %q", v)} + } + return SwitchOp{Target: v, Force: force} +} diff --git a/cmd/kubens/flags_test.go b/cmd/kubens/flags_test.go index e3e75516..19102a10 100644 --- a/cmd/kubens/flags_test.go +++ b/cmd/kubens/flags_test.go @@ -48,12 +48,30 @@ func Test_parseArgs_new(t *testing.T) { {name: "switch by name", args: []string{"foo"}, want: SwitchOp{Target: "foo"}}, + {name: "switch by name force short flag", + args: []string{"foo", "-f"}, + want: SwitchOp{Target: "foo", Force: true}}, + {name: "switch by name force long flag", + args: []string{"foo", "--force"}, + want: SwitchOp{Target: "foo", Force: true}}, + {name: "switch by name force short flag before name", + args: []string{"-f", "foo"}, + want: SwitchOp{Target: "foo", Force: true}}, + {name: "switch by name force long flag before name", + args: []string{"--force", "foo"}, + want: SwitchOp{Target: "foo", Force: true}}, + {name: "switch by name unknown arguments", + args: []string{"foo", "-x"}, + want: UnsupportedOp{Err: fmt.Errorf("unsupported arguments %q", []string{"foo", "-x"})}}, + {name: "switch by name unknown arguments", + args: []string{"-x", "foo"}, + want: UnsupportedOp{Err: fmt.Errorf("unsupported arguments %q", []string{"-x", "foo"})}}, {name: "switch by swap", args: []string{"-"}, want: SwitchOp{Target: "-"}}, {name: "unrecognized flag", args: []string{"-x"}, - want: UnsupportedOp{Err: fmt.Errorf("unsupported option '-x'")}}, + want: UnsupportedOp{Err: fmt.Errorf("unsupported option %q", "-x")}}, {name: "too many args", args: []string{"a", "b", "c"}, want: UnsupportedOp{Err: fmt.Errorf("too many arguments")}}, diff --git a/cmd/kubens/fzf.go b/cmd/kubens/fzf.go index 8a497704..f0933986 100644 --- a/cmd/kubens/fzf.go +++ b/cmd/kubens/fzf.go @@ -65,7 +65,7 @@ func (op InteractiveSwitchOp) Run(_, stderr io.Writer) error { if choice == "" { return errors.New("you did not choose any of the options") } - name, err := switchNamespace(kc, choice) + name, err := switchNamespace(kc, choice, false) if err != nil { return errors.Wrap(err, "failed to switch namespace") } diff --git a/cmd/kubens/help.go b/cmd/kubens/help.go index 1337b0bd..027d333a 100644 --- a/cmd/kubens/help.go +++ b/cmd/kubens/help.go @@ -35,6 +35,7 @@ func printUsage(out io.Writer) error { help := `USAGE: %PROG% : list the namespaces in the current context %PROG% : change the active namespace of current context + %PROG% --force/-f : force change the active namespace of current context (even if it doesn't exist) %PROG% - : switch to the previous namespace in this context %PROG% -c, --current : show the current namespace %PROG% -h,--help : show this message diff --git a/cmd/kubens/switch.go b/cmd/kubens/switch.go index 7568683b..bcf2a701 100644 --- a/cmd/kubens/switch.go +++ b/cmd/kubens/switch.go @@ -29,6 +29,7 @@ import ( type SwitchOp struct { Target string // '-' for back and forth, or NAME + Force bool // force switch even if the namespace doesn't exist } func (s SwitchOp) Run(_, stderr io.Writer) error { @@ -38,7 +39,7 @@ func (s SwitchOp) Run(_, stderr io.Writer) error { return errors.Wrap(err, "kubeconfig error") } - toNS, err := switchNamespace(kc, s.Target) + toNS, err := switchNamespace(kc, s.Target, s.Force) if err != nil { return err } @@ -46,7 +47,7 @@ func (s SwitchOp) Run(_, stderr io.Writer) error { return err } -func switchNamespace(kc *kubeconfig.Kubeconfig, ns string) (string, error) { +func switchNamespace(kc *kubeconfig.Kubeconfig, ns string, force bool) (string, error) { ctx := kc.GetCurrentContext() if ctx == "" { return "", errors.New("current-context is not set") @@ -69,12 +70,14 @@ func switchNamespace(kc *kubeconfig.Kubeconfig, ns string) (string, error) { ns = prev } - ok, err := namespaceExists(kc, ns) - if err != nil { - return "", errors.Wrap(err, "failed to query if namespace exists (is cluster accessible?)") - } - if !ok { - return "", errors.Errorf("no namespace exists with name \"%s\"", ns) + if !force { + ok, err := namespaceExists(kc, ns) + if err != nil { + return "", errors.Wrap(err, "failed to query if namespace exists (is cluster accessible?)") + } + if !ok { + return "", errors.Errorf("no namespace exists with name \"%s\"", ns) + } } if err := kc.SetNamespace(ctx, ns); err != nil {