diff --git a/deploy/deploy.go b/deploy/deploy.go index 388a391e..d56794a3 100644 --- a/deploy/deploy.go +++ b/deploy/deploy.go @@ -10,10 +10,10 @@ import ( "os" "os/exec" "path/filepath" - "strconv" "strings" "time" + "github.com/blang/semver" dtypes "github.com/docker/docker/api/types" dclient "github.com/docker/docker/client" "github.com/openconfig/gnmi/errlist" @@ -203,33 +203,10 @@ func (d *Deployment) Deploy(ctx context.Context, kubecfg string) (rerr error) { return fmt.Errorf("failed to create k8s client: %w", err) } - log.Infof("Checking kubectl versions.") - output, err := run.OutCommand("kubectl", "version", "--output=yaml") - if err != nil { - return fmt.Errorf("failed get kubectl version: %w", err) - } - kubeYAML := kubeVersion{} - if err := yaml.Unmarshal(output, &kubeYAML); err != nil { - return fmt.Errorf("failed get kubectl version: %w", err) - } - kClientVersion, err := getVersion(kubeYAML.ClientVersion.GitVersion) - if err != nil { - return fmt.Errorf("failed to parse k8s client version: %w", err) - } - kServerVersion, err := getVersion(kubeYAML.ServerVersion.GitVersion) - if err != nil { - return fmt.Errorf("failed to parse k8s server version: %w", err) - } - origMajor := kClientVersion.Major - kClientVersion.Major -= 2 - if kServerVersion.Less(kClientVersion) { - log.Warning("Kube client and server versions are not within expected range.") - } - kClientVersion.Major = origMajor + 2 - if kClientVersion.Less(kServerVersion) { - log.Warning("Kube client and server versions are not within expected range.") + log.Infof("Validating kubectl version") + if err := validateKubectlVersion(); err != nil { + return fmt.Errorf("kubectl version outside of supported range: %v", err) } - log.V(1).Info("Found k8s versions:\n", string(output)) ctx, cancel := context.WithCancel(ctx) @@ -295,6 +272,55 @@ func (d *Deployment) Deploy(ctx context.Context, kubecfg string) (rerr error) { return nil } +func validateKubectlVersion() error { + output, err := run.OutCommand("kubectl", "version", "--output=yaml") + if err != nil { + return fmt.Errorf("failed get kubectl version: %w", err) + } + log.V(1).Info("Found k8s versions:\n", string(output)) + kubeYAML := kubeVersion{} + if err := yaml.Unmarshal(output, &kubeYAML); err != nil { + return fmt.Errorf("failed get kubectl version: %w", err) + } + kClientVersion, err := parseVersion(kubeYAML.ClientVersion.GitVersion) + if err != nil { + return fmt.Errorf("failed to parse k8s client version: %w", err) + } + kServerVersion, err := parseVersion(kubeYAML.ServerVersion.GitVersion) + if err != nil { + return fmt.Errorf("failed to parse k8s server version: %w", err) + } + origMajor := kClientVersion.Major + if kClientVersion.Major < 2 { + kClientVersion.Major = 0 + } else { + kClientVersion.Major -= 2 + } + if kServerVersion.LT(kClientVersion) { + log.Warning("Kube client and server versions are not within expected range.") + } + kClientVersion.Major = origMajor + 2 + if kClientVersion.LT(kServerVersion) { + log.Warning("Kube client and server versions are not within expected range.") + } + return nil +} + +// parseVersion takes a github semver string and parses it into a comparable struct +// with prereleases and builds stripped. +func parseVersion(s string) (semver.Version, error) { + if !strings.HasPrefix(s, "v") { + return semver.Version{}, fmt.Errorf("missing prefix on major version") + } + v, err := semver.Parse(s[1:]) + if err != nil { + return semver.Version{}, err + } + v.Pre = nil + v.Build = nil + return v, nil +} + func (d *Deployment) Delete() error { log.Infof("Deleting cluster...") if err := d.Cluster.Delete(); err != nil { @@ -397,53 +423,6 @@ type KindSpec struct { AdditionalManifests []string `yaml:"additionalManifests" kne:"yaml"` } -type version struct { - Major int - Minor int - Patch int -} - -func (v version) String() string { - return fmt.Sprintf("v%d.%d.%d", v.Major, v.Minor, v.Patch) -} - -func (v version) Less(t *version) bool { - if v.Major == t.Major { - if v.Minor == t.Minor { - return v.Patch < t.Patch - } - return v.Minor < t.Minor - } - return v.Major < t.Major -} - -// getVersion takes a git version tag string "v1.20.1" and returns a version -// comparable version struct. -func getVersion(s string) (*version, error) { - versions := strings.Split(s, ".") - if len(versions) != 3 { - return nil, fmt.Errorf("failed to get versions from: %s", s) - } - v := &version{} - var err error - if !strings.HasPrefix(versions[0], "v") { - return nil, fmt.Errorf("missing prefix on major version: %s", s) - } - v.Major, err = strconv.Atoi(versions[0][1:]) - if err != nil { - return nil, fmt.Errorf("failed to convert major version: %s", s) - } - v.Minor, err = strconv.Atoi(versions[1]) - if err != nil { - return nil, fmt.Errorf("failed to convert minor version: %s", s) - } - v.Patch, err = strconv.Atoi(versions[2]) - if err != nil { - return nil, fmt.Errorf("failed to convert patch version: %s", s) - } - return v, nil -} - func (k *KindSpec) checkDependencies() error { var errs errlist.List bins := []string{"kind"} @@ -456,7 +435,7 @@ func (k *KindSpec) checkDependencies() error { return errs.Err() } if k.Version != "" { - wantV, err := getVersion(k.Version) + wantV, err := parseVersion(k.Version) if err != nil { return fmt.Errorf("failed to parse desired kind version: %w", err) } @@ -471,11 +450,11 @@ func (k *KindSpec) checkDependencies() error { return fmt.Errorf("failed to parse kind version from: %s", stdout) } - gotV, err := getVersion(vKindFields[1]) + gotV, err := parseVersion(vKindFields[1]) if err != nil { return fmt.Errorf("kind version check failed: %w", err) } - if gotV.Less(wantV) { + if gotV.LT(wantV) { return fmt.Errorf("kind version check failed: got %s, want %s. install with `go install sigs.k8s.io/kind@%s`", gotV, wantV, wantV) } log.Infof("kind version valid: got %s want %s", gotV, wantV) diff --git a/deploy/deploy_test.go b/deploy/deploy_test.go index 683e6c4d..9b0d1a6b 100644 --- a/deploy/deploy_test.go +++ b/deploy/deploy_test.go @@ -349,28 +349,28 @@ func TestKindSpec(t *testing.T) { Name: "test", Version: "versionfoo", }, - wantErr: "failed to get versions from", + wantErr: "No Major.Minor.Patch elements found", }, { desc: "failed kind version - invalid major", k: &KindSpec{ Name: "test", Version: "vr.1.1", }, - wantErr: "failed to convert major version", + wantErr: `Invalid character(s) found in major number "r"`, }, { desc: "failed kind version - invalid minor", k: &KindSpec{ Name: "test", Version: "v0.foo.15", }, - wantErr: "failed to convert minor version", + wantErr: `Invalid character(s) found in minor number "foo"`, }, { desc: "failed kind version - invalid patch", k: &KindSpec{ Name: "test", Version: "v0.1.foo", }, - wantErr: "failed to convert patch version", + wantErr: `Invalid character(s) found in patch number "foo"`, }, { desc: "failed kind version less check", k: &KindSpec{ @@ -421,6 +421,16 @@ func TestKindSpec(t *testing.T) { {Cmd: "kind", Args: []string{"version"}, Stdout: "kind v0.15.0 go1.18.2 linux/amd64"}, {Cmd: "kind", Args: []string{"create", "cluster", "--name", "test"}}, }, + }, { + desc: "kind prerelease version pass", + k: &KindSpec{ + Name: "test", + Version: "v0.15.0", + }, + resp: []fexec.Response{ + {Cmd: "kind", Args: []string{"version"}, Stdout: "kind v0.15.0-prelease go1.18.2 linux/amd64"}, + {Cmd: "kind", Args: []string{"create", "cluster", "--name", "test"}}, + }, }, { desc: "kind version pass - major", k: &KindSpec{ diff --git a/go.mod b/go.mod index de99bdaf..6aa4b173 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.21 require ( cloud.google.com/go/pubsub v1.33.0 github.com/aristanetworks/arista-ceoslab-operator/v2 v2.0.2 + github.com/blang/semver v3.5.1+incompatible github.com/docker/docker v24.0.7+incompatible github.com/ghodss/yaml v1.0.0 github.com/golang/glog v1.1.2 diff --git a/go.sum b/go.sum index 14c62d8f..7e4680a4 100644 --- a/go.sum +++ b/go.sum @@ -756,6 +756,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=