From 11d93083d0654c866c10c50c5b2fef3670713bc0 Mon Sep 17 00:00:00 2001 From: Chandarith Chea <45872539+chandarith-chea@users.noreply.github.com> Date: Sat, 14 Nov 2026 09:55:01 +0700 Subject: [PATCH 1/2] docker version reports the engine's commit and build time /version said GitCommit "preview" with no build time, so docker version could not tell engine builds apart. go build already records the commit (vcs.revision, vcs.time, vcs.modified); dockivad now reports it: the first 12 characters of the commit, "-dirty" for uncommitted changes, and the commit's time as BuildTime. It also returns an "Engine" component with those details, as dockerd does, so docker version's Engine section shows the Go version, commit and build time instead of blanks. A build outside a git checkout reports "unknown". --- dockivad/api.go | 39 ++++++++++++++++++++++++++++++++++++++- dockivad/engine_test.go | 15 ++++++++++++++- 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/dockivad/api.go b/dockivad/api.go index 41626fb..0157b93 100644 --- a/dockivad/api.go +++ b/dockivad/api.go @@ -13,6 +13,7 @@ import ( "path/filepath" "regexp" "runtime" + "runtime/debug" "slices" "sort" "strconv" @@ -804,7 +805,13 @@ func (b *engine) api(authorized func(*http.Request) bool) http.Handler { _, _ = w.Write([]byte("OK")) return case path == "/version" && method == "GET": - send(w, 200, obj{"Version": "0.18.0", "ApiVersion": "1.45", "MinAPIVersion": "1.24", "Os": "linux", "Arch": "arm64", "GitCommit": "preview", "GoVersion": runtime.Version(), "KernelVersion": "dockiva-engine", "Experimental": true, "Components": []any{obj{"Name": "dockivad", "Version": "0.18.0", "Details": obj{"ControlPlane": "Go", "PythonRuntime": "false"}}}}) + commit, built := buildStamp() + engine := obj{"ApiVersion": "1.45", "MinAPIVersion": "1.24", "Os": "linux", "Arch": "arm64", "GitCommit": commit, "GoVersion": runtime.Version(), "KernelVersion": "dockiva-engine", "Experimental": "true", "BuildTime": ""} + if !built.IsZero() { + engine["BuildTime"] = built.UTC().Format(time.ANSIC) + } + send(w, 200, obj{"Version": "0.18.0", "ApiVersion": "1.45", "MinAPIVersion": "1.24", "Os": "linux", "Arch": "arm64", "GitCommit": commit, "GoVersion": runtime.Version(), "KernelVersion": "dockiva-engine", "Experimental": true, "BuildTime": stampTime(built), + "Components": []any{obj{"Name": "Engine", "Version": "0.18.0", "Details": engine}, obj{"Name": "dockivad", "Version": "0.18.0", "Details": obj{"ControlPlane": "Go", "PythonRuntime": "false"}}}}) return case path == "/info" && method == "GET": send(w, 200, obj{"ID": "dockiva-engine", "Name": "dockiva-engine", "ServerVersion": "0.18.0", "OSType": "linux", "Architecture": "aarch64", "OperatingSystem": "Dockiva Engine on " + runtime.GOOS, "Driver": "private-copy", "Containers": len(b.names()), "NCPU": min(runtime.NumCPU(), 64), "MemTotal": 0, "SecurityOptions": []any{}, "Warnings": []string{"Not a production isolation boundary"}}) @@ -1260,3 +1267,33 @@ func inspectNetwork(c obj) string { } var hostnamePattern = regexp.MustCompile(`^[a-zA-Z0-9]([a-zA-Z0-9.-]{0,62})$`) + +// buildStamp is the engine commit `go build` recorded (vcs.revision, 12 characters, +// "-dirty" for uncommitted changes) and that commit's time, for docker version. +// A build outside a git checkout reports "unknown" and no time. +func buildStamp() (string, time.Time) { + commit, when, dirty := "unknown", time.Time{}, false + if info, ok := debug.ReadBuildInfo(); ok { + for _, s := range info.Settings { + switch s.Key { + case "vcs.revision": + commit = s.Value[:min(12, len(s.Value))] + case "vcs.time": + when, _ = time.Parse(time.RFC3339, s.Value) + case "vcs.modified": + dirty = s.Value == "true" + } + } + } + if dirty && commit != "unknown" { + commit += "-dirty" + } + return commit, when +} + +func stampTime(t time.Time) string { + if t.IsZero() { + return "" + } + return t.UTC().Format(time.RFC3339) +} diff --git a/dockivad/engine_test.go b/dockivad/engine_test.go index 6268d38..0882eb4 100644 --- a/dockivad/engine_test.go +++ b/dockivad/engine_test.go @@ -4,6 +4,7 @@ import ( "archive/tar" "bytes" "encoding/binary" + "encoding/json" "io" "net/http" "net/http/httptest" @@ -168,9 +169,21 @@ func TestAPIContractAndRejectedOptions(t *testing.T) { if request(t, h, "GET", "/_ping", "", "").Code != 401 { t.Fatal("missing auth accepted") } - if request(t, h, "GET", "/version", "", "secret").Code != 200 { + v := request(t, h, "GET", "/version", "", "secret") + var version struct { + GitCommit string + Components []struct { + Name string + Details map[string]string + } + } + if v.Code != 200 || json.Unmarshal(v.Body.Bytes(), &version) != nil { t.Fatal("version") } + commit, _ := buildStamp() // test binaries carry no vcs stamp: "unknown" + if version.GitCommit != commit || len(version.Components) == 0 || version.Components[0].Name != "Engine" || version.Components[0].Details["GitCommit"] != commit { + t.Fatalf("version commit: %s", v.Body.String()) + } if request(t, h, "GET", "/v1.99/info", "", "secret").Code != 400 { t.Fatal("version range") } From 731ce0056d4f7b7c9dd6c8ebbbd78b859d40987b Mon Sep 17 00:00:00 2001 From: Chandarith Chea <45872539+chandarith-chea@users.noreply.github.com> Date: Sat, 14 Nov 2026 09:55:50 +0700 Subject: [PATCH 2/2] docker version: keep dockivad as the first component verify_dockivad.py identifies the Go control plane by Components[0]. --- dockivad/api.go | 2 +- dockivad/engine_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dockivad/api.go b/dockivad/api.go index 0157b93..3ae0b00 100644 --- a/dockivad/api.go +++ b/dockivad/api.go @@ -811,7 +811,7 @@ func (b *engine) api(authorized func(*http.Request) bool) http.Handler { engine["BuildTime"] = built.UTC().Format(time.ANSIC) } send(w, 200, obj{"Version": "0.18.0", "ApiVersion": "1.45", "MinAPIVersion": "1.24", "Os": "linux", "Arch": "arm64", "GitCommit": commit, "GoVersion": runtime.Version(), "KernelVersion": "dockiva-engine", "Experimental": true, "BuildTime": stampTime(built), - "Components": []any{obj{"Name": "Engine", "Version": "0.18.0", "Details": engine}, obj{"Name": "dockivad", "Version": "0.18.0", "Details": obj{"ControlPlane": "Go", "PythonRuntime": "false"}}}}) + "Components": []any{obj{"Name": "dockivad", "Version": "0.18.0", "Details": obj{"ControlPlane": "Go", "PythonRuntime": "false"}}, obj{"Name": "Engine", "Version": "0.18.0", "Details": engine}}}) return case path == "/info" && method == "GET": send(w, 200, obj{"ID": "dockiva-engine", "Name": "dockiva-engine", "ServerVersion": "0.18.0", "OSType": "linux", "Architecture": "aarch64", "OperatingSystem": "Dockiva Engine on " + runtime.GOOS, "Driver": "private-copy", "Containers": len(b.names()), "NCPU": min(runtime.NumCPU(), 64), "MemTotal": 0, "SecurityOptions": []any{}, "Warnings": []string{"Not a production isolation boundary"}}) diff --git a/dockivad/engine_test.go b/dockivad/engine_test.go index 0882eb4..1794a22 100644 --- a/dockivad/engine_test.go +++ b/dockivad/engine_test.go @@ -181,7 +181,7 @@ func TestAPIContractAndRejectedOptions(t *testing.T) { t.Fatal("version") } commit, _ := buildStamp() // test binaries carry no vcs stamp: "unknown" - if version.GitCommit != commit || len(version.Components) == 0 || version.Components[0].Name != "Engine" || version.Components[0].Details["GitCommit"] != commit { + if version.GitCommit != commit || len(version.Components) != 2 || version.Components[0].Name != "dockivad" || version.Components[1].Name != "Engine" || version.Components[1].Details["GitCommit"] != commit { t.Fatalf("version commit: %s", v.Body.String()) } if request(t, h, "GET", "/v1.99/info", "", "secret").Code != 400 {