client: wrap volume create api options with client options

Signed-off-by: Austin Vazquez <austin.vazquez@docker.com>
This commit is contained in:
Austin Vazquez
2025-10-20 07:47:01 -05:00
parent 2ba58d3c7f
commit a2fd724453
10 changed files with 108 additions and 61 deletions

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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"))
}

View File

@@ -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,

View File

@@ -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))

View File

@@ -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).

View File

@@ -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")

View File

@@ -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) {

View File

@@ -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)

View File

@@ -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
}