Merge pull request #51285 from thaJeztah/client_fixes

client: assorted fixes and refactor
This commit is contained in:
Sebastiaan van Stijn
2025-10-24 18:44:10 +02:00
committed by GitHub
46 changed files with 189 additions and 162 deletions

View File

@@ -26,7 +26,7 @@ type ExecCreateOptions struct {
// ExecCreateResult holds the result of creating a container exec.
type ExecCreateResult struct {
container.ExecCreateResponse
ID string
}
// ExecCreate creates a new exec configuration to run an exec process.
@@ -58,7 +58,7 @@ func (cli *Client) ExecCreate(ctx context.Context, containerID string, options E
var response container.ExecCreateResponse
err = json.NewDecoder(resp.Body).Decode(&response)
return ExecCreateResult{ExecCreateResponse: response}, err
return ExecCreateResult{ID: response.ID}, err
}
type execStartAttachOptions struct {
@@ -127,26 +127,21 @@ func (cli *Client) ExecAttach(ctx context.Context, execID string, options ExecAt
return ExecAttachResult{HijackedResponse: response}, err
}
// ExecInspect holds information returned by exec inspect.
//
// It provides a subset of the information included in [container.ExecInspectResponse].
//
// TODO(thaJeztah): include all fields of [container.ExecInspectResponse] ?
type ExecInspect struct {
ExecID string `json:"ID"`
ContainerID string `json:"ContainerID"`
Running bool `json:"Running"`
ExitCode int `json:"ExitCode"`
Pid int `json:"Pid"`
}
// ExecInspectOptions holds options for inspecting a container exec.
type ExecInspectOptions struct {
}
// ExecInspectResult holds the result of inspecting a container exec.
//
// It provides a subset of the information included in [container.ExecInspectResponse].
//
// TODO(thaJeztah): include all fields of [container.ExecInspectResponse] ?
type ExecInspectResult struct {
ExecInspect
ID string
ContainerID string
Running bool
ExitCode int
PID int
}
// ExecInspect returns information about a specific exec process on the docker host.
@@ -168,11 +163,11 @@ func (cli *Client) ExecInspect(ctx context.Context, execID string, options ExecI
ec = *response.ExitCode
}
return ExecInspectResult{ExecInspect: ExecInspect{
ExecID: response.ID,
return ExecInspectResult{
ID: response.ID,
ContainerID: response.ContainerID,
Running: response.Running,
ExitCode: ec,
Pid: response.Pid,
}}, nil
PID: response.Pid,
}, nil
}

View File

@@ -68,11 +68,11 @@ func TestExecCreate(t *testing.T) {
)
assert.NilError(t, err)
r, err := client.ExecCreate(context.Background(), "container_id", ExecCreateOptions{
res, err := client.ExecCreate(context.Background(), "container_id", ExecCreateOptions{
User: "user",
})
assert.NilError(t, err)
assert.Check(t, is.Equal(r.ID, "exec_id"))
assert.Check(t, is.Equal(res.ID, "exec_id"))
}
func TestExecStartError(t *testing.T) {
@@ -141,6 +141,6 @@ func TestExecInspect(t *testing.T) {
inspect, err := client.ExecInspect(context.Background(), "exec_id", ExecInspectOptions{})
assert.NilError(t, err)
assert.Check(t, is.Equal(inspect.ExecID, "exec_id"))
assert.Check(t, is.Equal(inspect.ID, "exec_id"))
assert.Check(t, is.Equal(inspect.ContainerID, "container_id"))
}

View File

@@ -42,5 +42,5 @@ func (cli *Client) ImageImport(ctx context.Context, source ImageImportSource, re
if err != nil {
return ImageImportResult{}, err
}
return ImageImportResult{body: resp.Body}, nil
return ImageImportResult{rc: resp.Body}, nil
}

View File

@@ -20,16 +20,19 @@ type ImageImportOptions struct {
// ImageImportResult holds the response body returned by the daemon for image import.
type ImageImportResult struct {
body io.ReadCloser
rc io.ReadCloser
}
func (r ImageImportResult) Read(p []byte) (n int, err error) {
return r.body.Read(p)
if r.rc == nil {
return 0, io.EOF
}
return r.rc.Read(p)
}
func (r ImageImportResult) Close() error {
if r.body == nil {
if r.rc == nil {
return nil
}
return r.body.Close()
return r.rc.Close()
}

View File

@@ -35,5 +35,5 @@ func (cli *Client) ImageRemove(ctx context.Context, imageID string, options Imag
var dels []image.DeleteResponse
err = json.NewDecoder(resp.Body).Decode(&dels)
return ImageRemoveResult{Deleted: dels}, err
return ImageRemoveResult{Items: dels}, err
}

View File

@@ -14,5 +14,5 @@ type ImageRemoveOptions struct {
// ImageRemoveResult holds the delete responses returned by the daemon.
type ImageRemoveResult struct {
Deleted []image.DeleteResponse
Items []image.DeleteResponse
}

View File

@@ -91,8 +91,8 @@ func TestImageRemove(t *testing.T) {
opts.Platforms = []ocispec.Platform{*removeCase.platform}
}
imageDeletes, err := client.ImageRemove(context.Background(), "image_id", opts)
res, err := client.ImageRemove(context.Background(), "image_id", opts)
assert.NilError(t, err)
assert.Check(t, is.Len(imageDeletes.Deleted, 2))
assert.Check(t, is.Len(res.Items, 2))
}
}

View File

@@ -9,6 +9,9 @@ import (
"github.com/moby/moby/api/types/swarm"
)
// NodeInspectOptions holds parameters to inspect nodes with.
type NodeInspectOptions struct{}
type NodeInspectResult struct {
Node swarm.Node
Raw []byte

View File

@@ -8,6 +8,11 @@ import (
"github.com/moby/moby/api/types/swarm"
)
// NodeListOptions holds parameters to list nodes with.
type NodeListOptions struct {
Filters Filters
}
type NodeListResult struct {
Items []swarm.Node
}

View File

@@ -5,6 +5,10 @@ import (
"net/url"
)
// NodeRemoveOptions holds parameters to remove nodes with.
type NodeRemoveOptions struct {
Force bool
}
type NodeRemoveResult struct{}
// NodeRemove removes a Node.

View File

@@ -3,8 +3,16 @@ package client
import (
"context"
"net/url"
"github.com/moby/moby/api/types/swarm"
)
// NodeUpdateOptions holds parameters to update nodes with.
type NodeUpdateOptions struct {
Version swarm.Version
Spec swarm.NodeSpec
}
type NodeUpdateResult struct{}
// NodeUpdate updates a Node.
@@ -16,7 +24,7 @@ func (cli *Client) NodeUpdate(ctx context.Context, nodeID string, options NodeUp
query := url.Values{}
query.Set("version", options.Version.String())
resp, err := cli.post(ctx, "/nodes/"+nodeID+"/update", query, options.Node, nil)
resp, err := cli.post(ctx, "/nodes/"+nodeID+"/update", query, options.Spec, nil)
defer ensureReaderClosed(resp)
return NodeUpdateResult{}, err
}

View File

@@ -17,20 +17,20 @@ func TestNodeUpdateError(t *testing.T) {
_, err = client.NodeUpdate(context.Background(), "node_id", NodeUpdateOptions{
Version: swarm.Version{},
Node: swarm.NodeSpec{},
Spec: swarm.NodeSpec{},
})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.NodeUpdate(context.Background(), "", NodeUpdateOptions{
Version: swarm.Version{},
Node: swarm.NodeSpec{},
Spec: swarm.NodeSpec{},
})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.NodeUpdate(context.Background(), " ", NodeUpdateOptions{
Version: swarm.Version{},
Node: swarm.NodeSpec{},
Spec: swarm.NodeSpec{},
})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty"))
@@ -49,7 +49,7 @@ func TestNodeUpdate(t *testing.T) {
_, err = client.NodeUpdate(context.Background(), "node_id", NodeUpdateOptions{
Version: swarm.Version{},
Node: swarm.NodeSpec{},
Spec: swarm.NodeSpec{},
})
assert.NilError(t, err)
}

View File

@@ -1,4 +0,0 @@
package client
// NodeInspectOptions holds parameters to inspect nodes with.
type NodeInspectOptions struct{}

View File

@@ -1,6 +0,0 @@
package client
// NodeListOptions holds parameters to list nodes with.
type NodeListOptions struct {
Filters Filters
}

View File

@@ -1,6 +0,0 @@
package client
// NodeRemoveOptions holds parameters to remove nodes with.
type NodeRemoveOptions struct {
Force bool
}

View File

@@ -1,9 +0,0 @@
package client
import "github.com/moby/moby/api/types/swarm"
// NodeUpdateOptions holds parameters to update nodes with.
type NodeUpdateOptions struct {
Version swarm.Version
Node swarm.NodeSpec
}

View File

@@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"net/url"
"slices"
"github.com/moby/moby/api/types/volume"
)
@@ -15,7 +16,11 @@ type VolumeListOptions struct {
// VolumeListResult holds the result from the [Client.VolumeList] method.
type VolumeListResult struct {
Items volume.ListResponse
// List of volumes.
Items []volume.Volume
// Warnings that occurred when fetching the list of volumes.
Warnings []string
}
// VolumeList returns the volumes configured in the docker host.
@@ -29,7 +34,22 @@ func (cli *Client) VolumeList(ctx context.Context, options VolumeListOptions) (V
return VolumeListResult{}, err
}
var res VolumeListResult
err = json.NewDecoder(resp.Body).Decode(&res.Items)
return res, err
var apiResp volume.ListResponse
err = json.NewDecoder(resp.Body).Decode(&apiResp)
if err != nil {
return VolumeListResult{}, err
}
res := VolumeListResult{
Items: make([]volume.Volume, 0, len(apiResp.Volumes)),
Warnings: slices.Clone(apiResp.Warnings),
}
for _, vol := range apiResp.Volumes {
if vol != nil {
res.Items = append(res.Items, *vol)
}
}
return res, nil
}

View File

@@ -64,6 +64,6 @@ func TestVolumeList(t *testing.T) {
result, err := client.VolumeList(context.Background(), VolumeListOptions{Filters: listCase.filters})
assert.NilError(t, err)
assert.Check(t, is.Len(result.Items.Volumes, 1))
assert.Check(t, is.Len(result.Items, 1))
}
}

View File

@@ -128,13 +128,13 @@ func (s *DockerAPISuite) TestExecAPIStartWithDetach(c *testing.T) {
assert.NilError(c, err)
defer apiClient.Close()
createResp, err := apiClient.ExecCreate(ctx, name, client.ExecCreateOptions{
res, err := apiClient.ExecCreate(ctx, name, client.ExecCreateOptions{
Cmd: []string{"true"},
AttachStderr: true,
})
assert.NilError(c, err)
_, body, err := request.Post(ctx, fmt.Sprintf("/exec/%s/start", createResp.ID), request.RawString(`{"Detach": true}`), request.JSON)
_, body, err := request.Post(ctx, fmt.Sprintf("/exec/%s/start", res.ID), request.RawString(`{"Detach": true}`), request.JSON)
assert.NilError(c, err)
b, err := request.ReadBody(body)

View File

@@ -802,7 +802,7 @@ func TestBuildHistoryDoesNotPreventRemoval(t *testing.T) {
res, err := apiClient.ImageRemove(ctx, "history-a", client.ImageRemoveOptions{})
assert.NilError(t, err)
assert.Check(t, slices.ContainsFunc(res.Deleted, func(r image.DeleteResponse) bool {
assert.Check(t, slices.ContainsFunc(res.Items, func(r image.DeleteResponse) bool {
return r.Deleted != ""
}))
}

View File

@@ -33,14 +33,14 @@ func TestExecWithCloseStdin(t *testing.T) {
cID := container.Run(ctx, t, apiClient)
const expected = "closeIO"
execResp, err := apiClient.ExecCreate(ctx, cID, client.ExecCreateOptions{
res, err := apiClient.ExecCreate(ctx, cID, client.ExecCreateOptions{
AttachStdin: true,
AttachStdout: true,
Cmd: []string{"sh", "-c", "cat && echo " + expected},
})
assert.NilError(t, err)
resp, err := apiClient.ExecAttach(ctx, execResp.ID, client.ExecAttachOptions{})
resp, err := apiClient.ExecAttach(ctx, res.ID, client.ExecAttachOptions{})
assert.NilError(t, err)
defer resp.Close()
@@ -88,7 +88,7 @@ func TestExec(t *testing.T) {
cID := container.Run(ctx, t, apiClient, container.WithTty(true), container.WithWorkingDir("/root"))
id, err := apiClient.ExecCreate(ctx, cID, client.ExecCreateOptions{
res, err := apiClient.ExecCreate(ctx, cID, client.ExecCreateOptions{
WorkingDir: "/tmp",
Env: []string{"FOO=BAR"},
AttachStdout: true,
@@ -96,11 +96,11 @@ func TestExec(t *testing.T) {
})
assert.NilError(t, err)
inspect, err := apiClient.ExecInspect(ctx, id.ID, client.ExecInspectOptions{})
inspect, err := apiClient.ExecInspect(ctx, res.ID, client.ExecInspectOptions{})
assert.NilError(t, err)
assert.Check(t, is.Equal(inspect.ExecID, id.ID))
assert.Check(t, is.Equal(inspect.ID, res.ID))
resp, err := apiClient.ExecAttach(ctx, id.ID, client.ExecAttachOptions{})
resp, err := apiClient.ExecAttach(ctx, res.ID, client.ExecAttachOptions{})
assert.NilError(t, err)
defer resp.Close()
r, err := io.ReadAll(resp.Reader)
@@ -126,12 +126,12 @@ func TestExecResize(t *testing.T) {
if runtime.GOOS == "windows" {
cmd = []string{"sleep", "240"}
}
resp, err := apiClient.ExecCreate(ctx, cID, client.ExecCreateOptions{
res, err := apiClient.ExecCreate(ctx, cID, client.ExecCreateOptions{
Tty: true, // Windows requires a TTY for the resize to work, otherwise fails with "is not a tty: failed precondition", see https://github.com/moby/moby/pull/48665#issuecomment-2412530345
Cmd: cmd,
})
assert.NilError(t, err)
execID := resp.ID
execID := res.ID
assert.NilError(t, err)
_, err = apiClient.ExecStart(ctx, execID, client.ExecStartOptions{
Detach: true,

View File

@@ -78,7 +78,7 @@ func TestAPIImageHistoryCrossPlatform(t *testing.T) {
imgID := build.GetImageIDFromBody(t, resp.Body)
t.Cleanup(func() {
apiClient.ImageRemove(ctx, imgID, client.ImageRemoveOptions{Force: true})
_, _ = apiClient.ImageRemove(ctx, imgID, client.ImageRemoveOptions{Force: true})
})
testCases := []struct {

View File

@@ -147,20 +147,20 @@ func TestRemoveWithPlatform(t *testing.T) {
Force: true,
})
assert.NilError(t, err)
assert.Check(t, is.Len(res.Deleted, 1))
for _, r := range res.Deleted {
assert.Check(t, is.Len(res.Items, 1))
for _, r := range res.Items {
assert.Check(t, is.Equal(r.Untagged, ""), "No image should be untagged")
}
checkPlatformDeleted(t, imageIdx, res.Deleted, tc.deleted)
checkPlatformDeleted(t, imageIdx, res.Items, tc.deleted)
}
// Delete the rest
resp, err := apiClient.ImageRemove(ctx, imgName, client.ImageRemoveOptions{})
assert.NilError(t, err)
assert.Check(t, is.Len(resp.Deleted, 2))
assert.Check(t, is.Equal(resp.Deleted[0].Untagged, imgName))
assert.Check(t, is.Equal(resp.Deleted[1].Deleted, imageIdx.Manifests[0].Digest.String()))
assert.Check(t, is.Len(resp.Items, 2))
assert.Check(t, is.Equal(resp.Items[0].Untagged, imgName))
assert.Check(t, is.Equal(resp.Items[1].Deleted, imageIdx.Manifests[0].Digest.String()))
// TODO(vvoland): Should it also include platform-specific manifests? https://github.com/moby/moby/pull/49982
}

View File

@@ -19,13 +19,13 @@ import (
// Do builds an image from the given context and returns the image ID.
func Do(ctx context.Context, t *testing.T, apiClient client.APIClient, buildCtx *fakecontext.Fake) string {
resp, err := apiClient.ImageBuild(ctx, buildCtx.AsTarReader(t), client.ImageBuildOptions{})
assert.NilError(t, err)
if resp.Body != nil {
defer resp.Body.Close()
}
assert.NilError(t, err)
img := GetImageIDFromBody(t, resp.Body)
t.Cleanup(func() {
apiClient.ImageRemove(ctx, img, client.ImageRemoveOptions{Force: true})
_, _ = apiClient.ImageRemove(ctx, img, client.ImageRemoveOptions{Force: true})
})
return img
}

View File

@@ -58,11 +58,11 @@ func Exec(ctx context.Context, apiClient client.APIClient, id string, cmd []stri
op(&execOptions)
}
cresp, err := apiClient.ExecCreate(ctx, id, execOptions)
res, err := apiClient.ExecCreate(ctx, id, execOptions)
if err != nil {
return ExecResult{}, err
}
execID := cresp.ID
execID := res.ID
// run it, with stdout/stderr attached
aresp, err := apiClient.ExecAttach(ctx, execID, client.ExecAttachOptions{})
@@ -77,12 +77,12 @@ func Exec(ctx context.Context, apiClient client.APIClient, id string, cmd []stri
}
// get the exit code
iresp, err := apiClient.ExecInspect(ctx, execID, client.ExecInspectOptions{})
inspect, err := apiClient.ExecInspect(ctx, execID, client.ExecInspectOptions{})
if err != nil {
return ExecResult{}, err
}
return ExecResult{ExitCode: iresp.ExitCode, outBuffer: &s.stdout, errBuffer: &s.stderr}, nil
return ExecResult{ExitCode: inspect.ExitCode, outBuffer: &s.stdout, errBuffer: &s.stderr}, nil
}
// ExecT calls Exec() and aborts the test if an error occurs.

View File

@@ -237,10 +237,10 @@ func ExecTask(ctx context.Context, t *testing.T, d *daemon.Daemon, task swarmtyp
apiClient := d.NewClientT(t)
defer apiClient.Close()
resp, err := apiClient.ExecCreate(ctx, task.Status.ContainerStatus.ContainerID, options)
res, err := apiClient.ExecCreate(ctx, task.Status.ContainerStatus.ContainerID, options)
assert.NilError(t, err, "error creating exec")
attach, err := apiClient.ExecAttach(ctx, resp.ID, client.ExecAttachOptions{})
attach, err := apiClient.ExecAttach(ctx, res.ID, client.ExecAttachOptions{})
assert.NilError(t, err, "error attaching to exec")
return attach.HijackedResponse
}

View File

@@ -25,7 +25,7 @@ func TestEventsExecDie(t *testing.T) {
cID := container.Run(ctx, t, apiClient)
id, err := apiClient.ExecCreate(ctx, cID, client.ExecCreateOptions{
res, err := apiClient.ExecCreate(ctx, cID, client.ExecCreateOptions{
Cmd: []string{"echo", "hello"},
})
assert.NilError(t, err)
@@ -34,7 +34,7 @@ func TestEventsExecDie(t *testing.T) {
Filters: make(client.Filters).Add("container", cID).Add("event", string(events.ActionExecDie)),
})
_, err = apiClient.ExecStart(ctx, id.ID, client.ExecStartOptions{
_, err = apiClient.ExecStart(ctx, res.ID, client.ExecStartOptions{
Detach: true,
Tty: false,
})
@@ -45,7 +45,7 @@ func TestEventsExecDie(t *testing.T) {
assert.Equal(t, m.Type, events.ContainerEventType)
assert.Equal(t, m.Actor.ID, cID)
assert.Equal(t, m.Action, events.ActionExecDie)
assert.Equal(t, m.Actor.Attributes["execID"], id.ID)
assert.Equal(t, m.Actor.Attributes["execID"], res.ID)
assert.Equal(t, m.Actor.Attributes["exitCode"], "0")
case err = <-errs:
assert.NilError(t, err)

View File

@@ -151,7 +151,9 @@ func TestRunMountImage(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
testImage := setupTestImage(t, ctx, apiClient, tc.name)
if testImage != "" {
defer apiClient.ImageRemove(ctx, testImage, client.ImageRemoveOptions{Force: true})
defer func() {
_, _ = apiClient.ImageRemove(ctx, testImage, client.ImageRemoveOptions{Force: true})
}()
}
cfg := containertypes.Config{

View File

@@ -50,18 +50,17 @@ func TestVolumesCreateAndList(t *testing.T) {
res, err := apiClient.VolumeList(ctx, client.VolumeListOptions{})
assert.NilError(t, err)
assert.Assert(t, len(res.Items.Volumes) > 0)
assert.Assert(t, len(res.Items) > 0)
volumes := res.Items.Volumes[:0]
for _, v := range res.Items.Volumes {
volumes := res.Items[:0]
for _, v := range res.Items {
if v.Name == namedV.Name {
volumes = append(volumes, v)
}
}
assert.Check(t, is.Equal(len(volumes), 1))
assert.Check(t, volumes[0] != nil)
assert.Check(t, is.DeepEqual(*volumes[0], expected, cmpopts.EquateEmpty()))
assert.Check(t, is.DeepEqual(volumes[0], expected, cmpopts.EquateEmpty()))
}
func TestVolumesRemove(t *testing.T) {

View File

@@ -60,7 +60,7 @@ func (d *Daemon) UpdateNode(ctx context.Context, t testing.TB, id string, f ...N
_, err := cli.NodeUpdate(ctx, node.ID, client.NodeUpdateOptions{
Version: node.Version,
Node: node.Spec,
Spec: node.Spec,
})
if i < 10 && err != nil && strings.Contains(err.Error(), "update out of sequence") {
time.Sleep(100 * time.Millisecond)

View File

@@ -130,7 +130,7 @@ func deleteAllVolumes(ctx context.Context, t testing.TB, c client.VolumeAPIClien
res, err := c.VolumeList(ctx, client.VolumeListOptions{})
assert.Check(t, err, "failed to list volumes")
for _, v := range res.Items.Volumes {
for _, v := range res.Items {
if _, ok := protectedVolumes[v.Name]; ok {
continue
}

View File

@@ -231,7 +231,7 @@ func getExistingVolumes(ctx context.Context, t testing.TB, testEnv *Execution) [
assert.NilError(t, err, "failed to list volumes")
var volumes []string
for _, vol := range res.Items.Volumes {
for _, vol := range res.Items {
volumes = append(volumes, vol.Name)
}
return volumes

View File

@@ -26,7 +26,7 @@ type ExecCreateOptions struct {
// ExecCreateResult holds the result of creating a container exec.
type ExecCreateResult struct {
container.ExecCreateResponse
ID string
}
// ExecCreate creates a new exec configuration to run an exec process.
@@ -58,7 +58,7 @@ func (cli *Client) ExecCreate(ctx context.Context, containerID string, options E
var response container.ExecCreateResponse
err = json.NewDecoder(resp.Body).Decode(&response)
return ExecCreateResult{ExecCreateResponse: response}, err
return ExecCreateResult{ID: response.ID}, err
}
type execStartAttachOptions struct {
@@ -127,26 +127,21 @@ func (cli *Client) ExecAttach(ctx context.Context, execID string, options ExecAt
return ExecAttachResult{HijackedResponse: response}, err
}
// ExecInspect holds information returned by exec inspect.
//
// It provides a subset of the information included in [container.ExecInspectResponse].
//
// TODO(thaJeztah): include all fields of [container.ExecInspectResponse] ?
type ExecInspect struct {
ExecID string `json:"ID"`
ContainerID string `json:"ContainerID"`
Running bool `json:"Running"`
ExitCode int `json:"ExitCode"`
Pid int `json:"Pid"`
}
// ExecInspectOptions holds options for inspecting a container exec.
type ExecInspectOptions struct {
}
// ExecInspectResult holds the result of inspecting a container exec.
//
// It provides a subset of the information included in [container.ExecInspectResponse].
//
// TODO(thaJeztah): include all fields of [container.ExecInspectResponse] ?
type ExecInspectResult struct {
ExecInspect
ID string
ContainerID string
Running bool
ExitCode int
PID int
}
// ExecInspect returns information about a specific exec process on the docker host.
@@ -168,11 +163,11 @@ func (cli *Client) ExecInspect(ctx context.Context, execID string, options ExecI
ec = *response.ExitCode
}
return ExecInspectResult{ExecInspect: ExecInspect{
ExecID: response.ID,
return ExecInspectResult{
ID: response.ID,
ContainerID: response.ContainerID,
Running: response.Running,
ExitCode: ec,
Pid: response.Pid,
}}, nil
PID: response.Pid,
}, nil
}

View File

@@ -42,5 +42,5 @@ func (cli *Client) ImageImport(ctx context.Context, source ImageImportSource, re
if err != nil {
return ImageImportResult{}, err
}
return ImageImportResult{body: resp.Body}, nil
return ImageImportResult{rc: resp.Body}, nil
}

View File

@@ -20,16 +20,19 @@ type ImageImportOptions struct {
// ImageImportResult holds the response body returned by the daemon for image import.
type ImageImportResult struct {
body io.ReadCloser
rc io.ReadCloser
}
func (r ImageImportResult) Read(p []byte) (n int, err error) {
return r.body.Read(p)
if r.rc == nil {
return 0, io.EOF
}
return r.rc.Read(p)
}
func (r ImageImportResult) Close() error {
if r.body == nil {
if r.rc == nil {
return nil
}
return r.body.Close()
return r.rc.Close()
}

View File

@@ -35,5 +35,5 @@ func (cli *Client) ImageRemove(ctx context.Context, imageID string, options Imag
var dels []image.DeleteResponse
err = json.NewDecoder(resp.Body).Decode(&dels)
return ImageRemoveResult{Deleted: dels}, err
return ImageRemoveResult{Items: dels}, err
}

View File

@@ -14,5 +14,5 @@ type ImageRemoveOptions struct {
// ImageRemoveResult holds the delete responses returned by the daemon.
type ImageRemoveResult struct {
Deleted []image.DeleteResponse
Items []image.DeleteResponse
}

View File

@@ -9,6 +9,9 @@ import (
"github.com/moby/moby/api/types/swarm"
)
// NodeInspectOptions holds parameters to inspect nodes with.
type NodeInspectOptions struct{}
type NodeInspectResult struct {
Node swarm.Node
Raw []byte

View File

@@ -8,6 +8,11 @@ import (
"github.com/moby/moby/api/types/swarm"
)
// NodeListOptions holds parameters to list nodes with.
type NodeListOptions struct {
Filters Filters
}
type NodeListResult struct {
Items []swarm.Node
}

View File

@@ -5,6 +5,10 @@ import (
"net/url"
)
// NodeRemoveOptions holds parameters to remove nodes with.
type NodeRemoveOptions struct {
Force bool
}
type NodeRemoveResult struct{}
// NodeRemove removes a Node.

View File

@@ -3,8 +3,16 @@ package client
import (
"context"
"net/url"
"github.com/moby/moby/api/types/swarm"
)
// NodeUpdateOptions holds parameters to update nodes with.
type NodeUpdateOptions struct {
Version swarm.Version
Spec swarm.NodeSpec
}
type NodeUpdateResult struct{}
// NodeUpdate updates a Node.
@@ -16,7 +24,7 @@ func (cli *Client) NodeUpdate(ctx context.Context, nodeID string, options NodeUp
query := url.Values{}
query.Set("version", options.Version.String())
resp, err := cli.post(ctx, "/nodes/"+nodeID+"/update", query, options.Node, nil)
resp, err := cli.post(ctx, "/nodes/"+nodeID+"/update", query, options.Spec, nil)
defer ensureReaderClosed(resp)
return NodeUpdateResult{}, err
}

View File

@@ -1,4 +0,0 @@
package client
// NodeInspectOptions holds parameters to inspect nodes with.
type NodeInspectOptions struct{}

View File

@@ -1,6 +0,0 @@
package client
// NodeListOptions holds parameters to list nodes with.
type NodeListOptions struct {
Filters Filters
}

View File

@@ -1,6 +0,0 @@
package client
// NodeRemoveOptions holds parameters to remove nodes with.
type NodeRemoveOptions struct {
Force bool
}

View File

@@ -1,9 +0,0 @@
package client
import "github.com/moby/moby/api/types/swarm"
// NodeUpdateOptions holds parameters to update nodes with.
type NodeUpdateOptions struct {
Version swarm.Version
Node swarm.NodeSpec
}

View File

@@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"net/url"
"slices"
"github.com/moby/moby/api/types/volume"
)
@@ -15,7 +16,11 @@ type VolumeListOptions struct {
// VolumeListResult holds the result from the [Client.VolumeList] method.
type VolumeListResult struct {
Items volume.ListResponse
// List of volumes.
Items []volume.Volume
// Warnings that occurred when fetching the list of volumes.
Warnings []string
}
// VolumeList returns the volumes configured in the docker host.
@@ -29,7 +34,22 @@ func (cli *Client) VolumeList(ctx context.Context, options VolumeListOptions) (V
return VolumeListResult{}, err
}
var res VolumeListResult
err = json.NewDecoder(resp.Body).Decode(&res.Items)
return res, err
var apiResp volume.ListResponse
err = json.NewDecoder(resp.Body).Decode(&apiResp)
if err != nil {
return VolumeListResult{}, err
}
res := VolumeListResult{
Items: make([]volume.Volume, 0, len(apiResp.Volumes)),
Warnings: slices.Clone(apiResp.Warnings),
}
for _, vol := range apiResp.Volumes {
if vol != nil {
res.Items = append(res.Items, *vol)
}
}
return res, nil
}