mirror of
https://github.com/ahmetb/kubectx.git
synced 2025-07-24 11:55:18 +00:00
Move kubeconfig loader utils to cmdutil pkg
Signed-off-by: Ahmet Alp Balkan <ahmetb@google.com>
This commit is contained in:
parent
1982becb15
commit
c4252b5795
@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/ahmetb/kubectx/internal/cmdutil"
|
||||
"github.com/ahmetb/kubectx/internal/kubeconfig"
|
||||
)
|
||||
|
||||
@ -13,7 +14,7 @@ import (
|
||||
type CurrentOp struct{}
|
||||
|
||||
func (_op CurrentOp) Run(stdout, _ io.Writer) error {
|
||||
kc := new(kubeconfig.Kubeconfig).WithLoader(defaultLoader)
|
||||
kc := new(kubeconfig.Kubeconfig).WithLoader(cmdutil.DefaultLoader)
|
||||
defer kc.Close()
|
||||
if err := kc.Parse(); err != nil {
|
||||
return errors.Wrap(err, "kubeconfig error")
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/ahmetb/kubectx/internal/cmdutil"
|
||||
"github.com/ahmetb/kubectx/internal/kubeconfig"
|
||||
"github.com/ahmetb/kubectx/internal/printer"
|
||||
)
|
||||
@ -35,7 +36,7 @@ func (op DeleteOp) Run(_, stderr io.Writer) error {
|
||||
// deleteContext deletes a context entry by NAME or current-context
|
||||
// indicated by ".".
|
||||
func deleteContext(name string) (deleteName string, wasActiveContext bool, err error) {
|
||||
kc := new(kubeconfig.Kubeconfig).WithLoader(defaultLoader)
|
||||
kc := new(kubeconfig.Kubeconfig).WithLoader(cmdutil.DefaultLoader)
|
||||
defer kc.Close()
|
||||
if err := kc.Parse(); err != nil {
|
||||
return deleteName, false, errors.Wrap(err, "kubeconfig error")
|
||||
|
@ -20,7 +20,7 @@ func (op UnsupportedOp) Run(_, _ io.Writer) error {
|
||||
// and decides which operation should be taken.
|
||||
func parseArgs(argv []string) Op {
|
||||
if len(argv) == 0 {
|
||||
if env.IsInteractiveMode(os.Stdout) {
|
||||
if cmdutil.IsInteractiveMode(os.Stdout) {
|
||||
return InteractiveSwitchOp{SelfCmd: os.Args[0]}
|
||||
}
|
||||
return ListOp{}
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/ahmetb/kubectx/internal/cmdutil"
|
||||
"github.com/ahmetb/kubectx/internal/env"
|
||||
"github.com/ahmetb/kubectx/internal/kubeconfig"
|
||||
"github.com/ahmetb/kubectx/internal/printer"
|
||||
@ -21,9 +22,9 @@ 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(defaultLoader)
|
||||
kc := new(kubeconfig.Kubeconfig).WithLoader(cmdutil.DefaultLoader)
|
||||
if err := kc.Parse(); err != nil {
|
||||
if isENOENT(err) {
|
||||
if cmdutil.IsNotFoundErr(err) {
|
||||
printer.Warning(stderr, "kubeconfig file not found")
|
||||
return nil
|
||||
}
|
||||
|
@ -3,12 +3,12 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"facette.io/natsort"
|
||||
"github.com/fatih/color"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/ahmetb/kubectx/internal/cmdutil"
|
||||
"github.com/ahmetb/kubectx/internal/kubeconfig"
|
||||
"github.com/ahmetb/kubectx/internal/printer"
|
||||
)
|
||||
@ -17,10 +17,10 @@ import (
|
||||
type ListOp struct{}
|
||||
|
||||
func (_ ListOp) Run(stdout, stderr io.Writer) error {
|
||||
kc := new(kubeconfig.Kubeconfig).WithLoader(defaultLoader)
|
||||
kc := new(kubeconfig.Kubeconfig).WithLoader(cmdutil.DefaultLoader)
|
||||
defer kc.Close()
|
||||
if err := kc.Parse(); err != nil {
|
||||
if isENOENT(err) {
|
||||
if cmdutil.IsNotFoundErr(err) {
|
||||
printer.Warning(stderr, "kubeconfig file not found")
|
||||
return nil
|
||||
}
|
||||
@ -50,14 +50,3 @@ func (_ ListOp) Run(stdout, stderr io.Writer) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// isENOENT determines if the underlying error is os.IsNotExist. Right now
|
||||
// errors from github.com/pkg/errors doesn't work with os.IsNotExist.
|
||||
func isENOENT(err error) bool {
|
||||
for e := err; e != nil; e = errors.Unwrap(e) {
|
||||
if os.IsNotExist(e) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/ahmetb/kubectx/internal/cmdutil"
|
||||
"github.com/ahmetb/kubectx/internal/kubeconfig"
|
||||
"github.com/ahmetb/kubectx/internal/printer"
|
||||
)
|
||||
@ -34,7 +35,7 @@ func parseRenameSyntax(v string) (string, string, bool) {
|
||||
// to the "new" value. If the old refers to the current-context,
|
||||
// current-context preference is also updated.
|
||||
func (op RenameOp) Run(_, stderr io.Writer) error {
|
||||
kc := new(kubeconfig.Kubeconfig).WithLoader(defaultLoader)
|
||||
kc := new(kubeconfig.Kubeconfig).WithLoader(cmdutil.DefaultLoader)
|
||||
defer kc.Close()
|
||||
if err := kc.Parse(); err != nil {
|
||||
return errors.Wrap(err, "kubeconfig error")
|
||||
|
@ -6,10 +6,12 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/ahmetb/kubectx/internal/cmdutil"
|
||||
)
|
||||
|
||||
func kubectxPrevCtxFile() (string, error) {
|
||||
home := homeDir()
|
||||
home := cmdutil.HomeDir()
|
||||
if home == "" {
|
||||
return "", errors.New("HOME or USERPROFILE environment variable not set")
|
||||
}
|
||||
|
@ -5,6 +5,8 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/ahmetb/kubectx/internal/testutil"
|
||||
)
|
||||
|
||||
func Test_readLastContext_nonExistingFile(t *testing.T) {
|
||||
@ -18,7 +20,7 @@ func Test_readLastContext_nonExistingFile(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_readLastContext(t *testing.T) {
|
||||
path, cleanup := testfile(t, "foo")
|
||||
path, cleanup := testutil.TempFile(t, "foo")
|
||||
defer cleanup()
|
||||
|
||||
s, err := readLastContext(path)
|
||||
@ -86,3 +88,4 @@ func Test_kubectxFilePath_error(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/ahmetb/kubectx/internal/cmdutil"
|
||||
"github.com/ahmetb/kubectx/internal/kubeconfig"
|
||||
"github.com/ahmetb/kubectx/internal/printer"
|
||||
)
|
||||
@ -36,7 +37,7 @@ func switchContext(name string) (string, error) {
|
||||
return "", errors.Wrap(err, "failed to determine state file")
|
||||
}
|
||||
|
||||
kc := new(kubeconfig.Kubeconfig).WithLoader(defaultLoader)
|
||||
kc := new(kubeconfig.Kubeconfig).WithLoader(cmdutil.DefaultLoader)
|
||||
defer kc.Close()
|
||||
if err := kc.Parse(); err != nil {
|
||||
return "", errors.Wrap(err, "kubeconfig error")
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/ahmetb/kubectx/internal/cmdutil"
|
||||
"github.com/ahmetb/kubectx/internal/kubeconfig"
|
||||
"github.com/ahmetb/kubectx/internal/printer"
|
||||
)
|
||||
@ -13,7 +14,7 @@ import (
|
||||
type UnsetOp struct{}
|
||||
|
||||
func (_ UnsetOp) Run(_, stderr io.Writer) error {
|
||||
kc := new(kubeconfig.Kubeconfig).WithLoader(defaultLoader)
|
||||
kc := new(kubeconfig.Kubeconfig).WithLoader(cmdutil.DefaultLoader)
|
||||
defer kc.Close()
|
||||
if err := kc.Parse(); err != nil {
|
||||
return errors.Wrap(err, "kubeconfig error")
|
||||
|
@ -1,9 +1,32 @@
|
||||
package main
|
||||
|
||||
import "io"
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/ahmetb/kubectx/internal/cmdutil"
|
||||
"github.com/ahmetb/kubectx/internal/kubeconfig"
|
||||
)
|
||||
|
||||
type CurrentOp struct{}
|
||||
|
||||
func (c CurrentOp) Run(stdout, stderr io.Writer) error {
|
||||
panic("implement me")
|
||||
func (c CurrentOp) Run(stdout, _ io.Writer) error {
|
||||
kc := new(kubeconfig.Kubeconfig).WithLoader(cmdutil.DefaultLoader)
|
||||
defer kc.Close()
|
||||
if err := kc.Parse(); err != nil {
|
||||
return errors.Wrap(err, "kubeconfig error")
|
||||
}
|
||||
|
||||
ctx := kc.GetCurrentContext()
|
||||
if ctx == "" {
|
||||
return errors.New("current-context is not set")
|
||||
}
|
||||
ns, err := kc.NamespaceOfContext(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to read namespace of %q", ctx)
|
||||
}
|
||||
_, err = fmt.Fprintln(stdout, ns)
|
||||
return errors.Wrap(err, "write error")
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
package env
|
||||
package cmdutil
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
@ -1,4 +1,4 @@
|
||||
package main
|
||||
package cmdutil
|
||||
|
||||
import (
|
||||
"os"
|
||||
@ -10,21 +10,13 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
defaultLoader kubeconfig.Loader = new(StandardKubeconfigLoader)
|
||||
DefaultLoader kubeconfig.Loader = new(StandardKubeconfigLoader)
|
||||
)
|
||||
|
||||
type StandardKubeconfigLoader struct{}
|
||||
|
||||
type kubeconfigFile struct{ *os.File }
|
||||
|
||||
func (kf *kubeconfigFile) Reset() error {
|
||||
if err := kf.Truncate(0); err != nil {
|
||||
return errors.Wrap(err, "failed to truncate file")
|
||||
}
|
||||
_, err := kf.Seek(0, 0)
|
||||
return errors.Wrap(err, "failed to seek in file")
|
||||
}
|
||||
|
||||
func (*StandardKubeconfigLoader) Load() (kubeconfig.ReadWriteResetCloser, error) {
|
||||
cfgPath, err := kubeconfigPath()
|
||||
if err != nil {
|
||||
@ -40,6 +32,14 @@ func (*StandardKubeconfigLoader) Load() (kubeconfig.ReadWriteResetCloser, error)
|
||||
return &kubeconfigFile{f}, nil
|
||||
}
|
||||
|
||||
func (kf *kubeconfigFile) Reset() error {
|
||||
if err := kf.Truncate(0); err != nil {
|
||||
return errors.Wrap(err, "failed to truncate file")
|
||||
}
|
||||
_, err := kf.Seek(0, 0)
|
||||
return errors.Wrap(err, "failed to seek in file")
|
||||
}
|
||||
|
||||
func kubeconfigPath() (string, error) {
|
||||
// KUBECONFIG env var
|
||||
if v := os.Getenv("KUBECONFIG"); v != "" {
|
||||
@ -52,14 +52,14 @@ func kubeconfigPath() (string, error) {
|
||||
}
|
||||
|
||||
// default path
|
||||
home := homeDir()
|
||||
home := HomeDir()
|
||||
if home == "" {
|
||||
return "", errors.New("HOME or USERPROFILE environment variable not set")
|
||||
}
|
||||
return filepath.Join(home, ".kube", "config"), nil
|
||||
}
|
||||
|
||||
func homeDir() string {
|
||||
func HomeDir() string {
|
||||
if v := os.Getenv("XDG_CACHE_HOME"); v != "" {
|
||||
return v
|
||||
}
|
||||
@ -69,3 +69,14 @@ 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.
|
||||
func IsNotFoundErr(err error) bool {
|
||||
for e := err; e != nil; e = errors.Unwrap(e) {
|
||||
if os.IsNotExist(e) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
package main
|
||||
package cmdutil
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@ -61,7 +60,7 @@ func Test_homeDir(t *testing.T) {
|
||||
unsets = append(unsets, testutil.WithEnvVar(e.k, e.v))
|
||||
}
|
||||
|
||||
got := homeDir()
|
||||
got := HomeDir()
|
||||
if got != c.want {
|
||||
t.Errorf("expected:%q got:%q", c.want, got)
|
||||
}
|
||||
@ -120,30 +119,12 @@ func Test_kubeconfigPath_envOvverideDoesNotSupportPathSeparator(t *testing.T) {
|
||||
|
||||
func TestStandardKubeconfigLoader_returnsNotFoundErr(t *testing.T) {
|
||||
defer testutil.WithEnvVar("KUBECONFIG", "foo")()
|
||||
kc := new(kubeconfig.Kubeconfig).WithLoader(defaultLoader)
|
||||
kc := new(kubeconfig.Kubeconfig).WithLoader(DefaultLoader)
|
||||
err := kc.Parse()
|
||||
if err == nil {
|
||||
t.Fatal("expected err")
|
||||
}
|
||||
if !isENOENT(err) {
|
||||
if !IsNotFoundErr(err) {
|
||||
t.Fatalf("expected ENOENT error; got=%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func testfile(t *testing.T, contents string) (path string, cleanup func()) {
|
||||
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)
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
package kubeconfig
|
39
internal/kubeconfig/namespace.go
Normal file
39
internal/kubeconfig/namespace.go
Normal file
@ -0,0 +1,39 @@
|
||||
package kubeconfig
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultNamespace = "default"
|
||||
)
|
||||
|
||||
func (k *Kubeconfig) contextNode(name string) (*yaml.Node, error) {
|
||||
contexts := valueOf(k.rootNode, "contexts")
|
||||
if contexts == nil {
|
||||
return nil, errors.New("\"contexts\" entry is nil")
|
||||
} else if contexts.Kind != yaml.SequenceNode {
|
||||
return nil, errors.New("\"contexts\" is not a sequence node")
|
||||
}
|
||||
|
||||
for _, contextNode := range contexts.Content {
|
||||
nameNode := valueOf(contextNode, "name")
|
||||
if nameNode.Kind == yaml.ScalarNode && nameNode.Value == name {
|
||||
return contextNode, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.Errorf("context with name %q not found", name)
|
||||
}
|
||||
|
||||
func (k *Kubeconfig) NamespaceOfContext(contextName string) (string, error) {
|
||||
ctx, err := k.contextNode(contextName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ns := valueOf(ctx, "namespace")
|
||||
if ns == nil || ns.Value == "" {
|
||||
return defaultNamespace, nil
|
||||
}
|
||||
return ns.Value, nil
|
||||
}
|
46
internal/kubeconfig/namespace_test.go
Normal file
46
internal/kubeconfig/namespace_test.go
Normal file
@ -0,0 +1,46 @@
|
||||
package kubeconfig
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ahmetb/kubectx/internal/testutil"
|
||||
)
|
||||
|
||||
func TestKubeconfig_NamespaceOfContext_ctxNotFound(t *testing.T) {
|
||||
kc := new(Kubeconfig).WithLoader(WithMockKubeconfigLoader(testutil.KC().
|
||||
WithCtxs(testutil.Ctx("c1")).ToYAML(t)))
|
||||
if err := kc.Parse(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err := kc.NamespaceOfContext("c2")
|
||||
if err == nil {
|
||||
t.Fatal("expected err")
|
||||
}
|
||||
}
|
||||
|
||||
func TestKubeconfig_NamespaceOfContext(t *testing.T) {
|
||||
kc := new(Kubeconfig).WithLoader(WithMockKubeconfigLoader(testutil.KC().
|
||||
WithCtxs(
|
||||
testutil.Ctx("c1"),
|
||||
testutil.Ctx("c2").Ns("c2n1")).ToYAML(t)))
|
||||
if err := kc.Parse(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
v1, err := kc.NamespaceOfContext("c1")
|
||||
if err != nil {
|
||||
t.Fatal("expected err")
|
||||
}
|
||||
if expected := `default`; v1 != expected {
|
||||
t.Fatalf("c1: expected=%q got=%q", expected, v1)
|
||||
}
|
||||
|
||||
v2, err := kc.NamespaceOfContext("c2")
|
||||
if err != nil {
|
||||
t.Fatal("expected err")
|
||||
}
|
||||
if expected := `c2n1`; v2 != expected {
|
||||
t.Fatalf("c2: expected=%q got=%q", expected, v2)
|
||||
}
|
||||
}
|
@ -12,7 +12,8 @@ type Context struct {
|
||||
Namespace string `yaml:"namespace,omitempty"`
|
||||
}
|
||||
|
||||
func Ctx(name string) *Context { return &Context{Name: name} }
|
||||
func Ctx(name string) *Context { return &Context{Name: name} }
|
||||
func (c *Context) Ns(ns string) *Context { c.Namespace = ns; return c }
|
||||
|
||||
type Kubeconfig map[string]interface{}
|
||||
|
||||
|
26
internal/testutil/tempfile.go
Normal file
26
internal/testutil/tempfile.go
Normal file
@ -0,0 +1,26 @@
|
||||
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)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user