Implement facilities to parse kubeconfig file

Signed-off-by: Ahmet Alp Balkan <ahmetb@google.com>
This commit is contained in:
Ahmet Alp Balkan 2020-04-10 13:02:00 -07:00
parent 7c2f8ffa75
commit da08491f0b
No known key found for this signature in database
GPG Key ID: 441833503E604E2C
6 changed files with 137 additions and 4 deletions

View File

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

View File

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

View File

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

View File

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

2
go.mod
View File

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

5
go.sum
View File

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