Browse code

client: wrap volume create api options with client options

Signed-off-by: Austin Vazquez <austin.vazquez@docker.com>

Austin Vazquez authored on 2025/10/20 21:47:01
Showing 10 changed files
... ...
@@ -14,7 +14,6 @@ import (
14 14
 	"github.com/moby/moby/api/types/registry"
15 15
 	"github.com/moby/moby/api/types/swarm"
16 16
 	"github.com/moby/moby/api/types/system"
17
-	"github.com/moby/moby/api/types/volume"
18 17
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
19 18
 )
20 19
 
... ...
@@ -195,7 +194,7 @@ type SystemAPIClient interface {
195 195
 
196 196
 // VolumeAPIClient defines API client methods for the volumes
197 197
 type VolumeAPIClient interface {
198
-	VolumeCreate(ctx context.Context, options volume.CreateOptions) (volume.Volume, error)
198
+	VolumeCreate(ctx context.Context, options VolumeCreateOptions) (VolumeCreateResult, error)
199 199
 	VolumeInspect(ctx context.Context, volumeID string) (VolumeInspectResult, error)
200 200
 	VolumeInspectWithRaw(ctx context.Context, volumeID string) (VolumeInspectResult, []byte, error)
201 201
 	VolumeList(ctx context.Context, options VolumeListOptions) (VolumeListResult, error)
... ...
@@ -7,15 +7,36 @@ import (
7 7
 	"github.com/moby/moby/api/types/volume"
8 8
 )
9 9
 
10
+// VolumeCreateOptions specifies the options to create a volume.
11
+type VolumeCreateOptions struct {
12
+	Name              string
13
+	Driver            string
14
+	DriverOpts        map[string]string
15
+	Labels            map[string]string
16
+	ClusterVolumeSpec *volume.ClusterVolumeSpec
17
+}
18
+
19
+// VolumeCreateResult is the result of a volume creation.
20
+type VolumeCreateResult struct {
21
+	Volume volume.Volume
22
+}
23
+
10 24
 // VolumeCreate creates a volume in the docker host.
11
-func (cli *Client) VolumeCreate(ctx context.Context, options volume.CreateOptions) (volume.Volume, error) {
12
-	resp, err := cli.post(ctx, "/volumes/create", nil, options, nil)
25
+func (cli *Client) VolumeCreate(ctx context.Context, options VolumeCreateOptions) (VolumeCreateResult, error) {
26
+	createRequest := volume.CreateOptions{
27
+		Name:              options.Name,
28
+		Driver:            options.Driver,
29
+		DriverOpts:        options.DriverOpts,
30
+		Labels:            options.Labels,
31
+		ClusterVolumeSpec: options.ClusterVolumeSpec,
32
+	}
33
+	resp, err := cli.post(ctx, "/volumes/create", nil, createRequest, nil)
13 34
 	defer ensureReaderClosed(resp)
14 35
 	if err != nil {
15
-		return volume.Volume{}, err
36
+		return VolumeCreateResult{}, err
16 37
 	}
17 38
 
18
-	var vol volume.Volume
19
-	err = json.NewDecoder(resp.Body).Decode(&vol)
20
-	return vol, err
39
+	var v volume.Volume
40
+	err = json.NewDecoder(resp.Body).Decode(&v)
41
+	return VolumeCreateResult{Volume: v}, err
21 42
 }
... ...
@@ -18,7 +18,7 @@ func TestVolumeCreateError(t *testing.T) {
18 18
 	client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
19 19
 	assert.NilError(t, err)
20 20
 
21
-	_, err = client.VolumeCreate(context.Background(), volume.CreateOptions{})
21
+	_, err = client.VolumeCreate(context.Background(), VolumeCreateOptions{})
22 22
 	assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
23 23
 }
24 24
 
... ...
@@ -45,7 +45,7 @@ func TestVolumeCreate(t *testing.T) {
45 45
 	}))
46 46
 	assert.NilError(t, err)
47 47
 
48
-	vol, err := client.VolumeCreate(context.Background(), volume.CreateOptions{
48
+	res, err := client.VolumeCreate(context.Background(), VolumeCreateOptions{
49 49
 		Name:   "myvolume",
50 50
 		Driver: "mydriver",
51 51
 		DriverOpts: map[string]string{
... ...
@@ -53,7 +53,8 @@ func TestVolumeCreate(t *testing.T) {
53 53
 		},
54 54
 	})
55 55
 	assert.NilError(t, err)
56
-	assert.Check(t, is.Equal(vol.Name, "volume"))
57
-	assert.Check(t, is.Equal(vol.Driver, "local"))
58
-	assert.Check(t, is.Equal(vol.Mountpoint, "mountpoint"))
56
+	v := res.Volume
57
+	assert.Check(t, is.Equal(v.Name, "volume"))
58
+	assert.Check(t, is.Equal(v.Driver, "local"))
59
+	assert.Check(t, is.Equal(v.Mountpoint, "mountpoint"))
59 60
 }
... ...
@@ -18,7 +18,6 @@ import (
18 18
 	"github.com/moby/moby/api/pkg/stdcopy"
19 19
 	containertypes "github.com/moby/moby/api/types/container"
20 20
 	"github.com/moby/moby/api/types/mount"
21
-	"github.com/moby/moby/api/types/volume"
22 21
 	"github.com/moby/moby/client"
23 22
 	"github.com/moby/moby/v2/daemon/config"
24 23
 	"github.com/moby/moby/v2/integration/internal/container"
... ...
@@ -551,7 +550,7 @@ func testLiveRestoreVolumeReferences(t *testing.T) {
551 551
 		t.Run(string(policy), func(t *testing.T) {
552 552
 			ctx := testutil.StartSpan(ctx, t)
553 553
 			volName := "test-live-restore-volume-references-" + string(policy)
554
-			_, err := c.VolumeCreate(ctx, volume.CreateOptions{Name: volName})
554
+			_, err := c.VolumeCreate(ctx, client.VolumeCreateOptions{Name: volName})
555 555
 			assert.NilError(t, err)
556 556
 
557 557
 			// Create a container that uses the volume
... ...
@@ -586,7 +585,7 @@ func testLiveRestoreVolumeReferences(t *testing.T) {
586 586
 	// Addresses https://github.com/moby/moby/issues/44422
587 587
 	t.Run("local volume with mount options", func(t *testing.T) {
588 588
 		ctx := testutil.StartSpan(ctx, t)
589
-		v, err := c.VolumeCreate(ctx, volume.CreateOptions{
589
+		res, err := c.VolumeCreate(ctx, client.VolumeCreateOptions{
590 590
 			Driver: "local",
591 591
 			Name:   "test-live-restore-volume-references-local",
592 592
 			DriverOpts: map[string]string{
... ...
@@ -595,6 +594,7 @@ func testLiveRestoreVolumeReferences(t *testing.T) {
595 595
 			},
596 596
 		})
597 597
 		assert.NilError(t, err)
598
+		v := res.Volume
598 599
 		m := mount.Mount{
599 600
 			Type:   mount.TypeVolume,
600 601
 			Source: v.Name,
... ...
@@ -8,7 +8,6 @@ import (
8 8
 	"io"
9 9
 	"testing"
10 10
 
11
-	"github.com/moby/moby/api/types/volume"
12 11
 	"github.com/moby/moby/client"
13 12
 	"github.com/moby/moby/v2/integration/internal/container"
14 13
 	"github.com/moby/moby/v2/integration/internal/requirement"
... ...
@@ -67,7 +66,7 @@ func TestAuthZPluginV2Disable(t *testing.T) {
67 67
 	d.Restart(t, "--authorization-plugin="+authzPluginNameWithTag)
68 68
 	d.LoadBusybox(ctx, t)
69 69
 
70
-	_, err = c.VolumeCreate(ctx, volume.CreateOptions{Driver: "local"})
70
+	_, err = c.VolumeCreate(ctx, client.VolumeCreateOptions{Driver: "local"})
71 71
 	assert.Assert(t, err != nil)
72 72
 	assert.ErrorContains(t, err, fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag))
73 73
 
... ...
@@ -76,7 +75,7 @@ func TestAuthZPluginV2Disable(t *testing.T) {
76 76
 	assert.NilError(t, err)
77 77
 
78 78
 	// now test to see if the docker api works.
79
-	_, err = c.VolumeCreate(ctx, volume.CreateOptions{Driver: "local"})
79
+	_, err = c.VolumeCreate(ctx, client.VolumeCreateOptions{Driver: "local"})
80 80
 	assert.NilError(t, err)
81 81
 }
82 82
 
... ...
@@ -93,7 +92,7 @@ func TestAuthZPluginV2RejectVolumeRequests(t *testing.T) {
93 93
 	// restart the daemon with the plugin
94 94
 	d.Restart(t, "--authorization-plugin="+authzPluginNameWithTag)
95 95
 
96
-	_, err = c.VolumeCreate(ctx, volume.CreateOptions{Driver: "local"})
96
+	_, err = c.VolumeCreate(ctx, client.VolumeCreateOptions{Driver: "local"})
97 97
 	assert.Assert(t, err != nil)
98 98
 	assert.ErrorContains(t, err, fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag))
99 99
 
... ...
@@ -10,7 +10,6 @@ import (
10 10
 
11 11
 	"github.com/moby/moby/api/types/events"
12 12
 	"github.com/moby/moby/api/types/mount"
13
-	"github.com/moby/moby/api/types/volume"
14 13
 	"github.com/moby/moby/client"
15 14
 	"github.com/moby/moby/v2/integration/internal/container"
16 15
 	"github.com/moby/moby/v2/internal/testutil/request"
... ...
@@ -101,7 +100,7 @@ func TestEventsVolumeCreate(t *testing.T) {
101 101
 		}
102 102
 	}
103 103
 
104
-	_, err := apiClient.VolumeCreate(ctx, volume.CreateOptions{Name: volName})
104
+	_, err := apiClient.VolumeCreate(ctx, client.VolumeCreateOptions{Name: volName})
105 105
 	assert.NilError(t, err)
106 106
 
107 107
 	filter := make(client.Filters).
... ...
@@ -13,7 +13,6 @@ import (
13 13
 	"github.com/moby/moby/api/types/mount"
14 14
 	"github.com/moby/moby/api/types/network"
15 15
 	"github.com/moby/moby/api/types/versions"
16
-	"github.com/moby/moby/api/types/volume"
17 16
 	"github.com/moby/moby/client"
18 17
 	"github.com/moby/moby/v2/daemon/volume/safepath"
19 18
 	"github.com/moby/moby/v2/integration/internal/container"
... ...
@@ -241,7 +240,7 @@ func setupTestVolume(t *testing.T, apiClient client.APIClient) string {
241 241
 	err := apiClient.VolumeRemove(ctx, volumeName, client.VolumeRemoveOptions{Force: true})
242 242
 	assert.NilError(t, err, "failed to clean volume")
243 243
 
244
-	_, err = apiClient.VolumeCreate(ctx, volume.CreateOptions{
244
+	_, err = apiClient.VolumeCreate(ctx, client.VolumeCreateOptions{
245 245
 		Name: volumeName,
246 246
 	})
247 247
 	assert.NilError(t, err, "failed to setup volume")
... ...
@@ -32,20 +32,21 @@ func TestVolumesCreateAndList(t *testing.T) {
32 32
 	if testEnv.DaemonInfo.OSType == "windows" {
33 33
 		name = strings.ToLower(name)
34 34
 	}
35
-	vol, err := apiClient.VolumeCreate(ctx, volume.CreateOptions{
35
+	create, err := apiClient.VolumeCreate(ctx, client.VolumeCreateOptions{
36 36
 		Name: name,
37 37
 	})
38 38
 	assert.NilError(t, err)
39
+	namedV := create.Volume
39 40
 
40 41
 	expected := volume.Volume{
41 42
 		// Ignore timestamp of CreatedAt
42
-		CreatedAt:  vol.CreatedAt,
43
+		CreatedAt:  namedV.CreatedAt,
43 44
 		Driver:     "local",
44 45
 		Scope:      "local",
45 46
 		Name:       name,
46 47
 		Mountpoint: filepath.Join(testEnv.DaemonInfo.DockerRootDir, "volumes", name, "_data"),
47 48
 	}
48
-	assert.Check(t, is.DeepEqual(vol, expected, cmpopts.EquateEmpty()))
49
+	assert.Check(t, is.DeepEqual(namedV, expected, cmpopts.EquateEmpty()))
49 50
 
50 51
 	res, err := apiClient.VolumeList(ctx, client.VolumeListOptions{})
51 52
 	assert.NilError(t, err)
... ...
@@ -54,7 +55,7 @@ func TestVolumesCreateAndList(t *testing.T) {
54 54
 
55 55
 	volumes := volList.Volumes[:0]
56 56
 	for _, v := range volList.Volumes {
57
-		if v.Name == vol.Name {
57
+		if v.Name == namedV.Name {
58 58
 			volumes = append(volumes, v)
59 59
 		}
60 60
 	}
... ...
@@ -160,13 +161,14 @@ func TestVolumesInspect(t *testing.T) {
160 160
 	apiClient := testEnv.APIClient()
161 161
 
162 162
 	now := time.Now()
163
-	vol, err := apiClient.VolumeCreate(ctx, volume.CreateOptions{})
163
+	create, err := apiClient.VolumeCreate(ctx, client.VolumeCreateOptions{})
164 164
 	assert.NilError(t, err)
165
+	v := create.Volume
165 166
 
166
-	inspected, err := apiClient.VolumeInspect(ctx, vol.Name)
167
+	inspected, err := apiClient.VolumeInspect(ctx, v.Name)
167 168
 	assert.NilError(t, err)
168 169
 
169
-	assert.Check(t, is.DeepEqual(inspected.Volume, vol, cmpopts.EquateEmpty()))
170
+	assert.Check(t, is.DeepEqual(inspected.Volume, v, cmpopts.EquateEmpty()))
170 171
 
171 172
 	// comparing CreatedAt field time for the new volume to now. Truncate to 1 minute precision to avoid false positive
172 173
 	createdAt, err := time.Parse(time.RFC3339, strings.TrimSpace(inspected.Volume.CreatedAt))
... ...
@@ -178,7 +180,7 @@ func TestVolumesInspect(t *testing.T) {
178 178
 	err = os.Chtimes(inspected.Volume.Mountpoint, modifiedAt, modifiedAt)
179 179
 	assert.NilError(t, err)
180 180
 
181
-	inspected, err = apiClient.VolumeInspect(ctx, vol.Name)
181
+	inspected, err = apiClient.VolumeInspect(ctx, v.Name)
182 182
 	assert.NilError(t, err)
183 183
 
184 184
 	createdAt2, err := time.Parse(time.RFC3339, strings.TrimSpace(inspected.Volume.CreatedAt))
... ...
@@ -261,45 +263,49 @@ func TestVolumePruneAnonymous(t *testing.T) {
261 261
 	apiClient := testEnv.APIClient()
262 262
 
263 263
 	// Create an anonymous volume
264
-	v, err := apiClient.VolumeCreate(ctx, volume.CreateOptions{})
264
+	created, err := apiClient.VolumeCreate(ctx, client.VolumeCreateOptions{})
265 265
 	assert.NilError(t, err)
266
+	anonV := created.Volume
266 267
 
267 268
 	// Create a named volume
268
-	vNamed, err := apiClient.VolumeCreate(ctx, volume.CreateOptions{
269
+	created, err = apiClient.VolumeCreate(ctx, client.VolumeCreateOptions{
269 270
 		Name: "test",
270 271
 	})
271 272
 	assert.NilError(t, err)
273
+	namedV := created.Volume
272 274
 
273 275
 	// Prune anonymous volumes
274
-	res, err := apiClient.VolumesPrune(ctx, client.VolumePruneOptions{})
276
+	prune, err := apiClient.VolumesPrune(ctx, client.VolumePruneOptions{})
275 277
 	assert.NilError(t, err)
276
-	assert.Check(t, is.Equal(len(res.Report.VolumesDeleted), 1))
277
-	assert.Check(t, is.Equal(res.Report.VolumesDeleted[0], v.Name))
278
+	report := prune.Report
279
+	assert.Check(t, is.Equal(len(report.VolumesDeleted), 1))
280
+	assert.Check(t, is.Equal(report.VolumesDeleted[0], anonV.Name))
278 281
 
279
-	_, err = apiClient.VolumeInspect(ctx, vNamed.Name)
282
+	_, err = apiClient.VolumeInspect(ctx, namedV.Name)
280 283
 	assert.NilError(t, err)
281 284
 
282 285
 	// Prune all volumes
283
-	_, err = apiClient.VolumeCreate(ctx, volume.CreateOptions{})
286
+	_, err = apiClient.VolumeCreate(ctx, client.VolumeCreateOptions{})
284 287
 	assert.NilError(t, err)
285
-	res, err = apiClient.VolumesPrune(ctx, client.VolumePruneOptions{
288
+	prune, err = apiClient.VolumesPrune(ctx, client.VolumePruneOptions{
286 289
 		All: true,
287 290
 	})
288 291
 	assert.NilError(t, err)
289
-	assert.Check(t, is.Equal(len(res.Report.VolumesDeleted), 2))
292
+	assert.Check(t, is.Equal(len(prune.Report.VolumesDeleted), 2))
290 293
 
291 294
 	// Create a named volume and an anonymous volume, and prune all
292
-	_, err = apiClient.VolumeCreate(ctx, volume.CreateOptions{})
295
+	_, err = apiClient.VolumeCreate(ctx, client.VolumeCreateOptions{})
293 296
 	assert.NilError(t, err)
294
-	_, err = apiClient.VolumeCreate(ctx, volume.CreateOptions{Name: "test"})
297
+	_, err = apiClient.VolumeCreate(ctx, client.VolumeCreateOptions{Name: "test"})
295 298
 	assert.NilError(t, err)
296 299
 
297
-	res, err = apiClient.VolumesPrune(ctx, client.VolumePruneOptions{
300
+	prune, err = apiClient.VolumesPrune(ctx, client.VolumePruneOptions{
298 301
 		All: true,
299 302
 	})
300 303
 
301 304
 	assert.NilError(t, err)
302
-	assert.Check(t, is.Equal(len(res.Report.VolumesDeleted), 2))
305
+	report = prune.Report
306
+	assert.Check(t, is.Equal(len(report.VolumesDeleted), 2))
303 307
 
304 308
 	// Validate that older API versions still have the old behavior of pruning all local volumes
305 309
 	clientOld, err := client.NewClientWithOpts(client.FromEnv, client.WithVersion("1.41"))
... ...
@@ -307,16 +313,19 @@ func TestVolumePruneAnonymous(t *testing.T) {
307 307
 	defer clientOld.Close()
308 308
 	assert.Equal(t, clientOld.ClientVersion(), "1.41")
309 309
 
310
-	v, err = apiClient.VolumeCreate(ctx, volume.CreateOptions{})
310
+	created, err = apiClient.VolumeCreate(ctx, client.VolumeCreateOptions{})
311 311
 	assert.NilError(t, err)
312
-	vNamed, err = apiClient.VolumeCreate(ctx, volume.CreateOptions{Name: "test-api141"})
312
+	anonV = created.Volume
313
+	created, err = apiClient.VolumeCreate(ctx, client.VolumeCreateOptions{Name: "test-api141"})
313 314
 	assert.NilError(t, err)
315
+	namedV = created.Volume
314 316
 
315
-	res, err = clientOld.VolumesPrune(ctx, client.VolumePruneOptions{})
317
+	prune, err = clientOld.VolumesPrune(ctx, client.VolumePruneOptions{})
316 318
 	assert.NilError(t, err)
317
-	assert.Check(t, is.Equal(len(res.Report.VolumesDeleted), 2))
318
-	assert.Check(t, is.Contains(res.Report.VolumesDeleted, v.Name))
319
-	assert.Check(t, is.Contains(res.Report.VolumesDeleted, vNamed.Name))
319
+	report = prune.Report
320
+	assert.Check(t, is.Equal(len(report.VolumesDeleted), 2))
321
+	assert.Check(t, is.Contains(report.VolumesDeleted, anonV.Name))
322
+	assert.Check(t, is.Contains(report.VolumesDeleted, namedV.Name))
320 323
 }
321 324
 
322 325
 func TestVolumePruneAnonFromImage(t *testing.T) {
... ...
@@ -14,7 +14,6 @@ import (
14 14
 	"github.com/moby/moby/api/types/registry"
15 15
 	"github.com/moby/moby/api/types/swarm"
16 16
 	"github.com/moby/moby/api/types/system"
17
-	"github.com/moby/moby/api/types/volume"
18 17
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
19 18
 )
20 19
 
... ...
@@ -195,7 +194,7 @@ type SystemAPIClient interface {
195 195
 
196 196
 // VolumeAPIClient defines API client methods for the volumes
197 197
 type VolumeAPIClient interface {
198
-	VolumeCreate(ctx context.Context, options volume.CreateOptions) (volume.Volume, error)
198
+	VolumeCreate(ctx context.Context, options VolumeCreateOptions) (VolumeCreateResult, error)
199 199
 	VolumeInspect(ctx context.Context, volumeID string) (VolumeInspectResult, error)
200 200
 	VolumeInspectWithRaw(ctx context.Context, volumeID string) (VolumeInspectResult, []byte, error)
201 201
 	VolumeList(ctx context.Context, options VolumeListOptions) (VolumeListResult, error)
... ...
@@ -7,15 +7,36 @@ import (
7 7
 	"github.com/moby/moby/api/types/volume"
8 8
 )
9 9
 
10
+// VolumeCreateOptions specifies the options to create a volume.
11
+type VolumeCreateOptions struct {
12
+	Name              string
13
+	Driver            string
14
+	DriverOpts        map[string]string
15
+	Labels            map[string]string
16
+	ClusterVolumeSpec *volume.ClusterVolumeSpec
17
+}
18
+
19
+// VolumeCreateResult is the result of a volume creation.
20
+type VolumeCreateResult struct {
21
+	Volume volume.Volume
22
+}
23
+
10 24
 // VolumeCreate creates a volume in the docker host.
11
-func (cli *Client) VolumeCreate(ctx context.Context, options volume.CreateOptions) (volume.Volume, error) {
12
-	resp, err := cli.post(ctx, "/volumes/create", nil, options, nil)
25
+func (cli *Client) VolumeCreate(ctx context.Context, options VolumeCreateOptions) (VolumeCreateResult, error) {
26
+	createRequest := volume.CreateOptions{
27
+		Name:              options.Name,
28
+		Driver:            options.Driver,
29
+		DriverOpts:        options.DriverOpts,
30
+		Labels:            options.Labels,
31
+		ClusterVolumeSpec: options.ClusterVolumeSpec,
32
+	}
33
+	resp, err := cli.post(ctx, "/volumes/create", nil, createRequest, nil)
13 34
 	defer ensureReaderClosed(resp)
14 35
 	if err != nil {
15
-		return volume.Volume{}, err
36
+		return VolumeCreateResult{}, err
16 37
 	}
17 38
 
18
-	var vol volume.Volume
19
-	err = json.NewDecoder(resp.Body).Decode(&vol)
20
-	return vol, err
39
+	var v volume.Volume
40
+	err = json.NewDecoder(resp.Body).Decode(&v)
41
+	return VolumeCreateResult{Volume: v}, err
21 42
 }