1
0
mirror of https://github.com/docker/cli.git synced 2026-01-06 05:41:44 +03:00

Merge pull request #6586 from thaJeztah/bump_modules

vendor: github.com/moby/moby/api, moby/moby/client master
This commit is contained in:
Paweł Gronowski
2025-10-29 23:53:04 +01:00
committed by GitHub
99 changed files with 1009 additions and 683 deletions

View File

@@ -377,24 +377,21 @@ func (cli *DockerCli) initializeFromClient() {
ctx, cancel := context.WithTimeout(cli.baseCtx, cli.getInitTimeout())
defer cancel()
ping, err := cli.client.Ping(ctx, client.PingOptions{})
ping, err := cli.client.Ping(ctx, client.PingOptions{
NegotiateAPIVersion: true,
ForceNegotiate: true,
})
if err != nil {
// Default to true if we fail to connect to daemon
cli.serverInfo = ServerInfo{HasExperimental: true}
if ping.APIVersion != "" {
cli.client.NegotiateAPIVersionPing(ping)
}
return
}
cli.serverInfo = ServerInfo{
HasExperimental: ping.Experimental,
OSType: ping.OSType,
BuildkitVersion: ping.BuilderVersion,
SwarmStatus: ping.SwarmStatus,
}
cli.client.NegotiateAPIVersionPing(ping)
}
// ContextStore returns the ContextStore

View File

