Browse Source

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 6 years ago
parent
commit
2137b8ccf2
4 changed files with 171 additions and 43 deletions
  1. 1 0
      daemon/info.go
  2. 101 26
      daemon/info_unix.go
  3. 67 17
      daemon/info_unix_test.go
  4. 2 0
      daemon/info_windows.go

+ 1 - 0
daemon/info.go

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

+ 101 - 26
daemon/info_unix.go

@@ -6,6 +6,7 @@ import (
 	"context"
 	"context"
 	"fmt"
 	"fmt"
 	"os/exec"
 	"os/exec"
+	"path/filepath"
 	"strings"
 	"strings"
 
 
 	"github.com/docker/docker/api/types"
 	"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
 	defaultRuntimeBinary := daemon.configStore.GetRuntime(v.DefaultRuntime).Path
 	if rv, err := exec.Command(defaultRuntimeBinary, "--version").Output(); err == nil {
 	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"
 			v.RuncCommit.ID = "N/A"
+		} else {
+			v.RuncCommit.ID = commit
 		}
 		}
 	} else {
 	} else {
 		logrus.Warnf("failed to retrieve %s version: %v", defaultRuntimeBinary, err)
 		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
 	// value as "ID" to prevent clients from reporting a version-mismatch
 	v.ContainerdCommit.Expected = v.ContainerdCommit.ID
 	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()
 	defaultInitBinary := daemon.configStore.GetInitPath()
 	if rv, err := exec.Command(defaultInitBinary, "--version").Output(); err == nil {
 	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 {
 	} else {
 		logrus.Warnf("failed to retrieve %s version: %s", defaultInitBinary, err)
 		logrus.Warnf("failed to retrieve %s version: %s", defaultInitBinary, err)
 		v.InitCommit.ID = "N/A"
 		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) {
 func fillDriverWarnings(v *types.Info) {
 	for _, pair := range v.DriverStatus {
 	for _, pair := range v.DriverStatus {
 		if pair[0] == "Data loop file" {
 		if pair[0] == "Data loop file" {
@@ -149,24 +196,52 @@ func getBackingFs(v *types.Info) string {
 	return ""
 	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), " - ")
 	parts := strings.Split(strings.TrimSpace(v), " - ")
 
 
 	if len(parts) >= 2 {
 	if len(parts) >= 2 {
 		gitParts := strings.Split(parts[1], ".")
 		gitParts := strings.Split(parts[1], ".")
 		if len(gitParts) == 2 && gitParts[0] == "git" {
 		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 == "" && commit == "" {
+		err = errors.Errorf("unknown output format: %s", v)
+	}
+	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.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
 }
 }

+ 67 - 17
daemon/info_unix_test.go

@@ -5,49 +5,99 @@ package daemon // import "github.com/docker/docker/daemon"
 import (
 import (
 	"testing"
 	"testing"
 
 
-	"github.com/docker/docker/api/types"
-	"github.com/docker/docker/dockerversion"
 	"gotest.tools/assert"
 	"gotest.tools/assert"
 	is "gotest.tools/assert/cmp"
 	is "gotest.tools/assert/cmp"
 )
 )
 
 
 func TestParseInitVersion(t *testing.T) {
 func TestParseInitVersion(t *testing.T) {
 	tests := []struct {
 	tests := []struct {
+		output  string
 		version string
 		version string
-		result  types.Commit
+		commit  string
 		invalid bool
 		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,
 			invalid: true,
 		}, {
 		}, {
-			version: "",
-			result:  types.Commit{ID: "N/A", Expected: dockerversion.InitCommitID},
+			output:  "",
 			invalid: true,
 			invalid: true,
 		}, {
 		}, {
-			version: "hello world",
-			result:  types.Commit{ID: "N/A", Expected: dockerversion.InitCommitID},
+			output:  "hello world",
 			invalid: true,
 			invalid: true,
 		},
 		},
 	}
 	}
 
 
 	for _, test := range tests {
 	for _, test := range tests {
-		ver, err := parseInitVersion(string(test.version))
+		version, commit, err := parseInitVersion(string(test.output))
 		if test.invalid {
 		if test.invalid {
 			assert.Check(t, is.ErrorContains(err, ""))
 			assert.Check(t, is.ErrorContains(err, ""))
 		} else {
 		} else {
 			assert.Check(t, err)
 			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)
 	}
 	}
 }
 }

+ 2 - 0
daemon/info_windows.go

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