mirror of
https://github.com/ahmetb/kubectx.git
synced 2026-03-18 03:42:12 +00:00
Compare commits
4 Commits
abalkan/go
...
abalkan/go
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7eb2221a43 | ||
|
|
44c37100bf | ||
|
|
572dda6f02 | ||
|
|
51d9e8e055 |
35
.github/workflows/bash-frozen.yml
vendored
35
.github/workflows/bash-frozen.yml
vendored
@@ -1,35 +0,0 @@
|
||||
name: Bash scripts frozen
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'kubectx'
|
||||
- 'kubens'
|
||||
|
||||
jobs:
|
||||
comment:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Comment on PR if author is not ahmetb
|
||||
if: github.event.pull_request.user.login != 'ahmetb'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const body = [
|
||||
'> [!WARNING]',
|
||||
'> **This PR will not be merged.**',
|
||||
'>',
|
||||
'> The bash implementation of `kubectx` and `kubens` is **frozen** and is provided only for convenience.',
|
||||
'> We are not accepting any improvements to the bash scripts.',
|
||||
'>',
|
||||
'> Please propose your improvements to the **Go implementation** instead.',
|
||||
].join('\n');
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
body: body
|
||||
});
|
||||
@@ -35,14 +35,12 @@ func (_op CurrentOp) Run(stdout, _ io.Writer) error {
|
||||
return fmt.Errorf("kubeconfig error: %w", err)
|
||||
}
|
||||
|
||||
v, err := kc.GetCurrentContext()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get current context: %w", err)
|
||||
}
|
||||
v := kc.GetCurrentContext()
|
||||
if v == "" {
|
||||
return errors.New("current-context is not set")
|
||||
}
|
||||
if _, err := fmt.Fprintln(stdout, v); err != nil {
|
||||
_, err := fmt.Fprintln(stdout, v)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write error: %w", err)
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -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,10 +58,7 @@ func deleteContext(name string) (deleteName string, wasActiveContext bool, err e
|
||||
return deleteName, false, fmt.Errorf("kubeconfig error: %w", err)
|
||||
}
|
||||
|
||||
cur, err := kc.GetCurrentContext()
|
||||
if err != nil {
|
||||
return deleteName, false, fmt.Errorf("failed to get current context: %w", err)
|
||||
}
|
||||
cur := kc.GetCurrentContext()
|
||||
// resolve "." to a real name
|
||||
if name == "." {
|
||||
if cur == "" {
|
||||
@@ -71,11 +68,7 @@ func deleteContext(name string) (deleteName string, wasActiveContext bool, err e
|
||||
name = cur
|
||||
}
|
||||
|
||||
exists, err := kc.ContextExists(name)
|
||||
if err != nil {
|
||||
return name, false, fmt.Errorf("failed to check context: %w", err)
|
||||
}
|
||||
if !exists {
|
||||
if !kc.ContextExists(name) {
|
||||
return name, false, errors.New("context does not exist")
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,6 @@ 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")
|
||||
@@ -51,6 +50,7 @@ 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,7 +85,6 @@ 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")
|
||||
@@ -93,12 +92,9 @@ func (op InteractiveDeleteOp) Run(_, stderr io.Writer) error {
|
||||
}
|
||||
return fmt.Errorf("kubeconfig error: %w", err)
|
||||
}
|
||||
kc.Close()
|
||||
|
||||
ctxNames, err := kc.ContextNames()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get context names: %w", err)
|
||||
}
|
||||
if len(ctxNames) == 0 {
|
||||
if len(kc.ContextNames()) == 0 {
|
||||
return errors.New("no contexts found in config")
|
||||
}
|
||||
|
||||
@@ -133,7 +129,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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -42,16 +42,10 @@ func (_ ListOp) Run(stdout, stderr io.Writer) error {
|
||||
return fmt.Errorf("kubeconfig error: %w", err)
|
||||
}
|
||||
|
||||
ctxs, err := kc.ContextNames()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get context names: %w", err)
|
||||
}
|
||||
ctxs := kc.ContextNames()
|
||||
natsort.Sort(ctxs)
|
||||
|
||||
cur, err := kc.GetCurrentContext()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get current context: %w", err)
|
||||
}
|
||||
cur := kc.GetCurrentContext()
|
||||
for _, c := range ctxs {
|
||||
s := c
|
||||
if c == cur {
|
||||
|
||||
@@ -52,27 +52,16 @@ func (op RenameOp) Run(_, stderr io.Writer) error {
|
||||
return fmt.Errorf("kubeconfig error: %w", err)
|
||||
}
|
||||
|
||||
cur, err := kc.GetCurrentContext()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get current context: %w", err)
|
||||
}
|
||||
cur := kc.GetCurrentContext()
|
||||
if op.Old == "." {
|
||||
op.Old = cur
|
||||
}
|
||||
|
||||
oldExists, err := kc.ContextExists(op.Old)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to check context: %w", err)
|
||||
}
|
||||
if !oldExists {
|
||||
if !kc.ContextExists(op.Old) {
|
||||
return fmt.Errorf("context \"%s\" not found, can't rename it", op.Old)
|
||||
}
|
||||
|
||||
newExists, err := kc.ContextExists(op.New)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to check context: %w", err)
|
||||
}
|
||||
if newExists {
|
||||
if kc.ContextExists(op.New) {
|
||||
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)
|
||||
@@ -90,7 +79,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
|
||||
|
||||
@@ -35,17 +35,10 @@ func (op ShellOp) Run(_, stderr io.Writer) error {
|
||||
if err := kc.Parse(); err != nil {
|
||||
return fmt.Errorf("kubeconfig error: %w", err)
|
||||
}
|
||||
exists, err := kc.ContextExists(op.Target)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to check context: %w", err)
|
||||
}
|
||||
if !exists {
|
||||
if !kc.ContextExists(op.Target) {
|
||||
return fmt.Errorf("no context exists with the name: \"%s\"", op.Target)
|
||||
}
|
||||
previousCtx, err := kc.GetCurrentContext()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get current context: %w", err)
|
||||
}
|
||||
previousCtx := kc.GetCurrentContext()
|
||||
|
||||
// Extract minimal kubeconfig using kubectl
|
||||
data, err := extractMinimalKubeconfig(kubectlPath, op.Target)
|
||||
|
||||
@@ -61,15 +61,8 @@ func switchContext(name string) (string, error) {
|
||||
return "", fmt.Errorf("kubeconfig error: %w", err)
|
||||
}
|
||||
|
||||
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 {
|
||||
prev := kc.GetCurrentContext()
|
||||
if !kc.ContextExists(name) {
|
||||
return "", fmt.Errorf("no context exists with the name: \"%s\"", name)
|
||||
}
|
||||
if err := kc.ModifyCurrentContext(name); err != nil {
|
||||
|
||||
@@ -31,10 +31,7 @@ func (c CurrentOp) Run(stdout, _ io.Writer) error {
|
||||
return fmt.Errorf("kubeconfig error: %w", err)
|
||||
}
|
||||
|
||||
ctx, err := kc.GetCurrentContext()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get current context: %w", err)
|
||||
}
|
||||
ctx := kc.GetCurrentContext()
|
||||
if ctx == "" {
|
||||
return errors.New("current-context is not set")
|
||||
}
|
||||
|
||||
@@ -37,7 +37,6 @@ 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")
|
||||
@@ -45,6 +44,7 @@ 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
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"slices"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
@@ -40,10 +39,7 @@ func (op ListOp) Run(stdout, stderr io.Writer) error {
|
||||
return fmt.Errorf("kubeconfig error: %w", err)
|
||||
}
|
||||
|
||||
ctx, err := kc.GetCurrentContext()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get current context: %w", err)
|
||||
}
|
||||
ctx := kc.GetCurrentContext()
|
||||
if ctx == "" {
|
||||
return errors.New("current-context is not set")
|
||||
}
|
||||
@@ -90,7 +86,6 @@ 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)
|
||||
}
|
||||
|
||||
@@ -49,10 +49,7 @@ func (s SwitchOp) Run(_, stderr io.Writer) error {
|
||||
}
|
||||
|
||||
func switchNamespace(kc *kubeconfig.Kubeconfig, ns string, force bool) (string, error) {
|
||||
ctx, err := kc.GetCurrentContext()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get current context: %w", err)
|
||||
}
|
||||
ctx := kc.GetCurrentContext()
|
||||
if ctx == "" {
|
||||
return "", errors.New("current-context is not set")
|
||||
}
|
||||
|
||||
@@ -42,10 +42,7 @@ func (_ UnsetOp) Run(_, stderr io.Writer) error {
|
||||
}
|
||||
|
||||
func clearNamespace(kc *kubeconfig.Kubeconfig) (string, error) {
|
||||
ctx, err := kc.GetCurrentContext()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get current context: %w", err)
|
||||
}
|
||||
ctx := kc.GetCurrentContext()
|
||||
ns := "default"
|
||||
if ctx == "" {
|
||||
return "", errors.New("current-context is not set")
|
||||
|
||||
@@ -50,25 +50,18 @@ func (k *Kubeconfig) contextNode(name string) (*yaml.RNode, error) {
|
||||
return context, nil
|
||||
}
|
||||
|
||||
func (k *Kubeconfig) ContextNames() ([]string, error) {
|
||||
func (k *Kubeconfig) ContextNames() []string {
|
||||
contexts, err := k.config.Pipe(yaml.Get("contexts"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get contexts: %w", err)
|
||||
}
|
||||
if contexts == nil {
|
||||
return nil, nil
|
||||
return nil
|
||||
}
|
||||
names, err := contexts.ElementValues("name")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get context names: %w", err)
|
||||
return nil
|
||||
}
|
||||
return names, nil
|
||||
return names
|
||||
}
|
||||
|
||||
func (k *Kubeconfig) ContextExists(name string) (bool, error) {
|
||||
names, err := k.ContextNames()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return slices.Contains(names, name), nil
|
||||
func (k *Kubeconfig) ContextExists(name string) bool {
|
||||
return slices.Contains(k.ContextNames(), name)
|
||||
}
|
||||
|
||||
@@ -33,10 +33,7 @@ func TestKubeconfig_ContextNames(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ctx, err := kc.ContextNames()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ctx := kc.ContextNames()
|
||||
expected := []string{"abc", "def", "ghi"}
|
||||
if diff := cmp.Diff(expected, ctx); diff != "" {
|
||||
t.Fatalf("%s", diff)
|
||||
@@ -49,10 +46,7 @@ func TestKubeconfig_ContextNames_noContextsEntry(t *testing.T) {
|
||||
if err := kc.Parse(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ctx, err := kc.ContextNames()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ctx := kc.ContextNames()
|
||||
var expected []string = nil
|
||||
if diff := cmp.Diff(expected, ctx); diff != "" {
|
||||
t.Fatalf("%s", diff)
|
||||
@@ -65,9 +59,10 @@ func TestKubeconfig_ContextNames_nonArrayContextsEntry(t *testing.T) {
|
||||
if err := kc.Parse(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err := kc.ContextNames()
|
||||
if err == nil {
|
||||
t.Fatal("expected error for non-array contexts entry")
|
||||
ctx := kc.ContextNames()
|
||||
var expected []string = nil
|
||||
if diff := cmp.Diff(expected, ctx); diff != "" {
|
||||
t.Fatalf("%s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,15 +77,13 @@ func TestKubeconfig_CheckContextExists(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if exists, err := kc.ContextExists("c1"); err != nil || !exists {
|
||||
if !kc.ContextExists("c1") {
|
||||
t.Fatal("c1 actually exists; reported false")
|
||||
}
|
||||
if exists, err := kc.ContextExists("c2"); err != nil || !exists {
|
||||
if !kc.ContextExists("c2") {
|
||||
t.Fatal("c2 actually exists; reported false")
|
||||
}
|
||||
if exists, err := kc.ContextExists("c3"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if exists {
|
||||
if kc.ContextExists("c3") {
|
||||
t.Fatal("c3 does not exist; but reported true")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,19 +15,17 @@
|
||||
package kubeconfig
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
// GetCurrentContext returns "current-context" value in given
|
||||
// kubeconfig object Node, or returns ("", nil) if not found.
|
||||
func (k *Kubeconfig) GetCurrentContext() (string, error) {
|
||||
// kubeconfig object Node, or returns "" if not found.
|
||||
func (k *Kubeconfig) GetCurrentContext() string {
|
||||
v, err := k.config.Pipe(yaml.Get("current-context"))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read current-context: %w", err)
|
||||
return ""
|
||||
}
|
||||
return yaml.GetValue(v), nil
|
||||
return yaml.GetValue(v)
|
||||
}
|
||||
|
||||
func (k *Kubeconfig) UnsetCurrentContext() error {
|
||||
|
||||
@@ -26,10 +26,7 @@ func TestKubeconfig_GetCurrentContext(t *testing.T) {
|
||||
if err := kc.Parse(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
v, err := kc.GetCurrentContext()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
v := kc.GetCurrentContext()
|
||||
|
||||
expected := "foo"
|
||||
if v != expected {
|
||||
@@ -43,10 +40,7 @@ func TestKubeconfig_GetCurrentContext_missingField(t *testing.T) {
|
||||
if err := kc.Parse(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
v, err := kc.GetCurrentContext()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
v := kc.GetCurrentContext()
|
||||
|
||||
expected := ""
|
||||
if v != expected {
|
||||
|
||||
@@ -87,8 +87,5 @@ func (k *Kubeconfig) Save() error {
|
||||
}
|
||||
enc := yaml.NewEncoder(k.f)
|
||||
enc.SetIndent(0)
|
||||
if err := enc.Encode(k.config.YNode()); err != nil {
|
||||
return err
|
||||
}
|
||||
return enc.Close()
|
||||
return enc.Encode(k.config.YNode())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user