detached 5272434

test(container): table test for Podman detection in Info

isPodman reads the version components ("Podman Engine"); a /version that
fails, is malformed or names no Podman means Docker. Driven through the real
moby client against a fake daemon.
commit
52724348c044a2bbf9af178c6996a5f3950c9a66
author
brice · · 2026-10-12 22:15 UTC
parent
25328fc
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +87 -5
M internal/sandbox/container/engine.go +13 -5
@@ -14,6 +14,7 @@ import (
1414 "github.com/moby/moby/api/types/container"
1515 "github.com/moby/moby/api/types/mount"
1616 "github.com/moby/moby/api/types/network"
17+ "github.com/moby/moby/api/types/system"
1718 "github.com/moby/moby/client"
1819 )
1920
@@ -114,15 +115,22 @@ func (e *dockerEngine) Info(ctx context.Context) (daemonInfo, error) {
114115 }
115116 // Best effort: a daemon that cannot say what it is is treated as Docker.
116117 if v, err := e.cli.ServerVersion(ctx, client.ServerVersionOptions{}); err == nil {
117- for _, c := range v.Components {
118- if strings.HasPrefix(c.Name, "Podman") {
119- di.Podman = true
120- }
121- }
118+ di.Podman = isPodman(v.Components)
122119 }
123120 return di, nil
124121 }
125122
123+// isPodman reports whether the daemon's version components name Podman (its
124+// compatibility service lists "Podman Engine"; Docker lists "Engine").
125+func isPodman(components []system.ComponentVersion) bool {
126+ for _, c := range components {
127+ if strings.HasPrefix(c.Name, "Podman") {
128+ return true
129+ }
130+ }
131+ return false
132+}
133+
126134 func (e *dockerEngine) EnsureImage(ctx context.Context, ref string) error {
127135 if _, err := e.cli.ImageInspect(ctx, ref); err == nil {
128136 return nil
@@ -14,6 +14,7 @@ import (
14 "github.com/moby/moby/api/types/container"14 "github.com/moby/moby/api/types/container"
15 "github.com/moby/moby/api/types/mount"15 "github.com/moby/moby/api/types/mount"
16 "github.com/moby/moby/api/types/network"16 "github.com/moby/moby/api/types/network"
17+ "github.com/moby/moby/api/types/system"
17 "github.com/moby/moby/client"18 "github.com/moby/moby/client"
18 )19 )
19 20
@@ -114,15 +115,22 @@ func (e *dockerEngine) Info(ctx context.Context) (daemonInfo, error) {
114 }115 }
115 // Best effort: a daemon that cannot say what it is is treated as Docker.116 // Best effort: a daemon that cannot say what it is is treated as Docker.
116 if v, err := e.cli.ServerVersion(ctx, client.ServerVersionOptions{}); err == nil {117 if v, err := e.cli.ServerVersion(ctx, client.ServerVersionOptions{}); err == nil {
117- for _, c := range v.Components {118+ di.Podman = isPodman(v.Components)
118- if strings.HasPrefix(c.Name, "Podman") {
119- di.Podman = true
120- }
121- }
122 }119 }
123 return di, nil120 return di, nil
124 }121 }
125 122
123+// isPodman reports whether the daemon's version components name Podman (its
124+// compatibility service lists "Podman Engine"; Docker lists "Engine").
125+func isPodman(components []system.ComponentVersion) bool {
126+ for _, c := range components {
127+ if strings.HasPrefix(c.Name, "Podman") {
128+ return true
129+ }
130+ }
131+ return false
132+}
133+
126 func (e *dockerEngine) EnsureImage(ctx context.Context, ref string) error {134 func (e *dockerEngine) EnsureImage(ctx context.Context, ref string) error {
127 if _, err := e.cli.ImageInspect(ctx, ref); err == nil {135 if _, err := e.cli.ImageInspect(ctx, ref); err == nil {
128 return nil136 return nil
A internal/sandbox/container/engine_test.go +74 -0
@@ -0,0 +1,74 @@
1+// SPDX-License-Identifier: MIT
2+
3+package container
4+
5+import (
6+ "net/http"
7+ "net/http/httptest"
8+ "testing"
9+
10+ "github.com/moby/moby/client"
11+)
12+
13+// infoDaemon plays the two endpoints Info reads. versionBody "" makes /version
14+// fail with a 500.
15+func infoDaemon(t *testing.T, versionBody string) *dockerEngine {
16+ t.Helper()
17+ mux := http.NewServeMux()
18+ mux.HandleFunc("/info", func(w http.ResponseWriter, _ *http.Request) {
19+ w.Header().Set("Content-Type", "application/json")
20+ _, _ = w.Write([]byte(`{"OSType":"linux","SecurityOptions":["name=seccomp,profile=builtin"],"Runtimes":{"runc":{"path":"runc"}}}`))
21+ })
22+ mux.HandleFunc("/version", func(w http.ResponseWriter, _ *http.Request) {
23+ if versionBody == "" {
24+ http.Error(w, `{"message":"boom"}`, http.StatusInternalServerError)
25+ return
26+ }
27+ w.Header().Set("Content-Type", "application/json")
28+ _, _ = w.Write([]byte(versionBody))
29+ })
30+ srv := httptest.NewServer(http.StripPrefix("/v1.43", mux))
31+ t.Cleanup(srv.Close)
32+ cli, err := client.New(client.WithHost("tcp://"+srv.Listener.Addr().String()), client.WithAPIVersion("1.43"))
33+ if err != nil {
34+ t.Fatal(err)
35+ }
36+ t.Cleanup(func() { _ = cli.Close() })
37+ return &dockerEngine{cli: cli}
38+}
39+
40+// GO-004: the Podman flag comes from the version components' names; a daemon
41+// whose /version fails (or names no Podman) is treated as Docker.
42+func TestInfoDetectsPodman(t *testing.T) {
43+ for name, tc := range map[string]struct {
44+ version string
45+ podman bool
46+ }{
47+ "podman": {`{"Version":"6.1.3","Components":[{"Name":"Podman Engine","Version":"6.1.3"},{"Name":"Conmon","Version":"2.2.1"}]}`, true},
48+ "podman later": {`{"Components":[{"Name":"Conmon","Version":"2"},{"Name":"Podman Engine","Version":"6"}]}`, true},
49+ "docker": {`{"Version":"29.9.0","Components":[{"Name":"Engine","Version":"29.9.0"},{"Name":"containerd","Version":"2"}]}`, false},
50+ "no components": {`{"Version":"29.9.0"}`, false},
51+ "lower case": {`{"Components":[{"Name":"podman engine","Version":"1"}]}`, false}, // the name is "Podman Engine": exact prefix
52+ "version fails": {"", false},
53+ "malformed json": {`{not json`, false},
54+ } {
55+ t.Run(name, func(t *testing.T) {
56+ di, err := infoDaemon(t, tc.version).Info(t.Context())
57+ if err != nil {
58+ t.Fatalf("a /version failure must not fail Info: %v", err)
59+ }
60+ if di.Podman != tc.podman {
61+ t.Fatalf("Podman=%v want %v", di.Podman, tc.podman)
62+ }
63+ if di.OSType != "linux" || len(di.Runtimes) != 1 || di.Rootless {
64+ t.Fatalf("the rest of Info is unchanged: %+v", di)
65+ }
66+ })
67+ }
68+}
69+
70+func TestIsPodman(t *testing.T) {
71+ if isPodman(nil) {
72+ t.Fatal("no components is not podman")
73+ }
74+}
@@ -0,0 +1,74 @@
1+// SPDX-License-Identifier: MIT
2+
3+package container
4+
5+import (
6+ "net/http"
7+ "net/http/httptest"
8+ "testing"
9+
10+ "github.com/moby/moby/client"
11+)
12+
13+// infoDaemon plays the two endpoints Info reads. versionBody "" makes /version
14+// fail with a 500.
15+func infoDaemon(t *testing.T, versionBody string) *dockerEngine {
16+ t.Helper()
17+ mux := http.NewServeMux()
18+ mux.HandleFunc("/info", func(w http.ResponseWriter, _ *http.Request) {
19+ w.Header().Set("Content-Type", "application/json")
20+ _, _ = w.Write([]byte(`{"OSType":"linux","SecurityOptions":["name=seccomp,profile=builtin"],"Runtimes":{"runc":{"path":"runc"}}}`))
21+ })
22+ mux.HandleFunc("/version", func(w http.ResponseWriter, _ *http.Request) {
23+ if versionBody == "" {
24+ http.Error(w, `{"message":"boom"}`, http.StatusInternalServerError)
25+ return
26+ }
27+ w.Header().Set("Content-Type", "application/json")
28+ _, _ = w.Write([]byte(versionBody))
29+ })
30+ srv := httptest.NewServer(http.StripPrefix("/v1.43", mux))
31+ t.Cleanup(srv.Close)
32+ cli, err := client.New(client.WithHost("tcp://"+srv.Listener.Addr().String()), client.WithAPIVersion("1.43"))
33+ if err != nil {
34+ t.Fatal(err)
35+ }
36+ t.Cleanup(func() { _ = cli.Close() })
37+ return &dockerEngine{cli: cli}
38+}
39+
40+// GO-004: the Podman flag comes from the version components' names; a daemon
41+// whose /version fails (or names no Podman) is treated as Docker.
42+func TestInfoDetectsPodman(t *testing.T) {
43+ for name, tc := range map[string]struct {
44+ version string
45+ podman bool
46+ }{
47+ "podman": {`{"Version":"6.1.3","Components":[{"Name":"Podman Engine","Version":"6.1.3"},{"Name":"Conmon","Version":"2.2.1"}]}`, true},
48+ "podman later": {`{"Components":[{"Name":"Conmon","Version":"2"},{"Name":"Podman Engine","Version":"6"}]}`, true},
49+ "docker": {`{"Version":"29.9.0","Components":[{"Name":"Engine","Version":"29.9.0"},{"Name":"containerd","Version":"2"}]}`, false},
50+ "no components": {`{"Version":"29.9.0"}`, false},
51+ "lower case": {`{"Components":[{"Name":"podman engine","Version":"1"}]}`, false}, // the name is "Podman Engine": exact prefix
52+ "version fails": {"", false},
53+ "malformed json": {`{not json`, false},
54+ } {
55+ t.Run(name, func(t *testing.T) {
56+ di, err := infoDaemon(t, tc.version).Info(t.Context())
57+ if err != nil {
58+ t.Fatalf("a /version failure must not fail Info: %v", err)
59+ }
60+ if di.Podman != tc.podman {
61+ t.Fatalf("Podman=%v want %v", di.Podman, tc.podman)
62+ }
63+ if di.OSType != "linux" || len(di.Runtimes) != 1 || di.Rootless {
64+ t.Fatalf("the rest of Info is unchanged: %+v", di)
65+ }
66+ })
67+ }
68+}
69+
70+func TestIsPodman(t *testing.T) {
71+ if isPodman(nil) {
72+ t.Fatal("no components is not podman")
73+ }
74+}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 4

Tags 11

↑↓ moveenter openesc close