Move kubeconfig loader utils to cmdutil pkg

Signed-off-by: Ahmet Alp Balkan <ahmetb@google.com>
This commit is contained in:
Ahmet Alp Balkan
2020-04-18 13:32:53 -07:00
parent 1982becb15
commit c4252b5795
19 changed files with 191 additions and 65 deletions

View File

@@ -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")

View File

@@ -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")

View File

@@ -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{}

View File

@@ -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
}

View File

@@ -1,71 +0,0 @@
package main
import (
"os"
"path/filepath"
"github.com/pkg/errors"
"github.com/ahmetb/kubectx/internal/kubeconfig"
)
var (
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 {
return nil, errors.Wrap(err, "cannot determine kubeconfig path")
}
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, errors.Wrap(err, "failed to open file")
}
return &kubeconfigFile{f}, nil
}
func kubeconfigPath() (string, error) {
// KUBECONFIG env var
if v := os.Getenv("KUBECONFIG"); v != "" {
list := filepath.SplitList(v)
if len(list) > 1 {
// TODO KUBECONFIG=file1:file2 currently not supported
return "", errors.New("multiple files in KUBECONFIG are currently not supported")
}
return v, nil
}
// default path
home := homeDir()
if home == "" {
return "", errors.New("HOME or USERPROFILE environment variable not set")
}
return filepath.Join(home, ".kube", "config"), nil
}
func homeDir() string {
if v := os.Getenv("XDG_CACHE_HOME"); v != "" {
return v
}
home := os.Getenv("HOME")
if home == "" {
home = os.Getenv("USERPROFILE") // windows
}
return home
}

View File

@@ -1,149 +0,0 @@
package main
import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
"github.com/ahmetb/kubectx/internal/kubeconfig"
"github.com/ahmetb/kubectx/internal/testutil"
)
func Test_homeDir(t *testing.T) {
type env struct{ k, v string }
cases := []struct {
name string
envs []env
want string
}{
{
name: "XDG_CACHE_HOME precedence",
envs: []env{
{"XDG_CACHE_HOME", "xdg"},
{"HOME", "home"},
},
want: "xdg",
},
{
name: "HOME over USERPROFILE",
envs: []env{
{"HOME", "home"},
{"USERPROFILE", "up"},
},
want: "home",
},
{
name: "only USERPROFILE available",
envs: []env{
{"XDG_CACHE_HOME", ""},
{"HOME", ""},
{"USERPROFILE", "up"},
},
want: "up",
},
{
name: "none available",
envs: []env{
{"XDG_CACHE_HOME", ""},
{"HOME", ""},
{"USERPROFILE", ""},
},
want: "",
},
}
for _, c := range cases {
t.Run(c.name, func(tt *testing.T) {
var unsets []func()
for _, e := range c.envs {
unsets = append(unsets, testutil.WithEnvVar(e.k, e.v))
}
got := homeDir()
if got != c.want {
t.Errorf("expected:%q got:%q", c.want, got)
}
for _, u := range unsets {
u()
}
})
}
}
func Test_kubeconfigPath(t *testing.T) {
defer testutil.WithEnvVar("HOME", "/x/y/z")()
expected := filepath.FromSlash("/x/y/z/.kube/config")
got, err := kubeconfigPath()
if err != nil {
t.Fatal(err)
}
if got != expected {
t.Fatalf("got=%q expected=%q", got, expected)
}
}
func Test_kubeconfigPath_noEnvVars(t *testing.T) {
defer testutil.WithEnvVar("XDG_CACHE_HOME", "")()
defer testutil.WithEnvVar("HOME", "")()
defer testutil.WithEnvVar("USERPROFILE", "")()
_, err := kubeconfigPath()
if err == nil {
t.Fatalf("expected error")
}
}
func Test_kubeconfigPath_envOvveride(t *testing.T) {
defer testutil.WithEnvVar("KUBECONFIG", "foo")()
v, err := kubeconfigPath()
if err != nil {
t.Fatal(err)
}
if expected := "foo"; v != expected {
t.Fatalf("expected=%q, got=%q", expected, v)
}
}
func Test_kubeconfigPath_envOvverideDoesNotSupportPathSeparator(t *testing.T) {
path := strings.Join([]string{"file1", "file2"}, string(os.PathListSeparator))
defer testutil.WithEnvVar("KUBECONFIG", path)()
_, err := kubeconfigPath()
if err == nil {
t.Fatal("expected error")
}
}
func TestStandardKubeconfigLoader_returnsNotFoundErr(t *testing.T) {
defer testutil.WithEnvVar("KUBECONFIG", "foo")()
kc := new(kubeconfig.Kubeconfig).WithLoader(defaultLoader)
err := kc.Parse()
if err == nil {
t.Fatal("expected err")
}
if !isENOENT(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)
}
}

View File

@@ -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
}

View File

@@ -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")

View File

@@ -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")
}

View File

@@ -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)
}
}

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")
}