Skip to content

Commit 0d8a2c2

Browse files
committed
auxdriver: Strict parsing of driver version
Add a driverVersion helper and auxdriver.Version type for parsing driver version yaml output. The helper read the output of the "driver version", parses the yaml, and validate that both version and commit are set. This will make it possible to validate the driver commit hash during the tests to ensure we test the driver built from the current code. We log or include in the error message both the driver version and commit hash, to make it easier to debug issues related to using the wrong driver. This changes fixes possible issue if "driver version" command logs errors or warnings. Previously this could break the code parsing the version since we combined stdout and stderr. Now we extract stderr from the command ExitError on errors.
1 parent 57da164 commit 0d8a2c2

File tree

6 files changed

+189
-73
lines changed

6 files changed

+189
-73
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ require (
8585
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738
8686
libvirt.org/go/libvirt v1.11006.0
8787
sigs.k8s.io/sig-storage-lib-external-provisioner/v6 v6.3.0
88+
sigs.k8s.io/yaml v1.4.0
8889
)
8990

9091
require (
@@ -252,7 +253,6 @@ require (
252253
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
253254
sigs.k8s.io/randfill v1.0.0 // indirect
254255
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect
255-
sigs.k8s.io/yaml v1.4.0 // indirect
256256
)
257257

258258
replace (

pkg/minikube/driver/auxdriver/install.go

Lines changed: 4 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import (
2121
"os"
2222
"os/exec"
2323
"path/filepath"
24-
"regexp"
2524
"strings"
2625
"time"
2726

@@ -127,22 +126,17 @@ func validateDriver(executable string, minimalVersion semver.Version) (string, e
127126
return path, fmt.Errorf("failed to find driver %q: %w", executable, err)
128127
}
129128

130-
cmd := exec.Command(path, "version")
131-
output, err := cmd.CombinedOutput()
129+
dv, err := driverVersion(path)
132130
if err != nil {
133-
return path, fmt.Errorf("%s failed: %w: %q", cmd, err, output)
131+
return path, err
134132
}
135133

136-
ev := extractDriverVersion(string(output))
137-
if len(ev) == 0 {
138-
return path, fmt.Errorf("%s: unable to extract version from %q", executable, output)
139-
}
134+
klog.Infof("%s version is %+v", path, dv)
140135

141-
actualVersion, err := semver.Make(ev)
136+
actualVersion, err := semver.Make(strings.TrimPrefix(dv.Version, "v"))
142137
if err != nil {
143138
return path, fmt.Errorf("%s: invalid driver version: %w", executable, err)
144139
}
145-
klog.Infof("%s version is %s", path, actualVersion)
146140

147141
if actualVersion.LT(minimalVersion) {
148142
return path, fmt.Errorf("%s is version %s, want %s or later", executable, actualVersion, minimalVersion)
@@ -151,23 +145,6 @@ func validateDriver(executable string, minimalVersion semver.Version) (string, e
151145
return path, nil
152146
}
153147

154-
// extractDriverVersion extracts the driver version.
155-
// KVM and Hyperkit drivers support the 'version' command, that display the information as:
156-
// version: vX.X.X
157-
// commit: XXXX
158-
// This method returns the version 'vX.X.X' or empty if the version isn't found.
159-
func extractDriverVersion(s string) string {
160-
versionRegex := regexp.MustCompile(`version:(.*)`)
161-
matches := versionRegex.FindStringSubmatch(s)
162-
163-
if len(matches) != 2 {
164-
return ""
165-
}
166-
167-
v := strings.TrimSpace(matches[1])
168-
return strings.TrimPrefix(v, "v")
169-
}
170-
171148
func driverExists(driverName string) bool {
172149
_, err := exec.LookPath(driverName)
173150
return err == nil

pkg/minikube/driver/auxdriver/install_test.go

Lines changed: 0 additions & 45 deletions
This file was deleted.
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
Copyright 2020 The Kubernetes Authors All rights reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package main
18+
19+
import (
20+
"fmt"
21+
"os"
22+
"path/filepath"
23+
"strings"
24+
)
25+
26+
const version = "v1.2.3"
27+
const commit = "1af8bdc072232de4b1fec3b6cc0e8337e118bc83"
28+
29+
func main() {
30+
if len(os.Args) != 2 {
31+
fmt.Fprintf(os.Stderr, "no command specified\n")
32+
os.Exit(1)
33+
}
34+
35+
if os.Args[1] != "version" {
36+
fmt.Fprintf(os.Stderr, "unknown command %q\n", os.Args[1])
37+
os.Exit(1)
38+
}
39+
40+
// We use a single executable to emulate different driver outputs.
41+
driverPersonality := strings.TrimSuffix(filepath.Base(os.Args[0]), ".exe")
42+
43+
switch driverPersonality {
44+
case "valid":
45+
fmt.Printf("version: %s\n", version)
46+
fmt.Printf("commit: %s\n", commit)
47+
case "no-version":
48+
fmt.Printf("commit: %s\n", commit)
49+
case "no-commit":
50+
fmt.Printf("version: %s\n", version)
51+
case "invalid":
52+
fmt.Println("invalid yaml")
53+
case "fail":
54+
fmt.Fprintf(os.Stderr, "no version for you!\n")
55+
os.Exit(1)
56+
default:
57+
fmt.Fprintf(os.Stderr, "unknown personality %q\n", driverPersonality)
58+
os.Exit(1)
59+
}
60+
}

pkg/minikube/driver/auxdriver/version.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,28 @@ limitations under the License.
1717
package auxdriver
1818

1919
import (
20+
"fmt"
21+
"os/exec"
22+
2023
"github.com/blang/semver/v4"
2124
"k8s.io/klog/v2"
2225
"k8s.io/minikube/pkg/minikube/driver"
26+
"sigs.k8s.io/yaml"
2327
)
2428

2529
// minHyperkitVersion is the minimum version of the minikube hyperkit driver compatible with the current minikube code
2630
var minHyperkitVersion *semver.Version
2731

2832
const minHyperkitVersionStr = "1.11.0"
2933

34+
// Version is auxiliary driver version info.
35+
type Version struct {
36+
// Version is driver version in vX.Y.Z format.
37+
Version string `json:"version"`
38+
// Commit is the git commit hash the driver was built from.
39+
Commit string `json:"commit"`
40+
}
41+
3042
func init() {
3143
v, err := semver.New(minHyperkitVersionStr)
3244
if err != nil {
@@ -51,3 +63,34 @@ func minAcceptableDriverVersion(driverName string, mkVer semver.Version) semver.
5163
return mkVer
5264
}
5365
}
66+
67+
// driverVersion returns auxiliary driver version.
68+
func driverVersion(path string) (Version, error) {
69+
v := Version{}
70+
71+
cmd := exec.Command(path, "version")
72+
output, err := cmd.Output()
73+
if err != nil {
74+
var stderr []byte
75+
if ee, ok := err.(*exec.ExitError); ok {
76+
stderr = ee.Stderr
77+
}
78+
return v, fmt.Errorf("command %s failed: %v: %s", cmd, err, stderr)
79+
}
80+
81+
if err := yaml.Unmarshal(output, &v); err != nil {
82+
return v, fmt.Errorf("invalid driver version: %q: %v", output, err)
83+
}
84+
85+
// Version is required to validate the driver in runtime.
86+
if v.Version == "" {
87+
return v, fmt.Errorf("version not specified: %+v", v)
88+
}
89+
90+
// Commit is required to validate the driver during tests.
91+
if v.Commit == "" {
92+
return v, fmt.Errorf("commit not specified: %+v", v)
93+
}
94+
95+
return v, nil
96+
}

pkg/minikube/driver/auxdriver/version_test.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ limitations under the License.
1717
package auxdriver
1818

1919
import (
20+
"os"
21+
"os/exec"
22+
"path/filepath"
23+
"runtime"
2024
"testing"
2125

2226
"github.com/blang/semver/v4"
@@ -50,3 +54,80 @@ func semanticVersion(s string) semver.Version {
5054
}
5155
return *r
5256
}
57+
58+
func TestDriverVersion(t *testing.T) {
59+
t.Run("valid", func(t *testing.T) {
60+
path := buildTestDriver(t, "valid")
61+
v, err := driverVersion(path)
62+
if err != nil {
63+
t.Fatalf("failed to get driver version: %s", err)
64+
}
65+
expected := Version{Version: "v1.2.3", Commit: "1af8bdc072232de4b1fec3b6cc0e8337e118bc83"}
66+
if v != expected {
67+
t.Errorf("Invalid driver version, got: %v, want: %v", v, expected)
68+
}
69+
})
70+
71+
t.Run("no version", func(t *testing.T) {
72+
path := buildTestDriver(t, "no-version")
73+
if _, err := driverVersion(path); err == nil {
74+
t.Fatalf("missing version did not fail")
75+
} else {
76+
t.Logf("expected error: %v", err)
77+
}
78+
})
79+
80+
t.Run("no commit", func(t *testing.T) {
81+
path := buildTestDriver(t, "no-commit")
82+
if _, err := driverVersion(path); err == nil {
83+
t.Fatalf("missing commit did not fail")
84+
} else {
85+
t.Logf("expected error: %v", err)
86+
}
87+
})
88+
89+
t.Run("invalid", func(t *testing.T) {
90+
path := buildTestDriver(t, "invalid")
91+
if _, err := driverVersion(path); err == nil {
92+
t.Fatalf("invalid yaml did not fail")
93+
} else {
94+
t.Logf("expected error: %v", err)
95+
}
96+
})
97+
98+
t.Run("fail", func(t *testing.T) {
99+
path := buildTestDriver(t, "fail")
100+
if _, err := driverVersion(path); err == nil {
101+
t.Fatalf("failing driver did not fail")
102+
} else {
103+
t.Logf("expected error: %v", err)
104+
}
105+
})
106+
107+
t.Run("missing", func(t *testing.T) {
108+
path := filepath.Join(t.TempDir(), "driver.exe")
109+
if _, err := driverVersion(path); err == nil {
110+
t.Fatalf("missing driver did not fail")
111+
} else {
112+
t.Logf("expected error: %v", err)
113+
}
114+
})
115+
}
116+
117+
func buildTestDriver(t *testing.T, name string) string {
118+
out := filepath.Join(t.TempDir(), name)
119+
if runtime.GOOS == "windows" {
120+
out += ".exe"
121+
}
122+
123+
cmd := exec.Command("go", "build", "-o", out, "testdata/driver.go")
124+
cmd.Stdout = os.Stdout
125+
cmd.Stderr = os.Stderr
126+
127+
t.Logf("Building %q", out)
128+
if err := cmd.Run(); err != nil {
129+
t.Fatal(err)
130+
}
131+
132+
return out
133+
}

0 commit comments

Comments
 (0)