fix(kubectl): parse yaml instead of csv

closes #1439
This commit is contained in:
Jan De Dobbeleer 2021-12-17 20:54:43 +01:00 committed by Jan De Dobbeleer
parent e887f4ecea
commit 9edd53e679
3 changed files with 53 additions and 52 deletions

View file

@ -2,7 +2,6 @@ package main
import ( import (
"path/filepath" "path/filepath"
"strings"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@ -14,23 +13,23 @@ type kubectl struct {
props properties props properties
env environmentInfo env environmentInfo
Context string Context string
KubeConfigContext KubeContext
}
type KubeConfigContext struct {
Cluster string `yaml:"cluster"`
User string `yaml:"user"`
Namespace string `yaml:"namespace"`
} }
type KubeConfig struct { type KubeConfig struct {
CurrentContext string `yaml:"current-context"` CurrentContext string `yaml:"current-context"`
Contexts []struct { Contexts []struct {
Context KubeConfigContext `yaml:"context"` Context *KubeContext `yaml:"context"`
Name string `yaml:"name"` Name string `yaml:"name"`
} `yaml:"contexts"` } `yaml:"contexts"`
} }
type KubeContext struct {
Cluster string `yaml:"cluster"`
User string `yaml:"user"`
Namespace string `yaml:"namespace"`
}
func (k *kubectl) string() string { func (k *kubectl) string() string {
segmentTemplate := k.props.getString(SegmentTemplate, "{{.Context}}{{if .Namespace}} :: {{.Namespace}}{{end}}") segmentTemplate := k.props.getString(SegmentTemplate, "{{.Context}}{{if .Namespace}} :: {{.Namespace}}{{end}}")
template := &textTemplate{ template := &textTemplate{
@ -65,7 +64,7 @@ func (k *kubectl) doParseKubeConfig() bool {
if len(kubeconfigs) == 0 { if len(kubeconfigs) == 0 {
kubeconfigs = []string{filepath.Join(k.env.homeDir(), ".kube/config")} kubeconfigs = []string{filepath.Join(k.env.homeDir(), ".kube/config")}
} }
contexts := make(map[string]KubeConfigContext) contexts := make(map[string]*KubeContext)
k.Context = "" k.Context = ""
for _, kubeconfig := range kubeconfigs { for _, kubeconfig := range kubeconfigs {
if len(kubeconfig) == 0 { if len(kubeconfig) == 0 {
@ -91,10 +90,13 @@ func (k *kubectl) doParseKubeConfig() bool {
} }
context, exists := contexts[k.Context] context, exists := contexts[k.Context]
if exists { if !exists {
k.KubeConfigContext = context continue
return true
} }
if context != nil {
k.KubeContext = *context
}
return true
} }
displayError := k.props.getBool(DisplayError, false) displayError := k.props.getBool(DisplayError, false)
@ -110,7 +112,7 @@ func (k *kubectl) doCallKubectl() bool {
if !k.env.hasCommand(cmd) { if !k.env.hasCommand(cmd) {
return false return false
} }
result, err := k.env.runCommand(cmd, "config", "view", "--minify", "--output", "jsonpath={..current-context},{..namespace},{..context.user},{..context.cluster}") result, err := k.env.runCommand(cmd, "config", "view", "--output", "yaml", "--minify")
displayError := k.props.getBool(DisplayError, false) displayError := k.props.getBool(DisplayError, false)
if err != nil && displayError { if err != nil && displayError {
k.setError("KUBECTL ERR") k.setError("KUBECTL ERR")
@ -120,15 +122,16 @@ func (k *kubectl) doCallKubectl() bool {
return false return false
} }
values := strings.Split(result, ",") var config KubeConfig
if len(values) < 4 { err = yaml.Unmarshal([]byte(result), &config)
if err != nil {
return false return false
} }
k.Context = values[0] k.Context = config.CurrentContext
k.Namespace = values[1] if len(config.Contexts) > 0 {
k.User = values[2] k.KubeContext = *config.Contexts[0].Context
k.Cluster = values[3] }
return len(k.Context) > 0 return true
} }
func (k *kubectl) setError(message string) { func (k *kubectl) setError(message string) {

View file

@ -1,6 +1,8 @@
package main package main
import ( import (
"fmt"
"io/ioutil"
"path/filepath" "path/filepath"
"testing" "testing"
@ -29,16 +31,16 @@ func TestKubectlSegment(t *testing.T) {
ExpectedString string ExpectedString string
Files map[string]string Files map[string]string
}{ }{
{Case: "disabled", Template: standardTemplate, KubectlExists: false, Context: "aaa", Namespace: "bbb", ExpectedString: "", ExpectedEnabled: false},
{ {
Case: "not enough arguments", Case: "kubeconfig incomplete",
Template: standardTemplate, Template: testKubectlAllInfoTemplate,
KubectlExists: true, ParseKubeConfig: true,
Context: "aaa", Kubeconfig: "currentcontextmarker" + lsep + "contextdefinitionincomplete",
Namespace: "bbb", Files: testKubeConfigFiles,
ExpectedString: "", ExpectedString: "ctx :: :: :: ",
ExpectedEnabled: false, ExpectedEnabled: true,
}, },
{Case: "disabled", Template: standardTemplate, KubectlExists: false, Context: "aaa", Namespace: "bbb", ExpectedEnabled: false},
{ {
Case: "all information", Case: "all information",
Template: testKubectlAllInfoTemplate, Template: testKubectlAllInfoTemplate,
@ -50,7 +52,7 @@ func TestKubectlSegment(t *testing.T) {
ExpectedString: "aaa :: bbb :: ccc :: ddd", ExpectedString: "aaa :: bbb :: ccc :: ddd",
ExpectedEnabled: true, ExpectedEnabled: true,
}, },
{Case: "no namespace", Template: standardTemplate, KubectlExists: true, Context: "aaa", Namespace: "", ExpectedString: "", ExpectedEnabled: false}, {Case: "no namespace", Template: standardTemplate, KubectlExists: true, Context: "aaa", ExpectedString: "aaa", ExpectedEnabled: true},
{ {
Case: "kubectl error", Case: "kubectl error",
Template: standardTemplate, Template: standardTemplate,
@ -100,30 +102,16 @@ func TestKubectlSegment(t *testing.T) {
ExpectedString: "KUBECONFIG ERR :: KUBECONFIG ERR :: KUBECONFIG ERR :: KUBECONFIG ERR", ExpectedString: "KUBECONFIG ERR :: KUBECONFIG ERR :: KUBECONFIG ERR :: KUBECONFIG ERR",
ExpectedEnabled: true, ExpectedEnabled: true,
}, },
{
Case: "kubeconfig incomplete",
Template: testKubectlAllInfoTemplate,
ParseKubeConfig: true,
Kubeconfig: "currentcontextmarker" + lsep + "contextdefinitionincomplete",
Files: testKubeConfigFiles,
ExpectedString: "ctx :: :: :: ",
ExpectedEnabled: true,
},
} }
for _, tc := range cases { for _, tc := range cases {
env := new(MockedEnvironment) env := new(MockedEnvironment)
env.On("hasCommand", "kubectl").Return(tc.KubectlExists) env.On("hasCommand", "kubectl").Return(tc.KubectlExists)
addCommaAndvalue := func(s string) string { var kubeconfig string
if s == "" { content, err := ioutil.ReadFile("./test/kubectl.yml")
return "" if err == nil {
} kubeconfig = fmt.Sprintf(string(content), tc.Cluster, tc.User, tc.Namespace, tc.Context)
return "," + s
} }
kubectlOut := tc.Context
kubectlOut += addCommaAndvalue(tc.Namespace)
kubectlOut += addCommaAndvalue(tc.User)
kubectlOut += addCommaAndvalue(tc.Cluster)
var kubectlErr error var kubectlErr error
if tc.KubectlErr { if tc.KubectlErr {
kubectlErr = &commandError{ kubectlErr = &commandError{
@ -131,9 +119,7 @@ func TestKubectlSegment(t *testing.T) {
exitCode: 1, exitCode: 1,
} }
} }
env.On("runCommand", "kubectl", env.On("runCommand", "kubectl", []string{"config", "view", "--output", "yaml", "--minify"}).Return(kubeconfig, kubectlErr)
[]string{"config", "view", "--minify", "--output", "jsonpath={..current-context},{..namespace},{..context.user},{..context.cluster}"}).Return(kubectlOut, kubectlErr)
env.On("getenv", "KUBECONFIG").Return(tc.Kubeconfig) env.On("getenv", "KUBECONFIG").Return(tc.Kubeconfig)
for path, content := range tc.Files { for path, content := range tc.Files {
env.On("getFileContent", path).Return(content) env.On("getFileContent", path).Return(content)

12
src/test/kubectl.yml Normal file
View file

@ -0,0 +1,12 @@
apiVersion: v1
clusters: null
contexts:
- context:
cluster: '%s'
user: '%s'
namespace: '%s'
name: jan
current-context: '%s'
kind: Config
preferences: {}
users: null