From da08491f0b55329c61e69d9ee25d36588e52c52a Mon Sep 17 00:00:00 2001 From: Ahmet Alp Balkan Date: Fri, 10 Apr 2020 13:02:00 -0700 Subject: [PATCH] Implement facilities to parse kubeconfig file Signed-off-by: Ahmet Alp Balkan --- cmd/kubectx/kubeconfig.go | 16 ++++++- cmd/kubectx/kubeconfig_test.go | 79 +++++++++++++++++++++++++++++++++- cmd/kubectx/list.go | 35 +++++++++++++++ cmd/kubectx/main.go | 4 +- go.mod | 2 + go.sum | 5 +++ 6 files changed, 137 insertions(+), 4 deletions(-) diff --git a/cmd/kubectx/kubeconfig.go b/cmd/kubectx/kubeconfig.go index fe32d0c..dec6a84 100644 --- a/cmd/kubectx/kubeconfig.go +++ b/cmd/kubectx/kubeconfig.go @@ -1,9 +1,11 @@ package main import ( - "errors" "os" "path/filepath" + + "github.com/pkg/errors" + "gopkg.in/yaml.v3" ) func kubeconfigPath() (string, error) { @@ -27,3 +29,15 @@ func kubeconfigPath() (string, error) { } return filepath.Join(home, ".kube", "config"), nil } + +func parseKubeconfig(path string) (kubeconfig, error) { + // TODO refactor to accept io.Reader instead of file + var v kubeconfig + + f, err := os.Open(path) + if err != nil { + return v, errors.Wrap(err, "file open error") + } + err = yaml.NewDecoder(f).Decode(&v) + return v, errors.Wrap(err, "yaml parse error") +} diff --git a/cmd/kubectx/kubeconfig_test.go b/cmd/kubectx/kubeconfig_test.go index 5c30a6f..d2179d5 100644 --- a/cmd/kubectx/kubeconfig_test.go +++ b/cmd/kubectx/kubeconfig_test.go @@ -1,10 +1,13 @@ package main import ( + "io/ioutil" "os" "path/filepath" "strings" "testing" + + "github.com/google/go-cmp/cmp" ) func Test_kubeconfigPath_homePath(t *testing.T) { @@ -72,5 +75,79 @@ func Test_kubeconfigPath_envOvverideDoesNotSupportPathSeparator(t *testing.T) { _, err := kubeconfigPath() if err == nil { t.Fatal("expected error")} - +} + + +func Test_parseKubeconfig_openError(t *testing.T) { + _, err := parseKubeconfig("/non/existing/path") + if err == nil { + t.Fatalf("expected error") + } + msg := err.Error() + expected := `file open error` + if !strings.Contains(msg, expected) { + t.Fatalf("expected=%q, got=%q", expected, msg) + } +} + +func Test_parseKubeconfig_yamlFormatError(t *testing.T) { + file, cleanup := testfile(t, `a: [1,2 `) // supposed to be invalid yaml + defer cleanup() + + _, err := parseKubeconfig(file) + if err == nil { + t.Fatal("expected error") + } + expected := "yaml parse error" + if got := err.Error(); !strings.Contains(got, expected) { + t.Fatalf("expected=%q; got=%q", expected, got) + } +} + +func Test_parseKubeconfig(t *testing.T) { + file, cleanup := testfile(t, + ` +apiVersion: v1 +current-context: foo +contexts: +- name: c3 +- name: c2 +- name: c1`) + defer cleanup() + + got, err := parseKubeconfig(file) + if err != nil { + t.Fatal(err) + } + + expected := kubeconfig{ + APIVersion: "v1", + CurrentContext: "foo", + Contexts: []context{ + {Name:"c3"},{Name:"c2"},{Name:"c1"}, + }, + } + + diff := cmp.Diff(expected, got) + if diff != "" { + t.Fatalf("got wrong object:\n%s", diff) + } +} + +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) + } } diff --git a/cmd/kubectx/list.go b/cmd/kubectx/list.go index 06ab7d0..57496cf 100644 --- a/cmd/kubectx/list.go +++ b/cmd/kubectx/list.go @@ -1 +1,36 @@ package main + +import ( + "io" + + "github.com/pkg/errors" +) + +type context struct { + Name string `yaml:"name"` +} + +type kubeconfig struct { + APIVersion string `yaml:"apiVersion"` + CurrentContext string `yaml:"current-context"` + Contexts []context `yaml:"contexts"` +} + +func printListContexts(out io.Writer) error { + cfgPath, err := kubeconfigPath() + if err != nil { + return errors.Wrap(err, "failed to determine kubeconfig path") + } + + cfg, err := parseKubeconfig(cfgPath) + if err != nil { + return errors.Wrap(err, "failed to read kubeconfig file") + } + _ = cfg + + // print each context + // - natural sort + // - highlight current context + + return nil +} diff --git a/cmd/kubectx/main.go b/cmd/kubectx/main.go index 6568fbb..c20551e 100644 --- a/cmd/kubectx/main.go +++ b/cmd/kubectx/main.go @@ -13,12 +13,12 @@ func main() { var op Op op = parseArgs(os.Args[1:]) + // TODO consider addin Run() operation to each operation type switch v := op.(type) { case HelpOp: printHelp(os.Stdout) case ListOp: - // TODO implement - panic("not implemented") + printListContexts(os.Stdout) case SwitchOp: // TODO implement panic("not implemented") diff --git a/go.mod b/go.mod index bd32419..0d192a4 100644 --- a/go.mod +++ b/go.mod @@ -5,4 +5,6 @@ go 1.14 require ( github.com/fatih/color v1.9.0 github.com/google/go-cmp v0.4.0 + github.com/pkg/errors v0.9.1 + gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c ) diff --git a/go.sum b/go.sum index 5a81e34..b365711 100644 --- a/go.sum +++ b/go.sum @@ -7,7 +7,12 @@ github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=