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