From 74ee3141f6593cafd041b03eb1658eb86df74cd7 Mon Sep 17 00:00:00 2001 From: Dirk Wilden Date: Tue, 5 Sep 2023 09:43:47 +0200 Subject: [PATCH] fix kubectl version detection --- CHANGELOG.md | 2 + internal/k8s/executer_test.go | 98 +++++++++++++++++++++++++++++++++-- internal/k8s/executor.go | 42 ++++++++------- internal/k8s/export_test.go | 2 + 4 files changed, 120 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ddb696fb..b2162565 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Fixed - [#162](https://github.com/deviceinsight/kafkactl/pull/162) Fix `consumer-group` crashes when a group member has no assignment +- [#160](https://github.com/deviceinsight/kafkactl/pull/160) Fix kubectl version detection +### Added - [#159](https://github.com/deviceinsight/kafkactl/issues/159) Add ability to read config file from `$PWD/kafkactl.yml` ## 3.2.0 - 2023-08-17 diff --git a/internal/k8s/executer_test.go b/internal/k8s/executer_test.go index 7b147c52..5f9a238c 100644 --- a/internal/k8s/executer_test.go +++ b/internal/k8s/executer_test.go @@ -2,22 +2,26 @@ package k8s_test import ( "encoding/json" + "strings" "testing" + "github.com/deviceinsight/kafkactl/output" + "github.com/deviceinsight/kafkactl/internal" "github.com/deviceinsight/kafkactl/internal/k8s" "github.com/deviceinsight/kafkactl/testutil" ) type TestRunner struct { - binary string - args []string + binary string + args []string + response []byte } func (runner *TestRunner) ExecuteAndReturn(binary string, args []string) ([]byte, error) { runner.binary = binary runner.args = args - return nil, nil + return runner.response, nil } func (runner *TestRunner) Execute(binary string, args []string) error { @@ -52,7 +56,8 @@ func TestExecWithImageAndImagePullSecretProvided(t *testing.T) { if err := json.Unmarshal([]byte(overrides), &podOverrides); err != nil { t.Fatalf("unable to unmarshall overrides: %v", err) } - if len(podOverrides.Spec.ImagePullSecrets) != 1 || podOverrides.Spec.ImagePullSecrets[0].Name != context.Kubernetes.ImagePullSecret { + if len(podOverrides.Spec.ImagePullSecrets) != 1 || + podOverrides.Spec.ImagePullSecrets[0].Name != context.Kubernetes.ImagePullSecret { t.Fatalf("wrong overrides: %s", overrides) } } @@ -71,6 +76,91 @@ func TestExecWithImageAndTagFails(t *testing.T) { testutil.AssertErrorContains(t, "image must not contain a tag", err) } +//nolint:gocognit +func TestParseKubectlVersion(t *testing.T) { + + var testRunner = TestRunner{} + var runner k8s.Runner = &testRunner + + type tests struct { + description string + kubectlOutput string + wantErr string + wantVersion k8s.Version + } + + for _, test := range []tests{ + { + description: "parse_fails_for_unparsable_output", + kubectlOutput: "unknown output", + wantErr: "unable to extract kubectl version", + }, + { + description: "parse_valid_kubectl_output_succeeds", + kubectlOutput: ` + { + "ClientVersion": { + "major": "1", + "minor": "27", + "gitVersion": "v1.27.1", + "gitCommit": "4c9411232e10168d7b050c49a1b59f6df9d7ea4b", + "gitTreeState": "clean", + "buildDate": "2023-04-14T13:14:41Z", + "goVersion": "go1.20.3", + "compiler": "gc", + "platform": "linux/amd64" + }, + "kustomizeVersion": "v5.0.1" + }`, + wantVersion: k8s.Version{ + Major: 1, + Minor: 27, + GitVersion: "v1.27.1", + }, + }, + } { + t.Run(test.description, func(t *testing.T) { + + var err error + + output.Fail = func(failError error) { + err = failError + } + + testRunner.response = []byte(test.kubectlOutput) + + version := k8s.GetKubectlVersion("kubectl", &runner) + + if test.wantErr != "" { + if err == nil { + t.Errorf("want error %q but got nil", test.wantErr) + } + + if !strings.Contains(err.Error(), test.wantErr) { + t.Errorf("want error %q got %q", test.wantErr, err) + } + + return + } + if err != nil { + t.Errorf("doesn't want error but got %s", err) + } + + if test.wantVersion.GitVersion != version.GitVersion { + t.Fatalf("different version expexted %s != %s", test.wantVersion.GitVersion, version.GitVersion) + } + + if test.wantVersion.Major != version.Major { + t.Fatalf("different major version expexted %d != %d", test.wantVersion.Major, version.Major) + } + + if test.wantVersion.Minor != version.Minor { + t.Fatalf("different minor version expexted %d != %d", test.wantVersion.Minor, version.Minor) + } + }) + } +} + func extractParam(t *testing.T, args []string, param string) string { var paramIdx int diff --git a/internal/k8s/executor.go b/internal/k8s/executor.go index 23baa4c5..a73225b7 100644 --- a/internal/k8s/executor.go +++ b/internal/k8s/executor.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "math/rand" - "regexp" "strconv" "strings" "time" @@ -16,9 +15,9 @@ import ( ) type Version struct { - Major int - Minor int - Patch int + Major int + Minor int + GitVersion string } type executor struct { @@ -48,42 +47,45 @@ func randomString(n int) string { } func getKubectlVersion(kubectlBinary string, runner *Runner) Version { - bytes, err := (*runner).ExecuteAndReturn(kubectlBinary, []string{"version", "--client", "--short"}) + bytes, err := (*runner).ExecuteAndReturn(kubectlBinary, []string{"version", "--client", "-o", "json"}) if err != nil { output.Fail(err) + return Version{} } if len(bytes) == 0 { return Version{} } - re := regexp.MustCompile(`v(?P\d+)\.(?P\d+)\.(?P\d+)`) - matches := re.FindStringSubmatch(string(bytes)) - - result := make(map[string]string) - for i, name := range re.SubexpNames() { - result[name] = matches[i] + type versionOutput struct { + ClientVersion struct { + Major string `json:"major"` + Minor string `json:"minor"` + GitVersion string `json:"gitVersion"` + } `json:"clientVersion"` } - major, err := strconv.Atoi(result["major"]) - if err != nil { - output.Fail(err) + var jsonOutput versionOutput + + if err := json.Unmarshal(bytes, &jsonOutput); err != nil { + output.Fail(fmt.Errorf("unable to extract kubectl version: %w", err)) + return Version{} } - minor, err := strconv.Atoi(result["minor"]) + major, err := strconv.Atoi(jsonOutput.ClientVersion.Major) if err != nil { output.Fail(err) } - patch, err := strconv.Atoi(result["patch"]) + minor, err := strconv.Atoi(jsonOutput.ClientVersion.Minor) if err != nil { output.Fail(err) } return Version{ - Major: major, - Minor: minor, - Patch: patch, + Major: major, + Minor: minor, + GitVersion: jsonOutput.ClientVersion.GitVersion, } } @@ -192,7 +194,7 @@ func filter(slice []string, predicate func(string) bool) (ret []string) { func (kubectl *executor) exec(args []string) error { cmd := fmt.Sprintf("exec: %s %s", kubectl.kubectlBinary, join(args)) - output.Debugf("kubectl version: %d.%d.%d", kubectl.version.Major, kubectl.version.Minor, kubectl.version.Patch) + output.Debugf("kubectl version: %s", kubectl.version.GitVersion) output.Debugf(cmd) err := (*kubectl.runner).Execute(kubectl.kubectlBinary, args) return err diff --git a/internal/k8s/export_test.go b/internal/k8s/export_test.go index fa2c2dc8..f9c2a580 100644 --- a/internal/k8s/export_test.go +++ b/internal/k8s/export_test.go @@ -2,4 +2,6 @@ package k8s var ParsePodEnvironment = parsePodEnvironment +var GetKubectlVersion = getKubectlVersion + var NewExecutor = newExecutor