Browse code

Add containerd, runc, and docker-init versions to /version

This patch adds version information about the containerd,
runc, and docker-init components to the /version endpoint.

With this patch applied, running:

```
curl --unix-socket /var/run/docker.sock http://localhost/version | jq .
```

Will produce this response:

```json
{
"Platform": {
"Name": ""
},
"Components": [
{
"Name": "Engine",
"Version": "dev",
"Details": {
"ApiVersion": "1.40",
"Arch": "amd64",
"BuildTime": "2018-11-08T10:23:42.000000000+00:00",
"Experimental": "false",
"GitCommit": "7d02782d2f",
"GoVersion": "go1.11.2",
"KernelVersion": "4.9.93-linuxkit-aufs",
"MinAPIVersion": "1.12",
"Os": "linux"
}
},
{
"Name": "containerd",
"Version": "v1.1.4",
"Details": {
"GitCommit": "9f2e07b1fc1342d1c48fe4d7bbb94cb6d1bf278b"
}
},
{
"Name": "runc",
"Version": "1.0.0-rc5+dev",
"Details": {
"GitCommit": "a00bf0190895aa465a5fbed0268888e2c8ddfe85"
}
},
{
"Name": "docker-init",
"Version": "0.18.0",
"Details": {
"GitCommit": "fec3683"
}
}
],
"Version": "dev",
"ApiVersion": "1.40",
"MinAPIVersion": "1.12",
"GitCommit": "7d02782d2f",
"GoVersion": "go1.11.2",
"Os": "linux",
"Arch": "amd64",
"KernelVersion": "4.9.93-linuxkit-aufs",
"BuildTime": "2018-11-08T10:23:42.000000000+00:00"
}
```

When using a recent version of the CLI, that information is included in the
output of `docker version`:

```
Client: Docker Engine - Community
Version: 18.09.0
API version: 1.39
Go version: go1.10.4
Git commit: 4d60db4
Built: Wed Nov 7 00:46:51 2018
OS/Arch: linux/amd64
Experimental: false

Server:
Engine:
Version: dev
API version: 1.40 (minimum version 1.12)
Go version: go1.11.2
Git commit: 7d02782d2f
Built: Thu Nov 8 10:23:42 2018
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: v1.1.4
GitCommit: 9f2e07b1fc1342d1c48fe4d7bbb94cb6d1bf278b
runc:
Version: 1.0.0-rc5+dev
GitCommit: a00bf0190895aa465a5fbed0268888e2c8ddfe85
docker-init:
Version: 0.18.0
GitCommit: fec3683
```

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>

Sebastiaan van Stijn authored on 2018/10/05 19:30:10
Showing 4 changed files
... ...
@@ -118,6 +118,7 @@ func (daemon *Daemon) SystemVersion() types.Version {
118 118
 
119 119
 	v.Platform.Name = dockerversion.PlatformName
120 120
 
121
+	daemon.fillPlatformVersion(&v)
121 122
 	return v
122 123
 }
123 124
 
