diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 54fae360..f10722c5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,37 +19,22 @@ on: - 'v*.*.*' jobs: goreleaser: + permissions: + contents: write runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@master + - run: git fetch --tags - name: Setup Go uses: actions/setup-go@v2 with: - go-version: '1.20' - - name: Install Snapcraft - uses: samuelmeuli/action-snapcraft@v1 - - name: Setup Snapcraft - run: | - # https://github.com/goreleaser/goreleaser/issues/1715 - mkdir -p $HOME/.cache/snapcraft/download - mkdir -p $HOME/.cache/snapcraft/stage-packages + go-version: '1.22' - name: GoReleaser uses: goreleaser/goreleaser-action@v2 with: version: latest - args: release --rm-dist + args: release --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Update new version for plugin 'ctx' in krew-index - uses: rajatjindal/krew-release-bot@v0.0.38 - with: - krew_template_file: .krew/ctx.yaml - - name: Update new version for plugin 'ns' in krew-index - uses: rajatjindal/krew-release-bot@v0.0.38 - with: - krew_template_file: .krew/ns.yaml - - name: Publish Snaps to the Snap Store (stable channel) - run: for snap in $(ls dist/*.snap); do snapcraft upload --release=stable $snap; done - env: - SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_TOKEN }} + diff --git a/.goreleaser.yml b/.goreleaser.yml index dec3285d..d1458c67 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://goreleaser.com/static/schema.json + # Copyright 2021 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,6 +16,8 @@ # This is an example goreleaser.yaml file with some sane defaults. # Make sure to check the documentation at http://goreleaser.com + +version: 2 before: hooks: - go mod download @@ -70,7 +74,11 @@ archives: format_overrides: - goos: windows format: zip - files: ["LICENSE"] + files: + - "LICENSE" + - "completion/plugins/kubectl_complete-ctx" + - src: "kubectx" + dst: "kubectx.sh" - id: kubens-archive name_template: |- kubens_{{ .Tag }}_{{ .Os }}_ @@ -90,7 +98,11 @@ archives: format_overrides: - goos: windows format: zip - files: ["LICENSE"] + files: + - "LICENSE" + - "completion/plugins/kubectl_complete-ns" + - src: "kubens" + dst: "kubens.sh" checksum: name_template: "checksums.txt" algorithm: sha256 @@ -98,20 +110,3 @@ release: extra_files: - glob: ./kubens - glob: ./kubectx -snapcrafts: - - id: kubectx - name: kubectx - summary: 'kubectx + kubens: Power tools for kubectl' - description: | - kubectx is a tool to switch between contexts (clusters) on kubectl faster. - kubens is a tool to switch between Kubernetes namespaces (and configure them for kubectl) easily. - grade: stable - confinement: classic - base: core20 - apps: - kubectx: - command: kubectx - completer: completion/kubectx.bash - kubens: - command: kubens - completer: completion/kubens.bash diff --git a/cmd/kubectx/current.go b/cmd/kubectx/current.go index 4bfcb7c7..46c2d884 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" ) @@ -30,7 +29,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() @@ -38,5 +37,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 ca5747b8..da0d670a 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" ) @@ -34,7 +34,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.", @@ -52,7 +52,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() @@ -70,7 +70,7 @@ 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") + return name, wasActiveContext, fmt.Errorf("failed to save modified kubeconfig file, %w", kc.Save()) } diff --git a/cmd/kubectx/fzf.go b/cmd/kubectx/fzf.go index 5006129d..d4d50546 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" @@ -46,7 +45,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() @@ -70,7 +69,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 @@ -84,7 +83,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() @@ -114,7 +113,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 020d2a3b..61dc71d8 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. @@ -49,7 +47,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 f3893f11..76ebfb77 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" @@ -37,7 +36,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/rename.go b/cmd/kubectx/rename.go index 6450f39a..e9cb511f 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" ) @@ -51,7 +50,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() @@ -60,26 +59,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/state.go b/cmd/kubectx/state.go index d6571594..030741f1 100644 --- a/cmd/kubectx/state.go +++ b/cmd/kubectx/state.go @@ -15,12 +15,12 @@ package main import ( + "errors" + "fmt" "io/ioutil" "os" "path/filepath" - "github.com/pkg/errors" - "github.com/ahmetb/kubectx/internal/cmdutil" ) @@ -47,7 +47,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) } diff --git a/cmd/kubectx/switch.go b/cmd/kubectx/switch.go index 5818d7c2..609fe2fa 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" ) @@ -37,39 +37,42 @@ 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") + if err != nil { + return fmt.Errorf("print error, %w", err) + } + return nil } // 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 @@ -79,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 ba79323e..700f179d 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" ) @@ -30,16 +29,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 964f3345..ea2fd1c4 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 00993ce6..ebe09ff2 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 f0933986..ce47feb9 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() @@ -67,7 +66,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 027d333a..a6b7f9be 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 38ee08da..0b50da20 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/switch.go b/cmd/kubens/switch.go index bcf2a701..0aec81ec 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,17 @@ 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) + } else { + return namespace != nil, nil + } } diff --git a/cmd/kubens/version.go b/cmd/kubens/version.go index 964f3345..ea2fd1c4 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/completion/plugins/kubectl_complete-ctx b/completion/plugins/kubectl_complete-ctx new file mode 100755 index 00000000..fb9edea4 --- /dev/null +++ b/completion/plugins/kubectl_complete-ctx @@ -0,0 +1,28 @@ +#!/bin/bash + +__kubectl_debug() +{ + if [[ -n ${BASH_COMP_DEBUG_FILE:-} ]]; then + echo "$*" >> "${BASH_COMP_DEBUG_FILE}" + fi +} + +_kube_namespaces() +{ + local args lastArgs; + + args=("${@:1}") + __kubectl_debug "arguments list ${args[*]}" + len=${#args[@]} + __kubectl_debug "arguments len is $len" + if [[ $len -gt 0 ]]; then + lastArgs="${args[$((len-1))]}" + else + lastArgs="" + fi + __kubectl_debug "arguments last args on which completion will be done $lastArgs" + COMPREPLY=("$(compgen -W "$(kubectl config get-contexts -o name)" -- $lastArgs )"); +} + +_kube_namespaces "$@" +echo "${COMPREPLY[*]}" \ No newline at end of file diff --git a/completion/plugins/kubectl_complete-ns b/completion/plugins/kubectl_complete-ns new file mode 100755 index 00000000..5905eaaf --- /dev/null +++ b/completion/plugins/kubectl_complete-ns @@ -0,0 +1,28 @@ +#!/bin/bash + +__kubectl_debug() +{ + if [[ -n ${BASH_COMP_DEBUG_FILE:-} ]]; then + echo "$*" >> "${BASH_COMP_DEBUG_FILE}" + fi +} + +_kube_namespaces() +{ + local args lastArgs; + + args=("${@:1}") + __kubectl_debug "arguments list ${args[*]}" + len=${#args[@]} + __kubectl_debug "arguments len is $len" + if [[ $len -gt 0 ]]; then + lastArgs="${args[$((len-1))]}" + else + lastArgs="" + fi + __kubectl_debug "arguments last args on which completion will be done $lastArgs" + COMPREPLY=("$(compgen -W "- $(kubectl get namespaces -o=jsonpath='{range .items[*].metadata.name}{@}{"\n"}{end}')" -- $lastArgs )"); +} + +_kube_namespaces "$@" +echo "${COMPREPLY[*]}" \ No newline at end of file diff --git a/go.mod b/go.mod index 608b6aab..151d865b 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,6 @@ require ( github.com/fatih/color v1.9.0 github.com/google/go-cmp v0.5.9 github.com/mattn/go-isatty v0.0.14 - github.com/pkg/errors v0.9.1 gopkg.in/yaml.v3 v3.0.1 k8s.io/apimachinery v0.27.3 k8s.io/client-go v0.27.3 diff --git a/go.sum b/go.sum index c260edc5..89165f84 100644 --- a/go.sum +++ b/go.sum @@ -172,8 +172,6 @@ github.com/onsi/ginkgo/v2 v2.9.1 h1:zie5Ly042PD3bsCvsSOPvRnFwyo3rKe64TJlD6nu0mk= github.com/onsi/ginkgo/v2 v2.9.1/go.mod h1:FEcmzVcCHl+4o9bQZVab+4dC9+j+91t2FHSzmGAPfuo= github.com/onsi/gomega v1.27.4 h1:Z2AnStgsdSayCMDiCU42qIz+HLqEPcgiOCXjAU/w+8E= github.com/onsi/gomega v1.27.4/go.mod h1:riYq/GJKh8hhoM01HN6Vmuy93AarCXCBGpvFDK3q3fQ= -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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= diff --git a/internal/cmdutil/util.go b/internal/cmdutil/util.go index cc9539fb..b2d3ad93 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 { diff --git a/internal/env/constants.go b/internal/env/constants.go index e6a2a923..c4fd5ba0 100644 --- a/internal/env/constants.go +++ b/internal/env/constants.go @@ -19,7 +19,7 @@ const ( // interactive context selection when fzf is installed. EnvFZFIgnore = "KUBECTX_IGNORE_FZF" - // EnvForceColor describes the environment variable to disable color usage + // EnvNoColor describes the environment variable to disable color usage // when printing current context in a list. EnvNoColor = `NO_COLOR` diff --git a/internal/kubeconfig/contextmodify.go b/internal/kubeconfig/contextmodify.go index 178e318f..6f3cbbdf 100644 --- a/internal/kubeconfig/contextmodify.go +++ b/internal/kubeconfig/contextmodify.go @@ -15,7 +15,7 @@ package kubeconfig import ( - "github.com/pkg/errors" + "errors" "gopkg.in/yaml.v3" ) diff --git a/internal/kubeconfig/contextmodify_test.go b/internal/kubeconfig/contextmodify_test.go index 50796341..25462dea 100644 --- a/internal/kubeconfig/contextmodify_test.go +++ b/internal/kubeconfig/contextmodify_test.go @@ -51,7 +51,7 @@ func TestKubeconfig_DeleteContextEntry(t *testing.T) { testutil.Ctx("c1"), testutil.Ctx("c2"), testutil.Ctx("c3")).ToYAML(t)) - kc := new(Kubeconfig).WithLoader(test) + kc := new(StandardKubeconfig).WithLoader(test) if err := kc.Parse(); err != nil { t.Fatal(err) } diff --git a/internal/kubeconfig/contexts.go b/internal/kubeconfig/contexts.go index e7743894..f408a9c6 100644 --- a/internal/kubeconfig/contexts.go +++ b/internal/kubeconfig/contexts.go @@ -15,7 +15,8 @@ package kubeconfig import ( - "github.com/pkg/errors" + "errors" + "fmt" "gopkg.in/yaml.v3" ) @@ -41,7 +42,7 @@ func (k *Kubeconfig) contextNode(name string) (*yaml.Node, error) { return contextNode, nil } } - return nil, errors.Errorf("context with name \"%s\" not found", name) + return nil, fmt.Errorf("context with name \"%s\" not found", name) } func (k *Kubeconfig) ContextNames() []string { diff --git a/internal/kubeconfig/kubeconfig.go b/internal/kubeconfig/kubeconfig.go index 09b88f23..1e1f4384 100644 --- a/internal/kubeconfig/kubeconfig.go +++ b/internal/kubeconfig/kubeconfig.go @@ -15,9 +15,10 @@ package kubeconfig import ( + "errors" "io" - "github.com/pkg/errors" + "fmt" "gopkg.in/yaml.v3" ) @@ -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.rootNode = v.Content[0] if k.rootNode.Kind != yaml.MappingNode { @@ -78,7 +79,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 2fa26b8d..5097a41e 100644 --- a/internal/kubeconfig/kubeconfigloader.go +++ b/internal/kubeconfig/kubeconfigloader.go @@ -15,11 +15,11 @@ package kubeconfig import ( + "errors" + "fmt" "github.com/ahmetb/kubectx/internal/cmdutil" "os" "path/filepath" - - "github.com/pkg/errors" ) var ( @@ -33,15 +33,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 +50,13 @@ 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") + if err != nil { + return fmt.Errorf("failed to seek in file, %w", err) + } + return nil } func kubeconfigPath() (string, error) { diff --git a/krew/README.md b/krew/README.md new file mode 100644 index 00000000..221e4dc4 --- /dev/null +++ b/krew/README.md @@ -0,0 +1,11 @@ +# Setup kubectx and kubens + + +manually install kubectx and kubens from manifest file +this add autocomplete plugin capabilities + +```shell +$ kubectl krew install --manifest=krew/ctx.yaml +$ kubectl krew install --manifest=krew/ns.yaml +``` + diff --git a/krew/ctx.yaml b/krew/ctx.yaml new file mode 100644 index 00000000..b0ad378f --- /dev/null +++ b/krew/ctx.yaml @@ -0,0 +1,81 @@ +apiVersion: krew.googlecontainertools.github.com/v1alpha2 +kind: Plugin +metadata: + name: ctx +spec: + homepage: https://github.com/ahmetb/kubectx + shortDescription: Switch between contexts in your kubeconfig + version: v0.9.5-2 + description: | + Also known as "kubectx", a utility to switch between context entries in + your kubeconfig file efficiently. + caveats: | + If fzf is installed on your machine, you can interactively choose + between the entries using the arrow keys, or by fuzzy searching + as you type. + See https://github.com/RouxAntoine/kubectx for customization and details. + platforms: + - selector: + matchLabels: + os: linux + arch: amd64 + uri: https://github.com/RouxAntoine/kubectx/releases/download/v0.9.5-2/kubectx_v0.9.5-2_linux_x86_64.tar.gz + sha256: + bin: kubectx.sh + files: + - from: kubectx.sh + to: . + - from: kubectx + to: . + - from: LICENSE + to: . + - from: completion/plugins/kubectl_complete-ctx + to: completion/kubectl_complete-ctx + - selector: + matchLabels: + os: linux + arch: arm64 + uri: https://github.com/RouxAntoine/kubectx/releases/download/v0.9.5-2/kubectx_v0.9.5-2_linux_arm64.tar.gz + sha256: + bin: kubectx.sh + files: + - from: kubectx.sh + to: . + - from: kubectx + to: . + - from: LICENSE + to: . + - from: completion/plugins/kubectl_complete-ctx + to: completion/kubectl_complete-ctx + - selector: + matchLabels: + os: darwin + arch: amd64 + uri: https://github.com/RouxAntoine/kubectx/releases/download/v0.9.5-2/kubectx_v0.9.5-2_darwin_x86_64.tar.gz + sha256: + bin: kubectx.sh + files: + - from: kubectx.sh + to: . + - from: kubectx + to: . + - from: LICENSE + to: . + - from: completion/plugins/kubectl_complete-ctx + to: completion/kubectl_complete-ctx + - selector: + matchLabels: + os: darwin + arch: arm64 + uri: https://github.com/RouxAntoine/kubectx/releases/download/v0.9.5-2/kubectx_v0.9.5-2_darwin_arm64.tar.gz + sha256: + bin: kubectx.sh + files: + - from: kubectx.sh + to: . + - from: kubectx + to: . + - from: LICENSE + to: . + - from: completion/plugins/kubectl_complete-ctx + to: completion/kubectl_complete-ctx diff --git a/krew/ns.yaml b/krew/ns.yaml new file mode 100644 index 00000000..a9cf2595 --- /dev/null +++ b/krew/ns.yaml @@ -0,0 +1,80 @@ +apiVersion: krew.googlecontainertools.github.com/v1alpha2 +kind: Plugin +metadata: + name: ns +spec: + homepage: https://github.com/ahmetb/kubectx + shortDescription: Switch between Kubernetes namespaces + version: v0.9.5-2 + description: | + Also known as "kubens", a utility to set your current namespace and switch + between them. + caveats: | + If fzf is installed on your machine, you can interactively choose + between the entries using the arrow keys, or by fuzzy searching + as you type. + platforms: + - selector: + matchLabels: + os: linux + arch: amd64 + uri: https://github.com/RouxAntoine/kubectx/releases/download/v0.9.5-2/kubens_v0.9.5-2_linux_x86_64.tar.gz + sha256: + bin: kubens.sh + files: + - from: kubens.sh + to: . + - from: kubens + to: . + - from: LICENSE + to: . + - from: completion/plugins/kubectl_complete-ns + to: completion/kubectl_complete-ns + - selector: + matchLabels: + os: linux + arch: arm64 + uri: https://github.com/RouxAntoine/kubectx/releases/download/v0.9.5-2/kubens_v0.9.5-2_linux_arm64.tar.gz + sha256: + bin: kubens.sh + files: + - from: kubens.sh + to: . + - from: kubens + to: . + - from: LICENSE + to: . + - from: completion/plugins/kubectl_complete-ns + to: completion/kubectl_complete-ns + - selector: + matchLabels: + os: darwin + arch: amd64 + uri: https://github.com/RouxAntoine/kubectx/releases/download/v0.9.5-2/kubens_v0.9.5-2_darwin_x86_64.tar.gz + sha256: + bin: kubens.sh + files: + - from: kubens.sh + to: . + - from: kubens + to: . + - from: LICENSE + to: . + - from: completion/plugins/kubectl_complete-ns + to: completion/kubectl_complete-ns + - selector: + matchLabels: + os: darwin + arch: arm64 + uri: https://github.com/RouxAntoine/kubectx/releases/download/v0.9.5-2/kubens_v0.9.5-2_darwin_arm64.tar.gz + sha256: + bin: kubens.sh + files: + - from: kubens.sh + to: . + - from: kubens + to: . + - from: LICENSE + to: . + - from: completion/plugins/kubectl_complete-ns + to: completion/kubectl_complete-ns