@@ -139,18 +139,22 @@ type fakeClient struct {
negotiated bool
}
func (c *fakeClient) Ping(_ context.Context, _ client.PingOptions) (client.PingResult, error) {
return c.pingFunc()
func (c *fakeClient) Ping(_ context.Context, options client.PingOptions) (client.PingResult, error) {
res, err := c.pingFunc()
if options.NegotiateAPIVersion {
if res.APIVersion != "" {
if c.negotiated || options.ForceNegotiate {
c.negotiated = true
}
}
}
return res, err
}
func (c *fakeClient) ClientVersion() string {
return c.version
}
func (c *fakeClient) NegotiateAPIVersionPing(client.PingResult) {
c.negotiated = true
}
func TestInitializeFromClient(t *testing.T) {
const defaultVersion = "v1.55"

View File

@@ -77,7 +77,7 @@ func ImageNamesWithBase(dockerCLI APIClientProvider, limit int) cobra.Completion
// Set DOCKER_COMPLETION_SHOW_CONTAINER_IDS=yes to also complete IDs.
func ContainerNames(dockerCLI APIClientProvider, all bool, filters ...func(container.Summary) bool) cobra.CompletionFunc {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
list, err := dockerCLI.Client().ContainerList(cmd.Context(), client.ContainerListOptions{
res, err := dockerCLI.Client().ContainerList(cmd.Context(), client.ContainerListOptions{
All: all,
})
if err != nil {
@@ -87,7 +87,7 @@ func ContainerNames(dockerCLI APIClientProvider, all bool, filters ...func(conta
showContainerIDs := os.Getenv("DOCKER_COMPLETION_SHOW_CONTAINER_IDS") == "yes"
var names []string
for _, ctr := range list {
for _, ctr := range res.Items {
skip := false
for _, fn := range filters {
if fn != nil && !fn(ctr) {

View File

@@ -28,17 +28,17 @@ func (c fakeCLI) Client() client.APIClient {
type fakeClient struct {
client.Client
containerListFunc func(context.Context, client.ContainerListOptions) ([]container.Summary, error)
containerListFunc func(context.Context, client.ContainerListOptions) (client.ContainerListResult, error)
imageListFunc func(context.Context, client.ImageListOptions) (client.ImageListResult, error)
networkListFunc func(context.Context, client.NetworkListOptions) (client.NetworkListResult, error)
volumeListFunc func(context.Context, client.VolumeListOptions) (client.VolumeListResult, error)
}
func (c *fakeClient) ContainerList(ctx context.Context, options client.ContainerListOptions) ([]container.Summary, error) {
func (c *fakeClient) ContainerList(ctx context.Context, options client.ContainerListOptions) (client.ContainerListResult, error) {
if c.containerListFunc != nil {
return c.containerListFunc(ctx, options)
}
return []container.Summary{}, nil
return client.ContainerListResult{}, nil
}
func (c *fakeClient) ImageList(ctx context.Context, options client.ImageListOptions) (client.ImageListResult, error) {
@@ -153,12 +153,12 @@ func TestCompleteContainerNames(t *testing.T) {
t.Setenv("DOCKER_COMPLETION_SHOW_CONTAINER_IDS", "yes")
}
comp := ContainerNames(fakeCLI{&fakeClient{
containerListFunc: func(_ context.Context, opts client.ContainerListOptions) ([]container.Summary, error) {
containerListFunc: func(_ context.Context, opts client.ContainerListOptions) (client.ContainerListResult, error) {
assert.Check(t, is.DeepEqual(opts, tc.expOpts))
if tc.expDirective == cobra.ShellCompDirectiveError {
return nil, errors.New("some error occurred")
return client.ContainerListResult{}, errors.New("some error occurred")
}
return tc.containers, nil
return client.ContainerListResult{Items: tc.containers}, nil
},
}}, tc.showAll, tc.filters...)

View File

@@ -74,7 +74,7 @@ func RunAttach(ctx context.Context, dockerCLI command.Cli, containerID string, o
// request channel to wait for client
waitCtx := context.WithoutCancel(ctx)
resultC, errC := apiClient.ContainerWait(waitCtx, containerID, "")
waitRes := apiClient.ContainerWait(waitCtx, containerID, client.ContainerWaitOptions{})
c, err := inspectContainerAndCheckState(ctx, apiClient, containerID)
if err != nil {
@@ -152,19 +152,19 @@ func RunAttach(ctx context.Context, dockerCLI command.Cli, containerID string, o
return err
}
return getExitStatus(errC, resultC)
return getExitStatus(waitRes)
}
func getExitStatus(errC <-chan error, resultC <-chan container.WaitResponse) error {
func getExitStatus(waitRes client.ContainerWaitResult) error {
select {
case result := <-resultC:
case result := <-waitRes.Result:
if result.Error != nil {
return errors.New(result.Error.Message)
}
if result.StatusCode != 0 {
return cli.StatusError{StatusCode: int(result.StatusCode)}
}
case err := <-errC:
case err := <-waitRes.Error:
return err
}

View File

@@ -125,7 +125,10 @@ func TestGetExitStatus(t *testing.T) {
resultC <- *testcase.result
}
err := getExitStatus(errC, resultC)
err := getExitStatus(client.ContainerWaitResult{
Result: resultC,
Error: errC,
})
if testcase.expectedError == nil {
assert.NilError(t, err)

View File

@@ -3,12 +3,35 @@ package container
import (
"context"
"io"
"reflect"
"strings"
"unsafe"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/system"
"github.com/moby/moby/client"
)
func mockContainerExportResult(content string) client.ContainerExportResult {
out := client.ContainerExportResult{}
// Set unexported field "rc"
v := reflect.ValueOf(&out).Elem()
f := v.FieldByName("rc")
r := io.NopCloser(strings.NewReader(content))
reflect.NewAt(f.Type(), unsafe.Pointer(f.UnsafeAddr())).Elem().Set(reflect.ValueOf(r))
return out
}
func mockContainerLogsResult(content string) client.ContainerLogsResult {
out := client.ContainerLogsResult{}
// Set unexported field "rc"
v := reflect.ValueOf(&out).Elem()
f := v.FieldByName("rc")
r := io.NopCloser(strings.NewReader(content))
reflect.NewAt(f.Type(), unsafe.Pointer(f.UnsafeAddr())).Elem().Set(reflect.ValueOf(r))
return out
}
type fakeClient struct {
client.Client
inspectFunc func(string) (client.ContainerInspectResult, error)
@@ -17,13 +40,13 @@ type fakeClient struct {
createContainerFunc func(options client.ContainerCreateOptions) (client.ContainerCreateResult, error)
containerStartFunc func(containerID string, options client.ContainerStartOptions) (client.ContainerStartResult, error)
imageCreateFunc func(ctx context.Context, parentReference string, options client.ImageCreateOptions) (client.ImageCreateResult, error)
infoFunc func() (system.Info, error)
containerStatPathFunc func(containerID, path string) (container.PathStat, error)
containerCopyFromFunc func(containerID, srcPath string) (io.ReadCloser, container.PathStat, error)
logFunc func(string, client.ContainerLogsOptions) (io.ReadCloser, error)
waitFunc func(string) (<-chan container.WaitResponse, <-chan error)
containerListFunc func(client.ContainerListOptions) ([]container.Summary, error)
containerExportFunc func(string) (io.ReadCloser, error)
infoFunc func() (client.SystemInfoResult, error)
containerStatPathFunc func(containerID, path string) (client.ContainerStatPathResult, error)
containerCopyFromFunc func(containerID, srcPath string) (client.CopyFromContainerResult, error)
logFunc func(string, client.ContainerLogsOptions) (client.ContainerLogsResult, error)
waitFunc func(string) client.ContainerWaitResult
containerListFunc func(client.ContainerListOptions) (client.ContainerListResult, error)
containerExportFunc func(string) (client.ContainerExportResult, error)
containerExecResizeFunc func(id string, options client.ExecResizeOptions) (client.ExecResizeResult, error)
containerRemoveFunc func(ctx context.Context, containerID string, options client.ContainerRemoveOptions) (client.ContainerRemoveResult, error)
containerRestartFunc func(ctx context.Context, containerID string, options client.ContainerRestartOptions) (client.ContainerRestartResult, error)
@@ -38,14 +61,14 @@ type fakeClient struct {
Version string
}
func (f *fakeClient) ContainerList(_ context.Context, options client.ContainerListOptions) ([]container.Summary, error) {
func (f *fakeClient) ContainerList(_ context.Context, options client.ContainerListOptions) (client.ContainerListResult, error) {
if f.containerListFunc != nil {
return f.containerListFunc(options)
}
return []container.Summary{}, nil
return client.ContainerListResult{}, nil
}
func (f *fakeClient) ContainerInspect(_ context.Context, containerID string, options client.ContainerInspectOptions) (client.ContainerInspectResult, error) {
func (f *fakeClient) ContainerInspect(_ context.Context, containerID string, _ client.ContainerInspectOptions) (client.ContainerInspectResult, error) {
if f.inspectFunc != nil {
return f.inspectFunc(containerID)
}
@@ -91,43 +114,43 @@ func (f *fakeClient) ImageCreate(ctx context.Context, parentReference string, op
return client.ImageCreateResult{}, nil
}
func (f *fakeClient) Info(_ context.Context) (system.Info, error) {
func (f *fakeClient) Info(context.Context, client.InfoOptions) (client.SystemInfoResult, error) {
if f.infoFunc != nil {
return f.infoFunc()
}
return system.Info{}, nil
return client.SystemInfoResult{}, nil
}
func (f *fakeClient) ContainerStatPath(_ context.Context, containerID, path string) (container.PathStat, error) {
func (f *fakeClient) ContainerStatPath(_ context.Context, containerID string, options client.ContainerStatPathOptions) (client.ContainerStatPathResult, error) {
if f.containerStatPathFunc != nil {
return f.containerStatPathFunc(containerID, path)
return f.containerStatPathFunc(containerID, options.Path)
}
return container.PathStat{}, nil
return client.ContainerStatPathResult{}, nil
}
func (f *fakeClient) CopyFromContainer(_ context.Context, containerID, srcPath string) (io.ReadCloser, container.PathStat, error) {
func (f *fakeClient) CopyFromContainer(_ context.Context, containerID string, options client.CopyFromContainerOptions) (client.CopyFromContainerResult, error) {
if f.containerCopyFromFunc != nil {
return f.containerCopyFromFunc(containerID, srcPath)
return f.containerCopyFromFunc(containerID, options.SourcePath)
}
return nil, container.PathStat{}, nil
return client.CopyFromContainerResult{}, nil
}
func (f *fakeClient) ContainerLogs(_ context.Context, containerID string, options client.ContainerLogsOptions) (io.ReadCloser, error) {
func (f *fakeClient) ContainerLogs(_ context.Context, containerID string, options client.ContainerLogsOptions) (client.ContainerLogsResult, error) {
if f.logFunc != nil {
return f.logFunc(containerID, options)
}
return nil, nil
return client.ContainerLogsResult{}, nil
}
func (f *fakeClient) ClientVersion() string {
return f.Version
}
func (f *fakeClient) ContainerWait(_ context.Context, containerID string, _ container.WaitCondition) (<-chan container.WaitResponse, <-chan error) {
func (f *fakeClient) ContainerWait(_ context.Context, containerID string, _ client.ContainerWaitOptions) client.ContainerWaitResult {
if f.waitFunc != nil {
return f.waitFunc(containerID)
}
return nil, nil
return client.ContainerWaitResult{}
}
func (f *fakeClient) ContainerStart(_ context.Context, containerID string, options client.ContainerStartOptions) (client.ContainerStartResult, error) {
@@ -137,11 +160,11 @@ func (f *fakeClient) ContainerStart(_ context.Context, containerID string, optio
return client.ContainerStartResult{}, nil
}
func (f *fakeClient) ContainerExport(_ context.Context, containerID string) (io.ReadCloser, error) {
func (f *fakeClient) ContainerExport(_ context.Context, containerID string, _ client.ContainerExportOptions) (client.ContainerExportResult, error) {
if f.containerExportFunc != nil {
return f.containerExportFunc(containerID)
}
return nil, nil
return client.ContainerExportResult{}, nil
}
func (f *fakeClient) ExecResize(_ context.Context, id string, options client.ExecResizeOptions) (client.ExecResizeResult, error) {
@@ -194,12 +217,12 @@ func (f *fakeClient) ContainerDiff(ctx context.Context, containerID string, _ cl
return client.ContainerDiffResult{}, nil
}
func (f *fakeClient) ContainerRename(ctx context.Context, oldName, newName string) error {
func (f *fakeClient) ContainerRename(ctx context.Context, oldName string, options client.ContainerRenameOptions) (client.ContainerRenameResult, error) {
if f.containerRenameFunc != nil {
return f.containerRenameFunc(ctx, oldName, newName)
return client.ContainerRenameResult{}, f.containerRenameFunc(ctx, oldName, options.NewName)
}
return nil
return client.ContainerRenameResult{}, nil
}
func (f *fakeClient) ContainerCommit(ctx context.Context, containerID string, options client.ContainerCommitOptions) (client.ContainerCommitResult, error) {

View File

@@ -9,6 +9,7 @@ import (
"github.com/docker/cli/cli/command/completion"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"github.com/moby/sys/capability"
"github.com/moby/sys/signal"
"github.com/spf13/cobra"
@@ -186,11 +187,11 @@ func completeLink(dockerCLI completion.APIClientProvider) cobra.CompletionFunc {
// of the build-in log drivers.
func completeLogDriver(dockerCLI completion.APIClientProvider) cobra.CompletionFunc {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
info, err := dockerCLI.Client().Info(cmd.Context())
res, err := dockerCLI.Client().Info(cmd.Context(), client.InfoOptions{})
if err != nil {
return builtInLogDrivers(), cobra.ShellCompDirectiveNoFileComp
}
drivers := info.Plugins.Log
drivers := res.Info.Plugins.Log
return drivers, cobra.ShellCompDirectiveNoFileComp
}
}
@@ -279,12 +280,12 @@ func completeUlimit(_ *cobra.Command, _ []string, _ string) ([]string, cobra.She
// completeVolumeDriver contacts the API to get the built-in and installed volume drivers.
func completeVolumeDriver(dockerCLI completion.APIClientProvider) cobra.CompletionFunc {
return func(cmd *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
info, err := dockerCLI.Client().Info(cmd.Context())
res, err := dockerCLI.Client().Info(cmd.Context(), client.InfoOptions{})
if err != nil {
// fallback: the built-in drivers
return []string{"local"}, cobra.ShellCompDirectiveNoFileComp
}
drivers := info.Plugins.Volume
drivers := res.Info.Plugins.Volume
return drivers, cobra.ShellCompDirectiveNoFileComp
}
}

View File

@@ -27,7 +27,7 @@ func TestCompleteLinuxCapabilityNames(t *testing.T) {
func TestCompletePid(t *testing.T) {
tests := []struct {
containerListFunc func(client.ContainerListOptions) ([]container.Summary, error)
containerListFunc func(client.ContainerListOptions) (client.ContainerListResult, error)
toComplete string
expectedCompletions []string
expectedDirective cobra.ShellCompDirective
@@ -43,10 +43,12 @@ func TestCompletePid(t *testing.T) {
expectedDirective: cobra.ShellCompDirectiveNoSpace,
},
{
containerListFunc: func(client.ContainerListOptions) ([]container.Summary, error) {
return []container.Summary{
*builders.Container("c1"),
*builders.Container("c2"),
containerListFunc: func(client.ContainerListOptions) (client.ContainerListResult, error) {
return client.ContainerListResult{
Items: []container.Summary{
*builders.Container("c1"),
*builders.Container("c2"),
},
}, nil
},
toComplete: "container:",

View File

@@ -230,11 +230,13 @@ func copyFromContainer(ctx context.Context, dockerCLI command.Cli, copyConfig cp
// if client requests to follow symbol link, then must decide target file to be copied
var rebaseName string
if copyConfig.followLink {
srcStat, err := apiClient.ContainerStatPath(ctx, copyConfig.container, srcPath)
src, err := apiClient.ContainerStatPath(ctx, copyConfig.container, client.ContainerStatPathOptions{
Path: srcPath,
})
// If the destination is a symbolic link, we should follow it.
if err == nil && srcStat.Mode&os.ModeSymlink != 0 {
linkTarget := srcStat.LinkTarget
if err == nil && src.Stat.Mode&os.ModeSymlink != 0 {
linkTarget := src.Stat.LinkTarget
if !isAbs(linkTarget) {
// Join with the parent directory.
srcParent, _ := archive.SplitPathDirEntry(srcPath)
@@ -249,11 +251,14 @@ func copyFromContainer(ctx context.Context, dockerCLI command.Cli, copyConfig cp
ctx, cancel := signal.NotifyContext(ctx, os.Interrupt)
defer cancel()
content, stat, err := apiClient.CopyFromContainer(ctx, copyConfig.container, srcPath)
cpRes, err := apiClient.CopyFromContainer(ctx, copyConfig.container, client.CopyFromContainerOptions{
SourcePath: srcPath,
})
if err != nil {
return err
}
defer content.Close()
content := cpRes.Content
defer func() { _ = content.Close() }()
if dstPath == "-" {
_, err = io.Copy(dockerCLI.Out(), content)
@@ -263,7 +268,7 @@ func copyFromContainer(ctx context.Context, dockerCLI command.Cli, copyConfig cp
srcInfo := archive.CopyInfo{
Path: srcPath,
Exists: true,
IsDir: stat.Mode.IsDir(),
IsDir: cpRes.Stat.Mode.IsDir(),
RebaseName: rebaseName,
}
@@ -315,10 +320,10 @@ func copyToContainer(ctx context.Context, dockerCLI command.Cli, copyConfig cpCo
apiClient := dockerCLI.Client()
// Prepare destination copy info by stat-ing the container path.
dstInfo := archive.CopyInfo{Path: dstPath}
if dstStat, err := apiClient.ContainerStatPath(ctx, copyConfig.container, dstPath); err == nil {
if dst, err := apiClient.ContainerStatPath(ctx, copyConfig.container, client.ContainerStatPathOptions{Path: dstPath}); err == nil {
// If the destination is a symbolic link, we should evaluate it.
if dstStat.Mode&os.ModeSymlink != 0 {
linkTarget := dstStat.LinkTarget
if dst.Stat.Mode&os.ModeSymlink != 0 {
linkTarget := dst.Stat.LinkTarget
if !isAbs(linkTarget) {
// Join with the parent directory.
dstParent, _ := archive.SplitPathDirEntry(dstPath)
@@ -326,14 +331,14 @@ func copyToContainer(ctx context.Context, dockerCLI command.Cli, copyConfig cpCo
}
dstInfo.Path = linkTarget
dstStat, err = apiClient.ContainerStatPath(ctx, copyConfig.container, linkTarget)
dst, err = apiClient.ContainerStatPath(ctx, copyConfig.container, client.ContainerStatPathOptions{Path: linkTarget})
}
// Validate the destination path
if err == nil {
if err := command.ValidateOutputPathFileMode(dstStat.Mode); err != nil {
if err := command.ValidateOutputPathFileMode(dst.Stat.Mode); err != nil {
return fmt.Errorf(`destination "%s:%s" must be a directory or a regular file: %w`, copyConfig.container, dstPath, err)
}
dstInfo.Exists, dstInfo.IsDir = true, dstStat.Mode.IsDir()
dstInfo.Exists, dstInfo.IsDir = true, dst.Stat.Mode.IsDir()
}
// Ignore any error and assume that the parent directory of the destination
@@ -399,22 +404,26 @@ func copyToContainer(ctx context.Context, dockerCLI command.Cli, copyConfig cpCo
}
options := client.CopyToContainerOptions{
CopyUIDGID: copyConfig.copyUIDGID,
DestinationPath: resolvedDstPath,
Content: content,
CopyUIDGID: copyConfig.copyUIDGID,
}
if copyConfig.quiet {
return apiClient.CopyToContainer(ctx, copyConfig.container, resolvedDstPath, content, options)
_, err := apiClient.CopyToContainer(ctx, copyConfig.container, options)
return err
}
ctx, cancel := signal.NotifyContext(ctx, os.Interrupt)
restore, done := copyProgress(ctx, dockerCLI.Err(), copyToContainerHeader, &copiedSize)
res := apiClient.CopyToContainer(ctx, copyConfig.container, resolvedDstPath, content, options)
// TODO(thaJeztah): error-handling looks odd here; should it be handled differently?
_, err := apiClient.CopyToContainer(ctx, copyConfig.container, options)
cancel()
<-done
restore()
_, _ = fmt.Fprintln(dockerCLI.Err(), "Successfully copied", progressHumanSize(copiedSize), "to", copyConfig.container+":"+dstInfo.Path)
return res
return err
}
// We use `:` as a delimiter between CONTAINER and PATH, but `:` could also be

View File

@@ -11,7 +11,7 @@ import (
"github.com/docker/cli/internal/test"
"github.com/moby/go-archive"
"github.com/moby/go-archive/compression"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/fs"
@@ -52,9 +52,11 @@ func TestRunCopyFromContainerToStdout(t *testing.T) {
tarContent := "the tar content"
cli := test.NewFakeCli(&fakeClient{
containerCopyFromFunc: func(ctr, srcPath string) (io.ReadCloser, container.PathStat, error) {
containerCopyFromFunc: func(ctr, srcPath string) (client.CopyFromContainerResult, error) {
assert.Check(t, is.Equal("container", ctr))
return io.NopCloser(strings.NewReader(tarContent)), container.PathStat{}, nil
return client.CopyFromContainerResult{
Content: io.NopCloser(strings.NewReader(tarContent)),
}, nil
},
})
err := runCopy(context.TODO(), cli, copyOptions{
@@ -73,10 +75,12 @@ func TestRunCopyFromContainerToFilesystem(t *testing.T) {
destDir := fs.NewDir(t, "cp-test")
cli := test.NewFakeCli(&fakeClient{
containerCopyFromFunc: func(ctr, srcPath string) (io.ReadCloser, container.PathStat, error) {
containerCopyFromFunc: func(ctr, srcPath string) (client.CopyFromContainerResult, error) {
assert.Check(t, is.Equal("container", ctr))
readCloser, err := archive.Tar(srcDir.Path(), compression.None)
return readCloser, container.PathStat{}, err
return client.CopyFromContainerResult{
Content: readCloser,
}, err
},
})
err := runCopy(context.TODO(), cli, copyOptions{
@@ -99,10 +103,12 @@ func TestRunCopyFromContainerToFilesystemMissingDestinationDirectory(t *testing.
defer destDir.Remove()
cli := test.NewFakeCli(&fakeClient{
containerCopyFromFunc: func(ctr, srcPath string) (io.ReadCloser, container.PathStat, error) {
containerCopyFromFunc: func(ctr, srcPath string) (client.CopyFromContainerResult, error) {
assert.Check(t, is.Equal("container", ctr))
readCloser, err := archive.TarWithOptions(destDir.Path(), &archive.TarOptions{})
return readCloser, container.PathStat{}, err
return client.CopyFromContainerResult{
Content: readCloser,
}, err
},
})
err := runCopy(context.TODO(), cli, copyOptions{

View File

@@ -410,7 +410,7 @@ func validatePullOpt(val string) error {
//
// The path should be an absolute path in the container, commonly
// /root/.docker/config.json.
func copyDockerConfigIntoContainer(ctx context.Context, dockerAPI client.APIClient, containerID string, configPath string, config *configfile.ConfigFile) error {
func copyDockerConfigIntoContainer(ctx context.Context, apiClient client.APIClient, containerID string, configPath string, config *configfile.ConfigFile) error {
var configBuf bytes.Buffer
if err := config.SaveToWriter(&configBuf); err != nil {
return fmt.Errorf("saving creds: %w", err)
@@ -433,8 +433,11 @@ func copyDockerConfigIntoContainer(ctx context.Context, dockerAPI client.APIClie
return fmt.Errorf("closing tar for config copy failed: %w", err)
}
if err := dockerAPI.CopyToContainer(ctx, containerID, "/",
&tarBuf, client.CopyToContainerOptions{}); err != nil {
_, err := apiClient.CopyToContainer(ctx, containerID, client.CopyToContainerOptions{
DestinationPath: "/",
Content: &tarBuf,
})
if err != nil {
return fmt.Errorf("copying config.json into container failed: %w", err)
}

View File

@@ -128,8 +128,10 @@ func TestCreateContainerImagePullPolicy(t *testing.T) {
defer func() { pullCounter++ }()
return client.ImageCreateResult{Body: io.NopCloser(strings.NewReader(""))}, nil
},
infoFunc: func() (system.Info, error) {
return system.Info{IndexServerAddress: "https://indexserver.example.com"}, nil
infoFunc: func() (client.SystemInfoResult, error) {
return client.SystemInfoResult{
Info: system.Info{IndexServerAddress: "https://indexserver.example.com"},
}, nil
},
}
fakeCLI := test.NewFakeCli(apiClient)

View File

@@ -9,6 +9,7 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/moby/moby/client"
"github.com/moby/sys/atomicwriter"
"github.com/spf13/cobra"
)
@@ -60,7 +61,7 @@ func runExport(ctx context.Context, dockerCLI command.Cli, opts exportOptions) e
output = writer
}
responseBody, err := dockerCLI.Client().ContainerExport(ctx, opts.container)
responseBody, err := dockerCLI.Client().ContainerExport(ctx, opts.container, client.ContainerExportOptions{})
if err != nil {
return err
}

View File

@@ -2,10 +2,10 @@ package container
import (
"io"
"strings"
"testing"
"github.com/docker/cli/internal/test"
"github.com/moby/moby/client"
"gotest.tools/v3/assert"
"gotest.tools/v3/fs"
)
@@ -15,8 +15,9 @@ func TestContainerExportOutputToFile(t *testing.T) {
defer dir.Remove()
cli := test.NewFakeCli(&fakeClient{
containerExportFunc: func(container string) (io.ReadCloser, error) {
return io.NopCloser(strings.NewReader("bar")), nil
containerExportFunc: func(container string) (client.ContainerExportResult, error) {
// FIXME(thaJeztah): how to mock this?
return mockContainerExportResult("bar"), nil
},
})
cmd := newExportCommand(cli)
@@ -33,8 +34,9 @@ func TestContainerExportOutputToFile(t *testing.T) {
func TestContainerExportOutputToIrregularFile(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
containerExportFunc: func(container string) (io.ReadCloser, error) {
return io.NopCloser(strings.NewReader("foo")), nil
containerExportFunc: func(container string) (client.ContainerExportResult, error) {
// FIXME(thaJeztah): how to mock this?
return mockContainerExportResult("foo"), nil
},
})
cmd := newExportCommand(cli)

View File

@@ -68,8 +68,8 @@ func newListCommand(dockerCLI command.Cli) *cobra.Command {
return &cmd
}
func buildContainerListOptions(options *psOptions) (*client.ContainerListOptions, error) {
listOptions := &client.ContainerListOptions{
func buildContainerListOptions(options *psOptions) (client.ContainerListOptions, error) {
listOptions := client.ContainerListOptions{
All: options.all,
Limit: options.last,
Size: options.size,
@@ -84,7 +84,7 @@ func buildContainerListOptions(options *psOptions) (*client.ContainerListOptions
if len(options.format) > 0 {
tmpl, err := templates.Parse(options.format)
if err != nil {
return nil, fmt.Errorf("failed to parse template: %w", err)
return client.ContainerListOptions{}, fmt.Errorf("failed to parse template: %w", err)
}
optionsProcessor := formatter.NewContainerContext()
@@ -92,7 +92,7 @@ func buildContainerListOptions(options *psOptions) (*client.ContainerListOptions
// This shouldn't error out but swallowing the error makes it harder
// to track down if preProcessor issues come up.
if err := tmpl.Execute(io.Discard, optionsProcessor); err != nil {
return nil, fmt.Errorf("failed to execute template: %w", err)
return client.ContainerListOptions{}, fmt.Errorf("failed to execute template: %w", err)
}
// if `size` was not explicitly set to false (with `--size=false`)
@@ -127,7 +127,7 @@ func runPs(ctx context.Context, dockerCLI command.Cli, options *psOptions) error
return err
}
containers, err := dockerCLI.Client().ContainerList(ctx, *listOptions)
res, err := dockerCLI.Client().ContainerList(ctx, listOptions)
if err != nil {
return err
}
@@ -137,5 +137,5 @@ func runPs(ctx context.Context, dockerCLI command.Cli, options *psOptions) error
Format: formatter.NewContainerFormat(options.format, options.quiet, listOptions.Size),
Trunc: !options.noTrunc,
}
return formatter.ContainerWrite(containerCtx, containers)
return formatter.ContainerWrite(containerCtx, res.Items)
}

View File

@@ -109,7 +109,7 @@ func TestContainerListBuildContainerListOptions(t *testing.T) {
func TestContainerListErrors(t *testing.T) {
testCases := []struct {
flags map[string]string
containerListFunc func(client.ContainerListOptions) ([]container.Summary, error)
containerListFunc func(client.ContainerListOptions) (client.ContainerListResult, error)
expectedError string
}{
{
@@ -125,8 +125,8 @@ func TestContainerListErrors(t *testing.T) {
expectedError: `wrong number of args for join`,
},
{
containerListFunc: func(_ client.ContainerListOptions) ([]container.Summary, error) {
return nil, errors.New("error listing containers")
containerListFunc: func(_ client.ContainerListOptions) (client.ContainerListResult, error) {
return client.ContainerListResult{}, errors.New("error listing containers")
},
expectedError: "error listing containers",
},
@@ -149,13 +149,15 @@ func TestContainerListErrors(t *testing.T) {
func TestContainerListWithoutFormat(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
containerListFunc: func(_ client.ContainerListOptions) ([]container.Summary, error) {
return []container.Summary{
*builders.Container("c1"),
*builders.Container("c2", builders.WithName("foo")),
*builders.Container("c3", builders.WithPort(80, 80, builders.TCP), builders.WithPort(81, 81, builders.TCP), builders.WithPort(82, 82, builders.TCP)),
*builders.Container("c4", builders.WithPort(81, 81, builders.UDP)),
*builders.Container("c5", builders.WithPort(82, 82, builders.IP("8.8.8.8"), builders.TCP)),
containerListFunc: func(_ client.ContainerListOptions) (client.ContainerListResult, error) {
return client.ContainerListResult{
Items: []container.Summary{
*builders.Container("c1"),
*builders.Container("c2", builders.WithName("foo")),
*builders.Container("c3", builders.WithPort(80, 80, builders.TCP), builders.WithPort(81, 81, builders.TCP), builders.WithPort(82, 82, builders.TCP)),
*builders.Container("c4", builders.WithPort(81, 81, builders.UDP)),
*builders.Container("c5", builders.WithPort(82, 82, builders.IP("8.8.8.8"), builders.TCP)),
},
}, nil
},
})
@@ -169,10 +171,12 @@ func TestContainerListWithoutFormat(t *testing.T) {
func TestContainerListNoTrunc(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
containerListFunc: func(_ client.ContainerListOptions) ([]container.Summary, error) {
return []container.Summary{
*builders.Container("c1"),
*builders.Container("c2", builders.WithName("foo/bar")),
containerListFunc: func(_ client.ContainerListOptions) (client.ContainerListResult, error) {
return client.ContainerListResult{
Items: []container.Summary{
*builders.Container("c1"),
*builders.Container("c2", builders.WithName("foo/bar")),
},
}, nil
},
})
@@ -188,10 +192,12 @@ func TestContainerListNoTrunc(t *testing.T) {
// Test for GitHub issue docker/docker#21772
func TestContainerListNamesMultipleTime(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
containerListFunc: func(_ client.ContainerListOptions) ([]container.Summary, error) {
return []container.Summary{
*builders.Container("c1"),
*builders.Container("c2", builders.WithName("foo/bar")),
containerListFunc: func(_ client.ContainerListOptions) (client.ContainerListResult, error) {
return client.ContainerListResult{
Items: []container.Summary{
*builders.Container("c1"),
*builders.Container("c2", builders.WithName("foo/bar")),
},
}, nil
},
})
@@ -207,10 +213,12 @@ func TestContainerListNamesMultipleTime(t *testing.T) {
// Test for GitHub issue docker/docker#30291
func TestContainerListFormatTemplateWithArg(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
containerListFunc: func(_ client.ContainerListOptions) ([]container.Summary, error) {
return []container.Summary{
*builders.Container("c1", builders.WithLabel("some.label", "value")),
*builders.Container("c2", builders.WithName("foo/bar"), builders.WithLabel("foo", "bar")),
containerListFunc: func(_ client.ContainerListOptions) (client.ContainerListResult, error) {
return client.ContainerListResult{
Items: []container.Summary{
*builders.Container("c1", builders.WithLabel("some.label", "value")),
*builders.Container("c2", builders.WithName("foo/bar"), builders.WithLabel("foo", "bar")),
},
}, nil
},
})
@@ -260,9 +268,9 @@ func TestContainerListFormatSizeSetsOption(t *testing.T) {
for _, tc := range tests {
t.Run(tc.doc, func(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
containerListFunc: func(options client.ContainerListOptions) ([]container.Summary, error) {
containerListFunc: func(options client.ContainerListOptions) (client.ContainerListResult, error) {
assert.Check(t, is.Equal(options.Size, tc.sizeExpected))
return []container.Summary{}, nil
return client.ContainerListResult{}, nil
},
})
cmd := newListCommand(cli)
@@ -280,10 +288,12 @@ func TestContainerListFormatSizeSetsOption(t *testing.T) {
func TestContainerListWithConfigFormat(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
containerListFunc: func(_ client.ContainerListOptions) ([]container.Summary, error) {
return []container.Summary{
*builders.Container("c1", builders.WithLabel("some.label", "value"), builders.WithSize(10700000)),
*builders.Container("c2", builders.WithName("foo/bar"), builders.WithLabel("foo", "bar"), builders.WithSize(3200000)),
containerListFunc: func(_ client.ContainerListOptions) (client.ContainerListResult, error) {
return client.ContainerListResult{
Items: []container.Summary{
*builders.Container("c1", builders.WithLabel("some.label", "value"), builders.WithSize(10700000)),
*builders.Container("c2", builders.WithName("foo/bar"), builders.WithLabel("foo", "bar"), builders.WithSize(3200000)),
},
}, nil
},
})
@@ -300,10 +310,12 @@ func TestContainerListWithConfigFormat(t *testing.T) {
func TestContainerListWithFormat(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
containerListFunc: func(_ client.ContainerListOptions) ([]container.Summary, error) {
return []container.Summary{
*builders.Container("c1", builders.WithLabel("some.label", "value")),
*builders.Container("c2", builders.WithName("foo/bar"), builders.WithLabel("foo", "bar")),
containerListFunc: func(_ client.ContainerListOptions) (client.ContainerListResult, error) {
return client.ContainerListResult{
Items: []container.Summary{
*builders.Container("c1", builders.WithLabel("some.label", "value")),
*builders.Container("c2", builders.WithName("foo/bar"), builders.WithLabel("foo", "bar")),
},
}, nil
},
})

View File

@@ -59,7 +59,7 @@ func runLogs(ctx context.Context, dockerCli command.Cli, opts *logsOptions) erro
return err
}
responseBody, err := dockerCli.Client().ContainerLogs(ctx, c.Container.ID, client.ContainerLogsOptions{
resp, err := dockerCli.Client().ContainerLogs(ctx, c.Container.ID, client.ContainerLogsOptions{
ShowStdout: true,
ShowStderr: true,
Since: opts.since,
@@ -72,12 +72,12 @@ func runLogs(ctx context.Context, dockerCli command.Cli, opts *logsOptions) erro
if err != nil {
return err
}
defer responseBody.Close()
defer func() { _ = resp.Close() }()
if c.Container.Config.Tty {
_, err = io.Copy(dockerCli.Out(), responseBody)
_, err = io.Copy(dockerCli.Out(), resp)
} else {
_, err = stdcopy.StdCopy(dockerCli.Out(), dockerCli.Err(), responseBody)
_, err = stdcopy.StdCopy(dockerCli.Out(), dockerCli.Err(), resp)
}
return err
}

View File

@@ -2,8 +2,6 @@ package container
import (
"context"
"io"
"strings"
"testing"
"github.com/docker/cli/internal/test"
@@ -13,12 +11,6 @@ import (
is "gotest.tools/v3/assert/cmp"
)
var logFn = func(expectedOut string) func(string, client.ContainerLogsOptions) (io.ReadCloser, error) {
return func(container string, opts client.ContainerLogsOptions) (io.ReadCloser, error) {
return io.NopCloser(strings.NewReader(expectedOut)), nil
}
}
func TestRunLogs(t *testing.T) {
inspectFn := func(containerID string) (client.ContainerInspectResult, error) {
return client.ContainerInspectResult{
@@ -41,7 +33,13 @@ func TestRunLogs(t *testing.T) {
doc: "successful logs",
expectedOut: "foo",
options: &logsOptions{},
client: &fakeClient{logFunc: logFn("foo"), inspectFunc: inspectFn},
client: &fakeClient{
logFunc: func(container string, opts client.ContainerLogsOptions) (client.ContainerLogsResult, error) {
// FIXME(thaJeztah): how to mock this?
return mockContainerLogsResult("foo"), nil
},
inspectFunc: inspectFn,
},
},
}

View File

@@ -1,14 +1,13 @@
package container
import (
"context"
"errors"
"fmt"
"strings"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/moby/moby/client"
"github.com/spf13/cobra"
)
@@ -19,7 +18,18 @@ func newRenameCommand(dockerCLI command.Cli) *cobra.Command {
Short: "Rename a container",
Args: cli.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
return runRename(cmd.Context(), dockerCLI, args[0], args[1])
oldName, newName := args[0], args[1]
if newName == "" {
// TODO(thaJeztah): remove once https://github.com/moby/moby/pull/51336 is merged and vendored.
return errors.New("new name cannot be blank")
}
_, err := dockerCLI.Client().ContainerRename(cmd.Context(), oldName, client.ContainerRenameOptions{
NewName: newName,
})
if err != nil {
return fmt.Errorf("failed to rename container: %w", err)
}
return nil
},
Annotations: map[string]string{
"aliases": "docker container rename, docker rename",
@@ -29,16 +39,3 @@ func newRenameCommand(dockerCLI command.Cli) *cobra.Command {
}
return cmd
}
func runRename(ctx context.Context, dockerCLI command.Cli, oldName, newName string) error {
newName = strings.TrimSpace(newName)
if newName == "" {
// TODO(thaJeztah): improve validation in ContainerRename and daemon; the daemon returns an obscure error when providing whitespace-only new-name:
// Error response from daemon: Error when allocating new name: Invalid container name (/ ), only [a-zA-Z0-9][a-zA-Z0-9_.-] are allowed
return errors.New("new name cannot be blank")
}
if err := dockerCLI.Client().ContainerRename(ctx, oldName, newName); err != nil {
return fmt.Errorf("failed to rename container: %w", err)
}
return nil
}

View File

@@ -89,14 +89,17 @@ func TestRunAttach(t *testing.T) {
HijackedResponse: client.NewHijackedResponse(clientConn, types.MediaTypeRawStream),
}, nil
},
waitFunc: func(_ string) (<-chan container.WaitResponse, <-chan error) {
waitFunc: func(_ string) client.ContainerWaitResult {
responseChan := make(chan container.WaitResponse, 1)
errChan := make(chan error)
responseChan <- container.WaitResponse{
StatusCode: 33,
}
return responseChan, errChan
return client.ContainerWaitResult{
Result: responseChan,
Error: errChan,
}
},
// use new (non-legacy) wait API
// see: https://github.com/docker/cli/commit/38591f20d07795aaef45d400df89ca12f29c603b
@@ -166,14 +169,17 @@ func TestRunAttachTermination(t *testing.T) {
HijackedResponse: client.NewHijackedResponse(clientConn, types.MediaTypeRawStream),
}, nil
},
waitFunc: func(_ string) (<-chan container.WaitResponse, <-chan error) {
waitFunc: func(_ string) client.ContainerWaitResult {
responseChan := make(chan container.WaitResponse, 1)
errChan := make(chan error)
<-killCh
responseChan <- container.WaitResponse{
StatusCode: 130,
}
return responseChan, errChan
return client.ContainerWaitResult{
Result: responseChan,
Error: errChan,
}
},
// use new (non-legacy) wait API
// see: https://github.com/docker/cli/commit/38591f20d07795aaef45d400df89ca12f29c603b

View File

@@ -183,7 +183,7 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
// to list containers and to filter events, but the "type" filter
// is not valid for filtering containers.
f := options.Filters.Clone().Add("type", string(events.ContainerEventType))
eventChan, errChan := apiClient.Events(ctx, client.EventsListOptions{
res := apiClient.Events(ctx, client.EventsListOptions{
Filters: f,
})
@@ -198,9 +198,9 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
return
case <-ctx.Done():
return
case event := <-eventChan:
case event := <-res.Messages:
c <- event
case err := <-errChan:
case err := <-res.Err:
// Prevent blocking if closeChan is full or unread
select {
case closeChan <- err:
@@ -229,7 +229,7 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
if err != nil {
return err
}
for _, ctr := range cs {
for _, ctr := range cs.Items {
if s := NewStats(ctr.ID); cStats.add(s) {
waitFirst.Add(1)
log.G(ctx).WithFields(map[string]any{

View File

@@ -9,6 +9,7 @@ import (
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/cli/command/formatter/tabwriter"
"github.com/moby/moby/client"
"github.com/spf13/cobra"
)
@@ -45,17 +46,19 @@ func newTopCommand(dockerCLI command.Cli) *cobra.Command {
}
func runTop(ctx context.Context, dockerCli command.Cli, opts *topOptions) error {
procList, err := dockerCli.Client().ContainerTop(ctx, opts.container, opts.args)
procList, err := dockerCli.Client().ContainerTop(ctx, opts.container, client.ContainerTopOptions{
Arguments: opts.args,
})
if err != nil {
return err
}
w := tabwriter.NewWriter(dockerCli.Out(), 20, 1, 3, ' ', 0)
fmt.Fprintln(w, strings.Join(procList.Titles, "\t"))
_, _ = fmt.Fprintln(w, strings.Join(procList.Titles, "\t"))
for _, proc := range procList.Processes {
fmt.Fprintln(w, strings.Join(proc, "\t"))
_, _ = fmt.Fprintln(w, strings.Join(proc, "\t"))
}
w.Flush()
_ = w.Flush()
return nil
}

View File

@@ -43,7 +43,7 @@ func newUnpauseCommand(dockerCLI command.Cli) *cobra.Command {
func runUnpause(ctx context.Context, dockerCLI command.Cli, opts *unpauseOptions) error {
apiClient := dockerCLI.Client()
errChan := parallelOperation(ctx, opts.containers, func(ctx context.Context, container string) error {
_, err := apiClient.ContainerUnpause(ctx, container, client.ContainerUnPauseOptions{})
_, err := apiClient.ContainerUnpause(ctx, container, client.ContainerUnpauseOptions{})
return err
})
var errs []error

View File

@@ -11,6 +11,7 @@ import (
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/opts"
containertypes "github.com/moby/moby/api/types/container"
"github.com/moby/moby/client"
"github.com/spf13/cobra"
)
@@ -60,9 +61,9 @@ func newUpdateCommand(dockerCLI command.Cli) *cobra.Command {
flags.Int64Var(&options.cpuPeriod, "cpu-period", 0, "Limit CPU CFS (Completely Fair Scheduler) period")
flags.Int64Var(&options.cpuQuota, "cpu-quota", 0, "Limit CPU CFS (Completely Fair Scheduler) quota")
flags.Int64Var(&options.cpuRealtimePeriod, "cpu-rt-period", 0, "Limit the CPU real-time period in microseconds")
flags.SetAnnotation("cpu-rt-period", "version", []string{"1.25"})
_ = flags.SetAnnotation("cpu-rt-period", "version", []string{"1.25"})
flags.Int64Var(&options.cpuRealtimeRuntime, "cpu-rt-runtime", 0, "Limit the CPU real-time runtime in microseconds")
flags.SetAnnotation("cpu-rt-runtime", "version", []string{"1.25"})
_ = flags.SetAnnotation("cpu-rt-runtime", "version", []string{"1.25"})
flags.StringVar(&options.cpusetCpus, "cpuset-cpus", "", "CPUs in which to allow execution (0-3, 0,1)")
flags.StringVar(&options.cpusetMems, "cpuset-mems", "", "MEMs in which to allow execution (0-3, 0,1)")
flags.Int64VarP(&options.cpuShares, "cpu-shares", "c", 0, "CPU shares (relative weight)")
@@ -72,10 +73,10 @@ func newUpdateCommand(dockerCLI command.Cli) *cobra.Command {
flags.StringVar(&options.restartPolicy, "restart", "", "Restart policy to apply when a container exits")
flags.Int64Var(&options.pidsLimit, "pids-limit", 0, `Tune container pids limit (set -1 for unlimited)`)
flags.SetAnnotation("pids-limit", "version", []string{"1.40"})
_ = flags.SetAnnotation("pids-limit", "version", []string{"1.40"})
flags.Var(&options.cpus, "cpus", "Number of CPUs")
flags.SetAnnotation("cpus", "version", []string{"1.29"})
_ = flags.SetAnnotation("cpus", "version", []string{"1.29"})
_ = cmd.RegisterFlagCompletionFunc("restart", completeRestartPolicies)
@@ -107,8 +108,8 @@ func runUpdate(ctx context.Context, dockerCli command.Cli, options *updateOption
pidsLimit = &options.pidsLimit
}
updateConfig := containertypes.UpdateConfig{
Resources: containertypes.Resources{
updateConfig := client.ContainerUpdateOptions{
Resources: &containertypes.Resources{
BlkioWeight: options.blkioWeight,
CpusetCpus: options.cpusetCpus,
CpusetMems: options.cpusetMems,
@@ -123,7 +124,7 @@ func runUpdate(ctx context.Context, dockerCli command.Cli, options *updateOption
NanoCPUs: options.cpus.Value(),
PidsLimit: pidsLimit,
},
RestartPolicy: restartPolicy,
RestartPolicy: &restartPolicy,
}
var (

View File

@@ -20,7 +20,9 @@ func waitExitOrRemoved(ctx context.Context, apiClient client.APIClient, containe
condition = container.WaitConditionRemoved
}
resultC, errC := apiClient.ContainerWait(ctx, containerID, condition)
waitRes := apiClient.ContainerWait(ctx, containerID, client.ContainerWaitOptions{
Condition: condition,
})
statusC := make(chan int)
go func() {
@@ -28,14 +30,14 @@ func waitExitOrRemoved(ctx context.Context, apiClient client.APIClient, containe
select {
case <-ctx.Done():
return
case result := <-resultC:
case result := <-waitRes.Result:
if result.Error != nil {
logrus.Errorf("Error waiting for container: %v", result.Error.Message)
statusC <- 125
} else {
statusC <- int(result.StatusCode)
}
case err := <-errC:
case err := <-waitRes.Error:
if errors.Is(err, context.Canceled) {
return
}

View File

@@ -12,7 +12,7 @@ import (
is "gotest.tools/v3/assert/cmp"
)
func waitFn(cid string) (<-chan container.WaitResponse, <-chan error) {
func waitFn(cid string) client.ContainerWaitResult {
resC := make(chan container.WaitResponse)
errC := make(chan error, 1)
var res container.WaitResponse
@@ -33,8 +33,10 @@ func waitFn(cid string) (<-chan container.WaitResponse, <-chan error) {
resC <- res
}
}()
return resC, errC
return client.ContainerWaitResult{
Result: resC,
Error: errC,
}
}
func TestWaitExitOrRemoved(t *testing.T) {

View File

@@ -4,10 +4,12 @@ import (
"context"
"errors"
"fmt"
"strconv"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/moby/moby/client"
"github.com/spf13/cobra"
)
@@ -42,12 +44,12 @@ func runWait(ctx context.Context, dockerCLI command.Cli, opts *waitOptions) erro
var errs []error
for _, ctr := range opts.containers {
resultC, errC := apiClient.ContainerWait(ctx, ctr, "")
res := apiClient.ContainerWait(ctx, ctr, client.ContainerWaitOptions{})
select {
case result := <-resultC:
_, _ = fmt.Fprintf(dockerCLI.Out(), "%d\n", result.StatusCode)
case err := <-errC:
case result := <-res.Result:
_, _ = fmt.Fprintln(dockerCLI.Out(), strconv.FormatInt(result.StatusCode, 10))
case err := <-res.Error:
errs = append(errs, err)
}
}

View File

@@ -8,7 +8,6 @@ import (
"time"
"github.com/moby/moby/api/types/image"
"github.com/moby/moby/api/types/system"
"github.com/moby/moby/client"
)
@@ -18,7 +17,7 @@ type fakeClient struct {
imageSaveFunc func(images []string, options ...client.ImageSaveOption) (client.ImageSaveResult, error)
imageRemoveFunc func(image string, options client.ImageRemoveOptions) (client.ImageRemoveResult, error)
imagePushFunc func(ref string, options client.ImagePushOptions) (client.ImagePushResponse, error)
infoFunc func() (system.Info, error)
infoFunc func() (client.SystemInfoResult, error)
imagePullFunc func(ref string, options client.ImagePullOptions) (client.ImagePullResponse, error)
imagesPruneFunc func(options client.ImagePruneOptions) (client.ImagePruneResult, error)
imageLoadFunc func(input io.Reader, options ...client.ImageLoadOption) (client.ImageLoadResult, error)
@@ -66,11 +65,11 @@ func (cli *fakeClient) ImagePush(_ context.Context, ref string, options client.I
return fakeStreamResult{ReadCloser: http.NoBody}, nil
}
func (cli *fakeClient) Info(_ context.Context) (system.Info, error) {
func (cli *fakeClient) Info(_ context.Context, _ client.InfoOptions) (client.SystemInfoResult, error) {
if cli.infoFunc != nil {
return cli.infoFunc()
}
return system.Info{}, nil
return client.SystemInfoResult{}, nil
}
func (cli *fakeClient) ImagePull(_ context.Context, ref string, options client.ImagePullOptions) (client.ImagePullResponse, error) {

View File

@@ -10,8 +10,8 @@ import (
type fakeClient struct {
client.Client
networkCreateFunc func(ctx context.Context, name string, options client.NetworkCreateOptions) (network.CreateResponse, error)
networkConnectFunc func(ctx context.Context, networkID, container string, config *network.EndpointSettings) error
networkDisconnectFunc func(ctx context.Context, networkID, container string, force bool) error
networkConnectFunc func(ctx context.Context, networkID string, options client.NetworkConnectOptions) (client.NetworkConnectResult, error)
networkDisconnectFunc func(ctx context.Context, networkID string, options client.NetworkDisconnectOptions) (client.NetworkDisconnectResult, error)
networkRemoveFunc func(ctx context.Context, networkID string) error
networkListFunc func(ctx context.Context, options client.NetworkListOptions) (client.NetworkListResult, error)
networkPruneFunc func(ctx context.Context, options client.NetworkPruneOptions) (client.NetworkPruneResult, error)
@@ -25,18 +25,18 @@ func (c *fakeClient) NetworkCreate(ctx context.Context, name string, options cli
return network.CreateResponse{}, nil
}
func (c *fakeClient) NetworkConnect(ctx context.Context, networkID, container string, config *network.EndpointSettings) error {
func (c *fakeClient) NetworkConnect(ctx context.Context, networkID string, options client.NetworkConnectOptions) (client.NetworkConnectResult, error) {
if c.networkConnectFunc != nil {
return c.networkConnectFunc(ctx, networkID, container, config)
return c.networkConnectFunc(ctx, networkID, options)
}
return nil
return client.NetworkConnectResult{}, nil
}
func (c *fakeClient) NetworkDisconnect(ctx context.Context, networkID, container string, force bool) error {
func (c *fakeClient) NetworkDisconnect(ctx context.Context, networkID string, options client.NetworkDisconnectOptions) (client.NetworkDisconnectResult, error) {
if c.networkDisconnectFunc != nil {
return c.networkDisconnectFunc(ctx, networkID, container, force)
return c.networkDisconnectFunc(ctx, networkID, options)
}
return nil
return client.NetworkDisconnectResult{}, nil
}
func (c *fakeClient) NetworkList(ctx context.Context, options client.NetworkListOptions) (client.NetworkListResult, error) {
@@ -46,11 +46,11 @@ func (c *fakeClient) NetworkList(ctx context.Context, options client.NetworkList
return client.NetworkListResult{}, nil
}
func (c *fakeClient) NetworkRemove(ctx context.Context, networkID string) error {
func (c *fakeClient) NetworkRemove(ctx context.Context, networkID string, _ client.NetworkRemoveOptions) (client.NetworkRemoveResult, error) {
if c.networkRemoveFunc != nil {
return c.networkRemoveFunc(ctx, networkID)
return client.NetworkRemoveResult{}, c.networkRemoveFunc(ctx, networkID)
}
return nil
return client.NetworkRemoveResult{}, nil
}
func (c *fakeClient) NetworkInspect(ctx context.Context, networkID string, opts client.NetworkInspectOptions) (client.NetworkInspectResult, error) {

View File

@@ -68,18 +68,21 @@ func runConnect(ctx context.Context, apiClient client.NetworkAPIClient, options
if err != nil {
return err
}
return apiClient.NetworkConnect(ctx, options.network, options.container, &network.EndpointSettings{
IPAMConfig: &network.EndpointIPAMConfig{
IPv4Address: toNetipAddr(options.ipaddress),
IPv6Address: toNetipAddr(options.ipv6address),
LinkLocalIPs: toNetipAddrSlice(options.linklocalips),
_, err = apiClient.NetworkConnect(ctx, options.network, client.NetworkConnectOptions{
Container: options.container,
EndpointConfig: &network.EndpointSettings{
IPAMConfig: &network.EndpointIPAMConfig{
IPv4Address: toNetipAddr(options.ipaddress),
IPv6Address: toNetipAddr(options.ipv6address),
LinkLocalIPs: toNetipAddrSlice(options.linklocalips),
},
Links: options.links.GetSlice(),
Aliases: options.aliases,
DriverOpts: driverOpts,
GwPriority: options.gwPriority,
},
Links: options.links.GetSlice(),
Aliases: options.aliases,
DriverOpts: driverOpts,
GwPriority: options.gwPriority,
})
return err
}
func convertDriverOpt(options []string) (map[string]string, error) {

View File

@@ -10,6 +10,7 @@ import (
"github.com/docker/cli/internal/test"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/moby/moby/api/types/network"
"github.com/moby/moby/client"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
@@ -17,7 +18,7 @@ import (
func TestNetworkConnectErrors(t *testing.T) {
testCases := []struct {
args []string
networkConnectFunc func(ctx context.Context, networkID, container string, config *network.EndpointSettings) error
networkConnectFunc func(ctx context.Context, networkID string, options client.NetworkConnectOptions) (client.NetworkConnectResult, error)
expectedError string
}{
{
@@ -25,8 +26,8 @@ func TestNetworkConnectErrors(t *testing.T) {
},
{
args: []string{"toto", "titi"},
networkConnectFunc: func(ctx context.Context, networkID, container string, config *network.EndpointSettings) error {
return errors.New("error connecting network")
networkConnectFunc: func(ctx context.Context, networkID string, options client.NetworkConnectOptions) (client.NetworkConnectResult, error) {
return client.NetworkConnectResult{}, errors.New("error connecting network")
},
expectedError: "error connecting network",
},
@@ -61,9 +62,9 @@ func TestNetworkConnectWithFlags(t *testing.T) {
GwPriority: 100,
}
cli := test.NewFakeCli(&fakeClient{
networkConnectFunc: func(ctx context.Context, networkID, container string, config *network.EndpointSettings) error {
assert.Check(t, is.DeepEqual(expectedConfig, config, cmpopts.EquateComparable(netip.Addr{})))
return nil
networkConnectFunc: func(ctx context.Context, networkID string, options client.NetworkConnectOptions) (client.NetworkConnectResult, error) {
assert.Check(t, is.DeepEqual(expectedConfig, options.EndpointConfig, cmpopts.EquateComparable(netip.Addr{})))
return client.NetworkConnectResult{}, nil
},
})
args := []string{"mynet", "myctr"}

View File

@@ -1,8 +1,6 @@
package network
import (
"context"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
@@ -12,9 +10,7 @@ import (
)
type disconnectOptions struct {
network string
container string
force bool
force bool
}
func newDisconnectCommand(dockerCLI command.Cli) *cobra.Command {
@@ -25,9 +21,12 @@ func newDisconnectCommand(dockerCLI command.Cli) *cobra.Command {
Short: "Disconnect a container from a network",
Args: cli.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
opts.network = args[0]
opts.container = args[1]
return runDisconnect(cmd.Context(), dockerCLI.Client(), opts)
network := args[0]
_, err := dockerCLI.Client().NetworkDisconnect(cmd.Context(), network, client.NetworkDisconnectOptions{
Container: args[1],
Force: opts.force,
})
return err
},
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) == 0 {
@@ -45,10 +44,6 @@ func newDisconnectCommand(dockerCLI command.Cli) *cobra.Command {
return cmd
}
func runDisconnect(ctx context.Context, apiClient client.NetworkAPIClient, opts disconnectOptions) error {
return apiClient.NetworkDisconnect(ctx, opts.network, opts.container, opts.force)
}
func isConnected(network string) func(container.Summary) bool {
return func(ctr container.Summary) bool {
if ctr.NetworkSettings == nil {

View File

@@ -7,13 +7,14 @@ import (
"testing"
"github.com/docker/cli/internal/test"
"github.com/moby/moby/client"
"gotest.tools/v3/assert"
)
func TestNetworkDisconnectErrors(t *testing.T) {
testCases := []struct {
args []string
networkDisconnectFunc func(ctx context.Context, networkID, container string, force bool) error
networkDisconnectFunc func(ctx context.Context, networkID string, options client.NetworkDisconnectOptions) (client.NetworkDisconnectResult, error)
expectedError string
}{
{
@@ -21,8 +22,8 @@ func TestNetworkDisconnectErrors(t *testing.T) {
},
{
args: []string{"toto", "titi"},
networkDisconnectFunc: func(ctx context.Context, networkID, container string, force bool) error {
return errors.New("error disconnecting network")
networkDisconnectFunc: func(ctx context.Context, networkID string, options client.NetworkDisconnectOptions) (client.NetworkDisconnectResult, error) {
return client.NetworkDisconnectResult{}, errors.New("error disconnecting network")
},
expectedError: "error disconnecting network",
},

View File

@@ -59,7 +59,8 @@ func runRemove(ctx context.Context, dockerCLI command.Cli, networks []string, op
continue
}
}
if err := apiClient.NetworkRemove(ctx, name); err != nil {
_, err = apiClient.NetworkRemove(ctx, name, client.NetworkRemoveOptions{})
if err != nil {
if opts.force && errdefs.IsNotFound(err) {
continue
}

View File

@@ -3,13 +3,12 @@ package node
import (
"context"
"github.com/moby/moby/api/types/system"
"github.com/moby/moby/client"
)
type fakeClient struct {
client.Client
infoFunc func() (system.Info, error)
infoFunc func() (client.SystemInfoResult, error)
nodeInspectFunc func() (client.NodeInspectResult, error)
nodeListFunc func() (client.NodeListResult, error)
nodeRemoveFunc func() (client.NodeRemoveResult, error)
@@ -47,11 +46,11 @@ func (cli *fakeClient) NodeUpdate(_ context.Context, nodeID string, options clie
return client.NodeUpdateResult{}, nil
}
func (cli *fakeClient) Info(context.Context) (system.Info, error) {
func (cli *fakeClient) Info(context.Context, client.InfoOptions) (client.SystemInfoResult, error) {
if cli.infoFunc != nil {
return cli.infoFunc()
}
return system.Info{}, nil
return client.SystemInfoResult{}, nil
}
func (cli *fakeClient) TaskInspect(_ context.Context, taskID string, _ client.TaskInspectOptions) (client.TaskInspectResult, error) {

View File

@@ -45,11 +45,11 @@ func newNodeCommand(dockerCLI command.Cli) *cobra.Command {
// the `/info` endpoint.
func Reference(ctx context.Context, apiClient client.APIClient, ref string) (string, error) {
if ref == "self" {
info, err := apiClient.Info(ctx)
res, err := apiClient.Info(ctx, client.InfoOptions{})
if err != nil {
return "", err
}
if info.Swarm.NodeID == "" {
if res.Info.Swarm.NodeID == "" {
// If there's no node ID in /info, the node probably
// isn't a manager. Call a swarm-specific endpoint to
// get a more specific error message.
@@ -61,7 +61,7 @@ func Reference(ctx context.Context, apiClient client.APIClient, ref string) (str
}
return "", errors.New("node ID not found in /info")
}
return info.Swarm.NodeID, nil
return res.Info.Swarm.NodeID, nil
}
return ref, nil
}

View File

@@ -10,7 +10,6 @@ import (
"github.com/docker/cli/cli/command/inspect"
"github.com/docker/go-units"
"github.com/moby/moby/api/types/swarm"
"github.com/moby/moby/api/types/system"
"github.com/moby/moby/client"
)
@@ -100,7 +99,7 @@ func newFormat(source string, quiet bool) formatter.Format {
}
// formatWrite writes the context.
func formatWrite(fmtCtx formatter.Context, nodes client.NodeListResult, info system.Info) error {
func formatWrite(fmtCtx formatter.Context, nodes client.NodeListResult, info client.SystemInfoResult) error {
nodeCtx := &nodeContext{
HeaderContext: formatter.HeaderContext{
Header: formatter.SubHeaderContext{
@@ -119,7 +118,7 @@ func formatWrite(fmtCtx formatter.Context, nodes client.NodeListResult, info sys
for _, node := range nodes.Items {
if err := format(&nodeContext{
n: node,
info: info,
info: info.Info.Swarm,
}); err != nil {
return err
}
@@ -131,7 +130,7 @@ func formatWrite(fmtCtx formatter.Context, nodes client.NodeListResult, info sys
type nodeContext struct {
formatter.HeaderContext
n swarm.Node
info system.Info
info swarm.Info
}
func (c *nodeContext) MarshalJSON() ([]byte, error) {
@@ -143,7 +142,7 @@ func (c *nodeContext) ID() string {
}
func (c *nodeContext) Self() bool {
return c.n.ID == c.info.Swarm.NodeID
return c.n.ID == c.info.NodeID
}
func (c *nodeContext) Hostname() string {
@@ -171,10 +170,10 @@ func (c *nodeContext) ManagerStatus() string {
}
func (c *nodeContext) TLSStatus() string {
if c.info.Swarm.Cluster == nil || reflect.DeepEqual(c.info.Swarm.Cluster.TLSInfo, swarm.TLSInfo{}) || reflect.DeepEqual(c.n.Description.TLSInfo, swarm.TLSInfo{}) {
if c.info.Cluster == nil || reflect.DeepEqual(c.info.Cluster.TLSInfo, swarm.TLSInfo{}) || reflect.DeepEqual(c.n.Description.TLSInfo, swarm.TLSInfo{}) {
return "Unknown"
}
if reflect.DeepEqual(c.n.Description.TLSInfo, c.info.Swarm.Cluster.TLSInfo) {
if reflect.DeepEqual(c.n.Description.TLSInfo, c.info.Cluster.TLSInfo) {
return "Ready"
}
return "Needs Rotation"

View File

@@ -208,7 +208,11 @@ foobar_boo Unknown
var out bytes.Buffer
tc.context.Output = &out
err := formatWrite(tc.context, nodes, system.Info{Swarm: swarm.Info{Cluster: &tc.clusterInfo}})
err := formatWrite(tc.context, nodes, client.SystemInfoResult{
Info: system.Info{
Swarm: swarm.Info{Cluster: &tc.clusterInfo},
},
})
if err != nil {
assert.Error(t, err, tc.expected)
} else {
@@ -221,7 +225,7 @@ foobar_boo Unknown
func TestNodeContextWriteJSON(t *testing.T) {
cases := []struct {
expected []map[string]any
info system.Info
info client.SystemInfoResult
}{
{
expected: []map[string]any{
@@ -229,7 +233,6 @@ func TestNodeContextWriteJSON(t *testing.T) {
{"Availability": "", "Hostname": "foobar_bar", "ID": "nodeID2", "ManagerStatus": "", "Status": "", "Self": false, "TLSStatus": "Unknown", "EngineVersion": ""},
{"Availability": "", "Hostname": "foobar_boo", "ID": "nodeID3", "ManagerStatus": "", "Status": "", "Self": false, "TLSStatus": "Unknown", "EngineVersion": "18.03.0-ce"},
},
info: system.Info{},
},
{
expected: []map[string]any{
@@ -237,11 +240,13 @@ func TestNodeContextWriteJSON(t *testing.T) {
{"Availability": "", "Hostname": "foobar_bar", "ID": "nodeID2", "ManagerStatus": "", "Status": "", "Self": false, "TLSStatus": "Needs Rotation", "EngineVersion": ""},
{"Availability": "", "Hostname": "foobar_boo", "ID": "nodeID3", "ManagerStatus": "", "Status": "", "Self": false, "TLSStatus": "Unknown", "EngineVersion": "18.03.0-ce"},
},
info: system.Info{
Swarm: swarm.Info{
Cluster: &swarm.ClusterInfo{
TLSInfo: swarm.TLSInfo{TrustRoot: "hi"},
RootRotationInProgress: true,
info: client.SystemInfoResult{
Info: system.Info{
Swarm: swarm.Info{
Cluster: &swarm.ClusterInfo{
TLSInfo: swarm.TLSInfo{TrustRoot: "hi"},
RootRotationInProgress: true,
},
},
},
},
@@ -279,7 +284,7 @@ func TestNodeContextWriteJSONField(t *testing.T) {
},
}
out := bytes.NewBufferString("")
err := formatWrite(formatter.Context{Format: "{{json .ID}}", Output: out}, nodes, system.Info{})
err := formatWrite(formatter.Context{Format: "{{json .ID}}", Output: out}, nodes, client.SystemInfoResult{})
if err != nil {
t.Fatal(err)
}

View File

@@ -20,7 +20,7 @@ func TestNodeInspectErrors(t *testing.T) {
args []string
flags map[string]string
nodeInspectFunc func() (client.NodeInspectResult, error)
infoFunc func() (system.Info, error)
infoFunc func() (client.SystemInfoResult, error)
expectedError string
}{
{
@@ -28,8 +28,8 @@ func TestNodeInspectErrors(t *testing.T) {
},
{
args: []string{"self"},
infoFunc: func() (system.Info, error) {
return system.Info{}, errors.New("error asking for node info")
infoFunc: func() (client.SystemInfoResult, error) {
return client.SystemInfoResult{}, errors.New("error asking for node info")
},
expectedError: "error asking for node info",
},
@@ -38,8 +38,8 @@ func TestNodeInspectErrors(t *testing.T) {
nodeInspectFunc: func() (client.NodeInspectResult, error) {
return client.NodeInspectResult{}, errors.New("error inspecting the node")
},
infoFunc: func() (system.Info, error) {
return system.Info{}, errors.New("error asking for node info")
infoFunc: func() (client.SystemInfoResult, error) {
return client.SystemInfoResult{}, errors.New("error asking for node info")
},
expectedError: "error inspecting the node",
},
@@ -48,8 +48,12 @@ func TestNodeInspectErrors(t *testing.T) {
nodeInspectFunc: func() (client.NodeInspectResult, error) {
return client.NodeInspectResult{}, errors.New("error inspecting the node")
},
infoFunc: func() (system.Info, error) {
return system.Info{Swarm: swarm.Info{NodeID: "abc"}}, nil
infoFunc: func() (client.SystemInfoResult, error) {
return client.SystemInfoResult{
Info: system.Info{
Swarm: swarm.Info{NodeID: "abc"},
},
}, nil
},
expectedError: "error inspecting the node",
},
@@ -58,8 +62,8 @@ func TestNodeInspectErrors(t *testing.T) {
flags: map[string]string{
"pretty": "true",
},
infoFunc: func() (system.Info, error) {
return system.Info{}, errors.New("error asking for node info")
infoFunc: func() (client.SystemInfoResult, error) {
return client.SystemInfoResult{}, errors.New("error asking for node info")
},
expectedError: "error asking for node info",
},

View File

@@ -10,7 +10,6 @@ import (
flagsHelper "github.com/docker/cli/cli/flags"
"github.com/docker/cli/opts"
"github.com/fvbommel/sortorder"
"github.com/moby/moby/api/types/system"
"github.com/moby/moby/client"
"github.com/spf13/cobra"
)
@@ -53,10 +52,10 @@ func runList(ctx context.Context, dockerCLI command.Cli, options listOptions) er
return err
}
var info system.Info
var info client.SystemInfoResult
if len(res.Items) > 0 && !options.quiet {
// only non-empty nodes and not quiet, should we call /info api
info, err = apiClient.Info(ctx)
info, err = apiClient.Info(ctx, client.InfoOptions{})
if err != nil {
return err
}

View File

@@ -19,7 +19,7 @@ import (
func TestNodeListErrorOnAPIFailure(t *testing.T) {
testCases := []struct {
nodeListFunc func() (client.NodeListResult, error)
infoFunc func() (system.Info, error)
infoFunc func() (client.SystemInfoResult, error)
expectedError string
}{
{
@@ -36,8 +36,8 @@ func TestNodeListErrorOnAPIFailure(t *testing.T) {
},
}, nil
},
infoFunc: func() (system.Info, error) {
return system.Info{}, errors.New("error asking for node info")
infoFunc: func() (client.SystemInfoResult, error) {
return client.SystemInfoResult{}, errors.New("error asking for node info")
},
expectedError: "error asking for node info",
},
@@ -65,10 +65,10 @@ func TestNodeList(t *testing.T) {
},
}, nil
},
infoFunc: func() (system.Info, error) {
return system.Info{
Swarm: swarm.Info{
NodeID: "nodeID1",
infoFunc: func() (client.SystemInfoResult, error) {
return client.SystemInfoResult{
Info: system.Info{
Swarm: swarm.Info{NodeID: "nodeID1"},
},
}, nil
},
@@ -106,10 +106,10 @@ func TestNodeListDefaultFormatFromConfig(t *testing.T) {
},
}, nil
},
infoFunc: func() (system.Info, error) {
return system.Info{
Swarm: swarm.Info{
NodeID: "nodeID1",
infoFunc: func() (client.SystemInfoResult, error) {
return client.SystemInfoResult{
Info: system.Info{
Swarm: swarm.Info{NodeID: "nodeID1"},
},
}, nil
},
@@ -132,10 +132,10 @@ func TestNodeListFormat(t *testing.T) {
},
}, nil
},
infoFunc: func() (system.Info, error) {
return system.Info{
Swarm: swarm.Info{
NodeID: "nodeID1",
infoFunc: func() (client.SystemInfoResult, error) {
return client.SystemInfoResult{
Info: system.Info{
Swarm: swarm.Info{NodeID: "nodeID1"},
},
}, nil
},

View File

@@ -11,7 +11,6 @@ import (
"github.com/docker/cli/internal/test"
"github.com/docker/cli/internal/test/builders"
"github.com/moby/moby/api/types/swarm"
"github.com/moby/moby/api/types/system"
"github.com/moby/moby/client"
"gotest.tools/v3/assert"
"gotest.tools/v3/golden"
@@ -21,15 +20,14 @@ func TestNodePsErrors(t *testing.T) {
testCases := []struct {
args []string
flags map[string]string
infoFunc func() (system.Info, error)
infoFunc func() (client.SystemInfoResult, error)
nodeInspectFunc func() (client.NodeInspectResult, error)
taskListFunc func(options client.TaskListOptions) (client.TaskListResult, error)
taskInspectFunc func(taskID string) (client.TaskInspectResult, error)
expectedError string
}{
{
infoFunc: func() (system.Info, error) {
return system.Info{}, errors.New("error asking for node info")
infoFunc: func() (client.SystemInfoResult, error) {
return client.SystemInfoResult{}, errors.New("error asking for node info")
},
expectedError: "error asking for node info",
},
@@ -52,7 +50,6 @@ func TestNodePsErrors(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
infoFunc: tc.infoFunc,
nodeInspectFunc: tc.nodeInspectFunc,
taskInspectFunc: tc.taskInspectFunc,
taskListFunc: tc.taskListFunc,
})
cmd := newPsCommand(cli)
@@ -71,7 +68,6 @@ func TestNodePs(t *testing.T) {
name string
args []string
flags map[string]string
infoFunc func() (system.Info, error)
nodeInspectFunc func() (client.NodeInspectResult, error)
taskListFunc func(options client.TaskListOptions) (client.TaskListResult, error)
taskInspectFunc func(taskID string) (client.TaskInspectResult, error)
@@ -148,7 +144,6 @@ func TestNodePs(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
infoFunc: tc.infoFunc,
nodeInspectFunc: tc.nodeInspectFunc,
taskInspectFunc: tc.taskInspectFunc,
taskListFunc: tc.taskListFunc,

View File

@@ -5,7 +5,6 @@ import (
"io"
"net/http"
"github.com/moby/moby/api/types/system"
"github.com/moby/moby/client"
)
@@ -70,8 +69,8 @@ func (c *fakeClient) PluginInspect(_ context.Context, name string, _ client.Plug
return client.PluginInspectResult{}, nil
}
func (*fakeClient) Info(context.Context) (system.Info, error) {
return system.Info{}, nil
func (*fakeClient) Info(context.Context, client.InfoOptions) (client.SystemInfoResult, error) {
return client.SystemInfoResult{}, nil
}
func (c *fakeClient) PluginUpgrade(_ context.Context, name string, options client.PluginUpgradeOptions) (client.PluginUpgradeResult, error) {

View File

@@ -160,25 +160,32 @@ func loginWithStoredCredentials(ctx context.Context, dockerCLI command.Cli, auth
_, _ = fmt.Fprint(dockerCLI.Err(), "\n\n")
response, err := dockerCLI.Client().RegistryLogin(ctx, authConfig)
resp, err := dockerCLI.Client().RegistryLogin(ctx, client.RegistryLoginOptions{
Username: authConfig.Username,
Password: authConfig.Password,
ServerAddress: authConfig.ServerAddress,
IdentityToken: authConfig.IdentityToken,
RegistryToken: authConfig.RegistryToken,
})
if err != nil {
if errdefs.IsUnauthorized(err) {
_, _ = fmt.Fprintln(dockerCLI.Err(), "Stored credentials invalid or expired")
} else {
_, _ = fmt.Fprintln(dockerCLI.Err(), "Login did not succeed, error:", err)
}
// TODO(thaJeztah): should this return the error here, or is there a reasong for continuing?
}
if response.IdentityToken != "" {
if resp.Auth.IdentityToken != "" {
authConfig.Password = ""
authConfig.IdentityToken = response.IdentityToken
authConfig.IdentityToken = resp.Auth.IdentityToken
}
if err := storeCredentials(dockerCLI.ConfigFile(), authConfig); err != nil {
return "", err
}
return response.Status, err
return resp.Auth.Status, err
}
func loginUser(ctx context.Context, dockerCLI command.Cli, opts loginOptions, defaultUsername, serverAddress string) (msg string, _ error) {
@@ -215,20 +222,26 @@ func loginWithUsernameAndPassword(ctx context.Context, dockerCLI command.Cli, op
return "", err
}
response, err := loginWithRegistry(ctx, dockerCLI.Client(), authConfig)
res, err := loginWithRegistry(ctx, dockerCLI.Client(), client.RegistryLoginOptions{
Username: authConfig.Username,
Password: authConfig.Password,
ServerAddress: authConfig.ServerAddress,
IdentityToken: authConfig.IdentityToken,
RegistryToken: authConfig.RegistryToken,
})
if err != nil {
return "", err
}
if response.IdentityToken != "" {
if res.Auth.IdentityToken != "" {
authConfig.Password = ""
authConfig.IdentityToken = response.IdentityToken
authConfig.IdentityToken = res.Auth.IdentityToken
}
if err = storeCredentials(dockerCLI.ConfigFile(), authConfig); err != nil {
return "", err
}
return response.Status, nil
return res.Auth.Status, nil
}
func loginWithDeviceCodeFlow(ctx context.Context, dockerCLI command.Cli) (msg string, _ error) {
@@ -238,13 +251,13 @@ func loginWithDeviceCodeFlow(ctx context.Context, dockerCLI command.Cli) (msg st
return "", err
}
response, err := loginWithRegistry(ctx, dockerCLI.Client(), registrytypes.AuthConfig{
response, err := loginWithRegistry(ctx, dockerCLI.Client(), client.RegistryLoginOptions{
Username: authConfig.Username,
Password: authConfig.Password,
ServerAddress: authConfig.ServerAddress,
// TODO(thaJeztah): Are these expected to be included?
Auth: authConfig.Auth,
// Auth: authConfig.Auth,
IdentityToken: authConfig.IdentityToken,
RegistryToken: authConfig.RegistryToken,
})
@@ -265,7 +278,7 @@ func loginWithDeviceCodeFlow(ctx context.Context, dockerCLI command.Cli) (msg st
return "", err
}
return response.Status, nil
return response.Auth.Status, nil
}
func storeCredentials(cfg *configfile.ConfigFile, authConfig registrytypes.AuthConfig) error {
@@ -286,32 +299,42 @@ func storeCredentials(cfg *configfile.ConfigFile, authConfig registrytypes.AuthC
return nil
}
func loginWithRegistry(ctx context.Context, apiClient client.SystemAPIClient, authConfig registrytypes.AuthConfig) (*registrytypes.AuthenticateOKBody, error) {
response, err := apiClient.RegistryLogin(ctx, authConfig)
func loginWithRegistry(ctx context.Context, apiClient client.SystemAPIClient, options client.RegistryLoginOptions) (client.RegistryLoginResult, error) {
res, err := apiClient.RegistryLogin(ctx, options)
if err != nil {
if client.IsErrConnectionFailed(err) {
// daemon isn't responding; attempt to login client side.
return loginClientSide(ctx, authConfig)
return loginClientSide(ctx, options)
}
return nil, err
return client.RegistryLoginResult{}, err
}
return &response, nil
return res, nil
}
func loginClientSide(ctx context.Context, auth registrytypes.AuthConfig) (*registrytypes.AuthenticateOKBody, error) {
func loginClientSide(ctx context.Context, options client.RegistryLoginOptions) (client.RegistryLoginResult, error) {
svc, err := registry.NewService(registry.ServiceOptions{})
if err != nil {
return nil, err
return client.RegistryLoginResult{}, err
}
auth := registrytypes.AuthConfig{
Username: options.Username,
Password: options.Password,
ServerAddress: options.ServerAddress,
IdentityToken: options.IdentityToken,
RegistryToken: options.RegistryToken,
}
token, err := svc.Auth(ctx, &auth, command.UserAgent())
if err != nil {
return nil, err
return client.RegistryLoginResult{}, err
}
return &registrytypes.AuthenticateOKBody{
Status: "Login Succeeded",
IdentityToken: token,
return client.RegistryLoginResult{
Auth: registrytypes.AuthenticateOKBody{
Status: "Login Succeeded",
IdentityToken: token,
},
}, nil
}

View File

@@ -16,7 +16,6 @@ import (
"github.com/docker/cli/internal/registry"
"github.com/docker/cli/internal/test"
registrytypes "github.com/moby/moby/api/types/registry"
"github.com/moby/moby/api/types/system"
"github.com/moby/moby/client"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
@@ -34,23 +33,23 @@ type fakeClient struct {
client.Client
}
func (*fakeClient) Info(context.Context) (system.Info, error) {
return system.Info{}, nil
func (*fakeClient) Info(context.Context, client.InfoOptions) (client.SystemInfoResult, error) {
return client.SystemInfoResult{}, nil
}
func (*fakeClient) RegistryLogin(_ context.Context, auth registrytypes.AuthConfig) (registrytypes.AuthenticateOKBody, error) {
if auth.Password == expiredPassword {
return registrytypes.AuthenticateOKBody{}, errors.New("Invalid Username or Password")
func (*fakeClient) RegistryLogin(_ context.Context, options client.RegistryLoginOptions) (client.RegistryLoginResult, error) {
if options.Password == expiredPassword {
return client.RegistryLoginResult{}, errors.New("invalid Username or Password")
}
if auth.Password == useToken {
return registrytypes.AuthenticateOKBody{
IdentityToken: auth.Password,
if options.Password == useToken {
return client.RegistryLoginResult{
Auth: registrytypes.AuthenticateOKBody{IdentityToken: options.Password},
}, nil
}
if auth.Username == unknownUser {
return registrytypes.AuthenticateOKBody{}, errors.New(errUnknownUser)
if options.Username == unknownUser {
return client.RegistryLoginResult{}, errors.New(errUnknownUser)
}
return registrytypes.AuthenticateOKBody{}, nil
return client.RegistryLoginResult{}, nil
}
func TestLoginWithCredStoreCreds(t *testing.T) {

View File

@@ -5,7 +5,6 @@ import (
"github.com/docker/cli/internal/test/builders"
"github.com/moby/moby/api/types/swarm"
"github.com/moby/moby/api/types/system"
"github.com/moby/moby/client"
)
@@ -15,7 +14,7 @@ type fakeClient struct {
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)
infoFunc func(ctx context.Context) (client.SystemInfoResult, error)
networkInspectFunc func(ctx context.Context, networkID string, options client.NetworkInspectOptions) (client.NetworkInspectResult, error)
nodeListFunc func(ctx context.Context, options client.NodeListOptions) (client.NodeListResult, error)
}
@@ -60,11 +59,11 @@ func (f *fakeClient) ServiceUpdate(ctx context.Context, serviceID string, option
return client.ServiceUpdateResult{}, nil
}
func (f *fakeClient) Info(ctx context.Context) (system.Info, error) {
if f.infoFunc == nil {
return system.Info{}, nil
func (f *fakeClient) Info(ctx context.Context, _ client.InfoOptions) (client.SystemInfoResult, error) {
if f.infoFunc != nil {
return f.infoFunc(ctx)
}
return f.infoFunc(ctx)
return client.SystemInfoResult{}, nil
}
func (f *fakeClient) NetworkInspect(ctx context.Context, networkID string, options client.NetworkInspectOptions) (client.NetworkInspectResult, error) {

View File

@@ -117,8 +117,12 @@ func TestUpdateNodeFilter(t *testing.T) {
filter := make(client.Filters).Add("node", "one", "two", "self")
apiClient := &fakeClient{
infoFunc: func(_ context.Context) (system.Info, error) {
return system.Info{Swarm: swarm.Info{NodeID: selfNodeID}}, nil
infoFunc: func(_ context.Context) (client.SystemInfoResult, error) {
return client.SystemInfoResult{
Info: system.Info{
Swarm: swarm.Info{NodeID: selfNodeID},
},
}, nil
},
}

View File

@@ -147,13 +147,13 @@ func (cli *fakeClient) ServiceRemove(_ context.Context, serviceID string, _ clie
return client.ServiceRemoveResult{}, nil
}
func (cli *fakeClient) NetworkRemove(_ context.Context, networkID string) error {
func (cli *fakeClient) NetworkRemove(_ context.Context, networkID string, _ client.NetworkRemoveOptions) (client.NetworkRemoveResult, error) {
if cli.networkRemoveFunc != nil {
return cli.networkRemoveFunc(networkID)
return client.NetworkRemoveResult{}, cli.networkRemoveFunc(networkID)
}
cli.removedNetworks = append(cli.removedNetworks, networkID)
return nil
return client.NetworkRemoveResult{}, nil
}
func (cli *fakeClient) SecretRemove(_ context.Context, secretID string, _ client.SecretRemoveOptions) (client.SecretRemoveResult, error) {

View File

@@ -10,6 +10,7 @@ import (
"github.com/docker/cli/cli/compose/convert"
composetypes "github.com/docker/cli/cli/compose/types"
"github.com/moby/moby/api/types/swarm"
"github.com/moby/moby/client"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
@@ -93,11 +94,11 @@ func runDeploy(ctx context.Context, dockerCLI command.Cli, flags *pflag.FlagSet,
// create services, but the API call for creating a network does not return a
// proper status code when it can't create a network in the "global" scope.
func checkDaemonIsSwarmManager(ctx context.Context, dockerCli command.Cli) error {
info, err := dockerCli.Client().Info(ctx)
res, err := dockerCli.Client().Info(ctx, client.InfoOptions{})
if err != nil {
return err
}
if !info.Swarm.ControlAvailable {
if !res.Info.Swarm.ControlAvailable {
return errors.New(`this node is not a swarm manager. Use "docker swarm init" or "docker swarm join" to connect this node to swarm and try again`)
}
return nil

View File

@@ -119,7 +119,7 @@ func removeNetworks(ctx context.Context, dockerCLI command.Cli, networks []netwo
var hasError bool
for _, nw := range networks {
_, _ = fmt.Fprintln(dockerCLI.Out(), "Removing network", nw.Name)
if err := dockerCLI.Client().NetworkRemove(ctx, nw.ID); err != nil {
if _, err := dockerCLI.Client().NetworkRemove(ctx, nw.ID, client.NetworkRemoveOptions{}); err != nil {
hasError = true
_, _ = fmt.Fprintf(dockerCLI.Err(), "Failed to remove network %s: %s", nw.ID, err)
}

View File

@@ -20,11 +20,14 @@ type fakeClient struct {
swarmUnlockFunc func(client.SwarmUnlockOptions) (client.SwarmUnlockResult, error)
}
func (cli *fakeClient) Info(context.Context) (system.Info, error) {
func (cli *fakeClient) Info(context.Context, client.InfoOptions) (client.SystemInfoResult, error) {
if cli.infoFunc != nil {
return cli.infoFunc()
inf, err := cli.infoFunc()
return client.SystemInfoResult{
Info: inf,
}, err
}
return system.Info{}, nil
return client.SystemInfoResult{}, nil
}
func (cli *fakeClient) NodeInspect(context.Context, string, client.NodeInspectOptions) (client.NodeInspectResult, error) {

View File

@@ -78,12 +78,12 @@ func runJoin(ctx context.Context, dockerCLI command.Cli, flags *pflag.FlagSet, o
return err
}
info, err := apiClient.Info(ctx)
res, err := apiClient.Info(ctx, client.InfoOptions{})
if err != nil {
return err
}
if info.Swarm.ControlAvailable {
if res.Info.Swarm.ControlAvailable {
_, _ = fmt.Fprintln(dockerCLI.Out(), "This node joined a swarm as a manager.")
} else {
_, _ = fmt.Fprintln(dockerCLI.Out(), "This node joined a swarm as a worker.")

View File

@@ -90,12 +90,12 @@ func runJoinToken(ctx context.Context, dockerCLI command.Cli, opts joinTokenOpti
return nil
}
info, err := apiClient.Info(ctx)
infoResp, err := apiClient.Info(ctx, client.InfoOptions{})
if err != nil {
return err
}
return printJoinCommand(ctx, dockerCLI, info.Swarm.NodeID, worker, manager)
return printJoinCommand(ctx, dockerCLI, infoResp.Info.Swarm.NodeID, worker, manager)
}
func printJoinCommand(ctx context.Context, dockerCLI command.Cli, nodeID string, worker bool, manager bool) error {

View File

@@ -41,12 +41,12 @@ func runUnlock(ctx context.Context, dockerCLI command.Cli) error {
// First see if the node is actually part of a swarm, and if it is actually locked first.
// If it's in any other state than locked, don't ask for the key.
info, err := apiClient.Info(ctx)
res, err := apiClient.Info(ctx, client.InfoOptions{})
if err != nil {
return err
}
switch info.Swarm.LocalNodeState {
switch res.Info.Swarm.LocalNodeState {
case swarm.LocalNodeStateInactive:
return errors.New("error: this node is not part of a swarm")
case swarm.LocalNodeStateLocked:

View File

@@ -6,7 +6,6 @@ import (
"github.com/moby/moby/api/types"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/events"
"github.com/moby/moby/api/types/system"
"github.com/moby/moby/client"
)
@@ -18,7 +17,7 @@ type fakeClient struct {
containerPruneFunc func(ctx context.Context, options client.ContainerPruneOptions) (client.ContainerPruneResult, error)
eventsFn func(context.Context, client.EventsListOptions) (<-chan events.Message, <-chan error)
imageListFunc func(ctx context.Context, options client.ImageListOptions) (client.ImageListResult, error)
infoFunc func(ctx context.Context) (system.Info, error)
infoFunc func(ctx context.Context, options client.InfoOptions) (client.SystemInfoResult, error)
networkListFunc func(ctx context.Context, options client.NetworkListOptions) (client.NetworkListResult, error)
networkPruneFunc func(ctx context.Context, options client.NetworkPruneOptions) (client.NetworkPruneResult, error)
nodeListFunc func(ctx context.Context, options client.NodeListOptions) (client.NodeListResult, error)
@@ -30,11 +29,14 @@ func (cli *fakeClient) ClientVersion() string {
return cli.version
}
func (cli *fakeClient) ContainerList(ctx context.Context, options client.ContainerListOptions) ([]container.Summary, error) {
func (cli *fakeClient) ContainerList(ctx context.Context, options client.ContainerListOptions) (client.ContainerListResult, error) {
if cli.containerListFunc != nil {
return cli.containerListFunc(ctx, options)
res, err := cli.containerListFunc(ctx, options)
return client.ContainerListResult{
Items: res,
}, err
}
return []container.Summary{}, nil
return client.ContainerListResult{}, nil
}
func (cli *fakeClient) ContainersPrune(ctx context.Context, opts client.ContainerPruneOptions) (client.ContainerPruneResult, error) {
@@ -44,8 +46,12 @@ func (cli *fakeClient) ContainersPrune(ctx context.Context, opts client.Containe
return client.ContainerPruneResult{}, nil
}
func (cli *fakeClient) Events(ctx context.Context, opts client.EventsListOptions) (<-chan events.Message, <-chan error) {
return cli.eventsFn(ctx, opts)
func (cli *fakeClient) Events(ctx context.Context, opts client.EventsListOptions) client.EventsResult {
eventC, errC := cli.eventsFn(ctx, opts)
return client.EventsResult{
Messages: eventC,
Err: errC,
}
}
func (cli *fakeClient) ImageList(ctx context.Context, options client.ImageListOptions) (client.ImageListResult, error) {
@@ -55,11 +61,11 @@ func (cli *fakeClient) ImageList(ctx context.Context, options client.ImageListOp
return client.ImageListResult{}, nil
}
func (cli *fakeClient) Info(ctx context.Context) (system.Info, error) {
func (cli *fakeClient) Info(ctx context.Context, options client.InfoOptions) (client.SystemInfoResult, error) {
if cli.infoFunc != nil {
return cli.infoFunc(ctx)
return cli.infoFunc(ctx, options)
}
return system.Info{}, nil
return client.SystemInfoResult{}, nil
}
func (cli *fakeClient) NetworkList(ctx context.Context, options client.NetworkListOptions) (client.NetworkListResult, error) {

View File

@@ -187,11 +187,11 @@ func containerNames(dockerCLI completion.APIClientProvider, cmd *cobra.Command,
// daemonNames contacts the API to get name and ID of the current docker daemon.
// In case of an error, an empty list is returned.
func daemonNames(dockerCLI completion.APIClientProvider, cmd *cobra.Command) []string {
info, err := dockerCLI.Client().Info(cmd.Context())
res, err := dockerCLI.Client().Info(cmd.Context(), client.InfoOptions{})
if err != nil {
return []string{}
}
return []string{info.Name, info.ID}
return []string{res.Info.Name, res.Info.ID}
}
// imageNames contacts the API to get a list of image names.

View File

@@ -48,10 +48,12 @@ func TestCompleteEventFilter(t *testing.T) {
},
{
client: &fakeClient{
infoFunc: func(ctx context.Context) (system.Info, error) {
return system.Info{
ID: "daemon-id",
Name: "daemon-name",
infoFunc: func(_ context.Context, _ client.InfoOptions) (client.SystemInfoResult, error) {
return client.SystemInfoResult{
Info: system.Info{
ID: "daemon-id",
Name: "daemon-name",
},
}, nil
},
},
@@ -60,8 +62,8 @@ func TestCompleteEventFilter(t *testing.T) {
},
{
client: &fakeClient{
infoFunc: func(ctx context.Context) (system.Info, error) {
return system.Info{}, errors.New("API error")
infoFunc: func(_ context.Context, _ client.InfoOptions) (client.SystemInfoResult, error) {
return client.SystemInfoResult{}, errors.New("API error")
},
},
toComplete: "daemon=",

View File

@@ -65,7 +65,7 @@ func runEvents(ctx context.Context, dockerCLI command.Cli, options *eventsOption
}
}
ctx, cancel := context.WithCancel(ctx)
evts, errs := dockerCLI.Client().Events(ctx, client.EventsListOptions{
eventRes := dockerCLI.Client().Events(ctx, client.EventsListOptions{
Since: options.since,
Until: options.until,
Filters: options.filter.Value(),
@@ -76,11 +76,11 @@ func runEvents(ctx context.Context, dockerCLI command.Cli, options *eventsOption
for {
select {
case event := <-evts:
case event := <-eventRes.Messages:
if err := handleEvent(out, event, tmpl); err != nil {
return err
}
case err := <-errs:
case err := <-eventRes.Err:
if err == io.EOF {
return nil
}
@@ -93,8 +93,11 @@ func handleEvent(out io.Writer, event events.Message, tmpl *template.Template) e
if tmpl == nil {
return prettyPrintEvent(out, event)
}
return formatEvent(out, event, tmpl)
if err := tmpl.Execute(out, event); err != nil {
return err
}
_, _ = out.Write([]byte{'\n'})
return nil
}
func makeTemplate(format string) (*template.Template, error) {
@@ -145,8 +148,3 @@ func prettyPrintEvent(out io.Writer, event events.Message) error {
_, _ = fmt.Fprint(out, "\n")
return nil
}
func formatEvent(out io.Writer, event events.Message, tmpl *template.Template) error {
defer out.Write([]byte{'\n'})
return tmpl.Execute(out, event)
}

View File

@@ -117,7 +117,7 @@ func runInfo(ctx context.Context, cmd *cobra.Command, dockerCli command.Cli, opt
// if a connection error occurs, it will be returned as an error.
// other errors are appended to the info.ServerErrors field.
func addServerInfo(ctx context.Context, dockerCli command.Cli, format string, info *dockerInfo) error {
dinfo, err := dockerCli.Client().Info(ctx)
res, err := dockerCli.Client().Info(ctx, client.InfoOptions{})
if err != nil {
// if no format is provided and we have an error, don't print the server info
if format == "" {
@@ -137,7 +137,7 @@ func addServerInfo(ctx context.Context, dockerCli command.Cli, format string, in
}
// only assign the server info if we have no error
info.Info = &dinfo
info.Info = &res.Info
return nil
}

View File

@@ -233,12 +233,12 @@ func inspectAll(ctx context.Context, dockerCLI command.Cli, getSize bool, typeCo
// isSwarmManager does an Info API call to verify that the daemon is
// a swarm manager.
isSwarmManager := func() bool {
info, err := dockerCLI.Client().Info(ctx)
res, err := dockerCLI.Client().Info(ctx, client.InfoOptions{})
if err != nil {
_, _ = fmt.Fprintln(dockerCLI.Err(), err)
return false
}
return info.Swarm.ControlAvailable
return res.Info.Swarm.ControlAvailable
}
return func(ref string) (any, []byte, error) {

View File

@@ -11,7 +11,6 @@ import (
"github.com/docker/cli/cli/trust"
"github.com/docker/cli/internal/test"
notaryfake "github.com/docker/cli/internal/test/notary"
"github.com/moby/moby/api/types/system"
"github.com/moby/moby/client"
"github.com/theupdateframework/notary"
notaryclient "github.com/theupdateframework/notary/client"
@@ -35,8 +34,8 @@ type fakeStreamResult struct {
func (e fakeStreamResult) Read(p []byte) (int, error) { return e.ReadCloser.Read(p) }
func (e fakeStreamResult) Close() error { return e.ReadCloser.Close() }
func (*fakeClient) Info(context.Context) (system.Info, error) {
return system.Info{}, nil
func (*fakeClient) Info(context.Context, client.InfoOptions) (client.SystemInfoResult, error) {
return client.SystemInfoResult{}, nil
}
func (*fakeClient) ImageInspect(context.Context, string, ...client.ImageInspectOption) (client.ImageInspectResult, error) {

View File

@@ -43,9 +43,9 @@ func (c *fakeClient) VolumesPrune(_ context.Context, opts client.VolumePruneOpti
return client.VolumePruneResult{}, nil
}
func (c *fakeClient) VolumeRemove(_ context.Context, volumeID string, options client.VolumeRemoveOptions) error {
func (c *fakeClient) VolumeRemove(_ context.Context, volumeID string, options client.VolumeRemoveOptions) (client.VolumeRemoveResult, error) {
if c.volumeRemoveFunc != nil {
return c.volumeRemoveFunc(volumeID, options.Force)
return client.VolumeRemoveResult{}, c.volumeRemoveFunc(volumeID, options.Force)
}
return nil
return client.VolumeRemoveResult{}, nil
}

View File

@@ -37,7 +37,7 @@ func newRemoveCommand(dockerCLI command.Cli) *cobra.Command {
flags := cmd.Flags()
flags.BoolVarP(&opts.force, "force", "f", false, "Force the removal of one or more volumes")
flags.SetAnnotation("force", "version", []string{"1.25"})
_ = flags.SetAnnotation("force", "version", []string{"1.25"})
return cmd
}
@@ -46,7 +46,7 @@ func runRemove(ctx context.Context, dockerCLI command.Cli, opts *removeOptions)
var errs []error
for _, name := range opts.volumes {
err := apiClient.VolumeRemove(ctx, name, client.VolumeRemoveOptions{
_, err := apiClient.VolumeRemove(ctx, name, client.VolumeRemoveOptions{
Force: opts.force,
})
if err != nil {

View File

@@ -58,11 +58,9 @@ func runUpdate(ctx context.Context, dockerCli command.Cli, volumeID, availabilit
if flags.Changed("availability") {
res.Volume.ClusterVolume.Spec.Availability = volume.Availability(availability)
}
return apiClient.VolumeUpdate(
ctx, res.Volume.ClusterVolume.ID, res.Volume.ClusterVolume.Version,
client.VolumeUpdateOptions{
Spec: &res.Volume.ClusterVolume.Spec,
},
)
_, err = apiClient.VolumeUpdate(ctx, res.Volume.ClusterVolume.ID, client.VolumeUpdateOptions{
Version: res.Volume.ClusterVolume.Version,
Spec: &res.Volume.ClusterVolume.Spec,
})
return err
}

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.20251028144225-90109a373da9 // master
github.com/moby/moby/client v0.1.0-beta.2.0.20251028144225-90109a373da9 // master
github.com/moby/moby/api v1.52.0-beta.2.0.20251029165853-ef17deb3a0fb // master
github.com/moby/moby/client v0.1.0-beta.2.0.20251029165853-ef17deb3a0fb // 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.20251028144225-90109a373da9 h1:DDumCtiHK751YhrY/tbUdMo5BN8dRarxbW4ScVh/KZg=
github.com/moby/moby/api v1.52.0-beta.2.0.20251028144225-90109a373da9/go.mod h1:v0K/motq8oWmx+rtApG1rBTIpQ8KUONUjpf+U73gags=
github.com/moby/moby/client v0.1.0-beta.2.0.20251028144225-90109a373da9 h1:iAQD/kAfqMAb2p48advqXGOT8eWyEv+Xge7KbcsZduk=
github.com/moby/moby/client v0.1.0-beta.2.0.20251028144225-90109a373da9/go.mod h1:1YrJTvhL771Q4xiwwe72NSS17lgsCF67xu8fEfSd77g=
github.com/moby/moby/api v1.52.0-beta.2.0.20251029165853-ef17deb3a0fb h1:t4RxpXuvv26rIVUBqUm0xgmaSmmTJTv5fzEDxg5piWE=
github.com/moby/moby/api v1.52.0-beta.2.0.20251029165853-ef17deb3a0fb/go.mod h1:v0K/motq8oWmx+rtApG1rBTIpQ8KUONUjpf+U73gags=
github.com/moby/moby/client v0.1.0-beta.2.0.20251029165853-ef17deb3a0fb h1:3F0iF/yIxE43JcK3rJp4k/TsVmfvFMgRyGT/tg+K1xI=
github.com/moby/moby/client v0.1.0-beta.2.0.20251029165853-ef17deb3a0fb/go.mod h1:1YrJTvhL771Q4xiwwe72NSS17lgsCF67xu8fEfSd77g=
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

@@ -0,0 +1,20 @@
// Code generated by go-swagger; DO NOT EDIT.
package network
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
// ConnectRequest NetworkConnectRequest represents the data to be used to connect a container to a network.
//
// swagger:model ConnectRequest
type ConnectRequest struct {
// The ID or name of the container to connect to the network.
// Example: 3613f73ba0e4
// Required: true
Container string `json:"Container"`
// endpoint config
EndpointConfig *EndpointSettings `json:"EndpointConfig,omitempty"`
}

View File

@@ -0,0 +1,21 @@
// Code generated by go-swagger; DO NOT EDIT.
package network
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
// DisconnectRequest NetworkDisconnectRequest represents the data to be used to disconnect a container from a network.
//
// swagger:model DisconnectRequest
type DisconnectRequest struct {
// The ID or name of the container to disconnect from the network.
// Example: 3613f73ba0e4
// Required: true
Container string `json:"Container"`
// Force the container to disconnect from the network.
// Example: false
Force bool `json:"Force"`
}

View File

@@ -259,23 +259,13 @@ func (cli *Client) Close() error {
// be negotiated when making the actual requests, and for which cases
// we cannot do the negotiation lazily.
func (cli *Client) checkVersion(ctx context.Context) error {
if !cli.manualOverride && cli.negotiateVersion && !cli.negotiated.Load() {
// Ensure exclusive write access to version and negotiated fields
cli.negotiateLock.Lock()
defer cli.negotiateLock.Unlock()
// May have been set during last execution of critical zone
if cli.negotiated.Load() {
return nil
}
ping, err := cli.Ping(ctx, PingOptions{})
if err != nil {
return err
}
return cli.negotiateAPIVersion(ping.APIVersion)
if cli.manualOverride || !cli.negotiateVersion || cli.negotiated.Load() {
return nil
}
return nil
_, err := cli.Ping(ctx, PingOptions{
NegotiateAPIVersion: true,
})
return err
}
// getAPIPath returns the versioned request path to call the API.
@@ -296,59 +286,6 @@ func (cli *Client) ClientVersion() string {
return cli.version
}
// NegotiateAPIVersion queries the API and updates the version to match the API
// version. NegotiateAPIVersion downgrades the client's API version to match the
// APIVersion if the ping version is lower than the default version. If the API
// version reported by the server is higher than the maximum version supported
// by the client, it uses the client's maximum version.
//
// If a manual override is in place, either through the "DOCKER_API_VERSION"
// ([EnvOverrideAPIVersion]) environment variable, or if the client is initialized
// with a fixed version ([WithVersion]), no negotiation is performed.
//
// If the API server's ping response does not contain an API version, or if the
// client did not get a successful ping response, it assumes it is connected with
// an old daemon that does not support API version negotiation, in which case it
// downgrades to the lowest supported API version.
func (cli *Client) NegotiateAPIVersion(ctx context.Context) {
if !cli.manualOverride {
// Avoid concurrent modification of version-related fields
cli.negotiateLock.Lock()
defer cli.negotiateLock.Unlock()
ping, err := cli.Ping(ctx, PingOptions{})
if err != nil {
// FIXME(thaJeztah): Ping returns an error when failing to connect to the API; we should not swallow the error here, and instead returning it.
return
}
// FIXME(thaJeztah): we should not swallow the error here, and instead returning it.
_ = cli.negotiateAPIVersion(ping.APIVersion)
}
}
// NegotiateAPIVersionPing downgrades the client's API version to match the
// APIVersion in the ping response. If the API version in pingResponse is higher
// than the maximum version supported by the client, it uses the client's maximum
// version.
//
// If a manual override is in place, either through the "DOCKER_API_VERSION"
// ([EnvOverrideAPIVersion]) environment variable, or if the client is initialized
// with a fixed version ([WithVersion]), no negotiation is performed.
//
// If the API server's ping response does not contain an API version, it falls
// back to the oldest API version supported.
func (cli *Client) NegotiateAPIVersionPing(pingResponse PingResult) {
// TODO(thaJeztah): should this take a "Ping" option? It only consumes the version. This method should be removed overall and not be exported.
if !cli.manualOverride {
// Avoid concurrent modification of version-related fields
cli.negotiateLock.Lock()
defer cli.negotiateLock.Unlock()
// FIXME(thaJeztah): we should not swallow the error here, and instead returning it.
_ = cli.negotiateAPIVersion(pingResponse.APIVersion)
}
}
// negotiateAPIVersion updates the version to match the API version from
// the ping response. It falls back to the lowest version supported if the
// API version is empty, or returns an error if the API version is lower than

View File

@@ -6,11 +6,7 @@ import (
"net"
"github.com/moby/moby/api/types"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/events"
"github.com/moby/moby/api/types/network"
"github.com/moby/moby/api/types/registry"
"github.com/moby/moby/api/types/swarm"
"github.com/moby/moby/api/types/system"
)
@@ -32,8 +28,6 @@ type stableAPIClient interface {
ClientVersion() string
DaemonHost() string
ServerVersion(ctx context.Context) (types.Version, error)
NegotiateAPIVersion(ctx context.Context)
NegotiateAPIVersionPing(PingResult)
HijackDialer
Dialer() func(context.Context) (net.Conn, error)
Close() error
@@ -62,26 +56,26 @@ type ContainerAPIClient interface {
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)
ContainerExport(ctx context.Context, container string, options ContainerExportOptions) (ContainerExportResult, error)
ContainerInspect(ctx context.Context, container string, options ContainerInspectOptions) (ContainerInspectResult, error)
ContainerKill(ctx context.Context, container string, options ContainerKillOptions) (ContainerKillResult, error)
ContainerList(ctx context.Context, options ContainerListOptions) ([]container.Summary, error)
ContainerLogs(ctx context.Context, container string, options ContainerLogsOptions) (io.ReadCloser, error)
ContainerList(ctx context.Context, options ContainerListOptions) (ContainerListResult, error)
ContainerLogs(ctx context.Context, container string, options ContainerLogsOptions) (ContainerLogsResult, error)
ContainerPause(ctx context.Context, container string, options ContainerPauseOptions) (ContainerPauseResult, error)
ContainerRemove(ctx context.Context, container string, options ContainerRemoveOptions) (ContainerRemoveResult, error)
ContainerRename(ctx context.Context, container, newContainerName string) error
ContainerRename(ctx context.Context, container string, options ContainerRenameOptions) (ContainerRenameResult, error)
ContainerResize(ctx context.Context, container string, options ContainerResizeOptions) (ContainerResizeResult, error)
ContainerRestart(ctx context.Context, container string, options ContainerRestartOptions) (ContainerRestartResult, error)
ContainerStatPath(ctx context.Context, container, path string) (container.PathStat, error)
ContainerStatPath(ctx context.Context, container string, options ContainerStatPathOptions) (ContainerStatPathResult, error)
ContainerStats(ctx context.Context, container string, options ContainerStatsOptions) (ContainerStatsResult, error)
ContainerStart(ctx context.Context, container string, options ContainerStartOptions) (ContainerStartResult, error)
ContainerStop(ctx context.Context, container string, options ContainerStopOptions) (ContainerStopResult, error)
ContainerTop(ctx context.Context, container string, arguments []string) (container.TopResponse, error)
ContainerUnpause(ctx context.Context, container string, options ContainerUnPauseOptions) (ContainerUnPauseResult, error)
ContainerUpdate(ctx context.Context, container string, updateConfig container.UpdateConfig) (container.UpdateResponse, error)
ContainerWait(ctx context.Context, container string, condition container.WaitCondition) (<-chan container.WaitResponse, <-chan error)
CopyFromContainer(ctx context.Context, container, srcPath string) (io.ReadCloser, container.PathStat, error)
CopyToContainer(ctx context.Context, container, path string, content io.Reader, options CopyToContainerOptions) error
ContainerTop(ctx context.Context, container string, options ContainerTopOptions) (ContainerTopResult, error)
ContainerUnpause(ctx context.Context, container string, options ContainerUnpauseOptions) (ContainerUnpauseResult, error)
ContainerUpdate(ctx context.Context, container string, updateConfig ContainerUpdateOptions) (ContainerUpdateResult, error)
ContainerWait(ctx context.Context, container string, options ContainerWaitOptions) ContainerWaitResult
CopyFromContainer(ctx context.Context, container string, options CopyFromContainerOptions) (CopyFromContainerResult, error)
CopyToContainer(ctx context.Context, container string, options CopyToContainerOptions) (CopyToContainerResult, error)
ContainersPrune(ctx context.Context, opts ContainerPruneOptions) (ContainerPruneResult, error)
}
@@ -122,12 +116,12 @@ type ImageAPIClient interface {
// NetworkAPIClient defines API client methods for the networks
type NetworkAPIClient interface {
NetworkConnect(ctx context.Context, network, container string, config *network.EndpointSettings) error
NetworkConnect(ctx context.Context, network string, options NetworkConnectOptions) (NetworkConnectResult, error)
NetworkCreate(ctx context.Context, name string, options NetworkCreateOptions) (network.CreateResponse, error)
NetworkDisconnect(ctx context.Context, network, container string, force bool) error
NetworkDisconnect(ctx context.Context, network string, options NetworkDisconnectOptions) (NetworkDisconnectResult, error)
NetworkInspect(ctx context.Context, network string, options NetworkInspectOptions) (NetworkInspectResult, error)
NetworkList(ctx context.Context, options NetworkListOptions) (NetworkListResult, error)
NetworkRemove(ctx context.Context, network string) error
NetworkRemove(ctx context.Context, network string, options NetworkRemoveOptions) (NetworkRemoveResult, error)
NetworksPrune(ctx context.Context, opts NetworkPruneOptions) (NetworkPruneResult, error)
}
@@ -179,9 +173,9 @@ type SwarmAPIClient interface {
// SystemAPIClient defines API client methods for the system
type SystemAPIClient interface {
Events(ctx context.Context, options EventsListOptions) (<-chan events.Message, <-chan error)
Info(ctx context.Context) (system.Info, error)
RegistryLogin(ctx context.Context, auth registry.AuthConfig) (registry.AuthenticateOKBody, error)
Events(ctx context.Context, options EventsListOptions) EventsResult
Info(ctx context.Context, options InfoOptions) (SystemInfoResult, error)
RegistryLogin(ctx context.Context, auth RegistryLoginOptions) (RegistryLoginResult, error)
DiskUsage(ctx context.Context, options DiskUsageOptions) (system.DiskUsage, error)
Ping(ctx context.Context, options PingOptions) (PingResult, error)
}
@@ -191,9 +185,9 @@ type VolumeAPIClient interface {
VolumeCreate(ctx context.Context, options VolumeCreateOptions) (VolumeCreateResult, error)
VolumeInspect(ctx context.Context, volumeID string, options VolumeInspectOptions) (VolumeInspectResult, error)
VolumeList(ctx context.Context, options VolumeListOptions) (VolumeListResult, error)
VolumeRemove(ctx context.Context, volumeID string, options VolumeRemoveOptions) error
VolumesPrune(ctx context.Context, opts VolumePruneOptions) (VolumePruneResult, error)
VolumeUpdate(ctx context.Context, volumeID string, version swarm.Version, options VolumeUpdateOptions) error
VolumeRemove(ctx context.Context, volumeID string, options VolumeRemoveOptions) (VolumeRemoveResult, error)
VolumesPrune(ctx context.Context, options VolumePruneOptions) (VolumePruneResult, error)
VolumeUpdate(ctx context.Context, volumeID string, options VolumeUpdateOptions) (VolumeUpdateResult, error)
}
// SecretAPIClient defines API client methods for secrets

View File

@@ -14,41 +14,57 @@ import (
"github.com/moby/moby/api/types/container"
)
type ContainerStatPathOptions struct {
Path string
}
type ContainerStatPathResult struct {
Stat container.PathStat
}
// ContainerStatPath returns stat information about a path inside the container filesystem.
func (cli *Client) ContainerStatPath(ctx context.Context, containerID, path string) (container.PathStat, error) {
func (cli *Client) ContainerStatPath(ctx context.Context, containerID string, options ContainerStatPathOptions) (ContainerStatPathResult, error) {
containerID, err := trimID("container", containerID)
if err != nil {
return container.PathStat{}, err
return ContainerStatPathResult{}, err
}
query := url.Values{}
query.Set("path", filepath.ToSlash(path)) // Normalize the paths used in the API.
query.Set("path", filepath.ToSlash(options.Path)) // Normalize the paths used in the API.
resp, err := cli.head(ctx, "/containers/"+containerID+"/archive", query, nil)
defer ensureReaderClosed(resp)
if err != nil {
return container.PathStat{}, err
return ContainerStatPathResult{}, err
}
return getContainerPathStatFromHeader(resp.Header)
stat, err := getContainerPathStatFromHeader(resp.Header)
if err != nil {
return ContainerStatPathResult{}, err
}
return ContainerStatPathResult{Stat: stat}, nil
}
// CopyToContainerOptions holds information
// about files to copy into a container
type CopyToContainerOptions struct {
DestinationPath string
Content io.Reader
AllowOverwriteDirWithFile bool
CopyUIDGID bool
}
type CopyToContainerResult struct{}
// CopyToContainer copies content into the container filesystem.
// Note that `content` must be a Reader for a TAR archive
func (cli *Client) CopyToContainer(ctx context.Context, containerID, dstPath string, content io.Reader, options CopyToContainerOptions) error {
func (cli *Client) CopyToContainer(ctx context.Context, containerID string, options CopyToContainerOptions) (CopyToContainerResult, error) {
containerID, err := trimID("container", containerID)
if err != nil {
return err
return CopyToContainerResult{}, err
}
query := url.Values{}
query.Set("path", filepath.ToSlash(dstPath)) // Normalize the paths used in the API.
query.Set("path", filepath.ToSlash(options.DestinationPath)) // Normalize the paths used in the API.
// Do not allow for an existing directory to be overwritten by a non-directory and vice versa.
if !options.AllowOverwriteDirWithFile {
query.Set("noOverwriteDirNonDir", "true")
@@ -58,29 +74,38 @@ func (cli *Client) CopyToContainer(ctx context.Context, containerID, dstPath str
query.Set("copyUIDGID", "true")
}
response, err := cli.putRaw(ctx, "/containers/"+containerID+"/archive", query, content, nil)
response, err := cli.putRaw(ctx, "/containers/"+containerID+"/archive", query, options.Content, nil)
defer ensureReaderClosed(response)
if err != nil {
return err
return CopyToContainerResult{}, err
}
return nil
return CopyToContainerResult{}, nil
}
type CopyFromContainerOptions struct {
SourcePath string
}
type CopyFromContainerResult struct {
Content io.ReadCloser
Stat container.PathStat
}
// CopyFromContainer gets the content from the container and returns it as a Reader
// for a TAR archive to manipulate it in the host. It's up to the caller to close the reader.
func (cli *Client) CopyFromContainer(ctx context.Context, containerID, srcPath string) (io.ReadCloser, container.PathStat, error) {
func (cli *Client) CopyFromContainer(ctx context.Context, containerID string, options CopyFromContainerOptions) (CopyFromContainerResult, error) {
containerID, err := trimID("container", containerID)
if err != nil {
return nil, container.PathStat{}, err
return CopyFromContainerResult{}, err
}
query := make(url.Values, 1)
query.Set("path", filepath.ToSlash(srcPath)) // Normalize the paths used in the API.
query.Set("path", filepath.ToSlash(options.SourcePath)) // Normalize the paths used in the API.
resp, err := cli.get(ctx, "/containers/"+containerID+"/archive", query, nil)
if err != nil {
return nil, container.PathStat{}, err
return CopyFromContainerResult{}, err
}
// In order to get the copy behavior right, we need to know information
@@ -91,9 +116,10 @@ func (cli *Client) CopyFromContainer(ctx context.Context, containerID, srcPath s
// can be when copying a file/dir from one location to another file/dir.
stat, err := getContainerPathStatFromHeader(resp.Header)
if err != nil {
return nil, stat, fmt.Errorf("unable to get resource stat from response: %s", err)
ensureReaderClosed(resp)
return CopyFromContainerResult{Stat: stat}, fmt.Errorf("unable to get resource stat from response: %s", err)
}
return resp.Body, stat, err
return CopyFromContainerResult{Content: resp.Body, Stat: stat}, nil
}
func getContainerPathStatFromHeader(header http.Header) (container.PathStat, error) {

View File

@@ -4,21 +4,59 @@ import (
"context"
"io"
"net/url"
"sync"
)
// ContainerExportOptions specifies options for container export operations.
type ContainerExportOptions struct {
// Currently no options are defined for ContainerExport
}
// ContainerExportResult represents the result of a container export operation.
type ContainerExportResult struct {
rc io.ReadCloser
close func() error
}
// ContainerExport retrieves the raw contents of a container
// and returns them as an [io.ReadCloser]. It's up to the caller
// to close the stream.
func (cli *Client) ContainerExport(ctx context.Context, containerID string) (io.ReadCloser, error) {
func (cli *Client) ContainerExport(ctx context.Context, containerID string, options ContainerExportOptions) (ContainerExportResult, error) {
containerID, err := trimID("container", containerID)
if err != nil {
return nil, err
return ContainerExportResult{}, err
}
resp, err := cli.get(ctx, "/containers/"+containerID+"/export", url.Values{}, nil)
if err != nil {
return nil, err
return ContainerExportResult{}, err
}
return resp.Body, nil
return newContainerExportResult(resp.Body), nil
}
func newContainerExportResult(rc io.ReadCloser) ContainerExportResult {
if rc == nil {
panic("nil io.ReadCloser")
}
return ContainerExportResult{
rc: rc,
close: sync.OnceValue(rc.Close),
}
}
// Read implements io.ReadCloser
func (r ContainerExportResult) Read(p []byte) (n int, err error) {
if r.rc == nil {
return 0, io.EOF
}
return r.rc.Read(p)
}
// Close implements io.ReadCloser
func (r ContainerExportResult) Close() error {
if r.close == nil {
return nil
}
return r.close()
}

View File

@@ -20,8 +20,12 @@ type ContainerListOptions struct {
Filters Filters
}
type ContainerListResult struct {
Items []container.Summary
}
// ContainerList returns the list of containers in the docker host.
func (cli *Client) ContainerList(ctx context.Context, options ContainerListOptions) ([]container.Summary, error) {
func (cli *Client) ContainerList(ctx context.Context, options ContainerListOptions) (ContainerListResult, error) {
query := url.Values{}
if options.All {
@@ -49,10 +53,10 @@ func (cli *Client) ContainerList(ctx context.Context, options ContainerListOptio
resp, err := cli.get(ctx, "/containers/json", query, nil)
defer ensureReaderClosed(resp)
if err != nil {
return nil, err
return ContainerListResult{}, err
}
var containers []container.Summary
err = json.NewDecoder(resp.Body).Decode(&containers)
return containers, err
return ContainerListResult{Items: containers}, err
}

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"io"
"net/url"
"sync"
"time"
"github.com/moby/moby/client/internal/timestamp"
@@ -22,6 +23,12 @@ type ContainerLogsOptions struct {
Details bool
}
// ContainerLogsResult is the result of a container logs operation.
type ContainerLogsResult struct {
rc io.ReadCloser
close func() error
}
// ContainerLogs returns the logs generated by a container in an [io.ReadCloser].
// It's up to the caller to close the stream.
//
@@ -48,10 +55,10 @@ type ContainerLogsOptions 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) ContainerLogs(ctx context.Context, containerID string, options ContainerLogsOptions) (io.ReadCloser, error) {
func (cli *Client) ContainerLogs(ctx context.Context, containerID string, options ContainerLogsOptions) (ContainerLogsResult, error) {
containerID, err := trimID("container", containerID)
if err != nil {
return nil, err
return ContainerLogsResult{}, err
}
query := url.Values{}
@@ -66,7 +73,7 @@ func (cli *Client) ContainerLogs(ctx context.Context, containerID string, option
if options.Since != "" {
ts, err := timestamp.GetTimestamp(options.Since, time.Now())
if err != nil {
return nil, fmt.Errorf(`invalid value for "since": %w`, err)
return ContainerLogsResult{}, fmt.Errorf(`invalid value for "since": %w`, err)
}
query.Set("since", ts)
}
@@ -74,7 +81,7 @@ func (cli *Client) ContainerLogs(ctx context.Context, containerID string, option
if options.Until != "" {
ts, err := timestamp.GetTimestamp(options.Until, time.Now())
if err != nil {
return nil, fmt.Errorf(`invalid value for "until": %w`, err)
return ContainerLogsResult{}, fmt.Errorf(`invalid value for "until": %w`, err)
}
query.Set("until", ts)
}
@@ -94,7 +101,33 @@ func (cli *Client) ContainerLogs(ctx context.Context, containerID string, option
resp, err := cli.get(ctx, "/containers/"+containerID+"/logs", query, nil)
if err != nil {
return nil, err
return ContainerLogsResult{}, err
}
return resp.Body, nil
return newContainerLogsResult(resp.Body), nil
}
func newContainerLogsResult(rc io.ReadCloser) ContainerLogsResult {
if rc == nil {
panic("rc cannot be nil")
}
return ContainerLogsResult{
rc: rc,
close: sync.OnceValue(rc.Close),
}
}
// Read implements the io.Reader interface.
func (r ContainerLogsResult) Read(p []byte) (n int, err error) {
if r.rc == nil {
return 0, io.EOF
}
return r.rc.Read(p)
}
// Close closes the underlying reader.
func (r ContainerLogsResult) Close() error {
if r.close == nil {
return nil
}
return r.close()
}

View File

@@ -5,16 +5,26 @@ import (
"net/url"
)
// ContainerRenameOptions represents the options for renaming a container.
type ContainerRenameOptions struct {
NewName string
}
// ContainerRenameResult represents the result of a container rename operation.
type ContainerRenameResult struct {
// This struct can be expanded in the future if needed
}
// ContainerRename changes the name of a given container.
func (cli *Client) ContainerRename(ctx context.Context, containerID, newContainerName string) error {
func (cli *Client) ContainerRename(ctx context.Context, containerID string, options ContainerRenameOptions) (ContainerRenameResult, error) {
containerID, err := trimID("container", containerID)
if err != nil {
return err
return ContainerRenameResult{}, err
}
query := url.Values{}
query.Set("name", newContainerName)
query.Set("name", options.NewName)
resp, err := cli.post(ctx, "/containers/"+containerID+"/rename", query, nil, nil)
defer ensureReaderClosed(resp)
return err
return ContainerRenameResult{}, err
}

View File

@@ -9,25 +9,36 @@ import (
"github.com/moby/moby/api/types/container"
)
// ContainerTopOptions defines options for container top operations.
type ContainerTopOptions struct {
Arguments []string
}
// ContainerTopResult represents the result of a ContainerTop operation.
type ContainerTopResult struct {
Processes [][]string
Titles []string
}
// ContainerTop shows process information from within a container.
func (cli *Client) ContainerTop(ctx context.Context, containerID string, arguments []string) (container.TopResponse, error) {
func (cli *Client) ContainerTop(ctx context.Context, containerID string, options ContainerTopOptions) (ContainerTopResult, error) {
containerID, err := trimID("container", containerID)
if err != nil {
return container.TopResponse{}, err
return ContainerTopResult{}, err
}
query := url.Values{}
if len(arguments) > 0 {
query.Set("ps_args", strings.Join(arguments, " "))
if len(options.Arguments) > 0 {
query.Set("ps_args", strings.Join(options.Arguments, " "))
}
resp, err := cli.get(ctx, "/containers/"+containerID+"/top", query, nil)
defer ensureReaderClosed(resp)
if err != nil {
return container.TopResponse{}, err
return ContainerTopResult{}, err
}
var response container.TopResponse
err = json.NewDecoder(resp.Body).Decode(&response)
return response, err
return ContainerTopResult{Processes: response.Processes, Titles: response.Titles}, err
}

View File

@@ -2,27 +2,27 @@ package client
import "context"
// ContainerUnPauseOptions holds options for [Client.ContainerUnpause].
type ContainerUnPauseOptions struct {
// ContainerUnpauseOptions holds options for [Client.ContainerUnpause].
type ContainerUnpauseOptions struct {
// Add future optional parameters here.
}
// ContainerUnPauseResult holds the result of [Client.ContainerUnpause],
type ContainerUnPauseResult struct {
// ContainerUnpauseResult holds the result of [Client.ContainerUnpause],
type ContainerUnpauseResult struct {
// Add future fields here.
}
// ContainerUnpause resumes the process execution within a container.
func (cli *Client) ContainerUnpause(ctx context.Context, containerID string, options ContainerUnPauseOptions) (ContainerUnPauseResult, error) {
func (cli *Client) ContainerUnpause(ctx context.Context, containerID string, options ContainerUnpauseOptions) (ContainerUnpauseResult, error) {
containerID, err := trimID("container", containerID)
if err != nil {
return ContainerUnPauseResult{}, err
return ContainerUnpauseResult{}, err
}
resp, err := cli.post(ctx, "/containers/"+containerID+"/unpause", nil, nil, nil)
defer ensureReaderClosed(resp)
if err != nil {
return ContainerUnPauseResult{}, err
return ContainerUnpauseResult{}, err
}
return ContainerUnPauseResult{}, nil
return ContainerUnpauseResult{}, nil
}

View File

@@ -7,20 +7,40 @@ import (
"github.com/moby/moby/api/types/container"
)
// ContainerUpdateOptions holds options for [Client.ContainerUpdate].
type ContainerUpdateOptions struct {
Resources *container.Resources
RestartPolicy *container.RestartPolicy
}
// ContainerUpdateResult is the result from updating a container.
type ContainerUpdateResult struct {
// Warnings encountered when updating the container.
Warnings []string
}
// ContainerUpdate updates the resources of a container.
func (cli *Client) ContainerUpdate(ctx context.Context, containerID string, updateConfig container.UpdateConfig) (container.UpdateResponse, error) {
func (cli *Client) ContainerUpdate(ctx context.Context, containerID string, options ContainerUpdateOptions) (ContainerUpdateResult, error) {
containerID, err := trimID("container", containerID)
if err != nil {
return container.UpdateResponse{}, err
return ContainerUpdateResult{}, err
}
updateConfig := container.UpdateConfig{}
if options.Resources != nil {
updateConfig.Resources = *options.Resources
}
if options.RestartPolicy != nil {
updateConfig.RestartPolicy = *options.RestartPolicy
}
resp, err := cli.post(ctx, "/containers/"+containerID+"/update", nil, updateConfig, nil)
defer ensureReaderClosed(resp)
if err != nil {
return container.UpdateResponse{}, err
return ContainerUpdateResult{}, err
}
var response container.UpdateResponse
err = json.NewDecoder(resp.Body).Decode(&response)
return response, err
return ContainerUpdateResult{Warnings: response.Warnings}, err
}

View File

@@ -13,43 +13,51 @@ import (
const containerWaitErrorMsgLimit = 2 * 1024 /* Max: 2KiB */
// ContainerWaitOptions holds options for [Client.ContainerWait].
type ContainerWaitOptions struct {
Condition container.WaitCondition
}
// ContainerWaitResult defines the result from the [Client.ContainerWait] method.
type ContainerWaitResult struct {
Result <-chan container.WaitResponse
Error <-chan error
}
// ContainerWait waits until the specified container is in a certain state
// indicated by the given condition, either "not-running" ([container.WaitConditionNotRunning])
// (default), "next-exit" ([container.WaitConditionNextExit]), or "removed".
// ([container.WaitConditionRemoved]).
// indicated by the given condition, either;
//
// If this client's API version is before 1.30, "condition" is ignored and
// ContainerWait returns immediately with the two channels, as the server
// waits as if the condition were "not-running".
// - "not-running" ([container.WaitConditionNotRunning]) (default)
// - "next-exit" ([container.WaitConditionNextExit])
// - "removed" ([container.WaitConditionRemoved])
//
// If this client's API version is at least 1.30, ContainerWait blocks until
// the request has been acknowledged by the server (with a response header),
// then returns two channels on which the caller can wait for the exit status
// of the container or an error if there was a problem either beginning the
// wait request or in getting the response. This allows the caller to
// synchronize ContainerWait with other calls, such as specifying a
// "next-exit" condition ([container.WaitConditionNextExit]) before
// issuing a [Client.ContainerStart] request.
func (cli *Client) ContainerWait(ctx context.Context, containerID string, condition container.WaitCondition) (<-chan container.WaitResponse, <-chan error) {
// ContainerWait blocks until the request has been acknowledged by the server
// (with a response header), then returns two channels on which the caller can
// wait for the exit status of the container or an error if there was a problem
// either beginning the wait request or in getting the response. This allows the
// caller to synchronize ContainerWait with other calls, such as specifying a
// "next-exit" condition ([container.WaitConditionNextExit]) before issuing a
// [Client.ContainerStart] request.
func (cli *Client) ContainerWait(ctx context.Context, containerID string, options ContainerWaitOptions) ContainerWaitResult {
resultC := make(chan container.WaitResponse)
errC := make(chan error, 1)
containerID, err := trimID("container", containerID)
if err != nil {
errC <- err
return resultC, errC
return ContainerWaitResult{Result: resultC, Error: errC}
}
query := url.Values{}
if condition != "" {
query.Set("condition", string(condition))
if options.Condition != "" {
query.Set("condition", string(options.Condition))
}
resp, err := cli.post(ctx, "/containers/"+containerID+"/wait", query, nil, nil)
if err != nil {
defer ensureReaderClosed(resp)
errC <- err
return resultC, errC
return ContainerWaitResult{Result: resultC, Error: errC}
}
go func() {
@@ -80,31 +88,5 @@ func (cli *Client) ContainerWait(ctx context.Context, containerID string, condit
resultC <- res
}()
return resultC, errC
}
// legacyContainerWait returns immediately and doesn't have an option to wait
// until the container is removed.
func (cli *Client) legacyContainerWait(ctx context.Context, containerID string) (<-chan container.WaitResponse, <-chan error) {
resultC := make(chan container.WaitResponse)
errC := make(chan error)
go func() {
resp, err := cli.post(ctx, "/containers/"+containerID+"/wait", nil, nil, nil)
if err != nil {
errC <- err
return
}
defer ensureReaderClosed(resp)
var res container.WaitResponse
if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
errC <- err
return
}
resultC <- res
}()
return resultC, errC
return ContainerWaitResult{Result: resultC, Error: errC}
}

View File

@@ -53,12 +53,12 @@ func (e objectNotFoundError) Error() string {
return fmt.Sprintf("Error: No such %s: %s", e.object, e.id)
}
// NewVersionError returns an error if the APIVersion required is less than the
// requiresVersion returns an error if the APIVersion required is less than the
// current supported version.
//
// It performs API-version negotiation if the Client is configured with this
// option, otherwise it assumes the latest API version is used.
func (cli *Client) NewVersionError(ctx context.Context, APIrequired, feature string) error {
func (cli *Client) requiresVersion(ctx context.Context, apiRequired, feature string) error {
// Make sure we negotiated (if the client is configured to do so),
// as code below contains API-version specific handling of options.
//
@@ -67,8 +67,8 @@ func (cli *Client) NewVersionError(ctx context.Context, APIrequired, feature str
if err := cli.checkVersion(ctx); err != nil {
return err
}
if cli.version != "" && versions.LessThan(cli.version, APIrequired) {
return fmt.Errorf("%q requires API version %s, but the Docker daemon API version is %s", feature, APIrequired, cli.version)
if cli.version != "" && versions.LessThan(cli.version, apiRequired) {
return fmt.Errorf("%q requires API version %s, but the Docker daemon API version is %s", feature, apiRequired, cli.version)
}
return nil
}

View File

@@ -32,7 +32,7 @@ func (cli *Client) ImageHistory(ctx context.Context, imageID string, historyOpts
}
if opts.apiOptions.Platform != nil {
if err := cli.NewVersionError(ctx, "1.48", "platform"); err != nil {
if err := cli.requiresVersion(ctx, "1.48", "platform"); err != nil {
return ImageHistoryResult{}, err
}

View File

@@ -24,14 +24,14 @@ func (cli *Client) ImageInspect(ctx context.Context, imageID string, inspectOpts
query := url.Values{}
if opts.apiOptions.Manifests {
if err := cli.NewVersionError(ctx, "1.48", "manifests"); err != nil {
if err := cli.requiresVersion(ctx, "1.48", "manifests"); err != nil {
return ImageInspectResult{}, err
}
query.Set("manifests", "1")
}
if opts.apiOptions.Platform != nil {
if err := cli.NewVersionError(ctx, "1.49", "platform"); err != nil {
if err := cli.requiresVersion(ctx, "1.49", "platform"); err != nil {
return ImageInspectResult{}, err
}
platform, err := encodePlatform(opts.apiOptions.Platform)

View File

@@ -28,7 +28,7 @@ func (cli *Client) ImageLoad(ctx context.Context, input io.Reader, loadOpts ...I
query.Set("quiet", "1")
}
if len(opts.apiOptions.Platforms) > 0 {
if err := cli.NewVersionError(ctx, "1.48", "platform"); err != nil {
if err := cli.requiresVersion(ctx, "1.48", "platform"); err != nil {
return ImageLoadResult{}, err
}

View File

@@ -50,7 +50,7 @@ func (cli *Client) ImagePush(ctx context.Context, image string, options ImagePus
}
if options.Platform != nil {
if err := cli.NewVersionError(ctx, "1.46", "platform"); err != nil {
if err := cli.requiresVersion(ctx, "1.46", "platform"); err != nil {
return nil, err
}

View File

@@ -24,7 +24,7 @@ func (cli *Client) ImageSave(ctx context.Context, imageIDs []string, saveOpts ..
}
if len(opts.apiOptions.Platforms) > 0 {
if err := cli.NewVersionError(ctx, "1.48", "platform"); err != nil {
if err := cli.requiresVersion(ctx, "1.48", "platform"); err != nil {
return ImageSaveResult{}, err
}
p, err := encodePlatforms(opts.apiOptions.Platforms...)

View File

@@ -8,17 +8,38 @@ import (
"github.com/moby/moby/api/types/registry"
)
type RegistryLoginOptions struct {
Username string
Password string
ServerAddress string
IdentityToken string
RegistryToken string
}
// RegistryLoginResult holds the result of a RegistryLogin query.
type RegistryLoginResult struct {
Auth registry.AuthenticateOKBody
}
// RegistryLogin authenticates the docker server with a given docker registry.
// It returns unauthorizedError when the authentication fails.
func (cli *Client) RegistryLogin(ctx context.Context, auth registry.AuthConfig) (registry.AuthenticateOKBody, error) {
func (cli *Client) RegistryLogin(ctx context.Context, options RegistryLoginOptions) (RegistryLoginResult, error) {
auth := registry.AuthConfig{
Username: options.Username,
Password: options.Password,
ServerAddress: options.ServerAddress,
IdentityToken: options.IdentityToken,
RegistryToken: options.RegistryToken,
}
resp, err := cli.post(ctx, "/auth", url.Values{}, auth, nil)
defer ensureReaderClosed(resp)
if err != nil {
return registry.AuthenticateOKBody{}, err
return RegistryLoginResult{}, err
}
var response registry.AuthenticateOKBody
err = json.NewDecoder(resp.Body).Decode(&response)
return response, err
return RegistryLoginResult{Auth: response}, err
}

View File

@@ -6,23 +6,35 @@ import (
"github.com/moby/moby/api/types/network"
)
// NetworkConnectOptions represents the data to be used to connect a container to the
// network.
type NetworkConnectOptions struct {
Container string
EndpointConfig *network.EndpointSettings
}
// NetworkConnectResult represents the result of a NetworkConnect operation.
type NetworkConnectResult struct {
// Currently empty; placeholder for future fields.
}
// NetworkConnect connects a container to an existent network in the docker host.
func (cli *Client) NetworkConnect(ctx context.Context, networkID, containerID string, config *network.EndpointSettings) error {
func (cli *Client) NetworkConnect(ctx context.Context, networkID string, options NetworkConnectOptions) (NetworkConnectResult, error) {
networkID, err := trimID("network", networkID)
if err != nil {
return err
return NetworkConnectResult{}, err
}
containerID, err = trimID("container", containerID)
containerID, err := trimID("container", options.Container)
if err != nil {
return err
return NetworkConnectResult{}, err
}
nc := NetworkConnectOptions{
nc := network.ConnectRequest{
Container: containerID,
EndpointConfig: config,
EndpointConfig: options.EndpointConfig,
}
resp, err := cli.post(ctx, "/networks/"+networkID+"/connect", nil, nc, nil)
defer ensureReaderClosed(resp)
return err
return NetworkConnectResult{}, err
}

View File

@@ -1,10 +0,0 @@
package client
import "github.com/moby/moby/api/types/network"
// NetworkConnectOptions represents the data to be used to connect a container to the
// network.
type NetworkConnectOptions struct {
Container string
EndpointConfig *network.EndpointSettings `json:",omitempty"`
}

View File

@@ -2,25 +2,39 @@ package client
import (
"context"
"github.com/moby/moby/api/types/network"
)
// NetworkDisconnectOptions represents the data to be used to disconnect a container
// from the network.
type NetworkDisconnectOptions struct {
Container string
Force bool
}
// NetworkDisconnectResult represents the result of a NetworkDisconnect operation.
type NetworkDisconnectResult struct {
// Currently empty; placeholder for future fields.
}
// NetworkDisconnect disconnects a container from an existent network in the docker host.
func (cli *Client) NetworkDisconnect(ctx context.Context, networkID, containerID string, force bool) error {
func (cli *Client) NetworkDisconnect(ctx context.Context, networkID string, options NetworkDisconnectOptions) (NetworkDisconnectResult, error) {
networkID, err := trimID("network", networkID)
if err != nil {
return err
return NetworkDisconnectResult{}, err
}
containerID, err = trimID("container", containerID)
containerID, err := trimID("container", options.Container)
if err != nil {
return err
return NetworkDisconnectResult{}, err
}
nd := NetworkDisconnectOptions{
req := network.DisconnectRequest{
Container: containerID,
Force: force,
Force: options.Force,
}
resp, err := cli.post(ctx, "/networks/"+networkID+"/disconnect", nil, nd, nil)
resp, err := cli.post(ctx, "/networks/"+networkID+"/disconnect", nil, req, nil)
defer ensureReaderClosed(resp)
return err
return NetworkDisconnectResult{}, err
}

View File

@@ -1,8 +0,0 @@
package client
// NetworkDisconnectOptions represents the data to be used to disconnect a container
// from the network.
type NetworkDisconnectOptions struct {
Container string
Force bool
}

View File

@@ -1,14 +1,26 @@
package client
import "context"
import (
"context"
)
// NetworkRemoveOptions specifies options for removing a network.
type NetworkRemoveOptions struct {
// No options currently; placeholder for future use.
}
// NetworkRemoveResult represents the result of a network removal operation.
type NetworkRemoveResult struct {
// No fields currently; placeholder for future use.
}
// NetworkRemove removes an existent network from the docker host.
func (cli *Client) NetworkRemove(ctx context.Context, networkID string) error {
func (cli *Client) NetworkRemove(ctx context.Context, networkID string, options NetworkRemoveOptions) (NetworkRemoveResult, error) {
networkID, err := trimID("network", networkID)
if err != nil {
return err
return NetworkRemoveResult{}, err
}
resp, err := cli.delete(ctx, "/networks/"+networkID, nil, nil)
defer ensureReaderClosed(resp)
return err
return NetworkRemoveResult{}, err
}

View File

@@ -12,7 +12,29 @@ import (
// PingOptions holds options for [client.Ping].
type PingOptions struct {
// Add future optional parameters here
// NegotiateAPIVersion queries the API and updates the version to match the API
// version. NegotiateAPIVersion downgrades the client's API version to match the
// APIVersion if the ping version is lower than the default version. If the API
// version reported by the server is higher than the maximum version supported
// by the client, it uses the client's maximum version.
//
// If a manual override is in place, either through the "DOCKER_API_VERSION"
// ([EnvOverrideAPIVersion]) environment variable, or if the client is initialized
// with a fixed version ([WithVersion]), no negotiation is performed.
//
// If the API server's ping response does not contain an API version, or if the
// client did not get a successful ping response, it assumes it is connected with
// an old daemon that does not support API version negotiation, in which case it
// downgrades to the lowest supported API version.
NegotiateAPIVersion bool
// ForceNegotiate forces the client to re-negotiate the API version, even if
// API-version negotiation already happened. This option cannot be
// used if the client is configured with a fixed version using (using
// [WithVersion] or [WithVersionFromEnv]).
//
// This option has no effect if NegotiateAPIVersion is not set.
ForceNegotiate bool
}
// PingResult holds the result of a [Client.Ping] API call.
@@ -50,6 +72,30 @@ type SwarmStatus struct {
// for other non-success status codes, failing to connect to the API, or failing
// to parse the API response.
func (cli *Client) Ping(ctx context.Context, options PingOptions) (PingResult, error) {
if cli.manualOverride {
return cli.ping(ctx)
}
if !options.NegotiateAPIVersion && !cli.negotiateVersion {
return cli.ping(ctx)
}
// Ensure exclusive write access to version and negotiated fields
cli.negotiateLock.Lock()
defer cli.negotiateLock.Unlock()
ping, err := cli.ping(ctx)
if err != nil {
return cli.ping(ctx)
}
if cli.negotiated.Load() && !options.ForceNegotiate {
return ping, nil
}
return ping, cli.negotiateAPIVersion(ping.APIVersion)
}
func (cli *Client) ping(ctx context.Context) (PingResult, error) {
// Using cli.buildRequest() + cli.doRequest() instead of cli.sendRequest()
// because ping requests are used during API version negotiation, so we want
// to hit the non-versioned /_ping endpoint, not /v1.xx/_ping

View File

@@ -19,11 +19,17 @@ type EventsListOptions struct {
Filters Filters
}
// EventsResult holds the result of an Events query.
type EventsResult struct {
Messages <-chan events.Message
Err <-chan error
}
// Events returns a stream of events in the daemon. It's up to the caller to close the stream
// by cancelling the context. Once the stream has been completely read an [io.EOF] error is
// sent over the error channel. If an error is sent, all processing is stopped. It's up
// to the caller to reopen the stream in the event of an error by reinvoking this method.
func (cli *Client) Events(ctx context.Context, options EventsListOptions) (<-chan events.Message, <-chan error) {
func (cli *Client) Events(ctx context.Context, options EventsListOptions) EventsResult {
messages := make(chan events.Message)
errs := make(chan error, 1)
@@ -76,7 +82,10 @@ func (cli *Client) Events(ctx context.Context, options EventsListOptions) (<-cha
}()
<-started
return messages, errs
return EventsResult{
Messages: messages,
Err: errs,
}
}
func buildEventsQueryParams(options EventsListOptions) (url.Values, error) {

View File

@@ -9,18 +9,26 @@ import (
"github.com/moby/moby/api/types/system"
)
type InfoOptions struct {
// No options currently; placeholder for future use
}
type SystemInfoResult struct {
Info system.Info
}
// Info returns information about the docker server.
func (cli *Client) Info(ctx context.Context) (system.Info, error) {
var info system.Info
func (cli *Client) Info(ctx context.Context, options InfoOptions) (SystemInfoResult, error) {
resp, err := cli.get(ctx, "/info", url.Values{}, nil)
defer ensureReaderClosed(resp)
if err != nil {
return info, err
return SystemInfoResult{}, err
}
var info system.Info
if err := json.NewDecoder(resp.Body).Decode(&info); err != nil {
return info, fmt.Errorf("Error reading remote info: %v", err)
return SystemInfoResult{}, fmt.Errorf("Error reading remote info: %v", err)
}
return info, nil
return SystemInfoResult{Info: info}, nil
}

View File

@@ -26,19 +26,19 @@ type VolumePruneResult struct {
}
// VolumesPrune requests the daemon to delete unused data
func (cli *Client) VolumesPrune(ctx context.Context, opts VolumePruneOptions) (VolumePruneResult, error) {
if opts.All {
if _, ok := opts.Filters["all"]; ok {
func (cli *Client) VolumesPrune(ctx context.Context, options VolumePruneOptions) (VolumePruneResult, error) {
if options.All {
if _, ok := options.Filters["all"]; ok {
return VolumePruneResult{}, errdefs.ErrInvalidArgument.WithMessage(`conflicting options: cannot specify both "all" and "all" filter`)
}
if opts.Filters == nil {
opts.Filters = Filters{}
if options.Filters == nil {
options.Filters = Filters{}
}
opts.Filters.Add("all", "true")
options.Filters.Add("all", "true")
}
query := url.Values{}
opts.Filters.updateURLValues(query)
options.Filters.updateURLValues(query)
resp, err := cli.post(ctx, "/volumes/prune", query, nil, nil)
defer ensureReaderClosed(resp)
@@ -48,7 +48,7 @@ func (cli *Client) VolumesPrune(ctx context.Context, opts VolumePruneOptions) (V
var report volume.PruneReport
if err := json.NewDecoder(resp.Body).Decode(&report); err != nil {
return VolumePruneResult{}, fmt.Errorf("Error retrieving volume prune report: %v", err)
return VolumePruneResult{}, fmt.Errorf("error retrieving volume prune report: %v", err)
}
return VolumePruneResult{Report: report}, nil

View File

@@ -5,17 +5,22 @@ import (
"net/url"
)
// VolumeRemoveOptions holds optional parameters for volume removal.
// VolumeRemoveOptions holds options for [Client.VolumeRemove].
type VolumeRemoveOptions struct {
// Force the removal of the volume
Force bool
}
// VolumeRemoveResult holds the result of [Client.VolumeRemove],
type VolumeRemoveResult struct {
// Add future fields here.
}
// VolumeRemove removes a volume from the docker host.
func (cli *Client) VolumeRemove(ctx context.Context, volumeID string, options VolumeRemoveOptions) error {
func (cli *Client) VolumeRemove(ctx context.Context, volumeID string, options VolumeRemoveOptions) (VolumeRemoveResult, error) {
volumeID, err := trimID("volume", volumeID)
if err != nil {
return err
return VolumeRemoveResult{}, err
}
query := url.Values{}
@@ -24,5 +29,8 @@ func (cli *Client) VolumeRemove(ctx context.Context, volumeID string, options Vo
}
resp, err := cli.delete(ctx, "/volumes/"+volumeID, query, nil)
defer ensureReaderClosed(resp)
return err
if err != nil {
return VolumeRemoveResult{}, err
}
return VolumeRemoveResult{}, nil
}

View File

@@ -8,23 +8,33 @@ import (
"github.com/moby/moby/api/types/volume"
)
// VolumeUpdateOptions holds options for [Client.VolumeUpdate].
type VolumeUpdateOptions struct {
Version swarm.Version
// Spec is the ClusterVolumeSpec to update the volume to.
Spec *volume.ClusterVolumeSpec `json:"Spec,omitempty"`
}
// VolumeUpdateResult holds the result of [Client.VolumeUpdate],
type VolumeUpdateResult struct {
// Add future fields here.
}
// VolumeUpdate updates a volume. This only works for Cluster Volumes, and
// only some fields can be updated.
func (cli *Client) VolumeUpdate(ctx context.Context, volumeID string, version swarm.Version, options VolumeUpdateOptions) error {
func (cli *Client) VolumeUpdate(ctx context.Context, volumeID string, options VolumeUpdateOptions) (VolumeUpdateResult, error) {
volumeID, err := trimID("volume", volumeID)
if err != nil {
return err
return VolumeUpdateResult{}, err
}
query := url.Values{}
query.Set("version", version.String())
query.Set("version", options.Version.String())
resp, err := cli.put(ctx, "/volumes/"+volumeID, query, options, nil)
defer ensureReaderClosed(resp)
return err
if err != nil {
return VolumeUpdateResult{}, err
}
return VolumeUpdateResult{}, nil
}

4
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.20251028144225-90109a373da9
# github.com/moby/moby/api v1.52.0-beta.2.0.20251029165853-ef17deb3a0fb
## explicit; go 1.23.0
github.com/moby/moby/api/pkg/authconfig
github.com/moby/moby/api/pkg/stdcopy
@@ -190,7 +190,7 @@ 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/volume
# github.com/moby/moby/client v0.1.0-beta.2.0.20251028144225-90109a373da9
# github.com/moby/moby/client v0.1.0-beta.2.0.20251029165853-ef17deb3a0fb
## explicit; go 1.23.0
github.com/moby/moby/client
github.com/moby/moby/client/internal