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>
... | ... |
@@ -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 |
} |