Skip to content

Commit

Permalink
Structural refactoring for multiple kubeconfig support (#219)
Browse files Browse the repository at this point in the history
  • Loading branch information
sedooe authored Jun 2, 2020
1 parent 170233b commit 1db00a2
Show file tree
Hide file tree
Showing 17 changed files with 134 additions and 121 deletions.
3 changes: 1 addition & 2 deletions cmd/kubectx/current.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,14 @@ import (

"github.com/pkg/errors"

"github.com/ahmetb/kubectx/internal/cmdutil"
"github.com/ahmetb/kubectx/internal/kubeconfig"
)

// CurrentOp prints the current context
type CurrentOp struct{}

func (_op CurrentOp) Run(stdout, _ io.Writer) error {
kc := new(kubeconfig.Kubeconfig).WithLoader(cmdutil.DefaultLoader)
kc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader)
defer kc.Close()
if err := kc.Parse(); err != nil {
return errors.Wrap(err, "kubeconfig error")
Expand Down
3 changes: 1 addition & 2 deletions cmd/kubectx/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (

"github.com/pkg/errors"

"github.com/ahmetb/kubectx/internal/cmdutil"
"github.com/ahmetb/kubectx/internal/kubeconfig"
"github.com/ahmetb/kubectx/internal/printer"
)
Expand Down Expand Up @@ -36,7 +35,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(cmdutil.DefaultLoader)
kc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader)
defer kc.Close()
if err := kc.Parse(); err != nil {
return deleteName, false, errors.Wrap(err, "kubeconfig error")
Expand Down
2 changes: 1 addition & 1 deletion cmd/kubectx/fzf.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ 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(cmdutil.DefaultLoader)
kc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader)
if err := kc.Parse(); err != nil {
if cmdutil.IsNotFoundErr(err) {
printer.Warning(stderr, "kubeconfig file not found")
Expand Down
2 changes: 1 addition & 1 deletion cmd/kubectx/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
type ListOp struct{}

func (_ ListOp) Run(stdout, stderr io.Writer) error {
kc := new(kubeconfig.Kubeconfig).WithLoader(cmdutil.DefaultLoader)
kc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader)
defer kc.Close()
if err := kc.Parse(); err != nil {
if cmdutil.IsNotFoundErr(err) {
Expand Down
3 changes: 1 addition & 2 deletions cmd/kubectx/rename.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (

"github.com/pkg/errors"

"github.com/ahmetb/kubectx/internal/cmdutil"
"github.com/ahmetb/kubectx/internal/kubeconfig"
"github.com/ahmetb/kubectx/internal/printer"
)
Expand Down Expand Up @@ -35,7 +34,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(cmdutil.DefaultLoader)
kc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader)
defer kc.Close()
if err := kc.Parse(); err != nil {
return errors.Wrap(err, "kubeconfig error")
Expand Down
3 changes: 1 addition & 2 deletions cmd/kubectx/switch.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (

"github.com/pkg/errors"

"github.com/ahmetb/kubectx/internal/cmdutil"
"github.com/ahmetb/kubectx/internal/kubeconfig"
"github.com/ahmetb/kubectx/internal/printer"
)
Expand Down Expand Up @@ -37,7 +36,7 @@ func switchContext(name string) (string, error) {
return "", errors.Wrap(err, "failed to determine state file")
}

kc := new(kubeconfig.Kubeconfig).WithLoader(cmdutil.DefaultLoader)
kc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader)
defer kc.Close()
if err := kc.Parse(); err != nil {
return "", errors.Wrap(err, "kubeconfig error")
Expand Down
3 changes: 1 addition & 2 deletions cmd/kubectx/unset.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (

"github.com/pkg/errors"

"github.com/ahmetb/kubectx/internal/cmdutil"
"github.com/ahmetb/kubectx/internal/kubeconfig"
"github.com/ahmetb/kubectx/internal/printer"
)
Expand All @@ -14,7 +13,7 @@ import (
type UnsetOp struct{}

func (_ UnsetOp) Run(_, stderr io.Writer) error {
kc := new(kubeconfig.Kubeconfig).WithLoader(cmdutil.DefaultLoader)
kc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader)
defer kc.Close()
if err := kc.Parse(); err != nil {
return errors.Wrap(err, "kubeconfig error")
Expand Down
3 changes: 1 addition & 2 deletions cmd/kubens/current.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,13 @@ import (

"github.com/pkg/errors"

"github.com/ahmetb/kubectx/internal/cmdutil"
"github.com/ahmetb/kubectx/internal/kubeconfig"
)

type CurrentOp struct{}

func (c CurrentOp) Run(stdout, _ io.Writer) error {
kc := new(kubeconfig.Kubeconfig).WithLoader(cmdutil.DefaultLoader)
kc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader)
defer kc.Close()
if err := kc.Parse(); err != nil {
return errors.Wrap(err, "kubeconfig error")
Expand Down
2 changes: 1 addition & 1 deletion cmd/kubens/fzf.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ type InteractiveSwitchOp struct {
// TODO(ahmetb) This method is heavily repetitive vs kubectx/fzf.go.
func (op InteractiveSwitchOp) Run(_, stderr io.Writer) error {
// parse kubeconfig just to see if it can be loaded
kc := new(kubeconfig.Kubeconfig).WithLoader(cmdutil.DefaultLoader)
kc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader)
if err := kc.Parse(); err != nil {
if cmdutil.IsNotFoundErr(err) {
printer.Warning(stderr, "kubeconfig file not found")
Expand Down
3 changes: 1 addition & 2 deletions cmd/kubens/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,14 @@ import (
_ "k8s.io/client-go/plugin/pkg/client/auth"
"k8s.io/client-go/tools/clientcmd"

"github.com/ahmetb/kubectx/internal/cmdutil"
"github.com/ahmetb/kubectx/internal/kubeconfig"
"github.com/ahmetb/kubectx/internal/printer"
)

type ListOp struct{}

func (op ListOp) Run(stdout, stderr io.Writer) error {
kc := new(kubeconfig.Kubeconfig).WithLoader(cmdutil.DefaultLoader)
kc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader)
defer kc.Close()
if err := kc.Parse(); err != nil {
return errors.Wrap(err, "kubeconfig error")
Expand Down
3 changes: 1 addition & 2 deletions cmd/kubens/switch.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
errors2 "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/ahmetb/kubectx/internal/cmdutil"
"github.com/ahmetb/kubectx/internal/kubeconfig"
"github.com/ahmetb/kubectx/internal/printer"
)
Expand All @@ -18,7 +17,7 @@ type SwitchOp struct {
}

func (s SwitchOp) Run(_, stderr io.Writer) error {
kc := new(kubeconfig.Kubeconfig).WithLoader(cmdutil.DefaultLoader)
kc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader)
defer kc.Close()
if err := kc.Parse(); err != nil {
return errors.Wrap(err, "kubeconfig error")
Expand Down
29 changes: 29 additions & 0 deletions internal/cmdutil/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package cmdutil

import (
"os"

"github.com/pkg/errors"
)

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
}

// 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
}
68 changes: 68 additions & 0 deletions internal/cmdutil/util_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package cmdutil

import (
"testing"

"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()
}
})
}
}
14 changes: 8 additions & 6 deletions internal/kubeconfig/helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ type MockKubeconfigLoader struct {
out bytes.Buffer
}

func (t *MockKubeconfigLoader) Read(p []byte) (n int, err error) { return t.in.Read(p) }
func (t *MockKubeconfigLoader) Write(p []byte) (n int, err error) { return t.out.Write(p) }
func (t *MockKubeconfigLoader) Close() error { return nil }
func (t *MockKubeconfigLoader) Reset() error { return nil }
func (t *MockKubeconfigLoader) Load() (ReadWriteResetCloser, error) { return t, nil }
func (t *MockKubeconfigLoader) Output() string { return t.out.String() }
func (t *MockKubeconfigLoader) Read(p []byte) (n int, err error) { return t.in.Read(p) }
func (t *MockKubeconfigLoader) Write(p []byte) (n int, err error) { return t.out.Write(p) }
func (t *MockKubeconfigLoader) Close() error { return nil }
func (t *MockKubeconfigLoader) Reset() error { return nil }
func (t *MockKubeconfigLoader) Load() ([]ReadWriteResetCloser, error) {
return []ReadWriteResetCloser{ReadWriteResetCloser(t)}, nil
}
func (t *MockKubeconfigLoader) Output() string { return t.out.String() }

func WithMockKubeconfigLoader(kubecfg string) *MockKubeconfigLoader {
return &MockKubeconfigLoader{in: strings.NewReader(kubecfg)}
Expand Down
7 changes: 5 additions & 2 deletions internal/kubeconfig/kubeconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type ReadWriteResetCloser interface {
}

type Loader interface {
Load() (ReadWriteResetCloser, error)
Load() ([]ReadWriteResetCloser, error)
}

type Kubeconfig struct {
Expand All @@ -38,11 +38,14 @@ func (k *Kubeconfig) Close() error {
}

func (k *Kubeconfig) Parse() error {
f, err := k.loader.Load()
files, err := k.loader.Load()
if err != nil {
return errors.Wrap(err, "failed to load")
}

// TODO since we don't support multiple kubeconfig files at the moment, there's just 1 file
f := files[0]

k.f = f
var v yaml.Node
if err := yaml.NewDecoder(f).Decode(&v); err != nil {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,35 +1,37 @@
package cmdutil
package kubeconfig

import (
"github.com/ahmetb/kubectx/internal/cmdutil"
"os"
"path/filepath"

"github.com/pkg/errors"

"github.com/ahmetb/kubectx/internal/kubeconfig"
)

var (
DefaultLoader kubeconfig.Loader = new(StandardKubeconfigLoader)
DefaultLoader Loader = new(StandardKubeconfigLoader)
)

type StandardKubeconfigLoader struct{}

type kubeconfigFile struct{ *os.File }

func (*StandardKubeconfigLoader) Load() (kubeconfig.ReadWriteResetCloser, error) {
func (*StandardKubeconfigLoader) Load() ([]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

// TODO we'll return all kubeconfig files when we start implementing multiple kubeconfig support
return []ReadWriteResetCloser{ReadWriteResetCloser(&kubeconfigFile{f})}, nil
}

func (kf *kubeconfigFile) Reset() error {
Expand All @@ -52,31 +54,9 @@ func kubeconfigPath() (string, error) {
}

// default path
home := HomeDir()
home := cmdutil.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
}

// 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
}
Loading

0 comments on commit 1db00a2

Please sign in to comment.