vendor: github.com/moby/moby/api, moby/moby/client master

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn
2025-10-24 22:08:17 +02:00
parent f74cd147bb
commit 056e314645
92 changed files with 639 additions and 580 deletions

View File

@ -115,7 +115,7 @@ func VolumeNames(dockerCLI APIClientProvider) cobra.CompletionFunc {
return nil, cobra.ShellCompDirectiveError
}
var names []string
for _, vol := range res.Items.Volumes {
for _, vol := range res.Items {
names = append(names, vol.Name)
}
return names, cobra.ShellCompDirectiveNoFileComp

View File

@ -310,7 +310,7 @@ func TestCompletePlatforms(t *testing.T) {
func TestCompleteVolumeNames(t *testing.T) {
tests := []struct {
doc string
volumes []*volume.Volume
volumes []volume.Volume
expOut []string
expDirective cobra.ShellCompDirective
}{
@ -320,7 +320,7 @@ func TestCompleteVolumeNames(t *testing.T) {
},
{
doc: "with results",
volumes: []*volume.Volume{
volumes: []volume.Volume{
{Name: "volume-c"},
{Name: "volume-b"},
{Name: "volume-a"},
@ -341,7 +341,7 @@ func TestCompleteVolumeNames(t *testing.T) {
if tc.expDirective == cobra.ShellCompDirectiveError {
return client.VolumeListResult{}, errors.New("some error occurred")
}
return client.VolumeListResult{Items: volume.ListResponse{Volumes: tc.volumes}}, nil
return client.VolumeListResult{Items: tc.volumes}, nil
},
}})

View File

@ -114,11 +114,11 @@ func RunAttach(ctx context.Context, dockerCLI command.Cli, containerID string, o
defer signal.StopCatch(sigc)
}
resp, errAttach := apiClient.ContainerAttach(ctx, containerID, options)
if errAttach != nil {
return errAttach
res, err := apiClient.ContainerAttach(ctx, containerID, options)
if err != nil {
return err
}
defer resp.Close()
defer res.HijackedResponse.Close()
// If use docker attach command to attach to a stop container, it will return
// "You cannot attach to a stopped container" error, it's ok, but when
@ -142,7 +142,7 @@ func RunAttach(ctx context.Context, dockerCLI command.Cli, containerID string, o
inputStream: in,
outputStream: dockerCLI.Out(),
errorStream: dockerCLI.Err(),
resp: resp,
resp: res.HijackedResponse,
tty: c.Config.Tty,
detachKeys: options.DetachKeys,
}

View File

@ -5,22 +5,16 @@ import (
"io"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/network"
"github.com/moby/moby/api/types/system"
"github.com/moby/moby/client"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
type fakeClient struct {
client.Client
inspectFunc func(string) (container.InspectResponse, error)
execInspectFunc func(execID string) (client.ExecInspectResult, error)
execCreateFunc func(containerID string, options client.ExecCreateOptions) (client.ExecCreateResult, error)
createContainerFunc func(config *container.Config,
hostConfig *container.HostConfig,
networkingConfig *network.NetworkingConfig,
platform *ocispec.Platform,
containerName string) (container.CreateResponse, error)
inspectFunc func(string) (container.InspectResponse, error)
execInspectFunc func(execID string) (client.ExecInspectResult, error)
execCreateFunc func(containerID string, options client.ExecCreateOptions) (client.ExecCreateResult, error)
createContainerFunc func(options client.ContainerCreateOptions) (client.ContainerCreateResult, error)
containerStartFunc func(containerID string, options client.ContainerStartOptions) error
imageCreateFunc func(ctx context.Context, parentReference string, options client.ImageCreateOptions) (client.ImageCreateResult, error)
infoFunc func() (system.Info, error)
@ -36,10 +30,10 @@ type fakeClient struct {
containerStopFunc func(ctx context.Context, containerID string, options client.ContainerStopOptions) error
containerKillFunc func(ctx context.Context, containerID, signal string) error
containerPruneFunc func(ctx context.Context, options client.ContainerPruneOptions) (client.ContainerPruneResult, error)
containerAttachFunc func(ctx context.Context, containerID string, options client.ContainerAttachOptions) (client.HijackedResponse, error)
containerDiffFunc func(ctx context.Context, containerID string) ([]container.FilesystemChange, error)
containerAttachFunc func(ctx context.Context, containerID string, options client.ContainerAttachOptions) (client.ContainerAttachResult, error)
containerDiffFunc func(ctx context.Context, containerID string) (client.ContainerDiffResult, error)
containerRenameFunc func(ctx context.Context, oldName, newName string) error
containerCommitFunc func(ctx context.Context, container string, options client.ContainerCommitOptions) (container.CommitResponse, error)
containerCommitFunc func(ctx context.Context, container string, options client.ContainerCommitOptions) (client.ContainerCommitResult, error)
containerPauseFunc func(ctx context.Context, container string) error
Version string
}
@ -76,18 +70,11 @@ func (*fakeClient) ExecStart(context.Context, string, client.ExecStartOptions) (
return client.ExecStartResult{}, nil
}
func (f *fakeClient) ContainerCreate(
_ context.Context,
config *container.Config,
hostConfig *container.HostConfig,
networkingConfig *network.NetworkingConfig,
platform *ocispec.Platform,
containerName string,
) (container.CreateResponse, error) {
func (f *fakeClient) ContainerCreate(_ context.Context, options client.ContainerCreateOptions) (client.ContainerCreateResult, error) {
if f.createContainerFunc != nil {
return f.createContainerFunc(config, hostConfig, networkingConfig, platform, containerName)
return f.createContainerFunc(options)
}
return container.CreateResponse{}, nil
return client.ContainerCreateResult{}, nil
}
func (f *fakeClient) ContainerRemove(ctx context.Context, containerID string, options client.ContainerRemoveOptions) error {
@ -192,19 +179,19 @@ func (f *fakeClient) ContainerStop(ctx context.Context, containerID string, opti
return nil
}
func (f *fakeClient) ContainerAttach(ctx context.Context, containerID string, options client.ContainerAttachOptions) (client.HijackedResponse, error) {
func (f *fakeClient) ContainerAttach(ctx context.Context, containerID string, options client.ContainerAttachOptions) (client.ContainerAttachResult, error) {
if f.containerAttachFunc != nil {
return f.containerAttachFunc(ctx, containerID, options)
}
return client.HijackedResponse{}, nil
return client.ContainerAttachResult{}, nil
}
func (f *fakeClient) ContainerDiff(ctx context.Context, containerID string) ([]container.FilesystemChange, error) {
func (f *fakeClient) ContainerDiff(ctx context.Context, containerID string, _ client.ContainerDiffOptions) (client.ContainerDiffResult, error) {
if f.containerDiffFunc != nil {
return f.containerDiffFunc(ctx, containerID)
}
return []container.FilesystemChange{}, nil
return client.ContainerDiffResult{}, nil
}
func (f *fakeClient) ContainerRename(ctx context.Context, oldName, newName string) error {
@ -215,11 +202,11 @@ func (f *fakeClient) ContainerRename(ctx context.Context, oldName, newName strin
return nil
}
func (f *fakeClient) ContainerCommit(ctx context.Context, containerID string, options client.ContainerCommitOptions) (container.CommitResponse, error) {
func (f *fakeClient) ContainerCommit(ctx context.Context, containerID string, options client.ContainerCommitOptions) (client.ContainerCommitResult, error) {
if f.containerCommitFunc != nil {
return f.containerCommitFunc(ctx, containerID, options)
}
return container.CommitResponse{}, nil
return client.ContainerCommitResult{}, nil
}
func (f *fakeClient) ContainerPause(ctx context.Context, containerID string) error {

View File

@ -7,7 +7,6 @@ import (
"testing"
"github.com/docker/cli/internal/test"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
@ -15,18 +14,14 @@ import (
func TestRunCommit(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
containerCommitFunc: func(
ctx context.Context,
ctr string,
options client.ContainerCommitOptions,
) (container.CommitResponse, error) {
containerCommitFunc: func(ctx context.Context, ctr string, options client.ContainerCommitOptions) (client.ContainerCommitResult, error) {
assert.Check(t, is.Equal(options.Author, "Author Name <author@name.com>"))
assert.Check(t, is.DeepEqual(options.Changes, []string{"EXPOSE 80"}))
assert.Check(t, is.Equal(options.Comment, "commit message"))
assert.Check(t, is.Equal(options.NoPause, true))
assert.Check(t, is.Equal(ctr, "container-id"))
return container.CommitResponse{ID: "image-id"}, nil
return client.ContainerCommitResult{ID: "image-id"}, nil
},
})
@ -52,12 +47,8 @@ func TestRunCommitClientError(t *testing.T) {
clientError := errors.New("client error")
cli := test.NewFakeCli(&fakeClient{
containerCommitFunc: func(
ctx context.Context,
ctr string,
options client.ContainerCommitOptions,
) (container.CommitResponse, error) {
return container.CommitResponse{}, clientError
containerCommitFunc: func(ctx context.Context, ctr string, options client.ContainerCommitOptions) (client.ContainerCommitResult, error) {
return client.ContainerCommitResult{}, clientError
},
})

View File

@ -332,7 +332,14 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = dockerCli.Out().GetTtySize()
response, err := dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, platform, options.name)
response, err := dockerCli.Client().ContainerCreate(ctx, client.ContainerCreateOptions{
Name: options.name,
// Image: config.Image, // TODO(thaJeztah): pass image-ref separate
Platform: platform,
Config: config,
HostConfig: hostConfig,
NetworkingConfig: networkingConfig,
})
if err != nil {
// Pull image if it does not exist locally and we have the PullImageMissing option. Default behavior.
if errdefs.IsNotFound(err) && namedRef != nil && options.pull == PullImageMissing {
@ -346,7 +353,14 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c
}
var retryErr error
response, retryErr = dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, platform, options.name)
response, retryErr = dockerCli.Client().ContainerCreate(ctx, client.ContainerCreateOptions{
Name: options.name,
// Image: config.Image, // TODO(thaJeztah): pass image-ref separate
Platform: platform,
Config: config,
HostConfig: hostConfig,
NetworkingConfig: networkingConfig,
})
if retryErr != nil {
return "", retryErr
}

View File

@ -16,10 +16,8 @@ import (
"github.com/docker/cli/internal/test/notary"
"github.com/google/go-cmp/cmp"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/network"
"github.com/moby/moby/api/types/system"
"github.com/moby/moby/client"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/spf13/pflag"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
@ -117,19 +115,13 @@ func TestCreateContainerImagePullPolicy(t *testing.T) {
pullCounter := 0
apiClient := &fakeClient{
createContainerFunc: func(
config *container.Config,
hostConfig *container.HostConfig,
networkingConfig *network.NetworkingConfig,
platform *ocispec.Platform,
containerName string,
) (container.CreateResponse, error) {
createContainerFunc: func(options client.ContainerCreateOptions) (client.ContainerCreateResult, error) {
defer func() { tc.ResponseCounter++ }()
switch tc.ResponseCounter {
case 0:
return container.CreateResponse{}, fakeNotFound{}
return client.ContainerCreateResult{}, fakeNotFound{}
default:
return container.CreateResponse{ID: containerID}, nil
return client.ContainerCreateResult{ID: containerID}, nil
}
},
imageCreateFunc: func(ctx context.Context, parentReference string, options client.ImageCreateOptions) (client.ImageCreateResult, error) {
@ -251,13 +243,8 @@ func TestNewCreateCommandWithContentTrustErrors(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
t.Setenv("DOCKER_CONTENT_TRUST", "true")
fakeCLI := test.NewFakeCli(&fakeClient{
createContainerFunc: func(config *container.Config,
hostConfig *container.HostConfig,
networkingConfig *network.NetworkingConfig,
platform *ocispec.Platform,
containerName string,
) (container.CreateResponse, error) {
return container.CreateResponse{}, errors.New("shouldn't try to pull image")
createContainerFunc: func(options client.ContainerCreateOptions) (client.ContainerCreateResult, error) {
return client.ContainerCreateResult{}, errors.New("shouldn't try to pull image")
},
})
fakeCLI.SetNotaryClient(tc.notaryFunc)
@ -296,13 +283,8 @@ func TestNewCreateCommandWithWarnings(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
fakeCLI := test.NewFakeCli(&fakeClient{
createContainerFunc: func(config *container.Config,
hostConfig *container.HostConfig,
networkingConfig *network.NetworkingConfig,
platform *ocispec.Platform,
containerName string,
) (container.CreateResponse, error) {
return container.CreateResponse{Warnings: tc.warnings}, nil
createContainerFunc: func(options client.ContainerCreateOptions) (client.ContainerCreateResult, error) {
return client.ContainerCreateResult{Warnings: tc.warnings}, nil
},
})
cmd := newCreateCommand(fakeCLI)
@ -335,15 +317,10 @@ func TestCreateContainerWithProxyConfig(t *testing.T) {
sort.Strings(expected)
fakeCLI := test.NewFakeCli(&fakeClient{
createContainerFunc: func(config *container.Config,
hostConfig *container.HostConfig,
networkingConfig *network.NetworkingConfig,
platform *ocispec.Platform,
containerName string,
) (container.CreateResponse, error) {
sort.Strings(config.Env)
assert.DeepEqual(t, config.Env, expected)
return container.CreateResponse{}, nil
createContainerFunc: func(options client.ContainerCreateOptions) (client.ContainerCreateResult, error) {
sort.Strings(options.Config.Env)
assert.DeepEqual(t, options.Config.Env, expected)
return client.ContainerCreateResult{}, nil
},
})
fakeCLI.SetConfigFile(&configfile.ConfigFile{

View File

@ -7,6 +7,7 @@ import (
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/cli/command/formatter"
"github.com/moby/moby/client"
"github.com/spf13/cobra"
)
@ -28,7 +29,7 @@ func newDiffCommand(dockerCLI command.Cli) *cobra.Command {
}
func runDiff(ctx context.Context, dockerCLI command.Cli, containerID string) error {
changes, err := dockerCLI.Client().ContainerDiff(ctx, containerID)
res, err := dockerCLI.Client().ContainerDiff(ctx, containerID, client.ContainerDiffOptions{})
if err != nil {
return err
}
@ -36,5 +37,5 @@ func runDiff(ctx context.Context, dockerCLI command.Cli, containerID string) err
Output: dockerCLI.Out(),
Format: newDiffFormat("{{.Type}} {{.Path}}"),
}
return diffFormatWrite(diffCtx, changes)
return diffFormatWrite(diffCtx, res)
}

View File

@ -9,28 +9,28 @@ import (
"github.com/docker/cli/internal/test"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
func TestRunDiff(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
containerDiffFunc: func(
ctx context.Context,
containerID string,
) ([]container.FilesystemChange, error) {
return []container.FilesystemChange{
{
Kind: container.ChangeModify,
Path: "/path/to/file0",
},
{
Kind: container.ChangeAdd,
Path: "/path/to/file1",
},
{
Kind: container.ChangeDelete,
Path: "/path/to/file2",
containerDiffFunc: func(ctx context.Context, containerID string) (client.ContainerDiffResult, error) {
return client.ContainerDiffResult{
Changes: []container.FilesystemChange{
{
Kind: container.ChangeModify,
Path: "/path/to/file0",
},
{
Kind: container.ChangeAdd,
Path: "/path/to/file1",
},
{
Kind: container.ChangeDelete,
Path: "/path/to/file2",
},
},
}, nil
},
@ -60,11 +60,8 @@ func TestRunDiffClientError(t *testing.T) {
clientError := errors.New("client error")
cli := test.NewFakeCli(&fakeClient{
containerDiffFunc: func(
ctx context.Context,
containerID string,
) ([]container.FilesystemChange, error) {
return nil, clientError
containerDiffFunc: func(ctx context.Context, containerID string) (client.ContainerDiffResult, error) {
return client.ContainerDiffResult{}, clientError
},
})

View File

@ -119,7 +119,7 @@ TWO=2
},
options: func() ExecOptions {
o := withDefaultOpts(ExecOptions{})
o.EnvFile.Set(tmpFile.Path())
_ = o.EnvFile.Set(tmpFile.Path())
return o
}(),
},
@ -132,8 +132,8 @@ TWO=2
},
options: func() ExecOptions {
o := withDefaultOpts(ExecOptions{})
o.EnvFile.Set(tmpFile.Path())
o.Env.Set("ONE=override")
_ = o.EnvFile.Set(tmpFile.Path())
_ = o.Env.Set("ONE=override")
return o
}(),
},
@ -150,7 +150,7 @@ TWO=2
func TestParseExecNoSuchFile(t *testing.T) {
execOpts := withDefaultOpts(ExecOptions{})
execOpts.EnvFile.Set("no-such-env-file")
assert.Check(t, execOpts.EnvFile.Set("no-such-env-file"))
execConfig, err := parseExec(execOpts, &configfile.ConfigFile{})
assert.ErrorContains(t, err, "no-such-env-file")
assert.Check(t, os.IsNotExist(err))
@ -195,7 +195,7 @@ func TestRunExec(t *testing.T) {
t.Run(testcase.doc, func(t *testing.T) {
fakeCLI := test.NewFakeCli(testcase.client)
err := RunExec(context.TODO(), fakeCLI, "thecontainer", testcase.options)
err := RunExec(context.TODO(), fakeCLI, "the-container", testcase.options)
if testcase.expectedError != "" {
assert.ErrorContains(t, err, testcase.expectedError)
} else if !assert.Check(t, err) {
@ -208,7 +208,7 @@ func TestRunExec(t *testing.T) {
}
func execCreateWithID(_ string, _ client.ExecCreateOptions) (client.ExecCreateResult, error) {
return client.ExecCreateResult{ExecCreateResponse: container.ExecCreateResponse{ID: "execid"}}, nil
return client.ExecCreateResult{ID: "exec-id"}, nil
}
func TestGetExecExitStatus(t *testing.T) {
@ -238,9 +238,7 @@ func TestGetExecExitStatus(t *testing.T) {
apiClient := &fakeClient{
execInspectFunc: func(id string) (client.ExecInspectResult, error) {
assert.Check(t, is.Equal(execID, id))
return client.ExecInspectResult{
ExecInspect: client.ExecInspect{ExitCode: testcase.exitCode},
}, testcase.inspectError
return client.ExecInspectResult{ExitCode: testcase.exitCode}, testcase.inspectError
},
}
err := getExecExitStatus(context.Background(), apiClient, execID)

View File

@ -3,6 +3,7 @@ package container
import (
"github.com/docker/cli/cli/command/formatter"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
)
const (
@ -21,9 +22,9 @@ func newDiffFormat(source string) formatter.Format {
}
// diffFormatWrite writes formatted diff using the [formatter.Context].
func diffFormatWrite(fmtCtx formatter.Context, changes []container.FilesystemChange) error {
func diffFormatWrite(fmtCtx formatter.Context, changes client.ContainerDiffResult) error {
return fmtCtx.Write(newDiffContext(), func(format func(subContext formatter.SubContext) error) error {
for _, change := range changes {
for _, change := range changes.Changes {
if err := format(&diffContext{c: change}); err != nil {
return err
}

View File

@ -6,6 +6,7 @@ import (
"github.com/docker/cli/cli/command/formatter"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"gotest.tools/v3/assert"
)
@ -40,10 +41,12 @@ D: /usr/app/old_app.js
},
}
diffs := []container.FilesystemChange{
{Kind: container.ChangeModify, Path: "/var/log/app.log"},
{Kind: container.ChangeAdd, Path: "/usr/app/app.js"},
{Kind: container.ChangeDelete, Path: "/usr/app/old_app.js"},
diffs := client.ContainerDiffResult{
Changes: []container.FilesystemChange{
{Kind: container.ChangeModify, Path: "/var/log/app.log"},
{Kind: container.ChangeAdd, Path: "/usr/app/app.js"},
{Kind: container.ChangeDelete, Path: "/usr/app/old_app.js"},
},
}
for _, tc := range cases {

View File

@ -290,7 +290,7 @@ func attachContainer(ctx context.Context, dockerCli command.Cli, containerID str
inputStream: in,
outputStream: out,
errorStream: cerr,
resp: resp,
resp: resp.HijackedResponse,
tty: config.Tty,
detachKeys: options.DetachKeys,
}
@ -301,7 +301,7 @@ func attachContainer(ctx context.Context, dockerCli command.Cli, containerID str
return errAttach
}()
}()
return resp.Close, nil
return resp.HijackedResponse.Close, nil
}
// withHelp decorates the error with a suggestion to use "--help".

View File

@ -18,9 +18,7 @@ import (
"github.com/moby/moby/api/pkg/streamformatter"
"github.com/moby/moby/api/types"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/network"
"github.com/moby/moby/client"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/spf13/pflag"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
@ -56,10 +54,8 @@ func TestRunValidateFlags(t *testing.T) {
func TestRunLabel(t *testing.T) {
fakeCLI := test.NewFakeCli(&fakeClient{
createContainerFunc: func(_ *container.Config, _ *container.HostConfig, _ *network.NetworkingConfig, _ *ocispec.Platform, _ string) (container.CreateResponse, error) {
return container.CreateResponse{
ID: "id",
}, nil
createContainerFunc: func(options client.ContainerCreateOptions) (client.ContainerCreateResult, error) {
return client.ContainerCreateResult{ID: "id"}, nil
},
Version: client.MaxAPIVersion,
})
@ -79,19 +75,19 @@ func TestRunAttach(t *testing.T) {
var conn net.Conn
attachCh := make(chan struct{})
fakeCLI := test.NewFakeCli(&fakeClient{
createContainerFunc: func(_ *container.Config, _ *container.HostConfig, _ *network.NetworkingConfig, _ *ocispec.Platform, _ string) (container.CreateResponse, error) {
return container.CreateResponse{
ID: "id",
}, nil
createContainerFunc: func(options client.ContainerCreateOptions) (client.ContainerCreateResult, error) {
return client.ContainerCreateResult{ID: "id"}, nil
},
containerAttachFunc: func(ctx context.Context, containerID string, options client.ContainerAttachOptions) (client.HijackedResponse, error) {
containerAttachFunc: func(ctx context.Context, containerID string, options client.ContainerAttachOptions) (client.ContainerAttachResult, error) {
server, clientConn := net.Pipe()
conn = server
t.Cleanup(func() {
_ = server.Close()
})
attachCh <- struct{}{}
return client.NewHijackedResponse(clientConn, types.MediaTypeRawStream), nil
return client.ContainerAttachResult{
HijackedResponse: client.NewHijackedResponse(clientConn, types.MediaTypeRawStream),
}, nil
},
waitFunc: func(_ string) (<-chan container.WaitResponse, <-chan error) {
responseChan := make(chan container.WaitResponse, 1)
@ -150,10 +146,8 @@ func TestRunAttachTermination(t *testing.T) {
killCh := make(chan struct{})
attachCh := make(chan struct{})
fakeCLI := test.NewFakeCli(&fakeClient{
createContainerFunc: func(_ *container.Config, _ *container.HostConfig, _ *network.NetworkingConfig, _ *ocispec.Platform, _ string) (container.CreateResponse, error) {
return container.CreateResponse{
ID: "id",
}, nil
createContainerFunc: func(options client.ContainerCreateOptions) (client.ContainerCreateResult, error) {
return client.ContainerCreateResult{ID: "id"}, nil
},
containerKillFunc: func(ctx context.Context, containerID, sig string) error {
if sig == "TERM" {
@ -161,14 +155,16 @@ func TestRunAttachTermination(t *testing.T) {
}
return nil
},
containerAttachFunc: func(ctx context.Context, containerID string, options client.ContainerAttachOptions) (client.HijackedResponse, error) {
containerAttachFunc: func(ctx context.Context, containerID string, options client.ContainerAttachOptions) (client.ContainerAttachResult, error) {
server, clientConn := net.Pipe()
conn = server
t.Cleanup(func() {
_ = server.Close()
})
attachCh <- struct{}{}
return client.NewHijackedResponse(clientConn, types.MediaTypeRawStream), nil
return client.ContainerAttachResult{
HijackedResponse: client.NewHijackedResponse(clientConn, types.MediaTypeRawStream),
}, nil
},
waitFunc: func(_ string) (<-chan container.WaitResponse, <-chan error) {
responseChan := make(chan container.WaitResponse, 1)
@ -227,13 +223,11 @@ func TestRunPullTermination(t *testing.T) {
attachCh := make(chan struct{})
fakeCLI := test.NewFakeCli(&fakeClient{
createContainerFunc: func(config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig,
platform *ocispec.Platform, containerName string,
) (container.CreateResponse, error) {
return container.CreateResponse{}, errors.New("shouldn't try to create a container")
createContainerFunc: func(options client.ContainerCreateOptions) (client.ContainerCreateResult, error) {
return client.ContainerCreateResult{}, errors.New("shouldn't try to create a container")
},
containerAttachFunc: func(ctx context.Context, containerID string, options client.ContainerAttachOptions) (client.HijackedResponse, error) {
return client.HijackedResponse{}, errors.New("shouldn't try to attach to a container")
containerAttachFunc: func(ctx context.Context, containerID string, options client.ContainerAttachOptions) (client.ContainerAttachResult, error) {
return client.ContainerAttachResult{}, errors.New("shouldn't try to attach to a container")
},
imageCreateFunc: func(ctx context.Context, parentReference string, options client.ImageCreateOptions) (client.ImageCreateResult, error) {
server, respReader := net.Pipe()
@ -325,13 +319,8 @@ func TestRunCommandWithContentTrustErrors(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
t.Setenv("DOCKER_CONTENT_TRUST", "true")
fakeCLI := test.NewFakeCli(&fakeClient{
createContainerFunc: func(config *container.Config,
hostConfig *container.HostConfig,
networkingConfig *network.NetworkingConfig,
platform *ocispec.Platform,
containerName string,
) (container.CreateResponse, error) {
return container.CreateResponse{}, errors.New("shouldn't try to pull image")
createContainerFunc: func(options client.ContainerCreateOptions) (client.ContainerCreateResult, error) {
return client.ContainerCreateResult{}, errors.New("shouldn't try to pull image")
},
})
fakeCLI.SetNotaryClient(tc.notaryFunc)

View File

@ -116,7 +116,7 @@ func RunStart(ctx context.Context, dockerCli command.Cli, opts *StartOptions) er
if errAttach != nil {
return errAttach
}
defer resp.Close()
defer resp.HijackedResponse.Close()
cErr := make(chan error, 1)
@ -127,7 +127,7 @@ func RunStart(ctx context.Context, dockerCli command.Cli, opts *StartOptions) er
inputStream: in,
outputStream: dockerCli.Out(),
errorStream: dockerCli.Err(),
resp: resp,
resp: resp.HijackedResponse,
tty: c.Config.Tty,
detachKeys: options.DetachKeys,
}
@ -166,7 +166,8 @@ func RunStart(ctx context.Context, dockerCli command.Cli, opts *StartOptions) er
}
}
if attachErr := <-cErr; attachErr != nil {
if _, ok := attachErr.(term.EscapeError); ok {
var escapeError term.EscapeError
if errors.As(attachErr, &escapeError) {
// The user entered the detach escape sequence.
return nil
}

View File

@ -40,10 +40,10 @@ func NewVolumeFormat(source string, quiet bool) Format {
}
// VolumeWrite writes formatted volumes using the Context
func VolumeWrite(ctx Context, volumes []*volume.Volume) error {
func VolumeWrite(ctx Context, volumes []volume.Volume) error {
render := func(format func(subContext SubContext) error) error {
for _, vol := range volumes {
if err := format(&volumeContext{v: *vol}); err != nil {
if err := format(&volumeContext{v: vol}); err != nil {
return err
}
}

View File

@ -124,7 +124,7 @@ foobar_bar
},
}
volumes := []*volume.Volume{
volumes := []volume.Volume{
{Name: "foobar_baz", Driver: "foo"},
{Name: "foobar_bar", Driver: "bar"},
}
@ -144,7 +144,7 @@ foobar_bar
}
func TestVolumeContextWriteJSON(t *testing.T) {
volumes := []*volume.Volume{
volumes := []volume.Volume{
{Driver: "foo", Name: "foobar_baz"},
{Driver: "bar", Name: "foobar_bar"},
}
@ -167,7 +167,7 @@ func TestVolumeContextWriteJSON(t *testing.T) {
}
func TestVolumeContextWriteJSONField(t *testing.T) {
volumes := []*volume.Volume{
volumes := []volume.Volume{
{Driver: "foo", Name: "foobar_baz"},
{Driver: "bar", Name: "foobar_bar"},
}

View File

@ -50,7 +50,6 @@ func TestNewImportCommandInvalidFile(t *testing.T) {
}
func TestNewImportCommandSuccess(t *testing.T) {
t.Skip("FIXME(thaJeztah): how to mock this?")
testCases := []struct {
name string
args []string

View File

@ -97,7 +97,7 @@ func TestNewLoadCommandSuccess(t *testing.T) {
// Body: io.NopCloser(strings.NewReader(`{"ID": "1"}`)),
// JSON: true,
// }, nil
return client.ImageLoadResult{}, nil
return client.ImageLoadResult{JSON: true}, nil
},
},
{

View File

@ -86,7 +86,7 @@ func runRemove(ctx context.Context, dockerCLI command.Cli, opts removeOptions, i
}
errs = append(errs, err)
} else {
for _, del := range res.Deleted {
for _, del := range res.Items {
if del.Deleted != "" {
_, _ = fmt.Fprintln(dockerCLI.Out(), "Deleted:", del.Deleted)
} else {

View File

@ -88,7 +88,7 @@ func TestNewRemoveCommandSuccess(t *testing.T) {
imageRemoveFunc: func(img string, options client.ImageRemoveOptions) (client.ImageRemoveResult, error) {
assert.Check(t, is.Equal("image1", img))
return client.ImageRemoveResult{
Deleted: []image.DeleteResponse{{Deleted: img}},
Items: []image.DeleteResponse{{Deleted: img}},
}, nil
},
},
@ -109,7 +109,7 @@ func TestNewRemoveCommandSuccess(t *testing.T) {
imageRemoveFunc: func(img string, options client.ImageRemoveOptions) (client.ImageRemoveResult, error) {
assert.Check(t, is.Equal("image1", img))
return client.ImageRemoveResult{
Deleted: []image.DeleteResponse{{Untagged: img}},
Items: []image.DeleteResponse{{Untagged: img}},
}, nil
},
},
@ -119,11 +119,11 @@ func TestNewRemoveCommandSuccess(t *testing.T) {
imageRemoveFunc: func(img string, options client.ImageRemoveOptions) (client.ImageRemoveResult, error) {
if img == "image1" {
return client.ImageRemoveResult{
Deleted: []image.DeleteResponse{{Untagged: img}},
Items: []image.DeleteResponse{{Untagged: img}},
}, nil
}
return client.ImageRemoveResult{
Deleted: []image.DeleteResponse{{Deleted: img}},
Items: []image.DeleteResponse{{Deleted: img}},
}, nil
},
},

View File

@ -59,8 +59,8 @@ func TestNodeDemoteNoChange(t *testing.T) {
}, nil
},
nodeUpdateFunc: func(nodeID string, options client.NodeUpdateOptions) (client.NodeUpdateResult, error) {
if options.Node.Role != swarm.NodeRoleWorker {
return client.NodeUpdateResult{}, errors.New("expected role worker, got " + string(options.Node.Role))
if options.Spec.Role != swarm.NodeRoleWorker {
return client.NodeUpdateResult{}, errors.New("expected role worker, got " + string(options.Spec.Role))
}
return client.NodeUpdateResult{}, nil
},
@ -78,8 +78,8 @@ func TestNodeDemoteMultipleNode(t *testing.T) {
}, nil
},
nodeUpdateFunc: func(nodeID string, options client.NodeUpdateOptions) (client.NodeUpdateResult, error) {
if options.Node.Role != swarm.NodeRoleWorker {
return client.NodeUpdateResult{}, errors.New("expected role worker, got " + string(options.Node.Role))
if options.Spec.Role != swarm.NodeRoleWorker {
return client.NodeUpdateResult{}, errors.New("expected role worker, got " + string(options.Spec.Role))
}
return client.NodeUpdateResult{}, nil
},

View File

@ -59,8 +59,8 @@ func TestNodePromoteNoChange(t *testing.T) {
}, nil
},
nodeUpdateFunc: func(nodeID string, options client.NodeUpdateOptions) (client.NodeUpdateResult, error) {
if options.Node.Role != swarm.NodeRoleManager {
return client.NodeUpdateResult{}, errors.New("expected role manager, got" + string(options.Node.Role))
if options.Spec.Role != swarm.NodeRoleManager {
return client.NodeUpdateResult{}, errors.New("expected role manager, got" + string(options.Spec.Role))
}
return client.NodeUpdateResult{}, nil
},
@ -78,8 +78,8 @@ func TestNodePromoteMultipleNode(t *testing.T) {
}, nil
},
nodeUpdateFunc: func(nodeID string, options client.NodeUpdateOptions) (client.NodeUpdateResult, error) {
if options.Node.Role != swarm.NodeRoleManager {
return client.NodeUpdateResult{}, errors.New("expected role manager, got" + string(options.Node.Role))
if options.Spec.Role != swarm.NodeRoleManager {
return client.NodeUpdateResult{}, errors.New("expected role manager, got" + string(options.Spec.Role))
}
return client.NodeUpdateResult{}, nil
},

View File

@ -66,7 +66,7 @@ func updateNodes(ctx context.Context, apiClient client.NodeAPIClient, nodes []st
}
_, err = apiClient.NodeUpdate(ctx, res.Node.ID, client.NodeUpdateOptions{
Version: res.Node.Version,
Node: res.Node.Spec,
Spec: res.Node.Spec,
})
if err != nil {
return err

View File

@ -91,8 +91,8 @@ func TestNodeUpdate(t *testing.T) {
}, nil
},
nodeUpdateFunc: func(nodeID string, options client.NodeUpdateOptions) (client.NodeUpdateResult, error) {
if options.Node.Role != swarm.NodeRoleManager {
return client.NodeUpdateResult{}, errors.New("expected role manager, got " + string(options.Node.Role))
if options.Spec.Role != swarm.NodeRoleManager {
return client.NodeUpdateResult{}, errors.New("expected role manager, got " + string(options.Spec.Role))
}
return client.NodeUpdateResult{}, nil
},
@ -108,8 +108,8 @@ func TestNodeUpdate(t *testing.T) {
}, nil
},
nodeUpdateFunc: func(nodeID string, options client.NodeUpdateOptions) (client.NodeUpdateResult, error) {
if options.Node.Availability != swarm.NodeAvailabilityDrain {
return client.NodeUpdateResult{}, errors.New("expected drain availability, got " + string(options.Node.Availability))
if options.Spec.Availability != swarm.NodeAvailabilityDrain {
return client.NodeUpdateResult{}, errors.New("expected drain availability, got " + string(options.Spec.Availability))
}
return client.NodeUpdateResult{}, nil
},
@ -125,8 +125,8 @@ func TestNodeUpdate(t *testing.T) {
}, nil
},
nodeUpdateFunc: func(nodeID string, options client.NodeUpdateOptions) (client.NodeUpdateResult, error) {
if _, present := options.Node.Annotations.Labels["lbl"]; !present {
return client.NodeUpdateResult{}, fmt.Errorf("expected 'lbl' label, got %v", options.Node.Annotations.Labels)
if _, present := options.Spec.Annotations.Labels["lbl"]; !present {
return client.NodeUpdateResult{}, fmt.Errorf("expected 'lbl' label, got %v", options.Spec.Annotations.Labels)
}
return client.NodeUpdateResult{}, nil
},
@ -142,8 +142,8 @@ func TestNodeUpdate(t *testing.T) {
}, nil
},
nodeUpdateFunc: func(nodeID string, options client.NodeUpdateOptions) (client.NodeUpdateResult, error) {
if value, present := options.Node.Annotations.Labels["key"]; !present || value != "value" {
return client.NodeUpdateResult{}, fmt.Errorf("expected 'key' label to be 'value', got %v", options.Node.Annotations.Labels)
if value, present := options.Spec.Annotations.Labels["key"]; !present || value != "value" {
return client.NodeUpdateResult{}, fmt.Errorf("expected 'key' label to be 'value', got %v", options.Spec.Annotations.Labels)
}
return client.NodeUpdateResult{}, nil
},
@ -161,8 +161,8 @@ func TestNodeUpdate(t *testing.T) {
}, nil
},
nodeUpdateFunc: func(nodeID string, options client.NodeUpdateOptions) (client.NodeUpdateResult, error) {
if len(options.Node.Annotations.Labels) > 0 {
return client.NodeUpdateResult{}, fmt.Errorf("expected no labels, got %v", options.Node.Annotations.Labels)
if len(options.Spec.Annotations.Labels) > 0 {
return client.NodeUpdateResult{}, fmt.Errorf("expected no labels, got %v", options.Spec.Annotations.Labels)
}
return client.NodeUpdateResult{}, nil
},

View File

@ -3,6 +3,7 @@ package plugin
import (
"context"
"io"
"strings"
"github.com/moby/moby/api/types/system"
"github.com/moby/moby/client"
@ -78,5 +79,5 @@ func (c *fakeClient) PluginUpgrade(_ context.Context, name string, options clien
return c.pluginUpgradeFunc(name, options)
}
// FIXME(thaJeztah): how to mock this?
return nil, nil
return io.NopCloser(strings.NewReader("")), nil
}

View File

@ -12,7 +12,7 @@ import (
type fakeClient struct {
client.Client
serviceInspectFunc func(ctx context.Context, serviceID string, options client.ServiceInspectOptions) (client.ServiceInspectResult, error)
serviceUpdateFunc func(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options client.ServiceUpdateOptions) (client.ServiceUpdateResult, error)
serviceUpdateFunc func(ctx context.Context, serviceID string, options client.ServiceUpdateOptions) (client.ServiceUpdateResult, error)
serviceListFunc func(context.Context, client.ServiceListOptions) (client.ServiceListResult, error)
taskListFunc func(context.Context, client.TaskListOptions) (client.TaskListResult, error)
infoFunc func(ctx context.Context) (system.Info, error)
@ -52,9 +52,9 @@ func (f *fakeClient) ServiceList(ctx context.Context, options client.ServiceList
return client.ServiceListResult{}, nil
}
func (f *fakeClient) ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options client.ServiceUpdateOptions) (client.ServiceUpdateResult, error) {
func (f *fakeClient) ServiceUpdate(ctx context.Context, serviceID string, options client.ServiceUpdateOptions) (client.ServiceUpdateResult, error) {
if f.serviceUpdateFunc != nil {
return f.serviceUpdateFunc(ctx, serviceID, version, service, options)
return f.serviceUpdateFunc(ctx, serviceID, options)
}
return client.ServiceUpdateResult{}, nil

View File

@ -95,7 +95,6 @@ func newCreateCommand(dockerCLI command.Cli) *cobra.Command {
func runCreate(ctx context.Context, dockerCLI command.Cli, flags *pflag.FlagSet, opts *serviceOptions) error {
apiClient := dockerCLI.Client()
createOpts := client.ServiceCreateOptions{}
service, err := opts.ToService(ctx, apiClient, flags)
if err != nil {
@ -121,19 +120,22 @@ func runCreate(ctx context.Context, dockerCLI command.Cli, flags *pflag.FlagSet,
}
// only send auth if flag was set
var encodedAuth string
if opts.registryAuth {
// Retrieve encoded auth token from the image reference
encodedAuth, err := command.RetrieveAuthTokenFromImage(dockerCLI.ConfigFile(), opts.image)
var err error
encodedAuth, err = command.RetrieveAuthTokenFromImage(dockerCLI.ConfigFile(), opts.image)
if err != nil {
return err
}
createOpts.EncodedRegistryAuth = encodedAuth
}
// query registry if flag disabling it was not set
createOpts.QueryRegistry = !opts.noResolveImage
response, err := apiClient.ServiceCreate(ctx, client.ServiceCreateOptions{
Spec: service,
response, err := apiClient.ServiceCreate(ctx, service, createOpts)
EncodedRegistryAuth: encodedAuth,
QueryRegistry: !opts.noResolveImage, // query registry if flag disabling it was not set.
})
if err != nil {
return err
}

View File

@ -40,7 +40,9 @@ func runRollback(ctx context.Context, dockerCLI command.Cli, options *serviceOpt
return err
}
response, err := apiClient.ServiceUpdate(ctx, res.Service.ID, res.Service.Version, res.Service.Spec, client.ServiceUpdateOptions{
response, err := apiClient.ServiceUpdate(ctx, res.Service.ID, client.ServiceUpdateOptions{
Version: res.Service.Version,
Spec: res.Service.Spec,
Rollback: "previous", // TODO(thaJeztah): this should have a const defined
})
if err != nil {

View File

@ -8,7 +8,6 @@ import (
"testing"
"github.com/docker/cli/internal/test"
"github.com/moby/moby/api/types/swarm"
"github.com/moby/moby/client"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
@ -18,7 +17,7 @@ func TestRollback(t *testing.T) {
testCases := []struct {
name string
args []string
serviceUpdateFunc func(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options client.ServiceUpdateOptions) (client.ServiceUpdateResult, error)
serviceUpdateFunc func(ctx context.Context, serviceID string, options client.ServiceUpdateOptions) (client.ServiceUpdateResult, error)
expectedDockerCliErr string
}{
{
@ -28,7 +27,7 @@ func TestRollback(t *testing.T) {
{
name: "rollback-service-with-warnings",
args: []string{"service-id"},
serviceUpdateFunc: func(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options client.ServiceUpdateOptions) (client.ServiceUpdateResult, error) {
serviceUpdateFunc: func(ctx context.Context, serviceID string, options client.ServiceUpdateOptions) (client.ServiceUpdateResult, error) {
return client.ServiceUpdateResult{
Warnings: []string{
"- warning 1",
@ -59,7 +58,7 @@ func TestRollbackWithErrors(t *testing.T) {
name string
args []string
serviceInspectFunc func(ctx context.Context, serviceID string, options client.ServiceInspectOptions) (client.ServiceInspectResult, error)
serviceUpdateFunc func(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, opts client.ServiceUpdateOptions) (client.ServiceUpdateResult, error)
serviceUpdateFunc func(ctx context.Context, serviceID string, options client.ServiceUpdateOptions) (client.ServiceUpdateResult, error)
expectedError string
}{
{
@ -82,7 +81,7 @@ func TestRollbackWithErrors(t *testing.T) {
{
name: "service-update-failed",
args: []string{"service-id"},
serviceUpdateFunc: func(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options client.ServiceUpdateOptions) (client.ServiceUpdateResult, error) {
serviceUpdateFunc: func(ctx context.Context, serviceID string, options client.ServiceUpdateOptions) (client.ServiceUpdateResult, error) {
return client.ServiceUpdateResult{}, fmt.Errorf("no such services: %s", serviceID)
},
expectedError: "no such services: service-id",

View File

@ -108,7 +108,10 @@ func runServiceScale(ctx context.Context, apiClient client.ServiceAPIClient, ser
return nil, errors.New("scale can only be used with replicated or replicated-job mode")
}
response, err := apiClient.ServiceUpdate(ctx, res.Service.ID, res.Service.Version, res.Service.Spec, client.ServiceUpdateOptions{})
response, err := apiClient.ServiceUpdate(ctx, res.Service.ID, client.ServiceUpdateOptions{
Version: res.Service.Version,
Spec: res.Service.Spec,
})
if err != nil {
return nil, err
}

View File

@ -46,14 +46,14 @@ func newUpdateCommand(dockerCLI command.Cli) *cobra.Command {
flags.String("image", "", "Service image tag")
flags.Var(&ShlexOpt{}, "args", "Service command args")
flags.Bool(flagRollback, false, "Rollback to previous specification")
flags.SetAnnotation(flagRollback, "version", []string{"1.25"})
_ = flags.SetAnnotation(flagRollback, "version", []string{"1.25"})
flags.Bool("force", false, "Force update even if no changes require it")
flags.SetAnnotation("force", "version", []string{"1.25"})
_ = flags.SetAnnotation("force", "version", []string{"1.25"})
addServiceFlags(flags, options, nil)
flags.Var(newListOptsVar(), flagEnvRemove, "Remove an environment variable")
flags.Var(newListOptsVar(), flagGroupRemove, "Remove a previously added supplementary user group from the container")
flags.SetAnnotation(flagGroupRemove, "version", []string{"1.25"})
_ = flags.SetAnnotation(flagGroupRemove, "version", []string{"1.25"})
flags.Var(newListOptsVar(), flagLabelRemove, "Remove a label by its key")
flags.Var(newListOptsVar(), flagContainerLabelRemove, "Remove a container label by its key")
flags.Var(newListOptsVar(), flagMountRemove, "Remove a mount by its target path")
@ -61,65 +61,65 @@ func newUpdateCommand(dockerCLI command.Cli) *cobra.Command {
flags.Var(&swarmopts.PortOpt{}, flagPublishRemove, "Remove a published port by its target port")
flags.Var(newListOptsVar(), flagConstraintRemove, "Remove a constraint")
flags.Var(newListOptsVar(), flagDNSRemove, "Remove a custom DNS server")
flags.SetAnnotation(flagDNSRemove, "version", []string{"1.25"})
_ = flags.SetAnnotation(flagDNSRemove, "version", []string{"1.25"})
flags.Var(newListOptsVar(), flagDNSOptionRemove, "Remove a DNS option")
flags.SetAnnotation(flagDNSOptionRemove, "version", []string{"1.25"})
_ = flags.SetAnnotation(flagDNSOptionRemove, "version", []string{"1.25"})
flags.Var(newListOptsVar(), flagDNSSearchRemove, "Remove a DNS search domain")
flags.SetAnnotation(flagDNSSearchRemove, "version", []string{"1.25"})
_ = flags.SetAnnotation(flagDNSSearchRemove, "version", []string{"1.25"})
flags.Var(newListOptsVar(), flagHostRemove, `Remove a custom host-to-IP mapping ("host:ip")`)
flags.SetAnnotation(flagHostRemove, "version", []string{"1.25"})
_ = flags.SetAnnotation(flagHostRemove, "version", []string{"1.25"})
flags.Var(&options.labels, flagLabelAdd, "Add or update a service label")
flags.Var(&options.containerLabels, flagContainerLabelAdd, "Add or update a container label")
flags.Var(&options.env, flagEnvAdd, "Add or update an environment variable")
flags.Var(newListOptsVar(), flagSecretRemove, "Remove a secret")
flags.SetAnnotation(flagSecretRemove, "version", []string{"1.25"})
_ = flags.SetAnnotation(flagSecretRemove, "version", []string{"1.25"})
flags.Var(&options.secrets, flagSecretAdd, "Add or update a secret on a service")
flags.SetAnnotation(flagSecretAdd, "version", []string{"1.25"})
_ = flags.SetAnnotation(flagSecretAdd, "version", []string{"1.25"})
flags.Var(newListOptsVar(), flagConfigRemove, "Remove a configuration file")
flags.SetAnnotation(flagConfigRemove, "version", []string{"1.30"})
_ = flags.SetAnnotation(flagConfigRemove, "version", []string{"1.30"})
flags.Var(&options.configs, flagConfigAdd, "Add or update a config file on a service")
flags.SetAnnotation(flagConfigAdd, "version", []string{"1.30"})
_ = flags.SetAnnotation(flagConfigAdd, "version", []string{"1.30"})
flags.Var(&options.mounts, flagMountAdd, "Add or update a mount on a service")
flags.Var(&options.constraints, flagConstraintAdd, "Add or update a placement constraint")
flags.Var(&options.placementPrefs, flagPlacementPrefAdd, "Add a placement preference")
flags.SetAnnotation(flagPlacementPrefAdd, "version", []string{"1.28"})
_ = flags.SetAnnotation(flagPlacementPrefAdd, "version", []string{"1.28"})
flags.Var(&placementPrefOpts{}, flagPlacementPrefRemove, "Remove a placement preference")
flags.SetAnnotation(flagPlacementPrefRemove, "version", []string{"1.28"})
_ = flags.SetAnnotation(flagPlacementPrefRemove, "version", []string{"1.28"})
flags.Var(&options.networks, flagNetworkAdd, "Add a network")
flags.SetAnnotation(flagNetworkAdd, "version", []string{"1.29"})
_ = flags.SetAnnotation(flagNetworkAdd, "version", []string{"1.29"})
flags.Var(newListOptsVar(), flagNetworkRemove, "Remove a network")
flags.SetAnnotation(flagNetworkRemove, "version", []string{"1.29"})
_ = flags.SetAnnotation(flagNetworkRemove, "version", []string{"1.29"})
flags.Var(&options.endpoint.publishPorts, flagPublishAdd, "Add or update a published port")
flags.Var(&options.groups, flagGroupAdd, "Add an additional supplementary user group to the container")
flags.SetAnnotation(flagGroupAdd, "version", []string{"1.25"})
_ = flags.SetAnnotation(flagGroupAdd, "version", []string{"1.25"})
flags.Var(&options.dns, flagDNSAdd, "Add or update a custom DNS server")
flags.SetAnnotation(flagDNSAdd, "version", []string{"1.25"})
_ = flags.SetAnnotation(flagDNSAdd, "version", []string{"1.25"})
flags.Var(&options.dnsOption, flagDNSOptionAdd, "Add or update a DNS option")
flags.SetAnnotation(flagDNSOptionAdd, "version", []string{"1.25"})
_ = flags.SetAnnotation(flagDNSOptionAdd, "version", []string{"1.25"})
flags.Var(&options.dnsSearch, flagDNSSearchAdd, "Add or update a custom DNS search domain")
flags.SetAnnotation(flagDNSSearchAdd, "version", []string{"1.25"})
_ = flags.SetAnnotation(flagDNSSearchAdd, "version", []string{"1.25"})
flags.Var(&options.hosts, flagHostAdd, `Add a custom host-to-IP mapping ("host:ip")`)
flags.SetAnnotation(flagHostAdd, "version", []string{"1.25"})
_ = flags.SetAnnotation(flagHostAdd, "version", []string{"1.25"})
flags.BoolVar(&options.init, flagInit, false, "Use an init inside each service container to forward signals and reap processes")
flags.SetAnnotation(flagInit, "version", []string{"1.37"})
_ = flags.SetAnnotation(flagInit, "version", []string{"1.37"})
flags.Var(&options.sysctls, flagSysCtlAdd, "Add or update a Sysctl option")
flags.SetAnnotation(flagSysCtlAdd, "version", []string{"1.40"})
_ = flags.SetAnnotation(flagSysCtlAdd, "version", []string{"1.40"})
flags.Var(newListOptsVar(), flagSysCtlRemove, "Remove a Sysctl option")
flags.SetAnnotation(flagSysCtlRemove, "version", []string{"1.40"})
_ = flags.SetAnnotation(flagSysCtlRemove, "version", []string{"1.40"})
flags.Var(&options.ulimits, flagUlimitAdd, "Add or update a ulimit option")
flags.SetAnnotation(flagUlimitAdd, "version", []string{"1.41"})
_ = flags.SetAnnotation(flagUlimitAdd, "version", []string{"1.41"})
flags.Var(newListOptsVar(), flagUlimitRemove, "Remove a ulimit option")
flags.SetAnnotation(flagUlimitRemove, "version", []string{"1.41"})
_ = flags.SetAnnotation(flagUlimitRemove, "version", []string{"1.41"})
flags.Int64Var(&options.oomScoreAdj, flagOomScoreAdj, 0, "Tune host's OOM preferences (-1000 to 1000) ")
flags.SetAnnotation(flagOomScoreAdj, "version", []string{"1.46"})
_ = flags.SetAnnotation(flagOomScoreAdj, "version", []string{"1.46"})
// Add needs parsing, Remove only needs the key
flags.Var(newListOptsVar(), flagGenericResourcesRemove, "Remove a Generic resource")
flags.SetAnnotation(flagGenericResourcesRemove, "version", []string{"1.32"})
_ = flags.SetAnnotation(flagGenericResourcesRemove, "version", []string{"1.32"})
flags.Var(newListOptsVarWithValidator(ValidateSingleGenericResource), flagGenericResourcesAdd, "Add a Generic resource")
flags.SetAnnotation(flagGenericResourcesAdd, "version", []string{"1.32"})
_ = flags.SetAnnotation(flagGenericResourcesAdd, "version", []string{"1.32"})
// TODO(thaJeztah): add completion for capabilities, stop-signal (currently non-exported in container package)
// _ = cmd.RegisterFlagCompletionFunc(flagCapAdd, completeLinuxCapabilityNames)
@ -164,7 +164,6 @@ func runUpdate(ctx context.Context, dockerCLI command.Cli, flags *pflag.FlagSet,
return err
}
spec := &res.Service.Spec
if rollback {
// Rollback can't be combined with other flags.
otherFlagsPassed := false
@ -182,10 +181,12 @@ func runUpdate(ctx context.Context, dockerCLI command.Cli, flags *pflag.FlagSet,
}
updateOpts := client.ServiceUpdateOptions{}
rollbackAction := "none"
if rollback {
updateOpts.Rollback = "previous"
rollbackAction = "previous"
}
spec := &res.Service.Spec
err = updateService(ctx, apiClient, flags, spec)
if err != nil {
return err
@ -202,14 +203,12 @@ func runUpdate(ctx context.Context, dockerCLI command.Cli, flags *pflag.FlagSet,
if err != nil {
return err
}
spec.TaskTemplate.ContainerSpec.Secrets = updatedSecrets
updatedConfigs, err := getUpdatedConfigs(ctx, apiClient, flags, spec.TaskTemplate.ContainerSpec)
if err != nil {
return err
}
spec.TaskTemplate.ContainerSpec.Configs = updatedConfigs
// set the credential spec value after get the updated configs, because we
@ -218,24 +217,31 @@ func runUpdate(ctx context.Context, dockerCLI command.Cli, flags *pflag.FlagSet,
updateCredSpecConfig(flags, spec.TaskTemplate.ContainerSpec)
// only send auth if flag was set
sendAuth, err := flags.GetBool(flagRegistryAuth)
if err != nil {
var encodedAuth string
var registryAuthFrom string
if ok, err := flags.GetBool(flagRegistryAuth); err != nil {
return err
}
if sendAuth {
} else if ok {
// Retrieve encoded auth token from the image reference
// This would be the old image if it didn't change in this update
image := spec.TaskTemplate.ContainerSpec.Image
encodedAuth, err := command.RetrieveAuthTokenFromImage(dockerCLI.ConfigFile(), image)
var err error
encodedAuth, err = command.RetrieveAuthTokenFromImage(dockerCLI.ConfigFile(), spec.TaskTemplate.ContainerSpec.Image)
if err != nil {
return err
}
updateOpts.EncodedRegistryAuth = encodedAuth
} else {
updateOpts.RegistryAuthFrom = swarm.RegistryAuthFromSpec
registryAuthFrom = swarm.RegistryAuthFromSpec
}
response, err := apiClient.ServiceUpdate(ctx, res.Service.ID, res.Service.Version, *spec, updateOpts)
response, err := apiClient.ServiceUpdate(ctx, res.Service.ID, client.ServiceUpdateOptions{
Version: res.Service.Version,
Spec: *spec,
EncodedRegistryAuth: encodedAuth,
RegistryAuthFrom: registryAuthFrom,
Rollback: rollbackAction,
})
if err != nil {
return err
}

View File

@ -31,7 +31,7 @@ type fakeClient struct {
nodeListFunc func(options client.NodeListOptions) (client.NodeListResult, error)
taskListFunc func(options client.TaskListOptions) (client.TaskListResult, error)
nodeInspectFunc func(ref string) (client.NodeInspectResult, error)
serviceUpdateFunc func(serviceID string, version swarm.Version, service swarm.ServiceSpec, options client.ServiceUpdateOptions) (client.ServiceUpdateResult, error)
serviceUpdateFunc func(serviceID string, options client.ServiceUpdateOptions) (client.ServiceUpdateResult, error)
serviceRemoveFunc func(serviceID string) (client.ServiceRemoveResult, error)
networkRemoveFunc func(networkID string) error
secretRemoveFunc func(secretID string) (client.SecretRemoveResult, error)
@ -130,9 +130,9 @@ func (cli *fakeClient) NodeInspect(_ context.Context, ref string, _ client.NodeI
return client.NodeInspectResult{}, nil
}
func (cli *fakeClient) ServiceUpdate(_ context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options client.ServiceUpdateOptions) (client.ServiceUpdateResult, error) {
func (cli *fakeClient) ServiceUpdate(_ context.Context, serviceID string, options client.ServiceUpdateOptions) (client.ServiceUpdateResult, error) {
if cli.serviceUpdateFunc != nil {
return cli.serviceUpdateFunc(serviceID, version, service, options)
return cli.serviceUpdateFunc(serviceID, options)
}
return client.ServiceUpdateResult{}, nil

View File

@ -233,7 +233,10 @@ func deployServices(ctx context.Context, dockerCLI command.Cli, services map[str
if svc, exists := existingServiceMap[name]; exists {
_, _ = fmt.Fprintf(out, "Updating service %s (id: %s)\n", name, svc.ID)
updateOpts := client.ServiceUpdateOptions{EncodedRegistryAuth: encodedAuth}
updateOpts := client.ServiceUpdateOptions{
Version: svc.Version,
EncodedRegistryAuth: encodedAuth,
}
switch resolveImage {
case resolveImageAlways:
@ -265,7 +268,8 @@ func deployServices(ctx context.Context, dockerCLI command.Cli, services map[str
// TODO move this to API client?
serviceSpec.TaskTemplate.ForceUpdate = svc.Spec.TaskTemplate.ForceUpdate
response, err := apiClient.ServiceUpdate(ctx, svc.ID, svc.Version, serviceSpec, updateOpts)
updateOpts.Spec = serviceSpec
response, err := apiClient.ServiceUpdate(ctx, svc.ID, updateOpts)
if err != nil {
return nil, fmt.Errorf("failed to update service %s: %w", name, err)
}
@ -281,7 +285,8 @@ func deployServices(ctx context.Context, dockerCLI command.Cli, services map[str
// query registry if flag disabling it was not set
queryRegistry := resolveImage == resolveImageAlways || resolveImage == resolveImageChanged
response, err := apiClient.ServiceCreate(ctx, serviceSpec, client.ServiceCreateOptions{
response, err := apiClient.ServiceCreate(ctx, client.ServiceCreateOptions{
Spec: serviceSpec,
EncodedRegistryAuth: encodedAuth,
QueryRegistry: queryRegistry,
})

View File

@ -42,10 +42,7 @@ func TestPruneServices(t *testing.T) {
func TestServiceUpdateResolveImageChanged(t *testing.T) {
namespace := convert.NewNamespace("mystack")
var (
receivedOptions client.ServiceUpdateOptions
receivedService swarm.ServiceSpec
)
var receivedOptions client.ServiceUpdateOptions
fakeCli := test.NewFakeCli(&fakeClient{
serviceListFunc: func(options client.ServiceListOptions) (client.ServiceListResult, error) {
@ -68,9 +65,8 @@ func TestServiceUpdateResolveImageChanged(t *testing.T) {
},
}, nil
},
serviceUpdateFunc: func(serviceID string, version swarm.Version, service swarm.ServiceSpec, options client.ServiceUpdateOptions) (client.ServiceUpdateResult, error) {
serviceUpdateFunc: func(serviceID string, options client.ServiceUpdateOptions) (client.ServiceUpdateResult, error) {
receivedOptions = options
receivedService = service
return client.ServiceUpdateResult{}, nil
},
})
@ -113,10 +109,9 @@ func TestServiceUpdateResolveImageChanged(t *testing.T) {
_, err := deployServices(ctx, fakeCli, spec, namespace, false, resolveImageChanged)
assert.NilError(t, err)
assert.Check(t, is.Equal(receivedOptions.QueryRegistry, tc.expectedQueryRegistry))
assert.Check(t, is.Equal(receivedService.TaskTemplate.ContainerSpec.Image, tc.expectedImage))
assert.Check(t, is.Equal(receivedService.TaskTemplate.ForceUpdate, tc.expectedForceUpdate))
assert.Check(t, is.Equal(receivedOptions.Spec.TaskTemplate.ContainerSpec.Image, tc.expectedImage))
assert.Check(t, is.Equal(receivedOptions.Spec.TaskTemplate.ForceUpdate, tc.expectedForceUpdate))
receivedService = swarm.ServiceSpec{}
receivedOptions = client.ServiceUpdateOptions{}
})
}

View File

@ -84,8 +84,9 @@ func runCA(ctx context.Context, dockerCLI command.Cli, flags *pflag.FlagSet, opt
}
updateSwarmSpec(&res.Swarm.Spec, flags, opts)
if _, err := apiClient.SwarmUpdate(ctx, res.Swarm.Version, client.SwarmUpdateOptions{
Swarm: res.Swarm.Spec,
if _, err := apiClient.SwarmUpdate(ctx, client.SwarmUpdateOptions{
Version: res.Swarm.Version,
Spec: res.Swarm.Spec,
}); err != nil {
return err
}

View File

@ -173,7 +173,7 @@ type swarmUpdateRecorder struct {
}
func (s *swarmUpdateRecorder) swarmUpdate(opts client.SwarmUpdateOptions) (client.SwarmUpdateResult, error) {
s.spec = opts.Swarm
s.spec = opts.Spec
return client.SwarmUpdateResult{}, nil
}

View File

@ -3,7 +3,6 @@ package swarm
import (
"context"
"github.com/moby/moby/api/types/swarm"
"github.com/moby/moby/api/types/system"
"github.com/moby/moby/client"
)
@ -70,7 +69,7 @@ func (cli *fakeClient) SwarmLeave(context.Context, client.SwarmLeaveOptions) (cl
return client.SwarmLeaveResult{}, nil
}
func (cli *fakeClient) SwarmUpdate(_ context.Context, _ swarm.Version, options client.SwarmUpdateOptions) (client.SwarmUpdateResult, error) {
func (cli *fakeClient) SwarmUpdate(_ context.Context, options client.SwarmUpdateOptions) (client.SwarmUpdateResult, error) {
if cli.swarmUpdateFunc != nil {
return cli.swarmUpdateFunc(options)
}

View File

@ -58,8 +58,9 @@ func runJoinToken(ctx context.Context, dockerCLI command.Cli, opts joinTokenOpti
return err
}
_, err = apiClient.SwarmUpdate(ctx, res.Swarm.Version, client.SwarmUpdateOptions{
Swarm: res.Swarm.Spec,
_, err = apiClient.SwarmUpdate(ctx, client.SwarmUpdateOptions{
Version: res.Swarm.Version,
Spec: res.Swarm.Spec,
RotateWorkerToken: worker,
RotateManagerToken: manager,
})

View File

@ -55,8 +55,10 @@ func runUnlockKey(ctx context.Context, dockerCLI command.Cli, opts unlockKeyOpti
return errors.New("cannot rotate because autolock is not turned on")
}
_, err = apiClient.SwarmUpdate(ctx, res.Swarm.Version, client.SwarmUpdateOptions{
Swarm: res.Swarm.Spec,
_, err = apiClient.SwarmUpdate(ctx, client.SwarmUpdateOptions{
Version: res.Swarm.Version,
Spec: res.Swarm.Spec,
RotateManagerUnlockKey: true,
})
if err != nil {

View File

@ -54,8 +54,9 @@ func runUpdate(ctx context.Context, dockerCLI command.Cli, flags *pflag.FlagSet,
curAutoLock := sw.Swarm.Spec.EncryptionConfig.AutoLockManagers
_, err = apiClient.SwarmUpdate(ctx, sw.Swarm.Version, client.SwarmUpdateOptions{
Swarm: sw.Swarm.Spec,
_, err = apiClient.SwarmUpdate(ctx, client.SwarmUpdateOptions{
Version: sw.Swarm.Version,
Spec: sw.Swarm.Spec,
})
if err != nil {
return err

View File

@ -120,33 +120,33 @@ func TestSwarmUpdate(t *testing.T) {
}, nil
},
swarmUpdateFunc: func(options client.SwarmUpdateOptions) (client.SwarmUpdateResult, error) {
if *options.Swarm.Orchestration.TaskHistoryRetentionLimit != 10 {
if *options.Spec.Orchestration.TaskHistoryRetentionLimit != 10 {
return client.SwarmUpdateResult{}, errors.New("historyLimit not correctly set")
}
heartbeatDuration, err := time.ParseDuration("10s")
if err != nil {
return client.SwarmUpdateResult{}, err
}
if options.Swarm.Dispatcher.HeartbeatPeriod != heartbeatDuration {
if options.Spec.Dispatcher.HeartbeatPeriod != heartbeatDuration {
return client.SwarmUpdateResult{}, errors.New("heartbeatPeriodLimit not correctly set")
}
certExpiryDuration, err := time.ParseDuration("20s")
if err != nil {
return client.SwarmUpdateResult{}, err
}
if options.Swarm.CAConfig.NodeCertExpiry != certExpiryDuration {
if options.Spec.CAConfig.NodeCertExpiry != certExpiryDuration {
return client.SwarmUpdateResult{}, errors.New("certExpiry not correctly set")
}
if len(options.Swarm.CAConfig.ExternalCAs) != 1 || options.Swarm.CAConfig.ExternalCAs[0].CACert != "trust-root" {
if len(options.Spec.CAConfig.ExternalCAs) != 1 || options.Spec.CAConfig.ExternalCAs[0].CACert != "trust-root" {
return client.SwarmUpdateResult{}, errors.New("externalCA not correctly set")
}
if *options.Swarm.Raft.KeepOldSnapshots != 10 {
if *options.Spec.Raft.KeepOldSnapshots != 10 {
return client.SwarmUpdateResult{}, errors.New("keepOldSnapshots not correctly set")
}
if options.Swarm.Raft.SnapshotInterval != 100 {
if options.Spec.Raft.SnapshotInterval != 100 {
return client.SwarmUpdateResult{}, errors.New("snapshotInterval not correctly set")
}
if !options.Swarm.EncryptionConfig.AutoLockManagers {
if !options.Spec.EncryptionConfig.AutoLockManagers {
return client.SwarmUpdateResult{}, errors.New("auto-lock not correctly set")
}
return client.SwarmUpdateResult{}, nil
@ -159,7 +159,7 @@ func TestSwarmUpdate(t *testing.T) {
flagAutolock: "true",
},
swarmUpdateFunc: func(options client.SwarmUpdateOptions) (client.SwarmUpdateResult, error) {
if *options.Swarm.Orchestration.TaskHistoryRetentionLimit != 10 {
if *options.Spec.Orchestration.TaskHistoryRetentionLimit != 10 {
return client.SwarmUpdateResult{}, errors.New("historyLimit not correctly set")
}
return client.SwarmUpdateResult{}, nil

View File

@ -309,8 +309,8 @@ func volumeNames(dockerCLI completion.APIClientProvider, cmd *cobra.Command) []s
if err != nil {
return []string{}
}
names := make([]string, 0, len(res.Items.Volumes))
for _, v := range res.Items.Volumes {
names := make([]string, 0, len(res.Items))
for _, v := range res.Items {
names = append(names, v.Name)
}
return names

View File

@ -139,11 +139,9 @@ func TestCompleteEventFilter(t *testing.T) {
client: &fakeClient{
volumeListFunc: func(ctx context.Context, options client.VolumeListOptions) (client.VolumeListResult, error) {
return client.VolumeListResult{
Items: volume.ListResponse{
Volumes: []*volume.Volume{
builders.Volume(builders.VolumeName("v1")),
builders.Volume(builders.VolumeName("v2")),
},
Items: []volume.Volume{
builders.Volume(builders.VolumeName("v1")),
builders.Volume(builders.VolumeName("v2")),
},
}, nil
},

View File

@ -84,7 +84,7 @@ func TestVolumeInspectWithoutFormat(t *testing.T) {
return client.VolumeInspectResult{}, fmt.Errorf("invalid volumeID, expected %s, got %s", "foo", volumeID)
}
return client.VolumeInspectResult{
Volume: *builders.Volume(),
Volume: builders.Volume(),
}, nil
},
},
@ -93,7 +93,7 @@ func TestVolumeInspectWithoutFormat(t *testing.T) {
args: []string{"foo", "bar"},
volumeInspectFunc: func(volumeID string) (client.VolumeInspectResult, error) {
return client.VolumeInspectResult{
Volume: *builders.Volume(builders.VolumeName(volumeID), builders.VolumeLabels(map[string]string{
Volume: builders.Volume(builders.VolumeName(volumeID), builders.VolumeLabels(map[string]string{
"foo": "bar",
})),
}, nil
@ -114,7 +114,7 @@ func TestVolumeInspectWithoutFormat(t *testing.T) {
func TestVolumeInspectWithFormat(t *testing.T) {
volumeInspectFunc := func(volumeID string) (client.VolumeInspectResult, error) {
return client.VolumeInspectResult{
Volume: *builders.Volume(builders.VolumeLabels(map[string]string{
Volume: builders.Volume(builders.VolumeLabels(map[string]string{
"foo": "bar",
})),
}, nil

View File

@ -71,13 +71,13 @@ func runList(ctx context.Context, dockerCLI command.Cli, options listOptions) er
// trick for filtering in place
n := 0
for _, vol := range res.Items.Volumes {
for _, vol := range res.Items {
if vol.ClusterVolume != nil {
res.Items.Volumes[n] = vol
res.Items[n] = vol
n++
}
}
res.Items.Volumes = res.Items.Volumes[:n]
res.Items = res.Items[:n]
if !options.quiet {
format = clusterTableFormat
} else {
@ -85,13 +85,13 @@ func runList(ctx context.Context, dockerCLI command.Cli, options listOptions) er
}
}
sort.Slice(res.Items.Volumes, func(i, j int) bool {
return sortorder.NaturalLess(res.Items.Volumes[i].Name, res.Items.Volumes[j].Name)
sort.Slice(res.Items, func(i, j int) bool {
return sortorder.NaturalLess(res.Items[i].Name, res.Items[j].Name)
})
volumeCtx := formatter.Context{
Output: dockerCLI.Out(),
Format: formatter.NewVolumeFormat(format, options.quiet),
}
return formatter.VolumeWrite(volumeCtx, res.Items.Volumes)
return formatter.VolumeWrite(volumeCtx, res.Items)
}

View File

@ -52,14 +52,12 @@ func TestVolumeListWithoutFormat(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
volumeListFunc: func(client.VolumeListOptions) (client.VolumeListResult, error) {
return client.VolumeListResult{
Items: volume.ListResponse{
Volumes: []*volume.Volume{
builders.Volume(),
builders.Volume(builders.VolumeName("foo"), builders.VolumeDriver("bar")),
builders.Volume(builders.VolumeName("baz"), builders.VolumeLabels(map[string]string{
"foo": "bar",
})),
},
Items: []volume.Volume{
builders.Volume(),
builders.Volume(builders.VolumeName("foo"), builders.VolumeDriver("bar")),
builders.Volume(builders.VolumeName("baz"), builders.VolumeLabels(map[string]string{
"foo": "bar",
})),
},
}, nil
},
@ -73,14 +71,12 @@ func TestVolumeListWithConfigFormat(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
volumeListFunc: func(client.VolumeListOptions) (client.VolumeListResult, error) {
return client.VolumeListResult{
Items: volume.ListResponse{
Volumes: []*volume.Volume{
builders.Volume(),
builders.Volume(builders.VolumeName("foo"), builders.VolumeDriver("bar")),
builders.Volume(builders.VolumeName("baz"), builders.VolumeLabels(map[string]string{
"foo": "bar",
})),
},
Items: []volume.Volume{
builders.Volume(),
builders.Volume(builders.VolumeName("foo"), builders.VolumeDriver("bar")),
builders.Volume(builders.VolumeName("baz"), builders.VolumeLabels(map[string]string{
"foo": "bar",
})),
},
}, nil
},
@ -97,14 +93,12 @@ func TestVolumeListWithFormat(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
volumeListFunc: func(client.VolumeListOptions) (client.VolumeListResult, error) {
return client.VolumeListResult{
Items: volume.ListResponse{
Volumes: []*volume.Volume{
builders.Volume(),
builders.Volume(builders.VolumeName("foo"), builders.VolumeDriver("bar")),
builders.Volume(builders.VolumeName("baz"), builders.VolumeLabels(map[string]string{
"foo": "bar",
})),
},
Items: []volume.Volume{
builders.Volume(),
builders.Volume(builders.VolumeName("foo"), builders.VolumeDriver("bar")),
builders.Volume(builders.VolumeName("baz"), builders.VolumeLabels(map[string]string{
"foo": "bar",
})),
},
}, nil
},
@ -119,12 +113,10 @@ func TestVolumeListSortOrder(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
volumeListFunc: func(client.VolumeListOptions) (client.VolumeListResult, error) {
return client.VolumeListResult{
Items: volume.ListResponse{
Volumes: []*volume.Volume{
builders.Volume(builders.VolumeName("volume-2-foo")),
builders.Volume(builders.VolumeName("volume-10-foo")),
builders.Volume(builders.VolumeName("volume-1-foo")),
},
Items: []volume.Volume{
builders.Volume(builders.VolumeName("volume-2-foo")),
builders.Volume(builders.VolumeName("volume-10-foo")),
builders.Volume(builders.VolumeName("volume-1-foo")),
},
}, nil
},
@ -139,101 +131,99 @@ func TestClusterVolumeList(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
volumeListFunc: func(client.VolumeListOptions) (client.VolumeListResult, error) {
return client.VolumeListResult{
Items: volume.ListResponse{
Volumes: []*volume.Volume{
{
Name: "volume1",
Scope: "global",
Driver: "driver1",
ClusterVolume: &volume.ClusterVolume{
Spec: volume.ClusterVolumeSpec{
Group: "group1",
AccessMode: &volume.AccessMode{
Scope: volume.ScopeSingleNode,
Sharing: volume.SharingOneWriter,
MountVolume: &volume.TypeMount{},
},
Availability: volume.AvailabilityActive,
Items: []volume.Volume{
{
Name: "volume1",
Scope: "global",
Driver: "driver1",
ClusterVolume: &volume.ClusterVolume{
Spec: volume.ClusterVolumeSpec{
Group: "group1",
AccessMode: &volume.AccessMode{
Scope: volume.ScopeSingleNode,
Sharing: volume.SharingOneWriter,
MountVolume: &volume.TypeMount{},
},
Availability: volume.AvailabilityActive,
},
},
{
Name: "volume2",
Scope: "global",
Driver: "driver1",
ClusterVolume: &volume.ClusterVolume{
Spec: volume.ClusterVolumeSpec{
Group: "group1",
AccessMode: &volume.AccessMode{
Scope: volume.ScopeSingleNode,
Sharing: volume.SharingOneWriter,
MountVolume: &volume.TypeMount{},
},
Availability: volume.AvailabilityPause,
},
Info: &volume.Info{
CapacityBytes: 100000000,
VolumeID: "driver1vol2",
},
},
},
{
Name: "volume3",
Scope: "global",
Driver: "driver2",
ClusterVolume: &volume.ClusterVolume{
Spec: volume.ClusterVolumeSpec{
Group: "group2",
AccessMode: &volume.AccessMode{
Scope: volume.ScopeMultiNode,
Sharing: volume.SharingAll,
MountVolume: &volume.TypeMount{},
},
Availability: volume.AvailabilityActive,
},
PublishStatus: []*volume.PublishStatus{
{
NodeID: "nodeid1",
State: volume.StatePublished,
},
},
Info: &volume.Info{
CapacityBytes: 100000000,
VolumeID: "driver1vol3",
},
},
},
{
Name: "volume4",
Scope: "global",
Driver: "driver2",
ClusterVolume: &volume.ClusterVolume{
Spec: volume.ClusterVolumeSpec{
Group: "group2",
AccessMode: &volume.AccessMode{
Scope: volume.ScopeMultiNode,
Sharing: volume.SharingAll,
MountVolume: &volume.TypeMount{},
},
Availability: volume.AvailabilityActive,
},
PublishStatus: []*volume.PublishStatus{
{
NodeID: "nodeid1",
State: volume.StatePublished,
}, {
NodeID: "nodeid2",
State: volume.StatePublished,
},
},
Info: &volume.Info{
CapacityBytes: 100000000,
VolumeID: "driver1vol4",
},
},
},
builders.Volume(builders.VolumeName("volume-local-1")),
},
{
Name: "volume2",
Scope: "global",
Driver: "driver1",
ClusterVolume: &volume.ClusterVolume{
Spec: volume.ClusterVolumeSpec{
Group: "group1",
AccessMode: &volume.AccessMode{
Scope: volume.ScopeSingleNode,
Sharing: volume.SharingOneWriter,
MountVolume: &volume.TypeMount{},
},
Availability: volume.AvailabilityPause,
},
Info: &volume.Info{
CapacityBytes: 100000000,
VolumeID: "driver1vol2",
},
},
},
{
Name: "volume3",
Scope: "global",
Driver: "driver2",
ClusterVolume: &volume.ClusterVolume{
Spec: volume.ClusterVolumeSpec{
Group: "group2",
AccessMode: &volume.AccessMode{
Scope: volume.ScopeMultiNode,
Sharing: volume.SharingAll,
MountVolume: &volume.TypeMount{},
},
Availability: volume.AvailabilityActive,
},
PublishStatus: []*volume.PublishStatus{
{
NodeID: "nodeid1",
State: volume.StatePublished,
},
},
Info: &volume.Info{
CapacityBytes: 100000000,
VolumeID: "driver1vol3",
},
},
},
{
Name: "volume4",
Scope: "global",
Driver: "driver2",
ClusterVolume: &volume.ClusterVolume{
Spec: volume.ClusterVolumeSpec{
Group: "group2",
AccessMode: &volume.AccessMode{
Scope: volume.ScopeMultiNode,
Sharing: volume.SharingAll,
MountVolume: &volume.TypeMount{},
},
Availability: volume.AvailabilityActive,
},
PublishStatus: []*volume.PublishStatus{
{
NodeID: "nodeid1",
State: volume.StatePublished,
}, {
NodeID: "nodeid2",
State: volume.StatePublished,
},
},
Info: &volume.Info{
CapacityBytes: 100000000,
VolumeID: "driver1vol4",
},
},
},
builders.Volume(builders.VolumeName("volume-local-1")),
},
}, nil
},

View File

@ -26,7 +26,7 @@ import (
"github.com/go-viper/mapstructure/v2"
"github.com/google/shlex"
"github.com/moby/moby/api/types/network"
"github.com/moby/moby/api/types/versions"
"github.com/moby/moby/client/pkg/versions"
"github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"
)

View File

@ -20,7 +20,7 @@ import (
cliflags "github.com/docker/cli/cli/flags"
"github.com/docker/cli/cli/version"
platformsignals "github.com/docker/cli/cmd/docker/internal/signals"
"github.com/moby/moby/api/types/versions"
"github.com/moby/moby/client/pkg/versions"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"

View File

@ -14,7 +14,7 @@ import (
"github.com/creack/pty"
"github.com/docker/cli/e2e/internal/fixtures"
"github.com/docker/cli/internal/test/environment"
"github.com/moby/moby/api/types/versions"
"github.com/moby/moby/client/pkg/versions"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/golden"

View File

@ -4,8 +4,8 @@ import "github.com/moby/moby/api/types/volume"
// Volume creates a volume with default values.
// Any number of volume function builder can be passed to augment it.
func Volume(builders ...func(vol *volume.Volume)) *volume.Volume {
vol := &volume.Volume{
func Volume(builders ...func(vol *volume.Volume)) volume.Volume {
vol := volume.Volume{
Name: "volume",
Driver: "local",
Mountpoint: "/data/volume",
@ -13,7 +13,7 @@ func Volume(builders ...func(vol *volume.Volume)) *volume.Volume {
}
for _, builder := range builders {
builder(vol)
builder(&vol)
}
return vol

View File

@ -28,8 +28,8 @@ require (
github.com/google/uuid v1.6.0
github.com/mattn/go-runewidth v0.0.17
github.com/moby/go-archive v0.1.0
github.com/moby/moby/api v1.52.0-beta.2.0.20251023134003-dcd668d5796b // master
github.com/moby/moby/client v0.1.0-beta.2.0.20251023134003-dcd668d5796b // master
github.com/moby/moby/api v1.52.0-beta.2.0.20251024193508-be8d6e2f2825 // master
github.com/moby/moby/client v0.1.0-beta.2.0.20251024193508-be8d6e2f2825 // master
github.com/moby/patternmatcher v0.6.0
github.com/moby/swarmkit/v2 v2.1.0
github.com/moby/sys/atomicwriter v0.1.0

View File

@ -170,10 +170,10 @@ github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3N
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo=
github.com/moby/moby/api v1.52.0-beta.2.0.20251023134003-dcd668d5796b h1:2y00KCjD3Dt6CFi8Zr7kh0ew32S2784EWbyKAXqQw2Q=
github.com/moby/moby/api v1.52.0-beta.2.0.20251023134003-dcd668d5796b/go.mod h1:/ou52HkRydg4+odrUR3vFsGgjIyHvprrpEQEkweL10s=
github.com/moby/moby/client v0.1.0-beta.2.0.20251023134003-dcd668d5796b h1:+JMltOoDeSwWJv4AMXE9e3dSe1h4LD4+bt6tqNO5h0s=
github.com/moby/moby/client v0.1.0-beta.2.0.20251023134003-dcd668d5796b/go.mod h1:sxVfwGqVgh7n+tdxA4gFToQ/lf+bM7zATnvQjVnsKT4=
github.com/moby/moby/api v1.52.0-beta.2.0.20251024193508-be8d6e2f2825 h1:hIQtHzNpFguJCWlgZ4z9L83YjLYpf9uP9+3cSYXP9hg=
github.com/moby/moby/api v1.52.0-beta.2.0.20251024193508-be8d6e2f2825/go.mod h1:/ou52HkRydg4+odrUR3vFsGgjIyHvprrpEQEkweL10s=
github.com/moby/moby/client v0.1.0-beta.2.0.20251024193508-be8d6e2f2825 h1:7kbhU8foMePfI9vB24bSld1RzJQpbquW8sZarquRHbY=
github.com/moby/moby/client v0.1.0-beta.2.0.20251024193508-be8d6e2f2825/go.mod h1:sxVfwGqVgh7n+tdxA4gFToQ/lf+bM7zATnvQjVnsKT4=
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
github.com/moby/swarmkit/v2 v2.1.0 h1:u+cJ5hSyF3HnzsyI+NtegYxdIPQIuibk7IbpXNxuISM=

View File

@ -138,6 +138,17 @@ type DiscreteGenericResource struct {
type ResourceRequirements struct {
Limits *Limit `json:",omitempty"`
Reservations *Resources `json:",omitempty"`
// Amount of swap in bytes - can only be used together with a memory limit
// -1 means unlimited
// a null pointer keeps the default behaviour of granting twice the memory
// amount in swap
SwapBytes *int64 `json:"SwapBytes,omitzero"`
// Tune container memory swappiness (0 to 100) - if not specified, defaults
// to the container OS's default - generally 60, or the value predefined in
// the image; set to -1 to unset a previously set value
MemorySwappiness *int64 `json:MemorySwappiness,omitzero"`
}
// Placement represents orchestration parameters.

View File

@ -8,7 +8,7 @@ import (
"strconv"
"github.com/moby/moby/api/types/build"
"github.com/moby/moby/api/types/versions"
"github.com/moby/moby/client/pkg/versions"
)
// BuildCachePruneOptions hold parameters to prune the build cache.

View File

@ -56,7 +56,7 @@ import (
cerrdefs "github.com/containerd/errdefs"
"github.com/docker/go-connections/sockets"
"github.com/moby/moby/api/types/versions"
"github.com/moby/moby/client/pkg/versions"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)

View File

@ -12,7 +12,6 @@ import (
"github.com/moby/moby/api/types/registry"
"github.com/moby/moby/api/types/swarm"
"github.com/moby/moby/api/types/system"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
// APIClient is an interface that clients that talk with a docker server must implement.
@ -58,10 +57,10 @@ type HijackDialer interface {
// ContainerAPIClient defines API client methods for the containers
type ContainerAPIClient interface {
ContainerAttach(ctx context.Context, container string, options ContainerAttachOptions) (HijackedResponse, error)
ContainerCommit(ctx context.Context, container string, options ContainerCommitOptions) (container.CommitResponse, error)
ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error)
ContainerDiff(ctx context.Context, container string) ([]container.FilesystemChange, error)
ContainerAttach(ctx context.Context, container string, options ContainerAttachOptions) (ContainerAttachResult, error)
ContainerCommit(ctx context.Context, container string, options ContainerCommitOptions) (ContainerCommitResult, error)
ContainerCreate(ctx context.Context, options ContainerCreateOptions) (ContainerCreateResult, error)
ContainerDiff(ctx context.Context, container string, options ContainerDiffOptions) (ContainerDiffResult, error)
ExecAPIClient
ContainerExport(ctx context.Context, container string) (io.ReadCloser, error)
ContainerInspect(ctx context.Context, container string) (container.InspectResponse, error)
@ -158,11 +157,11 @@ type PluginAPIClient interface {
// ServiceAPIClient defines API client methods for the services
type ServiceAPIClient interface {
ServiceCreate(ctx context.Context, service swarm.ServiceSpec, options ServiceCreateOptions) (ServiceCreateResult, error)
ServiceCreate(ctx context.Context, options ServiceCreateOptions) (ServiceCreateResult, error)
ServiceInspect(ctx context.Context, serviceID string, options ServiceInspectOptions) (ServiceInspectResult, error)
ServiceList(ctx context.Context, options ServiceListOptions) (ServiceListResult, error)
ServiceRemove(ctx context.Context, serviceID string, options ServiceRemoveOptions) (ServiceRemoveResult, error)
ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options ServiceUpdateOptions) (ServiceUpdateResult, error)
ServiceUpdate(ctx context.Context, serviceID string, options ServiceUpdateOptions) (ServiceUpdateResult, error)
ServiceLogs(ctx context.Context, serviceID string, options ServiceLogsOptions) (ServiceLogsResult, error)
TaskLogs(ctx context.Context, taskID string, options TaskLogsOptions) (TaskLogsResult, error)
TaskInspect(ctx context.Context, taskID string, options TaskInspectOptions) (TaskInspectResult, error)
@ -177,7 +176,7 @@ type SwarmAPIClient interface {
SwarmUnlock(ctx context.Context, options SwarmUnlockOptions) (SwarmUnlockResult, error)
SwarmLeave(ctx context.Context, options SwarmLeaveOptions) (SwarmLeaveResult, error)
SwarmInspect(ctx context.Context, options SwarmInspectOptions) (SwarmInspectResult, error)
SwarmUpdate(ctx context.Context, version swarm.Version, options SwarmUpdateOptions) (SwarmUpdateResult, error)
SwarmUpdate(ctx context.Context, options SwarmUpdateOptions) (SwarmUpdateResult, error)
}
// SystemAPIClient defines API client methods for the system

View File

@ -2,6 +2,7 @@ package client
import (
"context"
"encoding/json"
"github.com/moby/moby/api/types/swarm"
)
@ -14,7 +15,7 @@ type ConfigInspectOptions struct {
// ConfigInspectResult holds the result from the ConfigInspect method.
type ConfigInspectResult struct {
Config swarm.Config
Raw []byte
Raw json.RawMessage
}
// ConfigInspect returns the config information with raw data
@ -24,7 +25,6 @@ func (cli *Client) ConfigInspect(ctx context.Context, id string, options ConfigI
return ConfigInspectResult{}, err
}
resp, err := cli.get(ctx, "/configs/"+id, nil, nil)
defer ensureReaderClosed(resp)
if err != nil {
return ConfigInspectResult{}, err
}

View File

@ -16,6 +16,11 @@ type ContainerAttachOptions struct {
Logs bool
}
// ContainerAttachResult is the result from attaching to a container.
type ContainerAttachResult struct {
HijackedResponse
}
// ContainerAttach attaches a connection to a container in the server.
// It returns a [HijackedResponse] with the hijacked connection
// and a reader to get output. It's up to the called to close
@ -44,10 +49,10 @@ type ContainerAttachOptions struct {
// [stdcopy.StdType]: https://pkg.go.dev/github.com/moby/moby/api/pkg/stdcopy#StdType
// [Stdout]: https://pkg.go.dev/github.com/moby/moby/api/pkg/stdcopy#Stdout
// [Stderr]: https://pkg.go.dev/github.com/moby/moby/api/pkg/stdcopy#Stderr
func (cli *Client) ContainerAttach(ctx context.Context, containerID string, options ContainerAttachOptions) (HijackedResponse, error) {
func (cli *Client) ContainerAttach(ctx context.Context, containerID string, options ContainerAttachOptions) (ContainerAttachResult, error) {
containerID, err := trimID("container", containerID)
if err != nil {
return HijackedResponse{}, err
return ContainerAttachResult{}, err
}
query := url.Values{}
@ -70,7 +75,12 @@ func (cli *Client) ContainerAttach(ctx context.Context, containerID string, opti
query.Set("logs", "1")
}
return cli.postHijacked(ctx, "/containers/"+containerID+"/attach", query, nil, http.Header{
hijacked, err := cli.postHijacked(ctx, "/containers/"+containerID+"/attach", query, nil, http.Header{
"Content-Type": {"text/plain"},
})
if err != nil {
return ContainerAttachResult{}, err
}
return ContainerAttachResult{HijackedResponse: hijacked}, nil
}

View File

@ -20,22 +20,27 @@ type ContainerCommitOptions struct {
Config *container.Config
}
// ContainerCommitResult is the result from committing a container.
type ContainerCommitResult struct {
ID string
}
// ContainerCommit applies changes to a container and creates a new tagged image.
func (cli *Client) ContainerCommit(ctx context.Context, containerID string, options ContainerCommitOptions) (container.CommitResponse, error) {
func (cli *Client) ContainerCommit(ctx context.Context, containerID string, options ContainerCommitOptions) (ContainerCommitResult, error) {
containerID, err := trimID("container", containerID)
if err != nil {
return container.CommitResponse{}, err
return ContainerCommitResult{}, err
}
var repository, tag string
if options.Reference != "" {
ref, err := reference.ParseNormalizedNamed(options.Reference)
if err != nil {
return container.CommitResponse{}, err
return ContainerCommitResult{}, err
}
if _, ok := ref.(reference.Digested); ok {
return container.CommitResponse{}, errors.New("refusing to create a tag with a digest reference")
return ContainerCommitResult{}, errors.New("refusing to create a tag with a digest reference")
}
ref = reference.TagNameOnly(ref)
@ -62,9 +67,9 @@ func (cli *Client) ContainerCommit(ctx context.Context, containerID string, opti
resp, err := cli.post(ctx, "/commit", query, options.Config, nil)
defer ensureReaderClosed(resp)
if err != nil {
return response, err
return ContainerCommitResult{}, err
}
err = json.NewDecoder(resp.Body).Decode(&response)
return response, err
return ContainerCommitResult{ID: response.ID}, err
}

View File

@ -10,49 +10,63 @@ import (
cerrdefs "github.com/containerd/errdefs"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/network"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
// ContainerCreate creates a new container based on the given configuration.
// It can be associated with a name, but it's not mandatory.
func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error) {
if config == nil {
return container.CreateResponse{}, cerrdefs.ErrInvalidArgument.WithMessage("config is nil")
func (cli *Client) ContainerCreate(ctx context.Context, options ContainerCreateOptions) (ContainerCreateResult, error) {
cfg := options.Config
if cfg == nil {
cfg = &container.Config{}
}
if options.Image != "" {
if cfg.Image != "" {
return ContainerCreateResult{}, cerrdefs.ErrInvalidArgument.WithMessage("either Image or config.Image should be set")
}
newCfg := *cfg
newCfg.Image = options.Image
cfg = &newCfg
}
if cfg.Image == "" {
return ContainerCreateResult{}, cerrdefs.ErrInvalidArgument.WithMessage("config.Image or Image is required")
}
var response container.CreateResponse
if hostConfig != nil {
hostConfig.CapAdd = normalizeCapabilities(hostConfig.CapAdd)
hostConfig.CapDrop = normalizeCapabilities(hostConfig.CapDrop)
if options.HostConfig != nil {
options.HostConfig.CapAdd = normalizeCapabilities(options.HostConfig.CapAdd)
options.HostConfig.CapDrop = normalizeCapabilities(options.HostConfig.CapDrop)
}
query := url.Values{}
if platform != nil {
if p := formatPlatform(*platform); p != "unknown" {
if options.Platform != nil {
if p := formatPlatform(*options.Platform); p != "unknown" {
query.Set("platform", p)
}
}
if containerName != "" {
query.Set("name", containerName)
if options.Name != "" {
query.Set("name", options.Name)
}
body := container.CreateRequest{
Config: config,
HostConfig: hostConfig,
NetworkingConfig: networkingConfig,
Config: cfg,
HostConfig: options.HostConfig,
NetworkingConfig: options.NetworkingConfig,
}
resp, err := cli.post(ctx, "/containers/create", query, body, nil)
defer ensureReaderClosed(resp)
if err != nil {
return response, err
return ContainerCreateResult{}, err
}
err = json.NewDecoder(resp.Body).Decode(&response)
return response, err
return ContainerCreateResult{ID: response.ID, Warnings: response.Warnings}, err
}
// formatPlatform returns a formatted string representing platform (e.g., "linux/arm/v7").

View File

@ -0,0 +1,25 @@
package client
import (
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/network"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
// ContainerCreateOptions holds parameters to create a container.
type ContainerCreateOptions struct {
Config *container.Config
HostConfig *container.HostConfig
NetworkingConfig *network.NetworkingConfig
Platform *ocispec.Platform
Name string
// Image is a shortcut for Config.Image - only one of Image or Config.Image should be set.
Image string
}
// ContainerCreateResult is the result from creating a container.
type ContainerCreateResult struct {
ID string
Warnings []string
}

View File

@ -9,22 +9,22 @@ import (
)
// ContainerDiff shows differences in a container filesystem since it was started.
func (cli *Client) ContainerDiff(ctx context.Context, containerID string) ([]container.FilesystemChange, error) {
func (cli *Client) ContainerDiff(ctx context.Context, containerID string, options ContainerDiffOptions) (ContainerDiffResult, error) {
containerID, err := trimID("container", containerID)
if err != nil {
return nil, err
return ContainerDiffResult{}, err
}
resp, err := cli.get(ctx, "/containers/"+containerID+"/changes", url.Values{}, nil)
defer ensureReaderClosed(resp)
if err != nil {
return nil, err
return ContainerDiffResult{}, err
}
var changes []container.FilesystemChange
err = json.NewDecoder(resp.Body).Decode(&changes)
if err != nil {
return nil, err
return ContainerDiffResult{}, err
}
return changes, err
return ContainerDiffResult{Changes: changes}, err
}

View File

@ -0,0 +1,13 @@
package client
import "github.com/moby/moby/api/types/container"
// ContainerDiffOptions holds parameters to show differences in a container filesystem.
type ContainerDiffOptions struct {
// Currently no options, but this allows for future extensibility
}
// ContainerDiffResult is the result from showing differences in a container filesystem.
type ContainerDiffResult struct {
Changes []container.FilesystemChange
}

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

@ -8,7 +8,7 @@ import (
cerrdefs "github.com/containerd/errdefs"
"github.com/containerd/errdefs/pkg/errhttp"
"github.com/moby/moby/api/types/versions"
"github.com/moby/moby/client/pkg/versions"
)
// errConnectionFailed implements an error returned when connection failed.

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

@ -6,7 +6,7 @@ import (
"net/url"
"github.com/moby/moby/api/types/image"
"github.com/moby/moby/api/types/versions"
"github.com/moby/moby/client/pkg/versions"
)
// ImageList returns a list of images in the docker host.

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

@ -2,6 +2,7 @@ package client
import (
"context"
"encoding/json"
"net/url"
"github.com/moby/moby/api/types/network"
@ -10,7 +11,7 @@ import (
// NetworkInspectResult contains the result of a network inspection.
type NetworkInspectResult struct {
Network network.Inspect
Raw []byte
Raw json.RawMessage
}
// NetworkInspect returns the information for a specific network configured in the docker host.
@ -28,7 +29,6 @@ func (cli *Client) NetworkInspect(ctx context.Context, networkID string, options
}
resp, err := cli.get(ctx, "/networks/"+networkID, query, nil)
defer ensureReaderClosed(resp)
if err != nil {
return NetworkInspectResult{}, err
}

View File

@ -9,9 +9,12 @@ 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
Raw json.RawMessage
}
// NodeInspect returns the node information.

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

@ -2,6 +2,7 @@ package client
import (
"context"
"encoding/json"
"github.com/moby/moby/api/types/plugin"
)
@ -13,8 +14,8 @@ type PluginInspectOptions struct {
// PluginInspectResult holds the result from the [Client.PluginInspect] method.
type PluginInspectResult struct {
Raw []byte
Plugin plugin.Plugin
Raw json.RawMessage
}
// PluginInspect inspects an existing plugin
@ -24,7 +25,6 @@ func (cli *Client) PluginInspect(ctx context.Context, name string, options Plugi
return PluginInspectResult{}, err
}
resp, err := cli.get(ctx, "/plugins/"+name+"/json", nil, nil)
defer ensureReaderClosed(resp)
if err != nil {
return PluginInspectResult{}, err
}

View File

@ -2,6 +2,7 @@ package client
import (
"context"
"encoding/json"
"github.com/moby/moby/api/types/swarm"
)
@ -14,7 +15,7 @@ type SecretInspectOptions struct {
// SecretInspectResult holds the result from the [Client.SecretInspect]. method.
type SecretInspectResult struct {
Secret swarm.Secret
Raw []byte
Raw json.RawMessage
}
// SecretInspect returns the secret information with raw data.
@ -24,7 +25,6 @@ func (cli *Client) SecretInspect(ctx context.Context, id string, options SecretI
return SecretInspectResult{}, err
}
resp, err := cli.get(ctx, "/secrets/"+id, nil, nil)
defer ensureReaderClosed(resp)
if err != nil {
return SecretInspectResult{}, err
}

View File

@ -16,6 +16,8 @@ import (
// ServiceCreateOptions contains the options to use when creating a service.
type ServiceCreateOptions struct {
Spec swarm.ServiceSpec
// EncodedRegistryAuth is the encoded registry authorization credentials to
// use when updating the service.
//
@ -39,33 +41,33 @@ type ServiceCreateResult struct {
}
// ServiceCreate creates a new service.
func (cli *Client) ServiceCreate(ctx context.Context, service swarm.ServiceSpec, options ServiceCreateOptions) (ServiceCreateResult, error) {
func (cli *Client) ServiceCreate(ctx context.Context, options ServiceCreateOptions) (ServiceCreateResult, error) {
// Make sure containerSpec is not nil when no runtime is set or the runtime is set to container
if service.TaskTemplate.ContainerSpec == nil && (service.TaskTemplate.Runtime == "" || service.TaskTemplate.Runtime == swarm.RuntimeContainer) {
service.TaskTemplate.ContainerSpec = &swarm.ContainerSpec{}
if options.Spec.TaskTemplate.ContainerSpec == nil && (options.Spec.TaskTemplate.Runtime == "" || options.Spec.TaskTemplate.Runtime == swarm.RuntimeContainer) {
options.Spec.TaskTemplate.ContainerSpec = &swarm.ContainerSpec{}
}
if err := validateServiceSpec(service); err != nil {
if err := validateServiceSpec(options.Spec); err != nil {
return ServiceCreateResult{}, err
}
// ensure that the image is tagged
var warnings []string
switch {
case service.TaskTemplate.ContainerSpec != nil:
if taggedImg := imageWithTagString(service.TaskTemplate.ContainerSpec.Image); taggedImg != "" {
service.TaskTemplate.ContainerSpec.Image = taggedImg
case options.Spec.TaskTemplate.ContainerSpec != nil:
if taggedImg := imageWithTagString(options.Spec.TaskTemplate.ContainerSpec.Image); taggedImg != "" {
options.Spec.TaskTemplate.ContainerSpec.Image = taggedImg
}
if options.QueryRegistry {
resolveWarning := resolveContainerSpecImage(ctx, cli, &service.TaskTemplate, options.EncodedRegistryAuth)
resolveWarning := resolveContainerSpecImage(ctx, cli, &options.Spec.TaskTemplate, options.EncodedRegistryAuth)
warnings = append(warnings, resolveWarning)
}
case service.TaskTemplate.PluginSpec != nil:
if taggedImg := imageWithTagString(service.TaskTemplate.PluginSpec.Remote); taggedImg != "" {
service.TaskTemplate.PluginSpec.Remote = taggedImg
case options.Spec.TaskTemplate.PluginSpec != nil:
if taggedImg := imageWithTagString(options.Spec.TaskTemplate.PluginSpec.Remote); taggedImg != "" {
options.Spec.TaskTemplate.PluginSpec.Remote = taggedImg
}
if options.QueryRegistry {
resolveWarning := resolvePluginSpecRemote(ctx, cli, &service.TaskTemplate, options.EncodedRegistryAuth)
resolveWarning := resolvePluginSpecRemote(ctx, cli, &options.Spec.TaskTemplate, options.EncodedRegistryAuth)
warnings = append(warnings, resolveWarning)
}
}
@ -74,7 +76,7 @@ func (cli *Client) ServiceCreate(ctx context.Context, service swarm.ServiceSpec,
if options.EncodedRegistryAuth != "" {
headers[registry.AuthHeader] = []string{options.EncodedRegistryAuth}
}
resp, err := cli.post(ctx, "/services/create", nil, service, headers)
resp, err := cli.post(ctx, "/services/create", nil, options.Spec, headers)
defer ensureReaderClosed(resp)
if err != nil {
return ServiceCreateResult{}, err

View File

@ -2,6 +2,7 @@ package client
import (
"context"
"encoding/json"
"fmt"
"net/url"
@ -16,7 +17,7 @@ type ServiceInspectOptions struct {
// ServiceInspectResult represents the result of a service inspect operation.
type ServiceInspectResult struct {
Service swarm.Service
Raw []byte
Raw json.RawMessage
}
// ServiceInspect retrieves detailed information about a specific service by its ID.
@ -29,7 +30,6 @@ func (cli *Client) ServiceInspect(ctx context.Context, serviceID string, options
query := url.Values{}
query.Set("insertDefaults", fmt.Sprintf("%v", options.InsertDefaults))
resp, err := cli.get(ctx, "/services/"+serviceID, query, nil)
defer ensureReaderClosed(resp)
if err != nil {
return ServiceInspectResult{}, err
}

View File

@ -12,6 +12,9 @@ import (
// ServiceUpdateOptions contains the options to be used for updating services.
type ServiceUpdateOptions struct {
Version swarm.Version
Spec swarm.ServiceSpec
// EncodedRegistryAuth is the encoded registry authorization credentials to
// use when updating the service.
//
@ -50,13 +53,13 @@ type ServiceUpdateResult struct {
// conflicting writes. It must be the value as set *before* the update.
// You can find this value in the [swarm.Service.Meta] field, which can
// be found using [Client.ServiceInspectWithRaw].
func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options ServiceUpdateOptions) (ServiceUpdateResult, error) {
func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, options ServiceUpdateOptions) (ServiceUpdateResult, error) {
serviceID, err := trimID("service", serviceID)
if err != nil {
return ServiceUpdateResult{}, err
}
if err := validateServiceSpec(service); err != nil {
if err := validateServiceSpec(options.Spec); err != nil {
return ServiceUpdateResult{}, err
}
@ -69,25 +72,25 @@ func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version
query.Set("rollback", options.Rollback)
}
query.Set("version", version.String())
query.Set("version", options.Version.String())
// ensure that the image is tagged
var warnings []string
switch {
case service.TaskTemplate.ContainerSpec != nil:
if taggedImg := imageWithTagString(service.TaskTemplate.ContainerSpec.Image); taggedImg != "" {
service.TaskTemplate.ContainerSpec.Image = taggedImg
case options.Spec.TaskTemplate.ContainerSpec != nil:
if taggedImg := imageWithTagString(options.Spec.TaskTemplate.ContainerSpec.Image); taggedImg != "" {
options.Spec.TaskTemplate.ContainerSpec.Image = taggedImg
}
if options.QueryRegistry {
resolveWarning := resolveContainerSpecImage(ctx, cli, &service.TaskTemplate, options.EncodedRegistryAuth)
resolveWarning := resolveContainerSpecImage(ctx, cli, &options.Spec.TaskTemplate, options.EncodedRegistryAuth)
warnings = append(warnings, resolveWarning)
}
case service.TaskTemplate.PluginSpec != nil:
if taggedImg := imageWithTagString(service.TaskTemplate.PluginSpec.Remote); taggedImg != "" {
service.TaskTemplate.PluginSpec.Remote = taggedImg
case options.Spec.TaskTemplate.PluginSpec != nil:
if taggedImg := imageWithTagString(options.Spec.TaskTemplate.PluginSpec.Remote); taggedImg != "" {
options.Spec.TaskTemplate.PluginSpec.Remote = taggedImg
}
if options.QueryRegistry {
resolveWarning := resolvePluginSpecRemote(ctx, cli, &service.TaskTemplate, options.EncodedRegistryAuth)
resolveWarning := resolvePluginSpecRemote(ctx, cli, &options.Spec.TaskTemplate, options.EncodedRegistryAuth)
warnings = append(warnings, resolveWarning)
}
}
@ -96,7 +99,7 @@ func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version
if options.EncodedRegistryAuth != "" {
headers.Set(registry.AuthHeader, options.EncodedRegistryAuth)
}
resp, err := cli.post(ctx, "/services/"+serviceID+"/update", query, service, headers)
resp, err := cli.post(ctx, "/services/"+serviceID+"/update", query, options.Spec, headers)
defer ensureReaderClosed(resp)
if err != nil {
return ServiceUpdateResult{}, 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

@ -10,7 +10,8 @@ import (
// SwarmUpdateOptions contains options for updating a swarm.
type SwarmUpdateOptions struct {
Swarm swarm.Spec
Version swarm.Version
Spec swarm.Spec
RotateWorkerToken bool
RotateManagerToken bool
RotateManagerUnlockKey bool
@ -20,13 +21,13 @@ type SwarmUpdateOptions struct {
type SwarmUpdateResult struct{}
// SwarmUpdate updates the swarm.
func (cli *Client) SwarmUpdate(ctx context.Context, version swarm.Version, options SwarmUpdateOptions) (SwarmUpdateResult, error) {
func (cli *Client) SwarmUpdate(ctx context.Context, options SwarmUpdateOptions) (SwarmUpdateResult, error) {
query := url.Values{}
query.Set("version", version.String())
query.Set("version", options.Version.String())
query.Set("rotateWorkerToken", strconv.FormatBool(options.RotateWorkerToken))
query.Set("rotateManagerToken", strconv.FormatBool(options.RotateManagerToken))
query.Set("rotateManagerUnlockKey", strconv.FormatBool(options.RotateManagerUnlockKey))
resp, err := cli.post(ctx, "/swarm/update", query, options.Swarm, nil)
resp, err := cli.post(ctx, "/swarm/update", query, options.Spec, nil)
defer ensureReaderClosed(resp)
return SwarmUpdateResult{}, err
}

View File

@ -2,6 +2,7 @@ package client
import (
"context"
"encoding/json"
"github.com/moby/moby/api/types/swarm"
)
@ -14,7 +15,7 @@ type TaskInspectOptions struct {
// TaskInspectResult contains the result of a task inspection.
type TaskInspectResult struct {
Task swarm.Task
Raw []byte
Raw json.RawMessage
}
// TaskInspect returns the task information and its raw representation.
@ -25,7 +26,6 @@ func (cli *Client) TaskInspect(ctx context.Context, taskID string, options TaskI
}
resp, err := cli.get(ctx, "/tasks/"+taskID, nil, nil)
defer ensureReaderClosed(resp)
if err != nil {
return TaskInspectResult{}, err
}

View File

@ -70,11 +70,11 @@ func encodePlatform(platform *ocispec.Platform) (string, error) {
return string(p), nil
}
func decodeWithRaw[T any](resp *http.Response, out *T) (raw []byte, _ error) {
func decodeWithRaw[T any](resp *http.Response, out *T) (raw json.RawMessage, _ error) {
if resp == nil || resp.Body == nil {
return nil, errors.New("empty response")
}
defer resp.Body.Close()
defer ensureReaderClosed(resp)
var buf bytes.Buffer
tr := io.TeeReader(resp.Body, &buf)

View File

@ -2,6 +2,7 @@ package client
import (
"context"
"encoding/json"
"github.com/moby/moby/api/types/volume"
)
@ -13,8 +14,8 @@ type VolumeInspectOptions struct {
// VolumeInspectResult holds the result from the [Client.VolumeInspect] method.
type VolumeInspectResult struct {
Raw []byte
Volume volume.Volume
Raw json.RawMessage
}
// VolumeInspect returns the information about a specific volume in the docker host.
@ -25,7 +26,6 @@ func (cli *Client) VolumeInspect(ctx context.Context, volumeID string, options V
}
resp, err := cli.get(ctx, "/volumes/"+volumeID, nil, nil)
defer ensureReaderClosed(resp)
if err != nil {
return VolumeInspectResult{}, err
}

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
}

6
vendor/modules.txt vendored
View File

@ -168,7 +168,7 @@ github.com/moby/docker-image-spec/specs-go/v1
github.com/moby/go-archive
github.com/moby/go-archive/compression
github.com/moby/go-archive/tarheader
# github.com/moby/moby/api v1.52.0-beta.2.0.20251023134003-dcd668d5796b
# github.com/moby/moby/api v1.52.0-beta.2.0.20251024193508-be8d6e2f2825
## explicit; go 1.23.0
github.com/moby/moby/api/pkg/authconfig
github.com/moby/moby/api/pkg/progress
@ -191,9 +191,8 @@ github.com/moby/moby/api/types/registry
github.com/moby/moby/api/types/storage
github.com/moby/moby/api/types/swarm
github.com/moby/moby/api/types/system
github.com/moby/moby/api/types/versions
github.com/moby/moby/api/types/volume
# github.com/moby/moby/client v0.1.0-beta.2.0.20251023134003-dcd668d5796b
# github.com/moby/moby/client v0.1.0-beta.2.0.20251024193508-be8d6e2f2825
## explicit; go 1.23.0
github.com/moby/moby/client
github.com/moby/moby/client/internal
@ -201,6 +200,7 @@ github.com/moby/moby/client/internal/timestamp
github.com/moby/moby/client/pkg/jsonmessage
github.com/moby/moby/client/pkg/security
github.com/moby/moby/client/pkg/stringid
github.com/moby/moby/client/pkg/versions
# github.com/moby/patternmatcher v0.6.0
## explicit; go 1.19
github.com/moby/patternmatcher