Browse code

Inspect: API v1.51 compatibility

For API < v1.52:
- In container inspect:
- Restore GraphDriver when a snapshotter is used.
- Remove field Storage
- Related to commit efa077848f912b1c8febe579f345387e38008eb0
- In image inspect:
- Restore GraphDriver when a snapshotter is used.
- Related to commit c441b2ef19feb9ce2b97eb0ebd40e0b1a45344a7

Co-authored-by: Sebastiaan van Stijn <github@gone.nl>
Signed-off-by: Rob Murray <rob.murray@docker.com>

Rob Murray authored on 2025/10/22 22:35:53
Showing 6 changed files
... ...
@@ -12,6 +12,7 @@ import (
12 12
 	"github.com/distribution/reference"
13 13
 	dockerspec "github.com/moby/docker-image-spec/specs-go/v1"
14 14
 	imagetypes "github.com/moby/moby/api/types/image"
15
+	"github.com/moby/moby/api/types/storage"
15 16
 	"github.com/moby/moby/v2/daemon/server/imagebackend"
16 17
 	"github.com/moby/moby/v2/internal/sliceutil"
17 18
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
... ...
@@ -98,6 +99,9 @@ func (i *ImageService) ImageInspect(ctx context.Context, refOrID string, opts im
98 98
 			},
99 99
 		},
100 100
 		Parent: parent, // field is deprecated with the legacy builder, but returned by the API if present.
101
+
102
+		// GraphDriver is omitted in API v1.52 unless using a graphdriver.
103
+		GraphDriverLegacy: &storage.DriverData{Name: i.snapshotter},
101 104
 	}
102 105
 
103 106
 	if multi.Best != nil {
... ...
@@ -7,6 +7,7 @@ import (
7 7
 	"github.com/moby/moby/api/types/container"
8 8
 	imagetypes "github.com/moby/moby/api/types/image"
9 9
 	"github.com/moby/moby/api/types/registry"
10
+	"github.com/moby/moby/api/types/storage"
10 11
 	"github.com/moby/moby/v2/daemon/internal/filters"
11 12
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
12 13
 )
... ...
@@ -92,4 +93,8 @@ type InspectData struct {
92 92
 	//
93 93
 	// This field is removed in API v1.45, but used for API <= v1.44 responses.
94 94
 	ContainerConfig *container.Config
95
+
96
+	// GraphDriverLegacy is used for API versions < v1.52, which included the
97
+	// name of the snapshotter the GraphDriver field.
98
+	GraphDriverLegacy *storage.DriverData
95 99
 }
... ...
@@ -5,6 +5,7 @@ import (
5 5
 	"net/http"
6 6
 
7 7
 	"github.com/moby/moby/api/types/container"
8
+	"github.com/moby/moby/api/types/storage"
8 9
 	"github.com/moby/moby/api/types/versions"
9 10
 	"github.com/moby/moby/v2/daemon/internal/compat"
10 11
 	"github.com/moby/moby/v2/daemon/internal/stringid"
... ...
@@ -69,6 +70,15 @@ func (c *containerRouter) getContainersByName(ctx context.Context, w http.Respon
69 69
 				},
70 70
 			}))
71 71
 		}
72
+
73
+		// Restore the GraphDriver field, now omitted when a snapshotter is used.
74
+		// Remove the Storage field that replaced it.
75
+		if ctr.GraphDriver == nil && ctr.Storage != nil && ctr.Storage.RootFS != nil && ctr.Storage.RootFS.Snapshot != nil {
76
+			ctr.GraphDriver = &storage.DriverData{
77
+				Name: ctr.Storage.RootFS.Snapshot.Name,
78
+			}
79
+			ctr.Storage = nil
80
+		}
72 81
 	}
73 82
 
74 83
 	return httputils.WriteJSON(w, http.StatusOK, compat.Wrap(ctr, wrapOpts...))
... ...
@@ -440,6 +440,11 @@ func (ir *imageRouter) getImagesByName(ctx context.Context, w http.ResponseWrite
440 440
 				"Config": legacyConfigFields["v1.50-v1.51"],
441 441
 			}))
442 442
 		}
