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>
This commit is contained in:
Sebastiaan van Stijn 2018-10-05 12:30:10 +02:00
parent beef00cb26
commit 2137b8ccf2
No known key found for this signature in database
GPG Key ID: 76698F39D527CE8C
4 changed files with 171 additions and 43 deletions

View File

@ -118,6 +118,7 @@ func (daemon *Daemon) SystemVersion() types.Version {
v.Platform.Name = dockerversion.PlatformName
daemon.fillPlatformVersion(&v)
return v
}

View File

@ -6,6 +6,7 @@ import (
"context"
"fmt"
"os/exec"
"path/filepath"
"strings"
"github.com/docker/docker/api/types"
@ -32,17 +33,11 @@ func (daemon *Daemon) fillPlatformInfo(v *types.Info, sysInfo *sysinfo.SysInfo)
defaultRuntimeBinary := daemon.configStore.GetRuntime(v.DefaultRuntime).Path
if rv, err := exec.Command(defaultRuntimeBinary, "--version").Output(); err == nil {
parts := strings.Split(strings.TrimSpace(string(rv)), "\n")
if len(parts) == 3 {
parts = strings.Split(parts[1], ": ")
if len(parts) == 2 {
v.RuncCommit.ID = strings.TrimSpace(parts[1])
}
}
if v.RuncCommit.ID == "" {
logrus.Warnf("failed to retrieve %s version: unknown output format: %s", defaultRuntimeBinary, string(rv))
if _, commit, err := parseRuncVersion(string(rv)); err != nil {
logrus.Warnf("failed to parse %s version: %v", defaultRuntimeBinary, err)
v.RuncCommit.ID = "N/A"
} else {
v.RuncCommit.ID = commit
}
} else {
logrus.Warnf("failed to retrieve %s version: %v", defaultRuntimeBinary, err)
@ -64,14 +59,19 @@ func (daemon *Daemon) fillPlatformInfo(v *types.Info, sysInfo *sysinfo.SysInfo)
// value as "ID" to prevent clients from reporting a version-mismatch
v.ContainerdCommit.Expected = v.ContainerdCommit.ID
// TODO is there still a need to check the expected version for tini?
// if not, we can change this, and just set "Expected" to v.InitCommit.ID
v.InitCommit.Expected = dockerversion.InitCommitID
defaultInitBinary := daemon.configStore.GetInitPath()
if rv, err := exec.Command(defaultInitBinary, "--version").Output(); err == nil {
ver, err := parseInitVersion(string(rv))
if err != nil {
logrus.Warnf("failed to retrieve %s version: %s", defaultInitBinary, err)
if _, commit, err := parseInitVersion(string(rv)); err != nil {
logrus.Warnf("failed to parse %s version: %s", defaultInitBinary, err)
v.InitCommit.ID = "N/A"
} else {
v.InitCommit.ID = commit
v.InitCommit.Expected = dockerversion.InitCommitID[0:len(commit)]
}
v.InitCommit = ver
} else {
logrus.Warnf("failed to retrieve %s version: %s", defaultInitBinary, err)
v.InitCommit.ID = "N/A"
@ -115,6 +115,53 @@ func (daemon *Daemon) fillPlatformInfo(v *types.Info, sysInfo *sysinfo.SysInfo)
}
}
func (daemon *Daemon) fillPlatformVersion(v *types.Version) {
if rv, err := daemon.containerd.Version(context.Background()); err == nil {
v.Components = append(v.Components, types.ComponentVersion{
Name: "containerd",
Version: rv.Version,
Details: map[string]string{
"GitCommit": rv.Revision,
},
})
}
defaultRuntime := daemon.configStore.GetDefaultRuntimeName()
defaultRuntimeBinary := daemon.configStore.GetRuntime(defaultRuntime).Path
if rv, err := exec.Command(defaultRuntimeBinary, "--version").Output(); err == nil {
if ver, commit, err := parseRuncVersion(string(rv)); err != nil {
logrus.Warnf("failed to parse %s version: %v", defaultRuntimeBinary, err)
} else {
v.Components = append(v.Components, types.ComponentVersion{
Name: defaultRuntime,
Version: ver,
Details: map[string]string{
"GitCommit": commit,
},
})
}
} else {
logrus.Warnf("failed to retrieve %s version: %v", defaultRuntimeBinary, err)
}
defaultInitBinary := daemon.configStore.GetInitPath()
if rv, err := exec.Command(defaultInitBinary, "--version").Output(); err == nil {
if ver, commit, err := parseInitVersion(string(rv)); err != nil {
logrus.Warnf("failed to parse %s version: %s", defaultInitBinary, err)
} else {
v.Components = append(v.Components, types.ComponentVersion{
Name: filepath.Base(defaultInitBinary),
Version: ver,
Details: map[string]string{
"GitCommit": commit,
},
})
}
} else {
logrus.Warnf("failed to retrieve %s version: %s", defaultInitBinary, err)
}
}
func fillDriverWarnings(v *types.Info) {
for _, pair := range v.DriverStatus {
if pair[0] == "Data loop file" {
@ -149,24 +196,52 @@ func getBackingFs(v *types.Info) string {
return ""
}
// parseInitVersion parses a Tini version string, and extracts the version.
func parseInitVersion(v string) (types.Commit, error) {
version := types.Commit{ID: "", Expected: dockerversion.InitCommitID}
// parseInitVersion parses a Tini version string, and extracts the "version"
// and "git commit" from the output.
//
// Output example from `docker-init --version`:
//
// tini version 0.18.0 - git.fec3683
func parseInitVersion(v string) (version string, commit string, err error) {
parts := strings.Split(strings.TrimSpace(v), " - ")
if len(parts) >= 2 {
gitParts := strings.Split(parts[1], ".")
if len(gitParts) == 2 && gitParts[0] == "git" {
version.ID = gitParts[1]
version.Expected = dockerversion.InitCommitID[0:len(version.ID)]
commit = gitParts[1]
}
}
if version.ID == "" && strings.HasPrefix(parts[0], "tini version ") {
version.ID = "v" + strings.TrimPrefix(parts[0], "tini version ")
if strings.HasPrefix(parts[0], "tini version ") {
version = strings.TrimPrefix(parts[0], "tini version ")
}
if version.ID == "" {
version.ID = "N/A"
return version, errors.Errorf("unknown output format: %s", v)
if version == "" && commit == "" {
err = errors.Errorf("unknown output format: %s", v)
}
return version, nil
return version, commit, err
}
// parseRuncVersion parses the output of `runc --version` and extracts the
// "version" and "git commit" from the output.
//
// Output example from `runc --version`:
//
// runc version 1.0.0-rc5+dev
// commit: 69663f0bd4b60df09991c08812a60108003fa340
// spec: 1.0.0
func parseRuncVersion(v string) (version string, commit string, err error) {
lines := strings.Split(strings.TrimSpace(v), "\n")
for _, line := range lines {
if strings.HasPrefix(line, "runc version") {
version = strings.TrimSpace(strings.TrimPrefix(line, "runc version"))
continue
}
if strings.HasPrefix(line, "commit:") {
commit = strings.TrimSpace(strings.TrimPrefix(line, "commit:"))
continue
}
}
if version == "" && commit == "" {
err = errors.Errorf("unknown output format: %s", v)
}
return version, commit, err
}

View File

@ -5,49 +5,99 @@ package daemon // import "github.com/docker/docker/daemon"
import (
"testing"
"github.com/docker/docker/api/types"
"github.com/docker/docker/dockerversion"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
)
func TestParseInitVersion(t *testing.T) {
tests := []struct {
output string
version string
result types.Commit
commit string
invalid bool
}{
{
version: "tini version 0.13.0 - git.949e6fa",
result: types.Commit{ID: "949e6fa", Expected: dockerversion.InitCommitID[0:7]},
output: "tini version 0.13.0 - git.949e6fa",
version: "0.13.0",
commit: "949e6fa",
}, {
version: "tini version 0.13.0\n",
result: types.Commit{ID: "v0.13.0", Expected: dockerversion.InitCommitID},
output: "tini version 0.13.0\n",
version: "0.13.0",
}, {
version: "tini version 0.13.2",
result: types.Commit{ID: "v0.13.2", Expected: dockerversion.InitCommitID},
output: "tini version 0.13.2",
version: "0.13.2",
}, {
version: "tini version0.13.2",
result: types.Commit{ID: "N/A", Expected: dockerversion.InitCommitID},
output: "tini version0.13.2",
invalid: true,
}, {
version: "",
result: types.Commit{ID: "N/A", Expected: dockerversion.InitCommitID},
output: "",
invalid: true,
}, {
version: "hello world",
result: types.Commit{ID: "N/A", Expected: dockerversion.InitCommitID},
output: "hello world",
invalid: true,
},
}
for _, test := range tests {
ver, err := parseInitVersion(string(test.version))
version, commit, err := parseInitVersion(string(test.output))
if test.invalid {
assert.Check(t, is.ErrorContains(err, ""))
} else {
assert.Check(t, err)
}
assert.Check(t, is.DeepEqual(test.result, ver))
assert.Equal(t, test.version, version)
assert.Equal(t, test.commit, commit)
}
}
func TestParseRuncVersion(t *testing.T) {
tests := []struct {
output string
version string
commit string
invalid bool
}{
{
output: `
runc version 1.0.0-rc5+dev
commit: 69663f0bd4b60df09991c08812a60108003fa340
spec: 1.0.0
`,
version: "1.0.0-rc5+dev",
commit: "69663f0bd4b60df09991c08812a60108003fa340",
},
{
output: `
runc version 1.0.0-rc5+dev
spec: 1.0.0
`,
version: "1.0.0-rc5+dev",
},
{
output: `
commit: 69663f0bd4b60df09991c08812a60108003fa340
spec: 1.0.0
`,
commit: "69663f0bd4b60df09991c08812a60108003fa340",
},
{
output: "",
invalid: true,
},
{
output: "hello world",
invalid: true,
},
}
for _, test := range tests {
version, commit, err := parseRuncVersion(string(test.output))
if test.invalid {
assert.Check(t, is.ErrorContains(err, ""))
} else {
assert.Check(t, err)
}
assert.Equal(t, test.version, version)
assert.Equal(t, test.commit, commit)
}
}

View File

@ -9,5 +9,7 @@ import (
func (daemon *Daemon) fillPlatformInfo(v *types.Info, sysInfo *sysinfo.SysInfo) {
}
func (daemon *Daemon) fillPlatformVersion(v *types.Version) {}
func fillDriverWarnings(v *types.Info) {
}