diff --git a/cmd/kubectx/current.go b/cmd/kubectx/current.go index a5886f3..a7fb0cc 100644 --- a/cmd/kubectx/current.go +++ b/cmd/kubectx/current.go @@ -35,12 +35,14 @@ func (_op CurrentOp) Run(stdout, _ io.Writer) error { return fmt.Errorf("kubeconfig error: %w", err) } - v := kc.GetCurrentContext() + v, err := kc.GetCurrentContext() + if err != nil { + return fmt.Errorf("failed to get current context: %w", err) + } if v == "" { return errors.New("current-context is not set") } - _, err := fmt.Fprintln(stdout, v) - if err != nil { + if _, err := fmt.Fprintln(stdout, v); err != nil { return fmt.Errorf("write error: %w", err) } return nil diff --git a/cmd/kubectx/delete.go b/cmd/kubectx/delete.go index d4c00e3..e21abdd 100644 --- a/cmd/kubectx/delete.go +++ b/cmd/kubectx/delete.go @@ -44,7 +44,7 @@ func (op DeleteOp) Run(_, stderr io.Writer) error { selfName()) } - printer.Success(stderr, `Deleted context %s.`, printer.SuccessColor.Sprint(deletedName)) + _ = printer.Success(stderr, `Deleted context %s.`, printer.SuccessColor.Sprint(deletedName)) } return nil } @@ -58,7 +58,10 @@ func deleteContext(name string) (deleteName string, wasActiveContext bool, err e return deleteName, false, fmt.Errorf("kubeconfig error: %w", err) } - cur := kc.GetCurrentContext() + cur, err := kc.GetCurrentContext() + if err != nil { + return deleteName, false, fmt.Errorf("failed to get current context: %w", err) + } // resolve "." to a real name if name == "." { if cur == "" { @@ -68,7 +71,11 @@ func deleteContext(name string) (deleteName string, wasActiveContext bool, err e name = cur } - if !kc.ContextExists(name) { + exists, err := kc.ContextExists(name) + if err != nil { + return name, false, fmt.Errorf("failed to check context: %w", err) + } + if !exists { return name, false, errors.New("context does not exist") } diff --git a/cmd/kubectx/fzf.go b/cmd/kubectx/fzf.go index 93cfe0b..56b33c9 100644 --- a/cmd/kubectx/fzf.go +++ b/cmd/kubectx/fzf.go @@ -43,6 +43,7 @@ func (op InteractiveSwitchOp) Run(_, stderr io.Writer) error { } // parse kubeconfig just to see if it can be loaded kc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader) + defer kc.Close() if err := kc.Parse(); err != nil { if cmdutil.IsNotFoundErr(err) { printer.Warning(stderr, "kubeconfig file not found") @@ -50,7 +51,6 @@ func (op InteractiveSwitchOp) Run(_, stderr io.Writer) error { } return fmt.Errorf("kubeconfig error: %w", err) } - kc.Close() cmd := exec.Command("fzf", "--ansi", "--no-preview") var out bytes.Buffer @@ -75,7 +75,7 @@ func (op InteractiveSwitchOp) Run(_, stderr io.Writer) error { if err != nil { return fmt.Errorf("failed to switch context: %w", err) } - printer.Success(stderr, "Switched to context \"%s\".", printer.SuccessColor.Sprint(name)) + _ = printer.Success(stderr, "Switched to context \"%s\".", printer.SuccessColor.Sprint(name)) return nil } @@ -85,6 +85,7 @@ func (op InteractiveDeleteOp) Run(_, stderr io.Writer) error { } // parse kubeconfig just to see if it can be loaded kc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader) + defer kc.Close() if err := kc.Parse(); err != nil { if cmdutil.IsNotFoundErr(err) { printer.Warning(stderr, "kubeconfig file not found") @@ -92,9 +93,12 @@ func (op InteractiveDeleteOp) Run(_, stderr io.Writer) error { } return fmt.Errorf("kubeconfig error: %w", err) } - kc.Close() - if len(kc.ContextNames()) == 0 { + ctxNames, err := kc.ContextNames() + if err != nil { + return fmt.Errorf("failed to get context names: %w", err) + } + if len(ctxNames) == 0 { return errors.New("no contexts found in config") } @@ -129,7 +133,7 @@ func (op InteractiveDeleteOp) Run(_, stderr io.Writer) error { selfName()) } - printer.Success(stderr, `Deleted context %s.`, printer.SuccessColor.Sprint(name)) + _ = printer.Success(stderr, `Deleted context %s.`, printer.SuccessColor.Sprint(name)) return nil } diff --git a/cmd/kubectx/isolated_shell_guard.go b/cmd/kubectx/isolated_shell_guard.go index bb8153d..78704c4 100644 --- a/cmd/kubectx/isolated_shell_guard.go +++ b/cmd/kubectx/isolated_shell_guard.go @@ -19,6 +19,6 @@ func checkIsolatedMode() error { return fmt.Errorf("you are in a locked single-context shell, use 'exit' to leave") } - cur := kc.GetCurrentContext() + cur, _ := kc.GetCurrentContext() return fmt.Errorf("you are in a locked single-context shell (\"%s\"), use 'exit' to leave", cur) } diff --git a/cmd/kubectx/list.go b/cmd/kubectx/list.go index efb8ca2..5df315c 100644 --- a/cmd/kubectx/list.go +++ b/cmd/kubectx/list.go @@ -42,10 +42,16 @@ func (_ ListOp) Run(stdout, stderr io.Writer) error { return fmt.Errorf("kubeconfig error: %w", err) } - ctxs := kc.ContextNames() + ctxs, err := kc.ContextNames() + if err != nil { + return fmt.Errorf("failed to get context names: %w", err) + } natsort.Sort(ctxs) - cur := kc.GetCurrentContext() + cur, err := kc.GetCurrentContext() + if err != nil { + return fmt.Errorf("failed to get current context: %w", err) + } for _, c := range ctxs { s := c if c == cur { diff --git a/cmd/kubectx/rename.go b/cmd/kubectx/rename.go index ed45baa..6eedb34 100644 --- a/cmd/kubectx/rename.go +++ b/cmd/kubectx/rename.go @@ -52,16 +52,27 @@ func (op RenameOp) Run(_, stderr io.Writer) error { return fmt.Errorf("kubeconfig error: %w", err) } - cur := kc.GetCurrentContext() + cur, err := kc.GetCurrentContext() + if err != nil { + return fmt.Errorf("failed to get current context: %w", err) + } if op.Old == "." { op.Old = cur } - if !kc.ContextExists(op.Old) { + oldExists, err := kc.ContextExists(op.Old) + if err != nil { + return fmt.Errorf("failed to check context: %w", err) + } + if !oldExists { return fmt.Errorf("context \"%s\" not found, can't rename it", op.Old) } - if kc.ContextExists(op.New) { + newExists, err := kc.ContextExists(op.New) + if err != nil { + return fmt.Errorf("failed to check context: %w", err) + } + if newExists { printer.Warning(stderr, "context \"%s\" exists, overwriting it.", op.New) if err := kc.DeleteContextEntry(op.New); err != nil { return fmt.Errorf("failed to delete new context to overwrite it: %w", err) @@ -79,7 +90,7 @@ func (op RenameOp) Run(_, stderr io.Writer) error { if err := kc.Save(); err != nil { return fmt.Errorf("failed to save modified kubeconfig: %w", err) } - printer.Success(stderr, "Context %s renamed to %s.", + _ = printer.Success(stderr, "Context %s renamed to %s.", printer.SuccessColor.Sprint(op.Old), printer.SuccessColor.Sprint(op.New)) return nil diff --git a/cmd/kubectx/shell.go b/cmd/kubectx/shell.go index 54510f0..9dbd769 100644 --- a/cmd/kubectx/shell.go +++ b/cmd/kubectx/shell.go @@ -35,10 +35,17 @@ func (op ShellOp) Run(_, stderr io.Writer) error { if err := kc.Parse(); err != nil { return fmt.Errorf("kubeconfig error: %w", err) } - if !kc.ContextExists(op.Target) { + exists, err := kc.ContextExists(op.Target) + if err != nil { + return fmt.Errorf("failed to check context: %w", err) + } + if !exists { return fmt.Errorf("no context exists with the name: \"%s\"", op.Target) } - previousCtx := kc.GetCurrentContext() + previousCtx, err := kc.GetCurrentContext() + if err != nil { + return fmt.Errorf("failed to get current context: %w", err) + } // Extract minimal kubeconfig using kubectl data, err := extractMinimalKubeconfig(kubectlPath, op.Target) diff --git a/cmd/kubectx/switch.go b/cmd/kubectx/switch.go index 44e0a48..3f6a614 100644 --- a/cmd/kubectx/switch.go +++ b/cmd/kubectx/switch.go @@ -61,8 +61,15 @@ func switchContext(name string) (string, error) { return "", fmt.Errorf("kubeconfig error: %w", err) } - prev := kc.GetCurrentContext() - if !kc.ContextExists(name) { + prev, err := kc.GetCurrentContext() + if err != nil { + return "", fmt.Errorf("failed to get current context: %w", err) + } + exists, err := kc.ContextExists(name) + if err != nil { + return "", fmt.Errorf("failed to check context: %w", err) + } + if !exists { return "", fmt.Errorf("no context exists with the name: \"%s\"", name) } if err := kc.ModifyCurrentContext(name); err != nil { diff --git a/cmd/kubens/current.go b/cmd/kubens/current.go index 3a19409..7b3b0f5 100644 --- a/cmd/kubens/current.go +++ b/cmd/kubens/current.go @@ -31,7 +31,10 @@ func (c CurrentOp) Run(stdout, _ io.Writer) error { return fmt.Errorf("kubeconfig error: %w", err) } - ctx := kc.GetCurrentContext() + ctx, err := kc.GetCurrentContext() + if err != nil { + return fmt.Errorf("failed to get current context: %w", err) + } if ctx == "" { return errors.New("current-context is not set") } diff --git a/cmd/kubens/fzf.go b/cmd/kubens/fzf.go index 95d49df..697c900 100644 --- a/cmd/kubens/fzf.go +++ b/cmd/kubens/fzf.go @@ -37,6 +37,7 @@ type InteractiveSwitchOp struct { func (op InteractiveSwitchOp) Run(_, stderr io.Writer) error { // parse kubeconfig just to see if it can be loaded kc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader) + defer kc.Close() if err := kc.Parse(); err != nil { if cmdutil.IsNotFoundErr(err) { printer.Warning(stderr, "kubeconfig file not found") @@ -44,7 +45,6 @@ func (op InteractiveSwitchOp) Run(_, stderr io.Writer) error { } return fmt.Errorf("kubeconfig error: %w", err) } - defer kc.Close() cmd := exec.Command("fzf", "--ansi", "--no-preview") var out bytes.Buffer @@ -69,6 +69,6 @@ func (op InteractiveSwitchOp) Run(_, stderr io.Writer) error { if err != nil { return fmt.Errorf("failed to switch namespace: %w", err) } - printer.Success(stderr, "Active namespace is \"%s\".", printer.SuccessColor.Sprint(name)) + _ = printer.Success(stderr, "Active namespace is \"%s\".", printer.SuccessColor.Sprint(name)) return nil } diff --git a/cmd/kubens/list.go b/cmd/kubens/list.go index 7097a02..dd7633a 100644 --- a/cmd/kubens/list.go +++ b/cmd/kubens/list.go @@ -20,6 +20,7 @@ import ( "fmt" "io" "os" + "slices" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" @@ -39,7 +40,10 @@ func (op ListOp) Run(stdout, stderr io.Writer) error { return fmt.Errorf("kubeconfig error: %w", err) } - ctx := kc.GetCurrentContext() + ctx, err := kc.GetCurrentContext() + if err != nil { + return fmt.Errorf("failed to get current context: %w", err) + } if ctx == "" { return errors.New("current-context is not set") } @@ -86,6 +90,7 @@ func queryNamespaces(kc *kubeconfig.Kubeconfig) ([]string, error) { return nil, fmt.Errorf("failed to list namespaces from k8s API: %w", err) } next = list.Continue + out = slices.Grow(out, len(list.Items)) for _, it := range list.Items { out = append(out, it.Name) } diff --git a/cmd/kubens/switch.go b/cmd/kubens/switch.go index b791fca..74777ac 100644 --- a/cmd/kubens/switch.go +++ b/cmd/kubens/switch.go @@ -49,7 +49,10 @@ func (s SwitchOp) Run(_, stderr io.Writer) error { } func switchNamespace(kc *kubeconfig.Kubeconfig, ns string, force bool) (string, error) { - ctx := kc.GetCurrentContext() + ctx, err := kc.GetCurrentContext() + if err != nil { + return "", fmt.Errorf("failed to get current context: %w", err) + } if ctx == "" { return "", errors.New("current-context is not set") } diff --git a/cmd/kubens/unset.go b/cmd/kubens/unset.go index 428698a..fe5c160 100644 --- a/cmd/kubens/unset.go +++ b/cmd/kubens/unset.go @@ -42,7 +42,10 @@ func (_ UnsetOp) Run(_, stderr io.Writer) error { } func clearNamespace(kc *kubeconfig.Kubeconfig) (string, error) { - ctx := kc.GetCurrentContext() + ctx, err := kc.GetCurrentContext() + if err != nil { + return "", fmt.Errorf("failed to get current context: %w", err) + } ns := "default" if ctx == "" { return "", errors.New("current-context is not set") diff --git a/internal/kubeconfig/contexts.go b/internal/kubeconfig/contexts.go index 10d417f..50fd11b 100644 --- a/internal/kubeconfig/contexts.go +++ b/internal/kubeconfig/contexts.go @@ -50,18 +50,25 @@ func (k *Kubeconfig) contextNode(name string) (*yaml.RNode, error) { return context, nil } -func (k *Kubeconfig) ContextNames() []string { +func (k *Kubeconfig) ContextNames() ([]string, error) { contexts, err := k.config.Pipe(yaml.Get("contexts")) if err != nil { - return nil + return nil, fmt.Errorf("failed to get contexts: %w", err) + } + if contexts == nil { + return nil, nil } names, err := contexts.ElementValues("name") if err != nil { - return nil + return nil, fmt.Errorf("failed to get context names: %w", err) } - return names + return names, nil } -func (k *Kubeconfig) ContextExists(name string) bool { - return slices.Contains(k.ContextNames(), name) +func (k *Kubeconfig) ContextExists(name string) (bool, error) { + names, err := k.ContextNames() + if err != nil { + return false, err + } + return slices.Contains(names, name), nil } diff --git a/internal/kubeconfig/contexts_test.go b/internal/kubeconfig/contexts_test.go index 28d7bc3..bd39ea7 100644 --- a/internal/kubeconfig/contexts_test.go +++ b/internal/kubeconfig/contexts_test.go @@ -33,7 +33,10 @@ func TestKubeconfig_ContextNames(t *testing.T) { t.Fatal(err) } - ctx := kc.ContextNames() + ctx, err := kc.ContextNames() + if err != nil { + t.Fatal(err) + } expected := []string{"abc", "def", "ghi"} if diff := cmp.Diff(expected, ctx); diff != "" { t.Fatalf("%s", diff) @@ -46,7 +49,10 @@ func TestKubeconfig_ContextNames_noContextsEntry(t *testing.T) { if err := kc.Parse(); err != nil { t.Fatal(err) } - ctx := kc.ContextNames() + ctx, err := kc.ContextNames() + if err != nil { + t.Fatal(err) + } var expected []string = nil if diff := cmp.Diff(expected, ctx); diff != "" { t.Fatalf("%s", diff) @@ -59,10 +65,9 @@ func TestKubeconfig_ContextNames_nonArrayContextsEntry(t *testing.T) { if err := kc.Parse(); err != nil { t.Fatal(err) } - ctx := kc.ContextNames() - var expected []string = nil - if diff := cmp.Diff(expected, ctx); diff != "" { - t.Fatalf("%s", diff) + _, err := kc.ContextNames() + if err == nil { + t.Fatal("expected error for non-array contexts entry") } } @@ -77,13 +82,15 @@ func TestKubeconfig_CheckContextExists(t *testing.T) { t.Fatal(err) } - if !kc.ContextExists("c1") { + if exists, err := kc.ContextExists("c1"); err != nil || !exists { t.Fatal("c1 actually exists; reported false") } - if !kc.ContextExists("c2") { + if exists, err := kc.ContextExists("c2"); err != nil || !exists { t.Fatal("c2 actually exists; reported false") } - if kc.ContextExists("c3") { + if exists, err := kc.ContextExists("c3"); err != nil { + t.Fatal(err) + } else if exists { t.Fatal("c3 does not exist; but reported true") } } diff --git a/internal/kubeconfig/currentcontext.go b/internal/kubeconfig/currentcontext.go index 43ff9cc..7d7e78a 100644 --- a/internal/kubeconfig/currentcontext.go +++ b/internal/kubeconfig/currentcontext.go @@ -15,17 +15,19 @@ package kubeconfig import ( + "fmt" + "sigs.k8s.io/kustomize/kyaml/yaml" ) // GetCurrentContext returns "current-context" value in given -// kubeconfig object Node, or returns "" if not found. -func (k *Kubeconfig) GetCurrentContext() string { +// kubeconfig object Node, or returns ("", nil) if not found. +func (k *Kubeconfig) GetCurrentContext() (string, error) { v, err := k.config.Pipe(yaml.Get("current-context")) if err != nil { - return "" + return "", fmt.Errorf("failed to read current-context: %w", err) } - return yaml.GetValue(v) + return yaml.GetValue(v), nil } func (k *Kubeconfig) UnsetCurrentContext() error { diff --git a/internal/kubeconfig/currentcontext_test.go b/internal/kubeconfig/currentcontext_test.go index f0db300..24ab2da 100644 --- a/internal/kubeconfig/currentcontext_test.go +++ b/internal/kubeconfig/currentcontext_test.go @@ -26,7 +26,10 @@ func TestKubeconfig_GetCurrentContext(t *testing.T) { if err := kc.Parse(); err != nil { t.Fatal(err) } - v := kc.GetCurrentContext() + v, err := kc.GetCurrentContext() + if err != nil { + t.Fatal(err) + } expected := "foo" if v != expected { @@ -40,7 +43,10 @@ func TestKubeconfig_GetCurrentContext_missingField(t *testing.T) { if err := kc.Parse(); err != nil { t.Fatal(err) } - v := kc.GetCurrentContext() + v, err := kc.GetCurrentContext() + if err != nil { + t.Fatal(err) + } expected := "" if v != expected { diff --git a/internal/kubeconfig/kubeconfig.go b/internal/kubeconfig/kubeconfig.go index 50a7e3c..fc619fa 100644 --- a/internal/kubeconfig/kubeconfig.go +++ b/internal/kubeconfig/kubeconfig.go @@ -87,5 +87,8 @@ func (k *Kubeconfig) Save() error { } enc := yaml.NewEncoder(k.f) enc.SetIndent(0) - return enc.Encode(k.config.YNode()) + if err := enc.Encode(k.config.YNode()); err != nil { + return err + } + return enc.Close() }