diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 30d097a..036dfb1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,7 +25,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v6 with: - go-version: '1.22' + go-version: '1.25' - id: go-cache-paths run: | echo "::set-output name=go-build::$(go env GOCACHE)" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 103ef02..99d6714 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,7 +29,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v6 with: - go-version: '1.22' + go-version: '1.25' - name: Install Snapcraft uses: samuelmeuli/action-snapcraft@v3 - name: Setup Snapcraft diff --git a/cmd/kubectx/current.go b/cmd/kubectx/current.go index 5b82e9c..a5886f3 100644 --- a/cmd/kubectx/current.go +++ b/cmd/kubectx/current.go @@ -15,11 +15,10 @@ package main import ( + "errors" "fmt" "io" - "github.com/pkg/errors" - "github.com/ahmetb/kubectx/internal/kubeconfig" ) @@ -33,7 +32,7 @@ func (_op CurrentOp) Run(stdout, _ io.Writer) error { kc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader) defer kc.Close() if err := kc.Parse(); err != nil { - return errors.Wrap(err, "kubeconfig error") + return fmt.Errorf("kubeconfig error: %w", err) } v := kc.GetCurrentContext() @@ -41,5 +40,8 @@ func (_op CurrentOp) Run(stdout, _ io.Writer) error { return errors.New("current-context is not set") } _, err := fmt.Fprintln(stdout, v) - return errors.Wrap(err, "write error") + if err != nil { + return fmt.Errorf("write error: %w", err) + } + return nil } diff --git a/cmd/kubectx/delete.go b/cmd/kubectx/delete.go index 260b336..d4c00e3 100644 --- a/cmd/kubectx/delete.go +++ b/cmd/kubectx/delete.go @@ -15,10 +15,10 @@ package main import ( + "errors" + "fmt" "io" - "github.com/pkg/errors" - "github.com/ahmetb/kubectx/internal/kubeconfig" "github.com/ahmetb/kubectx/internal/printer" ) @@ -37,7 +37,7 @@ func (op DeleteOp) Run(_, stderr io.Writer) error { // TODO inefficiency here. we open/write/close the same file many times. deletedName, wasActiveContext, err := deleteContext(ctx) if err != nil { - return errors.Wrapf(err, "error deleting context \"%s\"", deletedName) + return fmt.Errorf("error deleting context \"%s\": %w", deletedName, err) } if wasActiveContext { printer.Warning(stderr, "You deleted the current context. Use \"%s\" to select a new context.", @@ -55,7 +55,7 @@ func deleteContext(name string) (deleteName string, wasActiveContext bool, err e kc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader) defer kc.Close() if err := kc.Parse(); err != nil { - return deleteName, false, errors.Wrap(err, "kubeconfig error") + return deleteName, false, fmt.Errorf("kubeconfig error: %w", err) } cur := kc.GetCurrentContext() @@ -73,7 +73,10 @@ func deleteContext(name string) (deleteName string, wasActiveContext bool, err e } if err := kc.DeleteContextEntry(name); err != nil { - return name, false, errors.Wrap(err, "failed to modify yaml doc") + return name, false, fmt.Errorf("failed to modify yaml doc: %w", err) } - return name, wasActiveContext, errors.Wrap(kc.Save(), "failed to save modified kubeconfig file") + if err := kc.Save(); err != nil { + return name, wasActiveContext, fmt.Errorf("failed to save modified kubeconfig file: %w", err) + } + return name, wasActiveContext, nil } diff --git a/cmd/kubectx/fzf.go b/cmd/kubectx/fzf.go index 087516b..93cfe0b 100644 --- a/cmd/kubectx/fzf.go +++ b/cmd/kubectx/fzf.go @@ -16,14 +16,13 @@ package main import ( "bytes" + "errors" "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" @@ -49,7 +48,7 @@ func (op InteractiveSwitchOp) Run(_, stderr io.Writer) error { printer.Warning(stderr, "kubeconfig file not found") return nil } - return errors.Wrap(err, "kubeconfig error") + return fmt.Errorf("kubeconfig error: %w", err) } kc.Close() @@ -63,7 +62,8 @@ func (op InteractiveSwitchOp) Run(_, stderr io.Writer) error { 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 { + var exitErr *exec.ExitError + if !errors.As(err, &exitErr) { return err } } @@ -73,7 +73,7 @@ func (op InteractiveSwitchOp) Run(_, stderr io.Writer) error { } name, err := switchContext(choice) if err != nil { - return errors.Wrap(err, "failed to switch context") + return fmt.Errorf("failed to switch context: %w", err) } printer.Success(stderr, "Switched to context \"%s\".", printer.SuccessColor.Sprint(name)) return nil @@ -90,7 +90,7 @@ func (op InteractiveDeleteOp) Run(_, stderr io.Writer) error { printer.Warning(stderr, "kubeconfig file not found") return nil } - return errors.Wrap(err, "kubeconfig error") + return fmt.Errorf("kubeconfig error: %w", err) } kc.Close() @@ -108,7 +108,8 @@ func (op InteractiveDeleteOp) Run(_, stderr io.Writer) error { 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 { + var exitErr *exec.ExitError + if !errors.As(err, &exitErr) { return err } } @@ -120,7 +121,7 @@ func (op InteractiveDeleteOp) Run(_, stderr io.Writer) error { name, wasActiveContext, err := deleteContext(choice) if err != nil { - return errors.Wrap(err, "failed to delete context") + return fmt.Errorf("failed to delete context: %w", err) } if wasActiveContext { diff --git a/cmd/kubectx/help.go b/cmd/kubectx/help.go index 95a636f..6b85d1c 100644 --- a/cmd/kubectx/help.go +++ b/cmd/kubectx/help.go @@ -20,8 +20,6 @@ import ( "os" "path/filepath" "strings" - - "github.com/pkg/errors" ) // HelpOp describes printing help. @@ -50,7 +48,10 @@ func printUsage(out io.Writer) error { help = strings.ReplaceAll(help, "%SPAC%", strings.Repeat(" ", len(selfName()))) _, err := fmt.Fprintf(out, "%s\n", help) - return errors.Wrap(err, "write error") + if err != nil { + return fmt.Errorf("write error: %w", err) + } + return nil } // selfName guesses how the user invoked the program. diff --git a/cmd/kubectx/list.go b/cmd/kubectx/list.go index d8b84f6..efb8ca2 100644 --- a/cmd/kubectx/list.go +++ b/cmd/kubectx/list.go @@ -19,7 +19,6 @@ import ( "io" "facette.io/natsort" - "github.com/pkg/errors" "github.com/ahmetb/kubectx/internal/cmdutil" "github.com/ahmetb/kubectx/internal/kubeconfig" @@ -40,7 +39,7 @@ func (_ ListOp) Run(stdout, stderr io.Writer) error { printer.Warning(stderr, "kubeconfig file not found") return nil } - return errors.Wrap(err, "kubeconfig error") + return fmt.Errorf("kubeconfig error: %w", err) } ctxs := kc.ContextNames() diff --git a/cmd/kubectx/main.go b/cmd/kubectx/main.go index 370fbe0..d7d7027 100644 --- a/cmd/kubectx/main.go +++ b/cmd/kubectx/main.go @@ -34,7 +34,7 @@ func main() { op := parseArgs(os.Args[1:]) if err := op.Run(color.Output, color.Error); err != nil { - printer.Error(color.Error, err.Error()) + printer.Error(color.Error, "%s", err) if _, ok := os.LookupEnv(env.EnvDebug); ok { // print stack trace in verbose mode diff --git a/cmd/kubectx/rename.go b/cmd/kubectx/rename.go index 32ddf96..ed45baa 100644 --- a/cmd/kubectx/rename.go +++ b/cmd/kubectx/rename.go @@ -15,11 +15,10 @@ package main import ( + "fmt" "io" "strings" - "github.com/pkg/errors" - "github.com/ahmetb/kubectx/internal/kubeconfig" "github.com/ahmetb/kubectx/internal/printer" ) @@ -33,12 +32,8 @@ type RenameOp struct { // parseRenameSyntax parses A=B form into [A,B] and returns // whether it is parsed correctly. func parseRenameSyntax(v string) (string, string, bool) { - s := strings.Split(v, "=") - if len(s) != 2 { - return "", "", false - } - new, old := s[0], s[1] - if new == "" || old == "" { + new, old, ok := strings.Cut(v, "=") + if !ok || new == "" || old == "" { return "", "", false } return new, old, true @@ -54,7 +49,7 @@ func (op RenameOp) Run(_, stderr io.Writer) error { kc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader) defer kc.Close() if err := kc.Parse(); err != nil { - return errors.Wrap(err, "kubeconfig error") + return fmt.Errorf("kubeconfig error: %w", err) } cur := kc.GetCurrentContext() @@ -63,26 +58,26 @@ func (op RenameOp) Run(_, stderr io.Writer) error { } if !kc.ContextExists(op.Old) { - return errors.Errorf("context \"%s\" not found, can't rename it", op.Old) + return fmt.Errorf("context \"%s\" not found, can't rename it", op.Old) } if kc.ContextExists(op.New) { printer.Warning(stderr, "context \"%s\" exists, overwriting it.", op.New) if err := kc.DeleteContextEntry(op.New); err != nil { - return errors.Wrap(err, "failed to delete new context to overwrite it") + return fmt.Errorf("failed to delete new context to overwrite it: %w", err) } } if err := kc.ModifyContextName(op.Old, op.New); err != nil { - return errors.Wrap(err, "failed to change context name") + return fmt.Errorf("failed to change context name: %w", err) } if op.Old == cur { if err := kc.ModifyCurrentContext(op.New); err != nil { - return errors.Wrap(err, "failed to set current-context to new name") + return fmt.Errorf("failed to set current-context to new name: %w", err) } } if err := kc.Save(); err != nil { - return errors.Wrap(err, "failed to save modified kubeconfig") + return fmt.Errorf("failed to save modified kubeconfig: %w", err) } printer.Success(stderr, "Context %s renamed to %s.", printer.SuccessColor.Sprint(op.Old), diff --git a/cmd/kubectx/shell.go b/cmd/kubectx/shell.go index a5d84e4..54510f0 100644 --- a/cmd/kubectx/shell.go +++ b/cmd/kubectx/shell.go @@ -8,7 +8,6 @@ import ( "runtime" "github.com/fatih/color" - "github.com/pkg/errors" "github.com/ahmetb/kubectx/internal/env" "github.com/ahmetb/kubectx/internal/kubeconfig" @@ -34,7 +33,7 @@ func (op ShellOp) Run(_, stderr io.Writer) error { kc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader) defer kc.Close() if err := kc.Parse(); err != nil { - return errors.Wrap(err, "kubeconfig error") + return fmt.Errorf("kubeconfig error: %w", err) } if !kc.ContextExists(op.Target) { return fmt.Errorf("no context exists with the name: \"%s\"", op.Target) @@ -44,20 +43,20 @@ func (op ShellOp) Run(_, stderr io.Writer) error { // Extract minimal kubeconfig using kubectl data, err := extractMinimalKubeconfig(kubectlPath, op.Target) if err != nil { - return errors.Wrap(err, "failed to extract kubeconfig for context") + return fmt.Errorf("failed to extract kubeconfig for context: %w", err) } // Write to temp file tmpFile, err := os.CreateTemp("", "kubectx-shell-*.yaml") if err != nil { - return errors.Wrap(err, "failed to create temp kubeconfig file") + return fmt.Errorf("failed to create temp kubeconfig file: %w", err) } tmpPath := tmpFile.Name() defer os.Remove(tmpPath) if _, err := tmpFile.Write(data); err != nil { tmpFile.Close() - return errors.Wrap(err, "failed to write temp kubeconfig") + return fmt.Errorf("failed to write temp kubeconfig: %w", err) } tmpFile.Close() diff --git a/cmd/kubectx/shell_test.go b/cmd/kubectx/shell_test.go index 34156ca..7ade4ce 100644 --- a/cmd/kubectx/shell_test.go +++ b/cmd/kubectx/shell_test.go @@ -2,7 +2,6 @@ package main import ( "bytes" - "os" "runtime" "testing" @@ -33,13 +32,7 @@ func Test_detectShell_unix(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - orig := os.Getenv("SHELL") - defer os.Setenv("SHELL", orig) - - os.Setenv("SHELL", tt.shellEnv) - if tt.shellEnv == "" { - os.Unsetenv("SHELL") - } + t.Setenv("SHELL", tt.shellEnv) got := detectShell() if got != tt.want { @@ -51,9 +44,7 @@ func Test_detectShell_unix(t *testing.T) { func Test_ShellOp_blockedWhenNested(t *testing.T) { // Simulate being inside an isolated shell - orig := os.Getenv(env.EnvIsolatedShell) - defer os.Setenv(env.EnvIsolatedShell, orig) - os.Setenv(env.EnvIsolatedShell, "1") + t.Setenv(env.EnvIsolatedShell, "1") op := ShellOp{Target: "some-context"} var stdout, stderr bytes.Buffer @@ -75,10 +66,7 @@ func Test_ShellOp_blockedWhenNested(t *testing.T) { } func Test_resolveKubectl_envVar(t *testing.T) { - orig := os.Getenv("KUBECTL") - defer os.Setenv("KUBECTL", orig) - - os.Setenv("KUBECTL", "/custom/path/kubectl") + t.Setenv("KUBECTL", "/custom/path/kubectl") got, err := resolveKubectl() if err != nil { t.Fatalf("unexpected error: %v", err) @@ -89,9 +77,7 @@ func Test_resolveKubectl_envVar(t *testing.T) { } func Test_resolveKubectl_inPath(t *testing.T) { - orig := os.Getenv("KUBECTL") - defer os.Setenv("KUBECTL", orig) - os.Unsetenv("KUBECTL") + t.Setenv("KUBECTL", "") // kubectl should be findable in PATH on most dev machines got, err := resolveKubectl() @@ -104,9 +90,7 @@ func Test_resolveKubectl_inPath(t *testing.T) { } func Test_checkIsolatedMode_notSet(t *testing.T) { - orig := os.Getenv(env.EnvIsolatedShell) - defer os.Setenv(env.EnvIsolatedShell, orig) - os.Unsetenv(env.EnvIsolatedShell) + t.Setenv(env.EnvIsolatedShell, "") err := checkIsolatedMode() if err != nil { @@ -115,9 +99,7 @@ func Test_checkIsolatedMode_notSet(t *testing.T) { } func Test_checkIsolatedMode_set(t *testing.T) { - orig := os.Getenv(env.EnvIsolatedShell) - defer os.Setenv(env.EnvIsolatedShell, orig) - os.Setenv(env.EnvIsolatedShell, "1") + t.Setenv(env.EnvIsolatedShell, "1") err := checkIsolatedMode() if err == nil { diff --git a/cmd/kubectx/state.go b/cmd/kubectx/state.go index d657159..90cb18c 100644 --- a/cmd/kubectx/state.go +++ b/cmd/kubectx/state.go @@ -15,12 +15,11 @@ package main import ( - "io/ioutil" + "errors" + "fmt" "os" "path/filepath" - "github.com/pkg/errors" - "github.com/ahmetb/kubectx/internal/cmdutil" ) @@ -35,7 +34,7 @@ func kubectxPrevCtxFile() (string, error) { // readLastContext returns the saved previous context // if the state file exists, otherwise returns "". func readLastContext(path string) (string, error) { - b, err := ioutil.ReadFile(path) + b, err := os.ReadFile(path) if os.IsNotExist(err) { return "", nil } @@ -47,7 +46,7 @@ func readLastContext(path string) (string, error) { func writeLastContext(path, value string) error { dir := filepath.Dir(path) if err := os.MkdirAll(dir, 0755); err != nil { - return errors.Wrap(err, "failed to create parent directories") + return fmt.Errorf("failed to create parent directories: %w", err) } - return ioutil.WriteFile(path, []byte(value), 0644) + return os.WriteFile(path, []byte(value), 0644) } diff --git a/cmd/kubectx/state_test.go b/cmd/kubectx/state_test.go index 1be60d0..5a3e347 100644 --- a/cmd/kubectx/state_test.go +++ b/cmd/kubectx/state_test.go @@ -15,12 +15,9 @@ package main import ( - "io/ioutil" "os" "path/filepath" "testing" - - "github.com/ahmetb/kubectx/internal/testutil" ) func Test_readLastContext_nonExistingFile(t *testing.T) { @@ -34,8 +31,11 @@ func Test_readLastContext_nonExistingFile(t *testing.T) { } func Test_readLastContext(t *testing.T) { - path, cleanup := testutil.TempFile(t, "foo") - defer cleanup() + dir := t.TempDir() + path := filepath.Join(dir, "testfile") + if err := os.WriteFile(path, []byte("foo"), 0644); err != nil { + t.Fatal(err) + } s, err := readLastContext(path) if err != nil { @@ -55,10 +55,7 @@ func Test_writeLastContext_err(t *testing.T) { } func Test_writeLastContext(t *testing.T) { - dir, err := ioutil.TempDir(os.TempDir(), "state-file-test") - if err != nil { - t.Fatal(err) - } + dir := t.TempDir() path := filepath.Join(dir, "foo", "bar") if err := writeLastContext(path, "ctx1"); err != nil { @@ -75,9 +72,7 @@ func Test_writeLastContext(t *testing.T) { } func Test_kubectxFilePath(t *testing.T) { - origHome := os.Getenv("HOME") - os.Setenv("HOME", filepath.FromSlash("/foo/bar")) - defer os.Setenv("HOME", origHome) + t.Setenv("HOME", filepath.FromSlash("/foo/bar")) expected := filepath.Join(filepath.FromSlash("/foo/bar"), ".kube", "kubectx") v, err := kubectxPrevCtxFile() @@ -90,12 +85,8 @@ func Test_kubectxFilePath(t *testing.T) { } func Test_kubectxFilePath_error(t *testing.T) { - origHome := os.Getenv("HOME") - origUserprofile := os.Getenv("USERPROFILE") - os.Unsetenv("HOME") - os.Unsetenv("USERPROFILE") - defer os.Setenv("HOME", origHome) - defer os.Setenv("USERPROFILE", origUserprofile) + t.Setenv("HOME", "") + t.Setenv("USERPROFILE", "") _, err := kubectxPrevCtxFile() if err == nil { diff --git a/cmd/kubectx/switch.go b/cmd/kubectx/switch.go index b245747..2740811 100644 --- a/cmd/kubectx/switch.go +++ b/cmd/kubectx/switch.go @@ -15,10 +15,10 @@ package main import ( + "errors" + "fmt" "io" - "github.com/pkg/errors" - "github.com/ahmetb/kubectx/internal/kubeconfig" "github.com/ahmetb/kubectx/internal/printer" ) @@ -40,39 +40,39 @@ func (op SwitchOp) Run(_, stderr io.Writer) error { newCtx, err = switchContext(op.Target) } if err != nil { - return errors.Wrap(err, "failed to switch context") + return fmt.Errorf("failed to switch context: %w", err) } err = printer.Success(stderr, "Switched to context \"%s\".", printer.SuccessColor.Sprint(newCtx)) - return errors.Wrap(err, "print error") + return fmt.Errorf("print error: %w", err) } // switchContext switches to specified context name. func switchContext(name string) (string, error) { prevCtxFile, err := kubectxPrevCtxFile() if err != nil { - return "", errors.Wrap(err, "failed to determine state file") + return "", fmt.Errorf("failed to determine state file: %w", err) } kc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader) defer kc.Close() if err := kc.Parse(); err != nil { - return "", errors.Wrap(err, "kubeconfig error") + return "", fmt.Errorf("kubeconfig error: %w", err) } prev := kc.GetCurrentContext() if !kc.ContextExists(name) { - return "", errors.Errorf("no context exists with the name: \"%s\"", name) + return "", fmt.Errorf("no context exists with the name: \"%s\"", name) } if err := kc.ModifyCurrentContext(name); err != nil { return "", err } if err := kc.Save(); err != nil { - return "", errors.Wrap(err, "failed to save kubeconfig") + return "", fmt.Errorf("failed to save kubeconfig: %w", err) } if prev != name { if err := writeLastContext(prevCtxFile, prev); err != nil { - return "", errors.Wrap(err, "failed to save previous context name") + return "", fmt.Errorf("failed to save previous context name: %w", err) } } return name, nil @@ -82,11 +82,11 @@ func switchContext(name string) (string, error) { func swapContext() (string, error) { prevCtxFile, err := kubectxPrevCtxFile() if err != nil { - return "", errors.Wrap(err, "failed to determine state file") + return "", fmt.Errorf("failed to determine state file: %w", err) } prev, err := readLastContext(prevCtxFile) if err != nil { - return "", errors.Wrap(err, "failed to read previous context file") + return "", fmt.Errorf("failed to read previous context file: %w", err) } if prev == "" { return "", errors.New("no previous context found") diff --git a/cmd/kubectx/unset.go b/cmd/kubectx/unset.go index f22e922..1e02a15 100644 --- a/cmd/kubectx/unset.go +++ b/cmd/kubectx/unset.go @@ -15,10 +15,9 @@ package main import ( + "fmt" "io" - "github.com/pkg/errors" - "github.com/ahmetb/kubectx/internal/kubeconfig" "github.com/ahmetb/kubectx/internal/printer" ) @@ -33,16 +32,19 @@ func (_ UnsetOp) Run(_, stderr io.Writer) error { kc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader) defer kc.Close() if err := kc.Parse(); err != nil { - return errors.Wrap(err, "kubeconfig error") + return fmt.Errorf("kubeconfig error: %w", err) } if err := kc.UnsetCurrentContext(); err != nil { - return errors.Wrap(err, "error while modifying current-context") + return fmt.Errorf("error while modifying current-context: %w", err) } if err := kc.Save(); err != nil { - return errors.Wrap(err, "failed to save kubeconfig file after modification") + return fmt.Errorf("failed to save kubeconfig file after modification: %w", err) } err := printer.Success(stderr, "Active context unset for kubectl.") - return errors.Wrap(err, "write error") + if err != nil { + return fmt.Errorf("write error: %w", err) + } + return nil } diff --git a/cmd/kubectx/version.go b/cmd/kubectx/version.go index dc5c8be..a5cd8f1 100644 --- a/cmd/kubectx/version.go +++ b/cmd/kubectx/version.go @@ -3,8 +3,6 @@ package main import ( "fmt" "io" - - "github.com/pkg/errors" ) var ( @@ -16,5 +14,8 @@ type VersionOp struct{} func (_ VersionOp) Run(stdout, _ io.Writer) error { _, err := fmt.Fprintf(stdout, "%s\n", version) - return errors.Wrap(err, "write error") + if err != nil { + return fmt.Errorf("write error: %w", err) + } + return nil } diff --git a/cmd/kubens/current.go b/cmd/kubens/current.go index 00993ce..3a19409 100644 --- a/cmd/kubens/current.go +++ b/cmd/kubens/current.go @@ -15,11 +15,10 @@ package main import ( + "errors" "fmt" "io" - "github.com/pkg/errors" - "github.com/ahmetb/kubectx/internal/kubeconfig" ) @@ -29,7 +28,7 @@ func (c CurrentOp) Run(stdout, _ io.Writer) error { kc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader) defer kc.Close() if err := kc.Parse(); err != nil { - return errors.Wrap(err, "kubeconfig error") + return fmt.Errorf("kubeconfig error: %w", err) } ctx := kc.GetCurrentContext() @@ -38,8 +37,11 @@ func (c CurrentOp) Run(stdout, _ io.Writer) error { } ns, err := kc.NamespaceOfContext(ctx) if err != nil { - return errors.Wrapf(err, "failed to read namespace of \"%s\"", ctx) + return fmt.Errorf("failed to read namespace of \"%s\": %w", ctx, err) } _, err = fmt.Fprintln(stdout, ns) - return errors.Wrap(err, "write error") + if err != nil { + return fmt.Errorf("write error: %w", err) + } + return nil } diff --git a/cmd/kubens/fzf.go b/cmd/kubens/fzf.go index f093398..95d49df 100644 --- a/cmd/kubens/fzf.go +++ b/cmd/kubens/fzf.go @@ -16,14 +16,13 @@ package main import ( "bytes" + "errors" "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" @@ -43,7 +42,7 @@ func (op InteractiveSwitchOp) Run(_, stderr io.Writer) error { printer.Warning(stderr, "kubeconfig file not found") return nil } - return errors.Wrap(err, "kubeconfig error") + return fmt.Errorf("kubeconfig error: %w", err) } defer kc.Close() @@ -57,7 +56,8 @@ func (op InteractiveSwitchOp) Run(_, stderr io.Writer) error { 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 { + var exitErr *exec.ExitError + if !errors.As(err, &exitErr) { return err } } @@ -67,7 +67,7 @@ func (op InteractiveSwitchOp) Run(_, stderr io.Writer) error { } name, err := switchNamespace(kc, choice, false) if err != nil { - return errors.Wrap(err, "failed to switch namespace") + return fmt.Errorf("failed to switch namespace: %w", err) } printer.Success(stderr, "Active namespace is \"%s\".", printer.SuccessColor.Sprint(name)) return nil diff --git a/cmd/kubens/help.go b/cmd/kubens/help.go index 027d333..9b7cab3 100644 --- a/cmd/kubens/help.go +++ b/cmd/kubens/help.go @@ -20,8 +20,6 @@ import ( "os" "path/filepath" "strings" - - "github.com/pkg/errors" ) // HelpOp describes printing help. @@ -45,7 +43,10 @@ func printUsage(out io.Writer) error { help = strings.ReplaceAll(help, "%PROG%", selfName()) _, err := fmt.Fprintf(out, "%s\n", help) - return errors.Wrap(err, "write error") + if err != nil { + return fmt.Errorf("write error: %w", err) + } + return nil } // selfName guesses how the user invoked the program. diff --git a/cmd/kubens/list.go b/cmd/kubens/list.go index 38ee08d..7097a02 100644 --- a/cmd/kubens/list.go +++ b/cmd/kubens/list.go @@ -16,11 +16,11 @@ package main import ( "context" + "errors" "fmt" "io" "os" - "github.com/pkg/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" _ "k8s.io/client-go/plugin/pkg/client/auth" @@ -36,7 +36,7 @@ func (op ListOp) Run(stdout, stderr io.Writer) error { kc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader) defer kc.Close() if err := kc.Parse(); err != nil { - return errors.Wrap(err, "kubeconfig error") + return fmt.Errorf("kubeconfig error: %w", err) } ctx := kc.GetCurrentContext() @@ -45,12 +45,12 @@ func (op ListOp) Run(stdout, stderr io.Writer) error { } curNs, err := kc.NamespaceOfContext(ctx) if err != nil { - return errors.Wrap(err, "cannot read current namespace") + return fmt.Errorf("cannot read current namespace: %w", err) } ns, err := queryNamespaces(kc) if err != nil { - return errors.Wrap(err, "could not list namespaces (is the cluster accessible?)") + return fmt.Errorf("could not list namespaces (is the cluster accessible?): %w", err) } for _, c := range ns { @@ -70,7 +70,7 @@ func queryNamespaces(kc *kubeconfig.Kubeconfig) ([]string, error) { clientset, err := newKubernetesClientSet(kc) if err != nil { - return nil, errors.Wrap(err, "failed to initialize k8s REST client") + return nil, fmt.Errorf("failed to initialize k8s REST client: %w", err) } var out []string @@ -83,7 +83,7 @@ func queryNamespaces(kc *kubeconfig.Kubeconfig) ([]string, error) { Continue: next, }) if err != nil { - return nil, errors.Wrap(err, "failed to list namespaces from k8s API") + return nil, fmt.Errorf("failed to list namespaces from k8s API: %w", err) } next = list.Continue for _, it := range list.Items { @@ -99,11 +99,11 @@ func queryNamespaces(kc *kubeconfig.Kubeconfig) ([]string, error) { func newKubernetesClientSet(kc *kubeconfig.Kubeconfig) (*kubernetes.Clientset, error) { b, err := kc.Bytes() if err != nil { - return nil, errors.Wrap(err, "failed to convert in-memory kubeconfig to yaml") + return nil, fmt.Errorf("failed to convert in-memory kubeconfig to yaml: %w", err) } cfg, err := clientcmd.RESTConfigFromKubeConfig(b) if err != nil { - return nil, errors.Wrap(err, "failed to initialize config") + return nil, fmt.Errorf("failed to initialize config: %w", err) } return kubernetes.NewForConfig(cfg) } diff --git a/cmd/kubens/main.go b/cmd/kubens/main.go index 21ba816..05b08c7 100644 --- a/cmd/kubens/main.go +++ b/cmd/kubens/main.go @@ -33,7 +33,7 @@ func main() { cmdutil.PrintDeprecatedEnvWarnings(color.Error, os.Environ()) op := parseArgs(os.Args[1:]) if err := op.Run(color.Output, color.Error); err != nil { - printer.Error(color.Error, err.Error()) + printer.Error(color.Error, "%s", err) if _, ok := os.LookupEnv(env.EnvDebug); ok { // print stack trace in verbose mode diff --git a/cmd/kubens/statefile.go b/cmd/kubens/statefile.go index b4e6305..03a5c3a 100644 --- a/cmd/kubens/statefile.go +++ b/cmd/kubens/statefile.go @@ -16,7 +16,6 @@ package main import ( "bytes" - "io/ioutil" "os" "path/filepath" "runtime" @@ -45,7 +44,7 @@ func (f NSFile) path() string { // Load reads the previous namespace setting, or returns empty if not exists. func (f NSFile) Load() (string, error) { - b, err := ioutil.ReadFile(f.path()) + b, err := os.ReadFile(f.path()) if err != nil { if os.IsNotExist(err) { return "", nil @@ -61,7 +60,7 @@ func (f NSFile) Save(value string) error { if err := os.MkdirAll(d, 0755); err != nil { return err } - return ioutil.WriteFile(f.path(), []byte(value), 0644) + return os.WriteFile(f.path(), []byte(value), 0644) } // isWindows determines if the process is running on windows OS. diff --git a/cmd/kubens/statefile_test.go b/cmd/kubens/statefile_test.go index b50878d..271ffab 100644 --- a/cmd/kubens/statefile_test.go +++ b/cmd/kubens/statefile_test.go @@ -15,8 +15,6 @@ package main import ( - "io/ioutil" - "os" "runtime" "strings" "testing" @@ -25,11 +23,7 @@ import ( ) func TestNSFile(t *testing.T) { - td, err := ioutil.TempDir(os.TempDir(), "") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(td) + td := t.TempDir() f := NewNSFile("foo") f.dir = td diff --git a/cmd/kubens/switch.go b/cmd/kubens/switch.go index bcf2a70..b791fca 100644 --- a/cmd/kubens/switch.go +++ b/cmd/kubens/switch.go @@ -16,10 +16,11 @@ package main import ( "context" + "errors" + "fmt" "io" "os" - "github.com/pkg/errors" errors2 "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -36,7 +37,7 @@ func (s SwitchOp) Run(_, stderr io.Writer) error { kc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader) defer kc.Close() if err := kc.Parse(); err != nil { - return errors.Wrap(err, "kubeconfig error") + return fmt.Errorf("kubeconfig error: %w", err) } toNS, err := switchNamespace(kc, s.Target, s.Force) @@ -54,18 +55,18 @@ func switchNamespace(kc *kubeconfig.Kubeconfig, ns string, force bool) (string, } curNS, err := kc.NamespaceOfContext(ctx) if err != nil { - return "", errors.Wrap(err, "failed to get current namespace") + return "", fmt.Errorf("failed to get current namespace: %w", err) } f := NewNSFile(ctx) prev, err := f.Load() if err != nil { - return "", errors.Wrap(err, "failed to load previous namespace from file") + return "", fmt.Errorf("failed to load previous namespace from file: %w", err) } if ns == "-" { if prev == "" { - return "", errors.Errorf("No previous namespace found for current context (%s)", ctx) + return "", fmt.Errorf("No previous namespace found for current context (%s)", ctx) } ns = prev } @@ -73,22 +74,22 @@ func switchNamespace(kc *kubeconfig.Kubeconfig, ns string, force bool) (string, if !force { ok, err := namespaceExists(kc, ns) if err != nil { - return "", errors.Wrap(err, "failed to query if namespace exists (is cluster accessible?)") + return "", fmt.Errorf("failed to query if namespace exists (is cluster accessible?): %w", err) } if !ok { - return "", errors.Errorf("no namespace exists with name \"%s\"", ns) + return "", fmt.Errorf("no namespace exists with name \"%s\"", ns) } } if err := kc.SetNamespace(ctx, ns); err != nil { - return "", errors.Wrapf(err, "failed to change to namespace \"%s\"", ns) + return "", fmt.Errorf("failed to change to namespace \"%s\": %w", ns, err) } if err := kc.Save(); err != nil { - return "", errors.Wrap(err, "failed to save kubeconfig file") + return "", fmt.Errorf("failed to save kubeconfig file: %w", err) } if curNS != ns { if err := f.Save(curNS); err != nil { - return "", errors.Wrap(err, "failed to save the previous namespace to file") + return "", fmt.Errorf("failed to save the previous namespace to file: %w", err) } } return ns, nil @@ -102,13 +103,15 @@ func namespaceExists(kc *kubeconfig.Kubeconfig, ns string) (bool, error) { clientset, err := newKubernetesClientSet(kc) if err != nil { - return false, errors.Wrap(err, "failed to initialize k8s REST client") + return false, fmt.Errorf("failed to initialize k8s REST client: %w", err) } namespace, err := clientset.CoreV1().Namespaces().Get(context.Background(), ns, metav1.GetOptions{}) if errors2.IsNotFound(err) { return false, nil } - return namespace != nil, errors.Wrapf(err, "failed to query "+ - "namespace %q from k8s API", ns) + if err != nil { + return false, fmt.Errorf("failed to query namespace %q from k8s API: %w", ns, err) + } + return namespace != nil, nil } diff --git a/cmd/kubens/version.go b/cmd/kubens/version.go index dc5c8be..a5cd8f1 100644 --- a/cmd/kubens/version.go +++ b/cmd/kubens/version.go @@ -3,8 +3,6 @@ package main import ( "fmt" "io" - - "github.com/pkg/errors" ) var ( @@ -16,5 +14,8 @@ type VersionOp struct{} func (_ VersionOp) Run(stdout, _ io.Writer) error { _, err := fmt.Fprintf(stdout, "%s\n", version) - return errors.Wrap(err, "write error") + if err != nil { + return fmt.Errorf("write error: %w", err) + } + return nil } diff --git a/go.mod b/go.mod index 7b25ef9..77b5cef 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,6 @@ require ( github.com/fatih/color v1.18.0 github.com/google/go-cmp v0.7.0 github.com/mattn/go-isatty v0.0.20 - github.com/pkg/errors v0.9.1 k8s.io/apimachinery v0.35.2 k8s.io/client-go v0.35.2 sigs.k8s.io/kustomize/kyaml v0.21.1 diff --git a/go.sum b/go.sum index 90db54d..7a4d423 100644 --- a/go.sum +++ b/go.sum @@ -65,8 +65,6 @@ github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= diff --git a/internal/cmdutil/util.go b/internal/cmdutil/util.go index cc9539f..df68c3f 100644 --- a/internal/cmdutil/util.go +++ b/internal/cmdutil/util.go @@ -15,9 +15,8 @@ package cmdutil import ( + "errors" "os" - - "github.com/pkg/errors" ) func HomeDir() string { @@ -28,8 +27,7 @@ func HomeDir() string { return home } -// IsNotFoundErr determines if the underlying error is os.IsNotExist. Right now -// errors from github.com/pkg/errors doesn't work with os.IsNotExist. +// IsNotFoundErr determines if the underlying error is os.IsNotExist. func IsNotFoundErr(err error) bool { for e := err; e != nil; e = errors.Unwrap(e) { if os.IsNotExist(e) { diff --git a/internal/kubeconfig/contexts.go b/internal/kubeconfig/contexts.go index b374d18..10d417f 100644 --- a/internal/kubeconfig/contexts.go +++ b/internal/kubeconfig/contexts.go @@ -15,7 +15,10 @@ package kubeconfig import ( - "github.com/pkg/errors" + "errors" + "fmt" + "slices" + "sigs.k8s.io/kustomize/kyaml/yaml" ) @@ -42,7 +45,7 @@ func (k *Kubeconfig) contextNode(name string) (*yaml.RNode, error) { return nil, err } if context == nil { - return nil, errors.Errorf("context with name \"%s\" not found", name) + return nil, fmt.Errorf("context with name \"%s\" not found", name) } return context, nil } @@ -60,11 +63,5 @@ func (k *Kubeconfig) ContextNames() []string { } func (k *Kubeconfig) ContextExists(name string) bool { - ctxNames := k.ContextNames() - for _, v := range ctxNames { - if v == name { - return true - } - } - return false + return slices.Contains(k.ContextNames(), name) } diff --git a/internal/kubeconfig/kubeconfig.go b/internal/kubeconfig/kubeconfig.go index 6f8d85c..50a7e3c 100644 --- a/internal/kubeconfig/kubeconfig.go +++ b/internal/kubeconfig/kubeconfig.go @@ -15,9 +15,10 @@ package kubeconfig import ( + "errors" + "fmt" "io" - "github.com/pkg/errors" "sigs.k8s.io/kustomize/kyaml/yaml" ) @@ -54,7 +55,7 @@ func (k *Kubeconfig) Close() error { func (k *Kubeconfig) Parse() error { files, err := k.loader.Load() if err != nil { - return errors.Wrap(err, "failed to load") + return fmt.Errorf("failed to load: %w", err) } // TODO since we don't support multiple kubeconfig files at the moment, there's just 1 file @@ -63,7 +64,7 @@ func (k *Kubeconfig) Parse() error { k.f = f var v yaml.Node if err := yaml.NewDecoder(f).Decode(&v); err != nil { - return errors.Wrap(err, "failed to decode") + return fmt.Errorf("failed to decode: %w", err) } k.config = yaml.NewRNode(&v) if k.config.YNode().Kind != yaml.MappingNode { @@ -82,7 +83,7 @@ func (k *Kubeconfig) Bytes() ([]byte, error) { func (k *Kubeconfig) Save() error { if err := k.f.Reset(); err != nil { - return errors.Wrap(err, "failed to reset file") + return fmt.Errorf("failed to reset file: %w", err) } enc := yaml.NewEncoder(k.f) enc.SetIndent(0) diff --git a/internal/kubeconfig/kubeconfigloader.go b/internal/kubeconfig/kubeconfigloader.go index 2fa26b8..f9e7d48 100644 --- a/internal/kubeconfig/kubeconfigloader.go +++ b/internal/kubeconfig/kubeconfigloader.go @@ -15,11 +15,12 @@ package kubeconfig import ( - "github.com/ahmetb/kubectx/internal/cmdutil" + "errors" + "fmt" "os" "path/filepath" - "github.com/pkg/errors" + "github.com/ahmetb/kubectx/internal/cmdutil" ) var ( @@ -33,15 +34,15 @@ type kubeconfigFile struct{ *os.File } func (*StandardKubeconfigLoader) Load() ([]ReadWriteResetCloser, error) { cfgPath, err := kubeconfigPath() if err != nil { - return nil, errors.Wrap(err, "cannot determine kubeconfig path") + return nil, fmt.Errorf("cannot determine kubeconfig path: %w", err) } f, err := os.OpenFile(cfgPath, os.O_RDWR, 0) if err != nil { if os.IsNotExist(err) { - return nil, errors.Wrap(err, "kubeconfig file not found") + return nil, fmt.Errorf("kubeconfig file not found: %w", err) } - return nil, errors.Wrap(err, "failed to open file") + return nil, fmt.Errorf("failed to open file: %w", err) } // TODO we'll return all kubeconfig files when we start implementing multiple kubeconfig support @@ -50,10 +51,10 @@ func (*StandardKubeconfigLoader) Load() ([]ReadWriteResetCloser, error) { func (kf *kubeconfigFile) Reset() error { if err := kf.Truncate(0); err != nil { - return errors.Wrap(err, "failed to truncate file") + return fmt.Errorf("failed to truncate file: %w", err) } _, err := kf.Seek(0, 0) - return errors.Wrap(err, "failed to seek in file") + return fmt.Errorf("failed to seek in file: %w", err) } func kubeconfigPath() (string, error) { diff --git a/internal/printer/printer.go b/internal/printer/printer.go index 7bee37c..a841189 100644 --- a/internal/printer/printer.go +++ b/internal/printer/printer.go @@ -43,17 +43,17 @@ func init() { } } -func Error(w io.Writer, format string, args ...interface{}) error { - _, err := fmt.Fprintf(w, ErrorColor.Sprint("error: ")+format+"\n", args...) +func Error(w io.Writer, format string, args ...any) error { + _, err := io.WriteString(w, ErrorColor.Sprint("error: ")+fmt.Sprintf(format, args...)+"\n") return err } -func Warning(w io.Writer, format string, args ...interface{}) error { - _, err := fmt.Fprintf(w, WarningColor.Sprint("warning: ")+format+"\n", args...) +func Warning(w io.Writer, format string, args ...any) error { + _, err := io.WriteString(w, WarningColor.Sprint("warning: ")+fmt.Sprintf(format, args...)+"\n") return err } -func Success(w io.Writer, format string, args ...interface{}) error { - _, err := fmt.Fprintf(w, SuccessColor.Sprint("✔ ")+fmt.Sprintf(format+"\n", args...)) +func Success(w io.Writer, format string, args ...any) error { + _, err := io.WriteString(w, SuccessColor.Sprint("✔ ")+fmt.Sprintf(format, args...)+"\n") return err } diff --git a/internal/testutil/kubeconfigbuilder.go b/internal/testutil/kubeconfigbuilder.go index 5c9b28c..3504985 100644 --- a/internal/testutil/kubeconfigbuilder.go +++ b/internal/testutil/kubeconfigbuilder.go @@ -31,7 +31,7 @@ type Context struct { func Ctx(name string) *Context { return &Context{Name: name} } func (c *Context) Ns(ns string) *Context { c.Context.Namespace = ns; return c } -type Kubeconfig map[string]interface{} +type Kubeconfig map[string]any func KC() *Kubeconfig { return &Kubeconfig{ @@ -39,9 +39,9 @@ func KC() *Kubeconfig { "kind": "Config"} } -func (k *Kubeconfig) Set(key string, v interface{}) *Kubeconfig { (*k)[key] = v; return k } -func (k *Kubeconfig) WithCurrentCtx(s string) *Kubeconfig { (*k)["current-context"] = s; return k } -func (k *Kubeconfig) WithCtxs(c ...*Context) *Kubeconfig { (*k)["contexts"] = c; return k } +func (k *Kubeconfig) Set(key string, v any) *Kubeconfig { (*k)[key] = v; return k } +func (k *Kubeconfig) WithCurrentCtx(s string) *Kubeconfig { (*k)["current-context"] = s; return k } +func (k *Kubeconfig) WithCtxs(c ...*Context) *Kubeconfig { (*k)["contexts"] = c; return k } func (k *Kubeconfig) ToYAML(t *testing.T) string { t.Helper() diff --git a/internal/testutil/tempfile.go b/internal/testutil/tempfile.go deleted file mode 100644 index 7c55233..0000000 --- a/internal/testutil/tempfile.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2021 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package testutil - -import ( - "io/ioutil" - "os" - "testing" -) - -func TempFile(t *testing.T, contents string) (path string, cleanup func()) { - // TODO consider removing, used only in one place. - t.Helper() - - f, err := ioutil.TempFile(os.TempDir(), "test-file") - if err != nil { - t.Fatalf("failed to create test file: %v", err) - } - path = f.Name() - if _, err := f.Write([]byte(contents)); err != nil { - t.Fatalf("failed to write to test file: %v", err) - } - - return path, func() { - f.Close() - os.Remove(path) - } -}