From a2fd7244535eff6727ef9b7ff43ae9d8a9f29950 Mon Sep 17 00:00:00 2001 From: Austin Vazquez Date: Mon, 20 Oct 2025 07:47:01 -0500 Subject: [PATCH] client: wrap volume create api options with client options Signed-off-by: Austin Vazquez --- client/client_interfaces.go | 3 +- client/volume_create.go | 33 +++++++-- client/volume_create_test.go | 11 +-- integration/daemon/daemon_test.go | 6 +- .../plugin/authz/authz_plugin_v2_test.go | 7 +- integration/system/event_test.go | 3 +- integration/volume/mount_test.go | 3 +- integration/volume/volume_test.go | 67 +++++++++++-------- .../moby/moby/client/client_interfaces.go | 3 +- .../moby/moby/client/volume_create.go | 33 +++++++-- 10 files changed, 108 insertions(+), 61 deletions(-) diff --git a/client/client_interfaces.go b/client/client_interfaces.go index 48b4e7c5f8..b9d18f827b 100644 --- a/client/client_interfaces.go +++ b/client/client_interfaces.go @@ -14,7 +14,6 @@ import ( "github.com/moby/moby/api/types/registry" "github.com/moby/moby/api/types/swarm" "github.com/moby/moby/api/types/system" - "github.com/moby/moby/api/types/volume" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) @@ -195,7 +194,7 @@ type SystemAPIClient interface { // VolumeAPIClient defines API client methods for the volumes type VolumeAPIClient interface { - VolumeCreate(ctx context.Context, options volume.CreateOptions) (volume.Volume, error) + VolumeCreate(ctx context.Context, options VolumeCreateOptions) (VolumeCreateResult, error) VolumeInspect(ctx context.Context, volumeID string) (VolumeInspectResult, error) VolumeInspectWithRaw(ctx context.Context, volumeID string) (VolumeInspectResult, []byte, error) VolumeList(ctx context.Context, options VolumeListOptions) (VolumeListResult, error) diff --git a/client/volume_create.go b/client/volume_create.go index dcbd453c57..7a03de15de 100644 --- a/client/volume_create.go +++ b/client/volume_create.go @@ -7,15 +7,36 @@ import ( "github.com/moby/moby/api/types/volume" ) +// VolumeCreateOptions specifies the options to create a volume. +type VolumeCreateOptions struct { + Name string + Driver string + DriverOpts map[string]string + Labels map[string]string + ClusterVolumeSpec *volume.ClusterVolumeSpec +} + +// VolumeCreateResult is the result of a volume creation. +type VolumeCreateResult struct { + Volume volume.Volume +} + // VolumeCreate creates a volume in the docker host. -func (cli *Client) VolumeCreate(ctx context.Context, options volume.CreateOptions) (volume.Volume, error) { - resp, err := cli.post(ctx, "/volumes/create", nil, options, nil) +func (cli *Client) VolumeCreate(ctx context.Context, options VolumeCreateOptions) (VolumeCreateResult, error) { + createRequest := volume.CreateOptions{ + Name: options.Name, + Driver: options.Driver, + DriverOpts: options.DriverOpts, + Labels: options.Labels, + ClusterVolumeSpec: options.ClusterVolumeSpec, + } + resp, err := cli.post(ctx, "/volumes/create", nil, createRequest, nil) defer ensureReaderClosed(resp) if err != nil { - return volume.Volume{}, err + return VolumeCreateResult{}, err } - var vol volume.Volume - err = json.NewDecoder(resp.Body).Decode(&vol) - return vol, err + var v volume.Volume + err = json.NewDecoder(resp.Body).Decode(&v) + return VolumeCreateResult{Volume: v}, err } diff --git a/client/volume_create_test.go b/client/volume_create_test.go index d9b7ad7f7c..d0f075033d 100644 --- a/client/volume_create_test.go +++ b/client/volume_create_test.go @@ -18,7 +18,7 @@ func TestVolumeCreateError(t *testing.T) { client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusInternalServerError, "Server error"))) assert.NilError(t, err) - _, err = client.VolumeCreate(context.Background(), volume.CreateOptions{}) + _, err = client.VolumeCreate(context.Background(), VolumeCreateOptions{}) assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal)) } @@ -45,7 +45,7 @@ func TestVolumeCreate(t *testing.T) { })) assert.NilError(t, err) - vol, err := client.VolumeCreate(context.Background(), volume.CreateOptions{ + res, err := client.VolumeCreate(context.Background(), VolumeCreateOptions{ Name: "myvolume", Driver: "mydriver", DriverOpts: map[string]string{ @@ -53,7 +53,8 @@ func TestVolumeCreate(t *testing.T) { }, }) assert.NilError(t, err) - assert.Check(t, is.Equal(vol.Name, "volume")) - assert.Check(t, is.Equal(vol.Driver, "local")) - assert.Check(t, is.Equal(vol.Mountpoint, "mountpoint")) + v := res.Volume + assert.Check(t, is.Equal(v.Name, "volume")) + assert.Check(t, is.Equal(v.Driver, "local")) + assert.Check(t, is.Equal(v.Mountpoint, "mountpoint")) } diff --git a/integration/daemon/daemon_test.go b/integration/daemon/daemon_test.go index d1c11af9b5..814ad47dc9 100644 --- a/integration/daemon/daemon_test.go +++ b/integration/daemon/daemon_test.go @@ -18,7 +18,6 @@ import ( "github.com/moby/moby/api/pkg/stdcopy" containertypes "github.com/moby/moby/api/types/container" "github.com/moby/moby/api/types/mount" - "github.com/moby/moby/api/types/volume" "github.com/moby/moby/client" "github.com/moby/moby/v2/daemon/config" "github.com/moby/moby/v2/integration/internal/container" @@ -551,7 +550,7 @@ func testLiveRestoreVolumeReferences(t *testing.T) { t.Run(string(policy), func(t *testing.T) { ctx := testutil.StartSpan(ctx, t) volName := "test-live-restore-volume-references-" + string(policy) - _, err := c.VolumeCreate(ctx, volume.CreateOptions{Name: volName}) + _, err := c.VolumeCreate(ctx, client.VolumeCreateOptions{Name: volName}) assert.NilError(t, err) // Create a container that uses the volume @@ -586,7 +585,7 @@ func testLiveRestoreVolumeReferences(t *testing.T) { // Addresses https://github.com/moby/moby/issues/44422 t.Run("local volume with mount options", func(t *testing.T) { ctx := testutil.StartSpan(ctx, t) - v, err := c.VolumeCreate(ctx, volume.CreateOptions{ + res, err := c.VolumeCreate(ctx, client.VolumeCreateOptions{ Driver: "local", Name: "test-live-restore-volume-references-local", DriverOpts: map[string]string{ @@ -595,6 +594,7 @@ func testLiveRestoreVolumeReferences(t *testing.T) { }, }) assert.NilError(t, err) + v := res.Volume m := mount.Mount{ Type: mount.TypeVolume, Source: v.Name, diff --git a/integration/plugin/authz/authz_plugin_v2_test.go b/integration/plugin/authz/authz_plugin_v2_test.go index ab630b76d7..590fcbfb07 100644 --- a/integration/plugin/authz/authz_plugin_v2_test.go +++ b/integration/plugin/authz/authz_plugin_v2_test.go @@ -8,7 +8,6 @@ import ( "io" "testing" - "github.com/moby/moby/api/types/volume" "github.com/moby/moby/client" "github.com/moby/moby/v2/integration/internal/container" "github.com/moby/moby/v2/integration/internal/requirement" @@ -67,7 +66,7 @@ func TestAuthZPluginV2Disable(t *testing.T) { d.Restart(t, "--authorization-plugin="+authzPluginNameWithTag) d.LoadBusybox(ctx, t) - _, err = c.VolumeCreate(ctx, volume.CreateOptions{Driver: "local"}) + _, err = c.VolumeCreate(ctx, client.VolumeCreateOptions{Driver: "local"}) assert.Assert(t, err != nil) assert.ErrorContains(t, err, fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag)) @@ -76,7 +75,7 @@ func TestAuthZPluginV2Disable(t *testing.T) { assert.NilError(t, err) // now test to see if the docker api works. - _, err = c.VolumeCreate(ctx, volume.CreateOptions{Driver: "local"}) + _, err = c.VolumeCreate(ctx, client.VolumeCreateOptions{Driver: "local"}) assert.NilError(t, err) } @@ -93,7 +92,7 @@ func TestAuthZPluginV2RejectVolumeRequests(t *testing.T) { // restart the daemon with the plugin d.Restart(t, "--authorization-plugin="+authzPluginNameWithTag) - _, err = c.VolumeCreate(ctx, volume.CreateOptions{Driver: "local"}) + _, err = c.VolumeCreate(ctx, client.VolumeCreateOptions{Driver: "local"}) assert.Assert(t, err != nil) assert.ErrorContains(t, err, fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag)) diff --git a/integration/system/event_test.go b/integration/system/event_test.go index 942013e001..a398ec137f 100644 --- a/integration/system/event_test.go +++ b/integration/system/event_test.go @@ -10,7 +10,6 @@ import ( "github.com/moby/moby/api/types/events" "github.com/moby/moby/api/types/mount" - "github.com/moby/moby/api/types/volume" "github.com/moby/moby/client" "github.com/moby/moby/v2/integration/internal/container" "github.com/moby/moby/v2/internal/testutil/request" @@ -101,7 +100,7 @@ func TestEventsVolumeCreate(t *testing.T) { } } - _, err := apiClient.VolumeCreate(ctx, volume.CreateOptions{Name: volName}) + _, err := apiClient.VolumeCreate(ctx, client.VolumeCreateOptions{Name: volName}) assert.NilError(t, err) filter := make(client.Filters). diff --git a/integration/volume/mount_test.go b/integration/volume/mount_test.go index da675cd886..699c3550f5 100644 --- a/integration/volume/mount_test.go +++ b/integration/volume/mount_test.go @@ -13,7 +13,6 @@ import ( "github.com/moby/moby/api/types/mount" "github.com/moby/moby/api/types/network" "github.com/moby/moby/api/types/versions" - "github.com/moby/moby/api/types/volume" "github.com/moby/moby/client" "github.com/moby/moby/v2/daemon/volume/safepath" "github.com/moby/moby/v2/integration/internal/container" @@ -241,7 +240,7 @@ func setupTestVolume(t *testing.T, apiClient client.APIClient) string { err := apiClient.VolumeRemove(ctx, volumeName, client.VolumeRemoveOptions{Force: true}) assert.NilError(t, err, "failed to clean volume") - _, err = apiClient.VolumeCreate(ctx, volume.CreateOptions{ + _, err = apiClient.VolumeCreate(ctx, client.VolumeCreateOptions{ Name: volumeName, }) assert.NilError(t, err, "failed to setup volume") diff --git a/integration/volume/volume_test.go b/integration/volume/volume_test.go index 33ce63b9d0..6ba5059c1f 100644 --- a/integration/volume/volume_test.go +++ b/integration/volume/volume_test.go @@ -32,20 +32,21 @@ func TestVolumesCreateAndList(t *testing.T) { if testEnv.DaemonInfo.OSType == "windows" { name = strings.ToLower(name) } - vol, err := apiClient.VolumeCreate(ctx, volume.CreateOptions{ + create, err := apiClient.VolumeCreate(ctx, client.VolumeCreateOptions{ Name: name, }) assert.NilError(t, err) + namedV := create.Volume expected := volume.Volume{ // Ignore timestamp of CreatedAt - CreatedAt: vol.CreatedAt, + CreatedAt: namedV.CreatedAt, Driver: "local", Scope: "local", Name: name, Mountpoint: filepath.Join(testEnv.DaemonInfo.DockerRootDir, "volumes", name, "_data"), } - assert.Check(t, is.DeepEqual(vol, expected, cmpopts.EquateEmpty())) + assert.Check(t, is.DeepEqual(namedV, expected, cmpopts.EquateEmpty())) res, err := apiClient.VolumeList(ctx, client.VolumeListOptions{}) assert.NilError(t, err) @@ -54,7 +55,7 @@ func TestVolumesCreateAndList(t *testing.T) { volumes := volList.Volumes[:0] for _, v := range volList.Volumes { - if v.Name == vol.Name { + if v.Name == namedV.Name { volumes = append(volumes, v) } } @@ -160,13 +161,14 @@ func TestVolumesInspect(t *testing.T) { apiClient := testEnv.APIClient() now := time.Now() - vol, err := apiClient.VolumeCreate(ctx, volume.CreateOptions{}) + create, err := apiClient.VolumeCreate(ctx, client.VolumeCreateOptions{}) + assert.NilError(t, err) + v := create.Volume + + inspected, err := apiClient.VolumeInspect(ctx, v.Name) assert.NilError(t, err) - inspected, err := apiClient.VolumeInspect(ctx, vol.Name) - assert.NilError(t, err) - - assert.Check(t, is.DeepEqual(inspected.Volume, vol, cmpopts.EquateEmpty())) + assert.Check(t, is.DeepEqual(inspected.Volume, v, cmpopts.EquateEmpty())) // comparing CreatedAt field time for the new volume to now. Truncate to 1 minute precision to avoid false positive createdAt, err := time.Parse(time.RFC3339, strings.TrimSpace(inspected.Volume.CreatedAt)) @@ -178,7 +180,7 @@ func TestVolumesInspect(t *testing.T) { err = os.Chtimes(inspected.Volume.Mountpoint, modifiedAt, modifiedAt) assert.NilError(t, err) - inspected, err = apiClient.VolumeInspect(ctx, vol.Name) + inspected, err = apiClient.VolumeInspect(ctx, v.Name) assert.NilError(t, err) createdAt2, err := time.Parse(time.RFC3339, strings.TrimSpace(inspected.Volume.CreatedAt)) @@ -261,45 +263,49 @@ func TestVolumePruneAnonymous(t *testing.T) { apiClient := testEnv.APIClient() // Create an anonymous volume - v, err := apiClient.VolumeCreate(ctx, volume.CreateOptions{}) + created, err := apiClient.VolumeCreate(ctx, client.VolumeCreateOptions{}) assert.NilError(t, err) + anonV := created.Volume // Create a named volume - vNamed, err := apiClient.VolumeCreate(ctx, volume.CreateOptions{ + created, err = apiClient.VolumeCreate(ctx, client.VolumeCreateOptions{ Name: "test", }) assert.NilError(t, err) + namedV := created.Volume // Prune anonymous volumes - res, err := apiClient.VolumesPrune(ctx, client.VolumePruneOptions{}) + prune, err := apiClient.VolumesPrune(ctx, client.VolumePruneOptions{}) assert.NilError(t, err) - assert.Check(t, is.Equal(len(res.Report.VolumesDeleted), 1)) - assert.Check(t, is.Equal(res.Report.VolumesDeleted[0], v.Name)) + report := prune.Report + assert.Check(t, is.Equal(len(report.VolumesDeleted), 1)) + assert.Check(t, is.Equal(report.VolumesDeleted[0], anonV.Name)) - _, err = apiClient.VolumeInspect(ctx, vNamed.Name) + _, err = apiClient.VolumeInspect(ctx, namedV.Name) assert.NilError(t, err) // Prune all volumes - _, err = apiClient.VolumeCreate(ctx, volume.CreateOptions{}) + _, err = apiClient.VolumeCreate(ctx, client.VolumeCreateOptions{}) assert.NilError(t, err) - res, err = apiClient.VolumesPrune(ctx, client.VolumePruneOptions{ + prune, err = apiClient.VolumesPrune(ctx, client.VolumePruneOptions{ All: true, }) assert.NilError(t, err) - assert.Check(t, is.Equal(len(res.Report.VolumesDeleted), 2)) + assert.Check(t, is.Equal(len(prune.Report.VolumesDeleted), 2)) // Create a named volume and an anonymous volume, and prune all - _, err = apiClient.VolumeCreate(ctx, volume.CreateOptions{}) + _, err = apiClient.VolumeCreate(ctx, client.VolumeCreateOptions{}) assert.NilError(t, err) - _, err = apiClient.VolumeCreate(ctx, volume.CreateOptions{Name: "test"}) + _, err = apiClient.VolumeCreate(ctx, client.VolumeCreateOptions{Name: "test"}) assert.NilError(t, err) - res, err = apiClient.VolumesPrune(ctx, client.VolumePruneOptions{ + prune, err = apiClient.VolumesPrune(ctx, client.VolumePruneOptions{ All: true, }) assert.NilError(t, err) - assert.Check(t, is.Equal(len(res.Report.VolumesDeleted), 2)) + report = prune.Report + assert.Check(t, is.Equal(len(report.VolumesDeleted), 2)) // Validate that older API versions still have the old behavior of pruning all local volumes clientOld, err := client.NewClientWithOpts(client.FromEnv, client.WithVersion("1.41")) @@ -307,16 +313,19 @@ func TestVolumePruneAnonymous(t *testing.T) { defer clientOld.Close() assert.Equal(t, clientOld.ClientVersion(), "1.41") - v, err = apiClient.VolumeCreate(ctx, volume.CreateOptions{}) + created, err = apiClient.VolumeCreate(ctx, client.VolumeCreateOptions{}) assert.NilError(t, err) - vNamed, err = apiClient.VolumeCreate(ctx, volume.CreateOptions{Name: "test-api141"}) + anonV = created.Volume + created, err = apiClient.VolumeCreate(ctx, client.VolumeCreateOptions{Name: "test-api141"}) assert.NilError(t, err) + namedV = created.Volume - res, err = clientOld.VolumesPrune(ctx, client.VolumePruneOptions{}) + prune, err = clientOld.VolumesPrune(ctx, client.VolumePruneOptions{}) assert.NilError(t, err) - assert.Check(t, is.Equal(len(res.Report.VolumesDeleted), 2)) - assert.Check(t, is.Contains(res.Report.VolumesDeleted, v.Name)) - assert.Check(t, is.Contains(res.Report.VolumesDeleted, vNamed.Name)) + report = prune.Report + assert.Check(t, is.Equal(len(report.VolumesDeleted), 2)) + assert.Check(t, is.Contains(report.VolumesDeleted, anonV.Name)) + assert.Check(t, is.Contains(report.VolumesDeleted, namedV.Name)) } func TestVolumePruneAnonFromImage(t *testing.T) { diff --git a/vendor/github.com/moby/moby/client/client_interfaces.go b/vendor/github.com/moby/moby/client/client_interfaces.go index 48b4e7c5f8..b9d18f827b 100644 --- a/vendor/github.com/moby/moby/client/client_interfaces.go +++ b/vendor/github.com/moby/moby/client/client_interfaces.go @@ -14,7 +14,6 @@ import ( "github.com/moby/moby/api/types/registry" "github.com/moby/moby/api/types/swarm" "github.com/moby/moby/api/types/system" - "github.com/moby/moby/api/types/volume" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) @@ -195,7 +194,7 @@ type SystemAPIClient interface { // VolumeAPIClient defines API client methods for the volumes type VolumeAPIClient interface { - VolumeCreate(ctx context.Context, options volume.CreateOptions) (volume.Volume, error) + VolumeCreate(ctx context.Context, options VolumeCreateOptions) (VolumeCreateResult, error) VolumeInspect(ctx context.Context, volumeID string) (VolumeInspectResult, error) VolumeInspectWithRaw(ctx context.Context, volumeID string) (VolumeInspectResult, []byte, error) VolumeList(ctx context.Context, options VolumeListOptions) (VolumeListResult, error) diff --git a/vendor/github.com/moby/moby/client/volume_create.go b/vendor/github.com/moby/moby/client/volume_create.go index dcbd453c57..7a03de15de 100644 --- a/vendor/github.com/moby/moby/client/volume_create.go +++ b/vendor/github.com/moby/moby/client/volume_create.go @@ -7,15 +7,36 @@ import ( "github.com/moby/moby/api/types/volume" ) +// VolumeCreateOptions specifies the options to create a volume. +type VolumeCreateOptions struct { + Name string + Driver string + DriverOpts map[string]string + Labels map[string]string + ClusterVolumeSpec *volume.ClusterVolumeSpec +} + +// VolumeCreateResult is the result of a volume creation. +type VolumeCreateResult struct { + Volume volume.Volume +} + // VolumeCreate creates a volume in the docker host. -func (cli *Client) VolumeCreate(ctx context.Context, options volume.CreateOptions) (volume.Volume, error) { - resp, err := cli.post(ctx, "/volumes/create", nil, options, nil) +func (cli *Client) VolumeCreate(ctx context.Context, options VolumeCreateOptions) (VolumeCreateResult, error) { + createRequest := volume.CreateOptions{ + Name: options.Name, + Driver: options.Driver, + DriverOpts: options.DriverOpts, + Labels: options.Labels, + ClusterVolumeSpec: options.ClusterVolumeSpec, + } + resp, err := cli.post(ctx, "/volumes/create", nil, createRequest, nil) defer ensureReaderClosed(resp) if err != nil { - return volume.Volume{}, err + return VolumeCreateResult{}, err } - var vol volume.Volume - err = json.NewDecoder(resp.Body).Decode(&vol) - return vol, err + var v volume.Volume + err = json.NewDecoder(resp.Body).Decode(&v) + return VolumeCreateResult{Volume: v}, err }