From 2137b8ccf27c16f230a50f9019ed0dbeee0840c1 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Fri, 5 Oct 2018 12:30:10 +0200 Subject: [PATCH] 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 --- daemon/info.go | 1 + daemon/info_unix.go | 127 +++++++++++++++++++++++++++++++-------- daemon/info_unix_test.go | 84 ++++++++++++++++++++------ daemon/info_windows.go | 2 + 4 files changed, 171 insertions(+), 43 deletions(-) diff --git a/daemon/info.go b/daemon/info.go index 5332ce1038..7b9a8588a2 100644 --- a/daemon/info.go +++ b/daemon/info.go @@ -118,6 +118,7 @@ func (daemon *Daemon) SystemVersion() types.Version { v.Platform.Name = dockerversion.PlatformName + daemon.fillPlatformVersion(&v) return v } diff --git a/daemon/info_unix.go b/daemon/info_unix.go index 6efdc16951..0bc255e41f 100644 --- a/daemon/info_unix.go +++ b/daemon/info_unix.go @@ -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 } diff --git a/daemon/info_unix_test.go b/daemon/info_unix_test.go index a5a4e06f98..7b61960262 100644 --- a/daemon/info_unix_test.go +++ b/daemon/info_unix_test.go @@ -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) } } diff --git a/daemon/info_windows.go b/daemon/info_windows.go index 2c1ff460c3..fe9b1e6732 100644 --- a/daemon/info_windows.go +++ b/daemon/info_windows.go @@ -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) { }