443
+
444
+		// Restore the GraphDriver field, now omitted when a snapshotter is used.
445
+		if imageInspect.GraphDriver == nil && inspectData.GraphDriverLegacy != nil {
446
+			imageInspect.GraphDriver = inspectData.GraphDriverLegacy
447
+		}
443 448
 	} else {
444 449
 		if inspectData.Parent != "" {
445 450
 			// field is deprecated, but still included in response when present (built with legacy builder).
... ...
@@ -19,16 +19,10 @@ func (s *DockerAPISuite) TestInspectAPIContainerResponse(c *testing.T) {
19 19
 
20 20
 	keysBase := []string{
21 21
 		"Id", "State", "Created", "Path", "Args", "Config", "Image", "NetworkSettings",
22
-		"ResolvConfPath", "HostnamePath", "HostsPath", "LogPath", "Name", "Driver", "MountLabel", "ProcessLabel",
22
+		"ResolvConfPath", "HostnamePath", "HostsPath", "LogPath", "Name", "Driver", "MountLabel", "ProcessLabel", "GraphDriver",
23 23
 		"Mounts",
24 24
 	}
25 25
 
26
-	if testEnv.UsingSnapshotter() {
27
-		keysBase = append(keysBase, "Storage")
28
-	} else {
29
-		keysBase = append(keysBase, "GraphDriver")
30
-	}
31
-
32 26
 	cases := []struct {
33 27
 		version string
34 28
 		keys    []string
... ...
@@ -4,6 +4,8 @@ import (
4 4
 	"testing"
5 5
 
6 6
 	containertypes "github.com/moby/moby/api/types/container"
7
+	"github.com/moby/moby/api/types/storage"
8
+	"github.com/moby/moby/client"
7 9
 	"github.com/moby/moby/v2/internal/testutil"
8 10
 	"github.com/moby/moby/v2/internal/testutil/daemon"
9 11
 	"gotest.tools/v3/assert"
... ...
@@ -84,3 +86,91 @@ func TestGraphDriverPersistence(t *testing.T) {
84 84
 	assert.Check(t, containerInspect.GraphDriver != nil, "GraphDriver should be set for graphdriver backend")
85 85
 	assert.Check(t, is.Equal(containerInspect.GraphDriver.Name, prevDriver), "Container graphdriver data should match")
86 86
 }
87
+
88
+// TestInspectGraphDriverAPIBC checks API backward compatibility of the GraphDriver field in image/container inspect.
89
+func TestInspectGraphDriverAPIBC(t *testing.T) {
90
+	skip.If(t, testEnv.DaemonInfo.OSType == "windows", "Windows does not support running sub-daemons")
91
+	t.Setenv("DOCKER_DRIVER", "")
92
+	t.Setenv("DOCKER_GRAPHDRIVER", "")
93
+	t.Setenv("TEST_INTEGRATION_USE_GRAPHDRIVER", "")
94
+	ctx := testutil.StartSpan(baseContext, t)
95
+
96
+	tests := []struct {
97
+		name          string
98
+		apiVersion    string
99
+		storageDriver string
100
+
101
+		expContainerdSnapshotter bool
102
+		expGraphDriver           string
103
+		expRootFSStorage         bool
104
+	}{
105
+		{
106
+			name:                     "vCurrent/containerd",
107
+			expContainerdSnapshotter: true,
108
+			expRootFSStorage:         true,
109
+		},
110
+		{
111
+			name:                     "v1.51/containerd",
112
+			apiVersion:               "v1.51",
113
+			expContainerdSnapshotter: true,
114
+			expGraphDriver:           "overlayfs",
115
+		},
116
+		{
117
+			name:           "vCurrent/graphdriver",
118
+			storageDriver:  "vfs",
119
+			expGraphDriver: "vfs",
120
+		},
121
+	}
122
+
123
+	for _, tc := range tests {
124
+		t.Run(tc.name, func(t *testing.T) {
125
+			d := daemon.New(t)
126
+			defer d.Stop(t)
127
+			d.StartWithBusybox(ctx, t, "--iptables=false", "--ip6tables=false", "--storage-driver="+tc.storageDriver)
128
+			c := d.NewClientT(t, client.WithVersion(tc.apiVersion))
129
+
130
+			// Check selection of containerd / storage-driver worked.
131
+			info := d.Info(t)
132
+			if tc.expContainerdSnapshotter {
133
+				assert.Check(t, is.Equal(info.Driver, "overlayfs"))
134
+				assert.Check(t, is.Equal(info.DriverStatus[0][0], "driver-type"))
135
+				assert.Check(t, is.Equal(info.DriverStatus[0][1], "io.containerd.snapshotter.v1"))
136
+			} else {
137
+				assert.Check(t, is.Equal(info.Driver, "vfs"))
138
+				assert.Check(t, is.Len(info.DriverStatus, 0))
139
+			}
140
+
141
+			const testImage = "busybox:latest"
142
+			ctr, err := c.ContainerCreate(ctx, &containertypes.Config{Image: testImage}, nil, nil, nil, "test-container")
143
+			assert.NilError(t, err)
144
+			defer func() { _ = c.ContainerRemove(ctx, ctr.ID, client.ContainerRemoveOptions{Force: true}) }()
145
+
146
+			if imageInspect, err := c.ImageInspect(ctx, testImage); assert.Check(t, err) {
147
+				if tc.expGraphDriver != "" {
148
+					if assert.Check(t, imageInspect.GraphDriver != nil) {
149
+						assert.Check(t, is.Equal(imageInspect.GraphDriver.Name, tc.expGraphDriver))
150
+					}
151
+				} else {
152
+					assert.Check(t, is.Nil(imageInspect.GraphDriver))
153
+				}
154
+			}
155
+
156
+			if containerInspect, err := c.ContainerInspect(ctx, ctr.ID); assert.Check(t, err) {
157
+				if tc.expGraphDriver != "" {
158
+					if assert.Check(t, containerInspect.GraphDriver != nil) {
159
+						assert.Check(t, is.Equal(containerInspect.GraphDriver.Name, tc.expGraphDriver))
160
+					}
161
+				} else {
162
+					assert.Check(t, is.Nil(containerInspect.GraphDriver))
163
+				}
164
+				if tc.expRootFSStorage {
165
+					assert.DeepEqual(t, containerInspect.Storage, &storage.Storage{
166
+						RootFS: &storage.RootFSStorage{Snapshot: &storage.RootFSStorageSnapshot{Name: "overlayfs"}},
167
+					})
168
+				} else {
169
+					assert.Check(t, is.Nil(containerInspect.Storage))
170
+				}
171
+			}
172
+		})
173
+	}
174
+}