... ...
@@ -6,6 +6,7 @@ import (
6 6
 	"context"
7 7
 	"fmt"
8 8
 	"os/exec"
9
+	"path/filepath"
9 10
 	"strings"
10 11
 
11 12
 	"github.com/docker/docker/api/types"
... ...
@@ -32,17 +33,11 @@ func (daemon *Daemon) fillPlatformInfo(v *types.Info, sysInfo *sysinfo.SysInfo)
32 32
 
33 33
 	defaultRuntimeBinary := daemon.configStore.GetRuntime(v.DefaultRuntime).Path
34 34
 	if rv, err := exec.Command(defaultRuntimeBinary, "--version").Output(); err == nil {
35
-		parts := strings.Split(strings.TrimSpace(string(rv)), "\n")
36
-		if len(parts) == 3 {
37
-			parts = strings.Split(parts[1], ": ")
38
-			if len(parts) == 2 {
39
-				v.RuncCommit.ID = strings.TrimSpace(parts[1])
40
-			}
41
-		}
42
-
43
-		if v.RuncCommit.ID == "" {
44
-			logrus.Warnf("failed to retrieve %s version: unknown output format: %s", defaultRuntimeBinary, string(rv))
35
+		if _, commit, err := parseRuncVersion(string(rv)); err != nil {
36
+			logrus.Warnf("failed to parse %s version: %v", defaultRuntimeBinary, err)
45 37
 			v.RuncCommit.ID = "N/A"
38
+		} else {
39
+			v.RuncCommit.ID = commit
46 40
 		}
47 41
 	} else {
48 42
 		logrus.Warnf("failed to retrieve %s version: %v", defaultRuntimeBinary, err)
... ...
@@ -64,14 +59,19 @@ func (daemon *Daemon) fillPlatformInfo(v *types.Info, sysInfo *sysinfo.SysInfo)
64 64
 	// value as "ID" to prevent clients from reporting a version-mismatch
65 65
 	v.ContainerdCommit.Expected = v.ContainerdCommit.ID
66 66
 
67
+	// TODO is there still a need to check the expected version for tini?
68
+	// if not, we can change this, and just set "Expected" to v.InitCommit.ID
69
+	v.InitCommit.Expected = dockerversion.InitCommitID
70
+
67 71
 	defaultInitBinary := daemon.configStore.GetInitPath()
68 72
 	if rv, err := exec.Command(defaultInitBinary, "--version").Output(); err == nil {
69
-		ver, err := parseInitVersion(string(rv))
70
-
71
-		if err != nil {
72
-			logrus.Warnf("failed to retrieve %s version: %s", defaultInitBinary, err)
73
+		if _, commit, err := parseInitVersion(string(rv)); err != nil {
74
+			logrus.Warnf("failed to parse %s version: %s", defaultInitBinary, err)
75
+			v.InitCommit.ID = "N/A"
76
+		} else {
77
+			v.InitCommit.ID = commit
78
+			v.InitCommit.Expected = dockerversion.InitCommitID[0:len(commit)]
73 79
 		}
74
-		v.InitCommit = ver
75 80
 	} else {
76 81
 		logrus.Warnf("failed to retrieve %s version: %s", defaultInitBinary, err)
77 82
 		v.InitCommit.ID = "N/A"
... ...
@@ -115,6 +115,53 @@ func (daemon *Daemon) fillPlatformInfo(v *types.Info, sysInfo *sysinfo.SysInfo)
115 115
 	}
116 116
 }
117 117
 
118
+func (daemon *Daemon) fillPlatformVersion(v *types.Version) {
119
+	if rv, err := daemon.containerd.Version(context.Background()); err == nil {
120
+		v.Components = append(v.Components, types.ComponentVersion{
121
+			Name:    "containerd",
122
+			Version: rv.Version,
123
+			Details: map[string]string{
124
+				"GitCommit": rv.Revision,
125
+			},
126
+		})
127
+	}
128
+
129
+	defaultRuntime := daemon.configStore.GetDefaultRuntimeName()
130
+	defaultRuntimeBinary := daemon.configStore.GetRuntime(defaultRuntime).Path
131
+	if rv, err := exec.Command(defaultRuntimeBinary, "--version").Output(); err == nil {
132
+		if ver, commit, err := parseRuncVersion(string(rv)); err != nil {
133
+			logrus.Warnf("failed to parse %s version: %v", defaultRuntimeBinary, err)
134
+		} else {
135
+			v.Components = append(v.Components, types.ComponentVersion{
136
+				Name:    defaultRuntime,
137
+				Version: ver,
138
+				Details: map[string]string{
139
+					"GitCommit": commit,
140
+				},
141
+			})
142
+		}
143
+	} else {
144
+		logrus.Warnf("failed to retrieve %s version: %v", defaultRuntimeBinary, err)
145
+	}
146
+
147
+	defaultInitBinary := daemon.configStore.GetInitPath()
148
+	if rv, err := exec.Command(defaultInitBinary, "--version").Output(); err == nil {
149
+		if ver, commit, err := parseInitVersion(string(rv)); err != nil {
150
+			logrus.Warnf("failed to parse %s version: %s", defaultInitBinary, err)
151
+		} else {
152
+			v.Components = append(v.Components, types.ComponentVersion{
153
+				Name:    filepath.Base(defaultInitBinary),
154
+				Version: ver,
155
+				Details: map[string]string{
156
+					"GitCommit": commit,
157
+				},
158
+			})
159
+		}
160
+	} else {
161
+		logrus.Warnf("failed to retrieve %s version: %s", defaultInitBinary, err)
162
+	}
163
+}
164
+
118 165
 func fillDriverWarnings(v *types.Info) {
119 166
 	for _, pair := range v.DriverStatus {
120 167
 		if pair[0] == "Data loop file" {
... ...
@@ -149,24 +196,52 @@ func getBackingFs(v *types.Info) string {
149 149
 	return ""
150 150
 }
151 151
 
152
-// parseInitVersion parses a Tini version string, and extracts the version.
153
-func parseInitVersion(v string) (types.Commit, error) {
154
-	version := types.Commit{ID: "", Expected: dockerversion.InitCommitID}
152
+// parseInitVersion parses a Tini version string, and extracts the "version"
153
+// and "git commit" from the output.
154
+//
155
+// Output example from `docker-init --version`:
156
+//
157
+//     tini version 0.18.0 - git.fec3683
158
+func parseInitVersion(v string) (version string, commit string, err error) {
155 159
 	parts := strings.Split(strings.TrimSpace(v), " - ")
156 160
 
157 161
 	if len(parts) >= 2 {
158 162
 		gitParts := strings.Split(parts[1], ".")
159 163
 		if len(gitParts) == 2 && gitParts[0] == "git" {
160
-			version.ID = gitParts[1]
161
-			version.Expected = dockerversion.InitCommitID[0:len(version.ID)]
164
+			commit = gitParts[1]
162 165
 		}
163 166
 	}
164
-	if version.ID == "" && strings.HasPrefix(parts[0], "tini version ") {
165
-		version.ID = "v" + strings.TrimPrefix(parts[0], "tini version ")
167
+	if strings.HasPrefix(parts[0], "tini version ") {
168
+		version = strings.TrimPrefix(parts[0], "tini version ")
169
+	}
170
+	if version == "" && commit == "" {
171
+		err = errors.Errorf("unknown output format: %s", v)
172
+	}
173
+	return version, commit, err
174
+}
175
+
176
+// parseRuncVersion parses the output of `runc --version` and extracts the
177
+// "version" and "git commit" from the output.
178
+//
179
+// Output example from `runc --version`:
180
+//
181
+//   runc version 1.0.0-rc5+dev
182
+//   commit: 69663f0bd4b60df09991c08812a60108003fa340
183
+//   spec: 1.0.0
184
+func parseRuncVersion(v string) (version string, commit string, err error) {
185
+	lines := strings.Split(strings.TrimSpace(v), "\n")
186
+	for _, line := range lines {
187
+		if strings.HasPrefix(line, "runc version") {
188
+			version = strings.TrimSpace(strings.TrimPrefix(line, "runc version"))
189
+			continue
190
+		}
191
+		if strings.HasPrefix(line, "commit:") {
192
+			commit = strings.TrimSpace(strings.TrimPrefix(line, "commit:"))
193
+			continue
194
+		}
166 195
 	}
167
-	if version.ID == "" {
168
-		version.ID = "N/A"
169
-		return version, errors.Errorf("unknown output format: %s", v)
196
+	if version == "" && commit == "" {
197
+		err = errors.Errorf("unknown output format: %s", v)
170 198
 	}
171
-	return version, nil
199
+	return version, commit, err
172 200
 }
... ...
@@ -5,49 +5,99 @@ package daemon // import "github.com/docker/docker/daemon"
5 5
 import (
6 6
 	"testing"
7 7
 
8
-	"github.com/docker/docker/api/types"
9
-	"github.com/docker/docker/dockerversion"
10 8
 	"gotest.tools/assert"
11 9
 	is "gotest.tools/assert/cmp"
12 10
 )
13 11
 
14 12
 func TestParseInitVersion(t *testing.T) {
15 13
 	tests := []struct {
14
+		output  string
16 15
 		version string
17
-		result  types.Commit
16
+		commit  string
18 17
 		invalid bool
19 18
 	}{
20 19
 		{
21
-			version: "tini version 0.13.0 - git.949e6fa",
22
-			result:  types.Commit{ID: "949e6fa", Expected: dockerversion.InitCommitID[0:7]},
20
+			output:  "tini version 0.13.0 - git.949e6fa",
21
+			version: "0.13.0",
22
+			commit:  "949e6fa",
23 23
 		}, {
24
-			version: "tini version 0.13.0\n",
25
-			result:  types.Commit{ID: "v0.13.0", Expected: dockerversion.InitCommitID},
24
+			output:  "tini version 0.13.0\n",
25
+			version: "0.13.0",
26 26
 		}, {
27
-			version: "tini version 0.13.2",
28
-			result:  types.Commit{ID: "v0.13.2", Expected: dockerversion.InitCommitID},
27
+			output:  "tini version 0.13.2",
28
+			version: "0.13.2",
29 29
 		}, {
30
-			version: "tini version0.13.2",
31
-			result:  types.Commit{ID: "N/A", Expected: dockerversion.InitCommitID},
30
+			output:  "tini version0.13.2",
32 31
 			invalid: true,
33 32
 		}, {
34
-			version: "",
35
-			result:  types.Commit{ID: "N/A", Expected: dockerversion.InitCommitID},
33
+			output:  "",
36 34
 			invalid: true,
37 35
 		}, {
38
-			version: "hello world",
39
-			result:  types.Commit{ID: "N/A", Expected: dockerversion.InitCommitID},
36
+			output:  "hello world",
40 37
 			invalid: true,
41 38
 		},
42 39
 	}
43 40
 
44 41
 	for _, test := range tests {
45
-		ver, err := parseInitVersion(string(test.version))
42
+		version, commit, err := parseInitVersion(string(test.output))
46 43
 		if test.invalid {
47 44
 			assert.Check(t, is.ErrorContains(err, ""))
48 45
 		} else {
49 46
 			assert.Check(t, err)
50 47
 		}
51
-		assert.Check(t, is.DeepEqual(test.result, ver))
48
+		assert.Equal(t, test.version, version)
49
+		assert.Equal(t, test.commit, commit)
50
+	}
51
+}
52
+
53
+func TestParseRuncVersion(t *testing.T) {
54
+	tests := []struct {
55
+		output  string
56
+		version string
57
+		commit  string
58
+		invalid bool
59
+	}{
60
+		{
61
+			output: `
62
+runc version 1.0.0-rc5+dev
63
+commit: 69663f0bd4b60df09991c08812a60108003fa340
64
+spec: 1.0.0
65
+`,
66
+			version: "1.0.0-rc5+dev",
67
+			commit:  "69663f0bd4b60df09991c08812a60108003fa340",
68
+		},
69
+		{
70
+			output: `
71
+runc version 1.0.0-rc5+dev
72
+spec: 1.0.0
73
+`,
74
+			version: "1.0.0-rc5+dev",
75
+		},
76
+		{
77
+			output: `
78
+commit: 69663f0bd4b60df09991c08812a60108003fa340
79
+spec: 1.0.0
80
+`,
81
+			commit: "69663f0bd4b60df09991c08812a60108003fa340",
82
+		},
83
+		{
84
+			output:  "",
85
+			invalid: true,
86
+		},
87
+		{
88
+			output:  "hello world",
89
+			invalid: true,
90
+		},
91
+	}
92
+
93
+	for _, test := range tests {
94
+		version, commit, err := parseRuncVersion(string(test.output))
95
+		if test.invalid {
96
+			assert.Check(t, is.ErrorContains(err, ""))
97
+		} else {
98
+			assert.Check(t, err)
99
+		}
100
+		assert.Equal(t, test.version, version)
101
+		assert.Equal(t, test.commit, commit)
52 102
 	}
53 103
 }
... ...
@@ -9,5 +9,7 @@ import (
9 9
 func (daemon *Daemon) fillPlatformInfo(v *types.Info, sysInfo *sysinfo.SysInfo) {
10 10
 }
11 11
 
12
+func (daemon *Daemon) fillPlatformVersion(v *types.Version) {}
13
+
12 14
 func fillDriverWarnings(v *types.Info) {
13 15
 }