vendor: github.com/docker/docker 6c3797923dcb (master, v28.0-dev)

full diff: 6968719093...6c3797923d

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn 2025-01-14 22:44:51 +01:00
parent 11999b16e4
commit 01da8a582f
No known key found for this signature in database
GPG Key ID: 76698F39D527CE8C
84 changed files with 1260 additions and 289 deletions

View File

@ -123,7 +123,8 @@ func TestNewAPIClientFromFlagsWithCustomHeadersFromEnv(t *testing.T) {
} }
func TestNewAPIClientFromFlagsWithAPIVersionFromEnv(t *testing.T) { func TestNewAPIClientFromFlagsWithAPIVersionFromEnv(t *testing.T) {
customVersion := "v3.3.3" const customVersion = "v3.3.3"
const expectedVersion = "3.3.3"
t.Setenv("DOCKER_API_VERSION", customVersion) t.Setenv("DOCKER_API_VERSION", customVersion)
t.Setenv("DOCKER_HOST", ":2375") t.Setenv("DOCKER_HOST", ":2375")
@ -131,7 +132,7 @@ func TestNewAPIClientFromFlagsWithAPIVersionFromEnv(t *testing.T) {
configFile := &configfile.ConfigFile{} configFile := &configfile.ConfigFile{}
apiclient, err := NewAPIClientFromFlags(opts, configFile) apiclient, err := NewAPIClientFromFlags(opts, configFile)
assert.NilError(t, err) assert.NilError(t, err)
assert.Equal(t, apiclient.ClientVersion(), customVersion) assert.Equal(t, apiclient.ClientVersion(), expectedVersion)
} }
type fakeClient struct { type fakeClient struct {

View File

@ -24,7 +24,7 @@ type fakeClient struct {
imagesPruneFunc func(pruneFilter filters.Args) (image.PruneReport, error) imagesPruneFunc func(pruneFilter filters.Args) (image.PruneReport, error)
imageLoadFunc func(input io.Reader, options image.LoadOptions) (image.LoadResponse, error) imageLoadFunc func(input io.Reader, options image.LoadOptions) (image.LoadResponse, error)
imageListFunc func(options image.ListOptions) ([]image.Summary, error) imageListFunc func(options image.ListOptions) ([]image.Summary, error)
imageInspectFunc func(img string) (image.InspectResponse, []byte, error) imageInspectFunc func(img string) (image.InspectResponse, error)
imageImportFunc func(source image.ImportSource, ref string, options image.ImportOptions) (io.ReadCloser, error) imageImportFunc func(source image.ImportSource, ref string, options image.ImportOptions) (io.ReadCloser, error)
imageHistoryFunc func(img string, options image.HistoryOptions) ([]image.HistoryResponseItem, error) imageHistoryFunc func(img string, options image.HistoryOptions) ([]image.HistoryResponseItem, error)
imageBuildFunc func(context.Context, io.Reader, types.ImageBuildOptions) (types.ImageBuildResponse, error) imageBuildFunc func(context.Context, io.Reader, types.ImageBuildOptions) (types.ImageBuildResponse, error)
@ -95,11 +95,11 @@ func (cli *fakeClient) ImageList(_ context.Context, options image.ListOptions) (
return []image.Summary{}, nil return []image.Summary{}, nil
} }
func (cli *fakeClient) ImageInspectWithRaw(_ context.Context, img string) (image.InspectResponse, []byte, error) { func (cli *fakeClient) ImageInspect(_ context.Context, img string, _ ...client.ImageInspectOption) (image.InspectResponse, error) {
if cli.imageInspectFunc != nil { if cli.imageInspectFunc != nil {
return cli.imageInspectFunc(img) return cli.imageInspectFunc(img)
} }
return image.InspectResponse{}, nil, nil return image.InspectResponse{}, nil
} }
func (cli *fakeClient) ImageImport(_ context.Context, source image.ImportSource, ref string, func (cli *fakeClient) ImageImport(_ context.Context, source image.ImportSource, ref string,

View File

@ -4,6 +4,7 @@
package image package image
import ( import (
"bytes"
"context" "context"
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
@ -11,6 +12,8 @@ import (
"github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/cli/command/inspect" "github.com/docker/cli/cli/command/inspect"
flagsHelper "github.com/docker/cli/cli/flags" flagsHelper "github.com/docker/cli/cli/flags"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/client"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -42,6 +45,11 @@ func newInspectCommand(dockerCli command.Cli) *cobra.Command {
func runInspect(ctx context.Context, dockerCLI command.Cli, opts inspectOptions) error { func runInspect(ctx context.Context, dockerCLI command.Cli, opts inspectOptions) error {
apiClient := dockerCLI.Client() apiClient := dockerCLI.Client()
return inspect.Inspect(dockerCLI.Out(), opts.refs, opts.format, func(ref string) (any, []byte, error) { return inspect.Inspect(dockerCLI.Out(), opts.refs, opts.format, func(ref string) (any, []byte, error) {
return apiClient.ImageInspectWithRaw(ctx, ref) var buf bytes.Buffer
resp, err := apiClient.ImageInspect(ctx, ref, client.ImageInspectWithRawResponse(&buf))
if err != nil {
return image.InspectResponse{}, nil, err
}
return resp, buf.Bytes(), err
}) })
} }

View File

@ -41,39 +41,39 @@ func TestNewInspectCommandSuccess(t *testing.T) {
name string name string
args []string args []string
imageCount int imageCount int
imageInspectFunc func(img string) (image.InspectResponse, []byte, error) imageInspectFunc func(img string) (image.InspectResponse, error)
}{ }{
{ {
name: "simple", name: "simple",
args: []string{"image"}, args: []string{"image"},
imageCount: 1, imageCount: 1,
imageInspectFunc: func(img string) (image.InspectResponse, []byte, error) { imageInspectFunc: func(img string) (image.InspectResponse, error) {
imageInspectInvocationCount++ imageInspectInvocationCount++
assert.Check(t, is.Equal("image", img)) assert.Check(t, is.Equal("image", img))
return image.InspectResponse{}, nil, nil return image.InspectResponse{}, nil
}, },
}, },
{ {
name: "format", name: "format",
imageCount: 1, imageCount: 1,
args: []string{"--format='{{.ID}}'", "image"}, args: []string{"--format='{{.ID}}'", "image"},
imageInspectFunc: func(img string) (image.InspectResponse, []byte, error) { imageInspectFunc: func(img string) (image.InspectResponse, error) {
imageInspectInvocationCount++ imageInspectInvocationCount++
return image.InspectResponse{ID: img}, nil, nil return image.InspectResponse{ID: img}, nil
}, },
}, },
{ {
name: "simple-many", name: "simple-many",
args: []string{"image1", "image2"}, args: []string{"image1", "image2"},
imageCount: 2, imageCount: 2,
imageInspectFunc: func(img string) (image.InspectResponse, []byte, error) { imageInspectFunc: func(img string) (image.InspectResponse, error) {
imageInspectInvocationCount++ imageInspectInvocationCount++
if imageInspectInvocationCount == 1 { if imageInspectInvocationCount == 1 {
assert.Check(t, is.Equal("image1", img)) assert.Check(t, is.Equal("image1", img))
} else { } else {
assert.Check(t, is.Equal("image2", img)) assert.Check(t, is.Equal("image2", img))
} }
return image.InspectResponse{}, nil, nil return image.InspectResponse{}, nil
}, },
}, },
} }

View File

@ -4,6 +4,7 @@
package system package system
import ( import (
"bytes"
"context" "context"
"fmt" "fmt"
"strings" "strings"
@ -13,7 +14,9 @@ import (
"github.com/docker/cli/cli/command/inspect" "github.com/docker/cli/cli/command/inspect"
flagsHelper "github.com/docker/cli/cli/flags" flagsHelper "github.com/docker/cli/cli/flags"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/network" "github.com/docker/docker/api/types/network"
"github.com/docker/docker/client"
"github.com/docker/docker/errdefs" "github.com/docker/docker/errdefs"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -67,7 +70,12 @@ func inspectContainers(ctx context.Context, dockerCli command.Cli, getSize bool)
func inspectImages(ctx context.Context, dockerCli command.Cli) inspect.GetRefFunc { func inspectImages(ctx context.Context, dockerCli command.Cli) inspect.GetRefFunc {
return func(ref string) (any, []byte, error) { return func(ref string) (any, []byte, error) {
return dockerCli.Client().ImageInspectWithRaw(ctx, ref) var buf bytes.Buffer
resp, err := dockerCli.Client().ImageInspect(ctx, ref, client.ImageInspectWithRawResponse(&buf))
if err != nil {
return image.InspectResponse{}, nil, err
}
return resp, buf.Bytes(), err
} }
} }

View File

@ -32,8 +32,8 @@ func (c *fakeClient) Info(context.Context) (system.Info, error) {
return system.Info{}, nil return system.Info{}, nil
} }
func (c *fakeClient) ImageInspectWithRaw(context.Context, string) (image.InspectResponse, []byte, error) { func (c *fakeClient) ImageInspect(context.Context, string, ...client.ImageInspectOption) (image.InspectResponse, error) {
return image.InspectResponse{}, []byte{}, nil return image.InspectResponse{}, nil
} }
func (c *fakeClient) ImagePush(context.Context, string, image.PushOptions) (io.ReadCloser, error) { func (c *fakeClient) ImagePush(context.Context, string, image.PushOptions) (io.ReadCloser, error) {

View File

@ -140,7 +140,7 @@ func validateTag(imgRefAndAuth trust.ImageRefAndAuth) error {
} }
func checkLocalImageExistence(ctx context.Context, apiClient client.APIClient, imageName string) error { func checkLocalImageExistence(ctx context.Context, apiClient client.APIClient, imageName string) error {
_, _, err := apiClient.ImageInspectWithRaw(ctx, imageName) _, err := apiClient.ImageInspect(ctx, imageName)
return err return err
} }

View File

@ -28,7 +28,7 @@ func Services(
ctx context.Context, ctx context.Context,
namespace Namespace, namespace Namespace,
config *composetypes.Config, config *composetypes.Config,
apiClient client.CommonAPIClient, apiClient client.APIClient,
) (map[string]swarm.ServiceSpec, error) { ) (map[string]swarm.ServiceSpec, error) {
result := make(map[string]swarm.ServiceSpec) result := make(map[string]swarm.ServiceSpec)
for _, service := range config.Services { for _, service := range config.Services {

View File

@ -13,7 +13,7 @@ require (
github.com/distribution/reference v0.6.0 github.com/distribution/reference v0.6.0
github.com/docker/cli-docs-tool v0.9.0 github.com/docker/cli-docs-tool v0.9.0
github.com/docker/distribution v2.8.3+incompatible github.com/docker/distribution v2.8.3+incompatible
github.com/docker/docker v27.0.2-0.20250110234321-69687190936d+incompatible // master (v-next) github.com/docker/docker v27.0.2-0.20250206180949-6c3797923dcb+incompatible // master (v-next)
github.com/docker/docker-credential-helpers v0.8.2 github.com/docker/docker-credential-helpers v0.8.2
github.com/docker/go-connections v0.5.0 github.com/docker/go-connections v0.5.0
github.com/docker/go-units v0.5.0 github.com/docker/go-units v0.5.0

View File

@ -51,8 +51,8 @@ github.com/docker/cli-docs-tool v0.9.0/go.mod h1:ClrwlNW+UioiRyH9GiAOe1o3J/TsY3T
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v27.0.2-0.20250110234321-69687190936d+incompatible h1:JqToo31hcYA8YAJwnaF1aq7WUhFRcMNPnkKCUzGFGJM= github.com/docker/docker v27.0.2-0.20250206180949-6c3797923dcb+incompatible h1:R6uFQLKwpUW5K3rRtO6PZ5Bhx4CsuiBla2lV+bZQT+Y=
github.com/docker/docker v27.0.2-0.20250110234321-69687190936d+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v27.0.2-0.20250206180949-6c3797923dcb+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=
github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0= github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=

View File

@ -62,7 +62,7 @@ info:
Engine releases in the near future should support this version of the API, Engine releases in the near future should support this version of the API,
so your client will continue to work even if it is talking to a newer Engine. so your client will continue to work even if it is talking to a newer Engine.
The API uses an open schema model, which means server may add extra properties The API uses an open schema model, which means the server may add extra properties
to responses. Likewise, the server will ignore any extra query parameters and to responses. Likewise, the server will ignore any extra query parameters and
request body properties. When you write clients, you need to ignore additional request body properties. When you write clients, you need to ignore additional
properties in responses to ensure they do not break when talking to newer properties in responses to ensure they do not break when talking to newer
@ -212,6 +212,7 @@ definitions:
- `bind` a mount of a file or directory from the host into the container. - `bind` a mount of a file or directory from the host into the container.
- `volume` a docker volume with the given `Name`. - `volume` a docker volume with the given `Name`.
- `image` a docker image
- `tmpfs` a `tmpfs`. - `tmpfs` a `tmpfs`.
- `npipe` a named pipe from the host into the container. - `npipe` a named pipe from the host into the container.
- `cluster` a Swarm cluster volume - `cluster` a Swarm cluster volume
@ -219,6 +220,7 @@ definitions:
enum: enum:
- "bind" - "bind"
- "volume" - "volume"
- "image"
- "tmpfs" - "tmpfs"
- "npipe" - "npipe"
- "cluster" - "cluster"
@ -350,6 +352,7 @@ definitions:
- `bind` Mounts a file or directory from the host into the container. Must exist prior to creating the container. - `bind` Mounts a file or directory from the host into the container. Must exist prior to creating the container.
- `volume` Creates a volume with the given name and options (or uses a pre-existing volume with the same name and options). These are **not** removed when the container is removed. - `volume` Creates a volume with the given name and options (or uses a pre-existing volume with the same name and options). These are **not** removed when the container is removed.
- `image` Mounts an image.
- `tmpfs` Create a tmpfs with the given options. The mount source cannot be specified for tmpfs. - `tmpfs` Create a tmpfs with the given options. The mount source cannot be specified for tmpfs.
- `npipe` Mounts a named pipe from the host into the container. Must exist prior to creating the container. - `npipe` Mounts a named pipe from the host into the container. Must exist prior to creating the container.
- `cluster` a Swarm cluster volume - `cluster` a Swarm cluster volume
@ -357,6 +360,7 @@ definitions:
enum: enum:
- "bind" - "bind"
- "volume" - "volume"
- "image"
- "tmpfs" - "tmpfs"
- "npipe" - "npipe"
- "cluster" - "cluster"
@ -431,6 +435,14 @@ definitions:
description: "Source path inside the volume. Must be relative without any back traversals." description: "Source path inside the volume. Must be relative without any back traversals."
type: "string" type: "string"
example: "dir-inside-volume/subdirectory" example: "dir-inside-volume/subdirectory"
ImageOptions:
description: "Optional configuration for the `image` type."
type: "object"
properties:
Subpath:
description: "Source path inside the image. Must be relative without any back traversals."
type: "string"
example: "dir-inside-image/subdirectory"
TmpfsOptions: TmpfsOptions:
description: "Optional configuration for the `tmpfs` type." description: "Optional configuration for the `tmpfs` type."
type: "object" type: "object"
@ -2004,6 +2016,21 @@ definitions:
compatibility. compatibility.
x-nullable: true x-nullable: true
$ref: "#/definitions/OCIDescriptor" $ref: "#/definitions/OCIDescriptor"
Manifests:
description: |
Manifests is a list of image manifests available in this image. It
provides a more detailed view of the platform-specific image manifests or
other image-attached data like build attestations.
Only available if the daemon provides a multi-platform image store
and the `manifests` option is set in the inspect request.
WARNING: This is experimental and may change at any time without any backward
compatibility.
type: "array"
x-nullable: true
items:
$ref: "#/definitions/ImageManifestSummary"
RepoTags: RepoTags:
description: | description: |
List of image names/tags in the local image cache that reference this List of image names/tags in the local image cache that reference this
@ -5299,6 +5326,551 @@ definitions:
type: "string" type: "string"
example: [] example: []
ContainerStatsResponse:
description: |
Statistics sample for a container.
type: "object"
x-go-name: "StatsResponse"
title: "ContainerStatsResponse"
properties:
name:
description: "Name of the container"
type: "string"
x-nullable: true
example: "boring_wozniak"
id:
description: "ID of the container"
type: "string"
x-nullable: true
example: "ede54ee1afda366ab42f824e8a5ffd195155d853ceaec74a927f249ea270c743"
read:
description: |
Date and time at which this sample was collected.
The value is formatted as [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt)
with nano-seconds.
type: "string"
format: "date-time"
example: "2025-01-16T13:55:22.165243637Z"
preread:
description: |
Date and time at which this first sample was collected. This field
is not propagated if the "one-shot" option is set. If the "one-shot"
option is set, this field may be omitted, empty, or set to a default
date (`0001-01-01T00:00:00Z`).
The value is formatted as [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt)
with nano-seconds.
type: "string"
format: "date-time"
example: "2025-01-16T13:55:21.160452595Z"
pids_stats:
$ref: "#/definitions/ContainerPidsStats"
blkio_stats:
$ref: "#/definitions/ContainerBlkioStats"
num_procs:
description: |
The number of processors on the system.
This field is Windows-specific and always zero for Linux containers.
type: "integer"
format: "uint32"
example: 16
storage_stats:
$ref: "#/definitions/ContainerStorageStats"
cpu_stats:
$ref: "#/definitions/ContainerCPUStats"
precpu_stats:
$ref: "#/definitions/ContainerCPUStats"
memory_stats:
$ref: "#/definitions/ContainerMemoryStats"
networks:
description: |
Network statistics for the container per interface.
This field is omitted if the container has no networking enabled.
x-nullable: true
additionalProperties:
$ref: "#/definitions/ContainerNetworkStats"
example:
eth0:
rx_bytes: 5338
rx_dropped: 0
rx_errors: 0
rx_packets: 36
tx_bytes: 648
tx_dropped: 0
tx_errors: 0
tx_packets: 8
eth5:
rx_bytes: 4641
rx_dropped: 0
rx_errors: 0
rx_packets: 26
tx_bytes: 690
tx_dropped: 0
tx_errors: 0
tx_packets: 9
ContainerBlkioStats:
description: |
BlkioStats stores all IO service stats for data read and write.
This type is Linux-specific and holds many fields that are specific to cgroups v1.
On a cgroup v2 host, all fields other than `io_service_bytes_recursive`
are omitted or `null`.
This type is only populated on Linux and omitted for Windows containers.
type: "object"
x-go-name: "BlkioStats"
x-nullable: true
properties:
io_service_bytes_recursive:
type: "array"
items:
$ref: "#/definitions/ContainerBlkioStatEntry"
io_serviced_recursive:
description: |
This field is only available when using Linux containers with
cgroups v1. It is omitted or `null` when using cgroups v2.
x-nullable: true
type: "array"
items:
$ref: "#/definitions/ContainerBlkioStatEntry"
io_queue_recursive:
description: |
This field is only available when using Linux containers with
cgroups v1. It is omitted or `null` when using cgroups v2.
x-nullable: true
type: "array"
items:
$ref: "#/definitions/ContainerBlkioStatEntry"
io_service_time_recursive:
description: |
This field is only available when using Linux containers with
cgroups v1. It is omitted or `null` when using cgroups v2.
x-nullable: true
type: "array"
items:
$ref: "#/definitions/ContainerBlkioStatEntry"
io_wait_time_recursive:
description: |
This field is only available when using Linux containers with
cgroups v1. It is omitted or `null` when using cgroups v2.
x-nullable: true
type: "array"
items:
$ref: "#/definitions/ContainerBlkioStatEntry"
io_merged_recursive:
description: |
This field is only available when using Linux containers with
cgroups v1. It is omitted or `null` when using cgroups v2.
x-nullable: true
type: "array"
items:
$ref: "#/definitions/ContainerBlkioStatEntry"
io_time_recursive:
description: |
This field is only available when using Linux containers with
cgroups v1. It is omitted or `null` when using cgroups v2.
x-nullable: true
type: "array"
items:
$ref: "#/definitions/ContainerBlkioStatEntry"
sectors_recursive:
description: |
This field is only available when using Linux containers with
cgroups v1. It is omitted or `null` when using cgroups v2.
x-nullable: true
type: "array"
items:
$ref: "#/definitions/ContainerBlkioStatEntry"
example:
io_service_bytes_recursive: [
{"major": 254, "minor": 0, "op": "read", "value": 7593984},
{"major": 254, "minor": 0, "op": "write", "value": 100}
]
io_serviced_recursive: null
io_queue_recursive: null
io_service_time_recursive: null
io_wait_time_recursive: null
io_merged_recursive: null
io_time_recursive: null
sectors_recursive: null
ContainerBlkioStatEntry:
description: |
Blkio stats entry.
This type is Linux-specific and omitted for Windows containers.
type: "object"
x-go-name: "BlkioStatEntry"
x-nullable: true
properties:
major:
type: "integer"
format: "uint64"
example: 254
minor:
type: "integer"
format: "uint64"
example: 0
op:
type: "string"
example: "read"
value:
type: "integer"
format: "uint64"
example: 7593984
ContainerCPUStats:
description: |
CPU related info of the container
type: "object"
x-go-name: "CPUStats"
x-nullable: true
properties:
cpu_usage:
$ref: "#/definitions/ContainerCPUUsage"
system_cpu_usage:
description: |
System Usage.
This field is Linux-specific and omitted for Windows containers.
type: "integer"
format: "uint64"
x-nullable: true
example: 5
online_cpus:
description: |
Number of online CPUs.
This field is Linux-specific and omitted for Windows containers.
type: "integer"
format: "uint32"
x-nullable: true
example: 5
throttling_data:
$ref: "#/definitions/ContainerThrottlingData"
ContainerCPUUsage:
description: |
All CPU stats aggregated since container inception.
type: "object"
x-go-name: "CPUUsage"
x-nullable: true
properties:
total_usage:
description: |
Total CPU time consumed in nanoseconds (Linux) or 100's of nanoseconds (Windows).
type: "integer"
format: "uint64"
example: 29912000
percpu_usage:
description: |
Total CPU time (in nanoseconds) consumed per core (Linux).
This field is Linux-specific when using cgroups v1. It is omitted
when using cgroups v2 and Windows containers.
type: "array"
x-nullable: true
items:
type: "integer"
format: "uint64"
example: 29912000
usage_in_kernelmode:
description: |
Time (in nanoseconds) spent by tasks of the cgroup in kernel mode (Linux),
or time spent (in 100's of nanoseconds) by all container processes in
kernel mode (Windows).
Not populated for Windows containers using Hyper-V isolation.
type: "integer"
format: "uint64"
example: 21994000
usage_in_usermode:
description: |
Time (in nanoseconds) spent by tasks of the cgroup in user mode (Linux),
or time spent (in 100's of nanoseconds) by all container processes in
kernel mode (Windows).
Not populated for Windows containers using Hyper-V isolation.
type: "integer"
format: "uint64"
example: 7918000
ContainerPidsStats:
description: |
PidsStats contains Linux-specific stats of a container's process-IDs (PIDs).
This type is Linux-specific and omitted for Windows containers.
type: "object"
x-go-name: "PidsStats"
x-nullable: true
properties:
current:
description: |
Current is the number of PIDs in the cgroup.
type: "integer"
format: "uint64"
x-nullable: true
example: 5
limit:
description: |
Limit is the hard limit on the number of pids in the cgroup.
A "Limit" of 0 means that there is no limit.
type: "integer"
format: "uint64"
x-nullable: true
example: 18446744073709551615
ContainerThrottlingData:
description: |
CPU throttling stats of the container.
This type is Linux-specific and omitted for Windows containers.
type: "object"
x-go-name: "ThrottlingData"
x-nullable: true
properties:
periods:
description: |
Number of periods with throttling active.
type: "integer"
format: "uint64"
example: 0
throttled_periods:
description: |
Number of periods when the container hit its throttling limit.
type: "integer"
format: "uint64"
example: 0
throttled_time:
description: |
Aggregated time (in nanoseconds) the container was throttled for.
type: "integer"
format: "uint64"
example: 0
ContainerMemoryStats:
description: |
Aggregates all memory stats since container inception on Linux.
Windows returns stats for commit and private working set only.
type: "object"
x-go-name: "MemoryStats"
properties:
usage:
description: |
Current `res_counter` usage for memory.
This field is Linux-specific and omitted for Windows containers.
type: "integer"
format: "uint64"
x-nullable: true
example: 0
max_usage:
description: |
Maximum usage ever recorded.
This field is Linux-specific and only supported on cgroups v1.
It is omitted when using cgroups v2 and for Windows containers.
type: "integer"
format: "uint64"
x-nullable: true
example: 0
stats:
description: |
All the stats exported via memory.stat. when using cgroups v2.
This field is Linux-specific and omitted for Windows containers.
type: "object"
additionalProperties:
type: "integer"
format: "uint64"
x-nullable: true
example:
{
"active_anon": 1572864,
"active_file": 5115904,
"anon": 1572864,
"anon_thp": 0,
"file": 7626752,
"file_dirty": 0,
"file_mapped": 2723840,
"file_writeback": 0,
"inactive_anon": 0,
"inactive_file": 2510848,
"kernel_stack": 16384,
"pgactivate": 0,
"pgdeactivate": 0,
"pgfault": 2042,
"pglazyfree": 0,
"pglazyfreed": 0,
"pgmajfault": 45,
"pgrefill": 0,
"pgscan": 0,
"pgsteal": 0,
"shmem": 0,
"slab": 1180928,
"slab_reclaimable": 725576,
"slab_unreclaimable": 455352,
"sock": 0,
"thp_collapse_alloc": 0,
"thp_fault_alloc": 1,
"unevictable": 0,
"workingset_activate": 0,
"workingset_nodereclaim": 0,
"workingset_refault": 0
}
failcnt:
description: |
Number of times memory usage hits limits.
This field is Linux-specific and only supported on cgroups v1.
It is omitted when using cgroups v2 and for Windows containers.
type: "integer"
format: "uint64"
x-nullable: true
example: 0
limit:
description: |
This field is Linux-specific and omitted for Windows containers.
type: "integer"
format: "uint64"
x-nullable: true
example: 8217579520
commitbytes:
description: |
Committed bytes.
This field is Windows-specific and omitted for Linux containers.
type: "integer"
format: "uint64"
x-nullable: true
example: 0
commitpeakbytes:
description: |
Peak committed bytes.
This field is Windows-specific and omitted for Linux containers.
type: "integer"
format: "uint64"
x-nullable: true
example: 0
privateworkingset:
description: |
Private working set.
This field is Windows-specific and omitted for Linux containers.
type: "integer"
format: "uint64"
x-nullable: true
example: 0
ContainerNetworkStats:
description: |
Aggregates the network stats of one container
type: "object"
x-go-name: "NetworkStats"
x-nullable: true
properties:
rx_bytes:
description: |
Bytes received. Windows and Linux.
type: "integer"
format: "uint64"
example: 5338
rx_packets:
description: |
Packets received. Windows and Linux.
type: "integer"
format: "uint64"
example: 36
rx_errors:
description: |
Received errors. Not used on Windows.
This field is Linux-specific and always zero for Windows containers.
type: "integer"
format: "uint64"
example: 0
rx_dropped:
description: |
Incoming packets dropped. Windows and Linux.
type: "integer"
format: "uint64"
example: 0
tx_bytes:
description: |
Bytes sent. Windows and Linux.
type: "integer"
format: "uint64"
example: 1200
tx_packets:
description: |
Packets sent. Windows and Linux.
type: "integer"
format: "uint64"
example: 12
tx_errors:
description: |
Sent errors. Not used on Windows.
This field is Linux-specific and always zero for Windows containers.
type: "integer"
format: "uint64"
example: 0
tx_dropped:
description: |
Outgoing packets dropped. Windows and Linux.
type: "integer"
format: "uint64"
example: 0
endpoint_id:
description: |
Endpoint ID. Not used on Linux.
This field is Windows-specific and omitted for Linux containers.
type: "string"
x-nullable: true
instance_id:
description: |
Instance ID. Not used on Linux.
This field is Windows-specific and omitted for Linux containers.
type: "string"
x-nullable: true
ContainerStorageStats:
description: |
StorageStats is the disk I/O stats for read/write on Windows.
This type is Windows-specific and omitted for Linux containers.
type: "object"
x-go-name: "StorageStats"
x-nullable: true
properties:
read_count_normalized:
type: "integer"
format: "uint64"
x-nullable: true
example: 7593984
read_size_bytes:
type: "integer"
format: "uint64"
x-nullable: true
example: 7593984
write_count_normalized:
type: "integer"
format: "uint64"
x-nullable: true
example: 7593984
write_size_bytes:
type: "integer"
format: "uint64"
x-nullable: true
example: 7593984
ContainerWaitResponse: ContainerWaitResponse:
description: "OK response to ContainerWait operation" description: "OK response to ContainerWait operation"
type: "object" type: "object"
@ -7757,99 +8329,7 @@ paths:
200: 200:
description: "no error" description: "no error"
schema: schema:
type: "object" $ref: "#/definitions/ContainerStatsResponse"
examples:
application/json:
read: "2015-01-08T22:57:31.547920715Z"
pids_stats:
current: 3
networks:
eth0:
rx_bytes: 5338
rx_dropped: 0
rx_errors: 0
rx_packets: 36
tx_bytes: 648
tx_dropped: 0
tx_errors: 0
tx_packets: 8
eth5:
rx_bytes: 4641
rx_dropped: 0
rx_errors: 0
rx_packets: 26
tx_bytes: 690
tx_dropped: 0
tx_errors: 0
tx_packets: 9
memory_stats:
stats:
total_pgmajfault: 0
cache: 0
mapped_file: 0
total_inactive_file: 0
pgpgout: 414
rss: 6537216
total_mapped_file: 0
writeback: 0
unevictable: 0
pgpgin: 477
total_unevictable: 0
pgmajfault: 0
total_rss: 6537216
total_rss_huge: 6291456
total_writeback: 0
total_inactive_anon: 0
rss_huge: 6291456
hierarchical_memory_limit: 67108864
total_pgfault: 964
total_active_file: 0
active_anon: 6537216
total_active_anon: 6537216
total_pgpgout: 414
total_cache: 0
inactive_anon: 0
active_file: 0
pgfault: 964
inactive_file: 0
total_pgpgin: 477
max_usage: 6651904
usage: 6537216
failcnt: 0
limit: 67108864
blkio_stats: {}
cpu_stats:
cpu_usage:
percpu_usage:
- 8646879
- 24472255
- 36438778
- 30657443
usage_in_usermode: 50000000
total_usage: 100215355
usage_in_kernelmode: 30000000
system_cpu_usage: 739306590000000
online_cpus: 4
throttling_data:
periods: 0
throttled_periods: 0
throttled_time: 0
precpu_stats:
cpu_usage:
percpu_usage:
- 8646879
- 24350896
- 36438778
- 30657443
usage_in_usermode: 50000000
total_usage: 100093996
usage_in_kernelmode: 30000000
system_cpu_usage: 9492140000000
online_cpus: 4
throttling_data:
periods: 0
throttled_periods: 0
throttled_time: 0
404: 404:
description: "no such container" description: "no such container"
schema: schema:
@ -8994,10 +9474,29 @@ paths:
operationId: "BuildPrune" operationId: "BuildPrune"
parameters: parameters:
- name: "keep-storage" - name: "keep-storage"
in: "query"
description: |
Amount of disk space in bytes to keep for cache
> **Deprecated**: This parameter is deprecated and has been renamed to "reserved-space".
> It is kept for backward compatibility and will be removed in API v1.49.
type: "integer"
format: "int64"
- name: "reserved-space"
in: "query" in: "query"
description: "Amount of disk space in bytes to keep for cache" description: "Amount of disk space in bytes to keep for cache"
type: "integer" type: "integer"
format: "int64" format: "int64"
- name: "max-used-space"
in: "query"
description: "Maximum amount of disk space allowed to keep for cache"
type: "integer"
format: "int64"
- name: "min-free-space"
in: "query"
description: "Target amount of free disk space after pruning"
type: "integer"
format: "int64"
- name: "all" - name: "all"
in: "query" in: "query"
type: "boolean" type: "boolean"
@ -9064,7 +9563,13 @@ paths:
parameters: parameters:
- name: "fromImage" - name: "fromImage"
in: "query" in: "query"
description: "Name of the image to pull. The name may include a tag or digest. This parameter may only be used when pulling an image. The pull is cancelled if the HTTP connection is closed." description: |
Name of the image to pull. If the name includes a tag or digest, specific behavior applies:
- If only `fromImage` includes a tag, that tag is used.
- If both `fromImage` and `tag` are provided, `tag` takes precedence.
- If `fromImage` includes a digest, the image is pulled by digest, and `tag` is ignored.
- If neither a tag nor digest is specified, all tags are pulled.
type: "string" type: "string"
- name: "fromSrc" - name: "fromSrc"
in: "query" in: "query"
@ -9158,6 +9663,12 @@ paths:
description: "Image name or id" description: "Image name or id"
type: "string" type: "string"
required: true required: true
- name: "manifests"
in: "query"
description: "Include Manifests in the image summary."
type: "boolean"
default: false
required: false
tags: ["Image"] tags: ["Image"]
/images/{name}/history: /images/{name}/history:
get: get:
@ -10826,9 +11337,7 @@ paths:
description: "Optional custom IP scheme for the network." description: "Optional custom IP scheme for the network."
$ref: "#/definitions/IPAM" $ref: "#/definitions/IPAM"
EnableIPv4: EnableIPv4:
description: | description: "Enable IPv4 on the network."
Enable IPv4 on the network.
To disable IPv4, the daemon must be started with experimental features enabled.
type: "boolean" type: "boolean"
example: true example: true
EnableIPv6: EnableIPv6:

View File

@ -148,7 +148,15 @@ type PidsStats struct {
} }
// Stats is Ultimate struct aggregating all types of stats of one container // Stats is Ultimate struct aggregating all types of stats of one container
type Stats struct { //
// Deprecated: use [StatsResponse] instead. This type will be removed in the next release.
type Stats = StatsResponse
// StatsResponse aggregates all types of stats of one container.
type StatsResponse struct {
Name string `json:"name,omitempty"`
ID string `json:"id,omitempty"`
// Common stats // Common stats
Read time.Time `json:"read"` Read time.Time `json:"read"`
PreRead time.Time `json:"preread"` PreRead time.Time `json:"preread"`
@ -165,17 +173,5 @@ type Stats struct {
CPUStats CPUStats `json:"cpu_stats,omitempty"` CPUStats CPUStats `json:"cpu_stats,omitempty"`
PreCPUStats CPUStats `json:"precpu_stats,omitempty"` // "Pre"="Previous" PreCPUStats CPUStats `json:"precpu_stats,omitempty"` // "Pre"="Previous"
MemoryStats MemoryStats `json:"memory_stats,omitempty"` MemoryStats MemoryStats `json:"memory_stats,omitempty"`
}
// StatsResponse is newly used Networks.
//
// TODO(thaJeztah): unify with [Stats]. This wrapper was to account for pre-api v1.21 changes, see https://github.com/moby/moby/commit/d3379946ec96fb6163cb8c4517d7d5a067045801
type StatsResponse struct {
Stats
Name string `json:"name,omitempty"`
ID string `json:"id,omitempty"`
// Networks request version >=1.21
Networks map[string]NetworkStats `json:"networks,omitempty"` Networks map[string]NetworkStats `json:"networks,omitempty"`
} }

View File

@ -127,4 +127,14 @@ type InspectResponse struct {
// WARNING: This is experimental and may change at any time without any backward // WARNING: This is experimental and may change at any time without any backward
// compatibility. // compatibility.
Descriptor *ocispec.Descriptor `json:"Descriptor,omitempty"` Descriptor *ocispec.Descriptor `json:"Descriptor,omitempty"`
// Manifests is a list of image manifests available in this image. It
// provides a more detailed view of the platform-specific image manifests or
// other image-attached data like build attestations.
//
// Only available if the daemon provides a multi-platform image store.
//
// WARNING: This is experimental and may change at any time without any backward
// compatibility.
Manifests []ManifestSummary `json:"Manifests,omitempty"`
} }

View File

@ -103,6 +103,11 @@ type LoadOptions struct {
Platforms []ocispec.Platform Platforms []ocispec.Platform
} }
type InspectOptions struct {
// Manifests returns the image manifests.
Manifests bool
}
// SaveOptions holds parameters to save images. // SaveOptions holds parameters to save images.
type SaveOptions struct { type SaveOptions struct {
// Platforms selects the platforms to save if the image is a // Platforms selects the platforms to save if the image is a

View File

@ -19,6 +19,8 @@ const (
TypeNamedPipe Type = "npipe" TypeNamedPipe Type = "npipe"
// TypeCluster is the type for Swarm Cluster Volumes. // TypeCluster is the type for Swarm Cluster Volumes.
TypeCluster Type = "cluster" TypeCluster Type = "cluster"
// TypeImage is the type for mounting another image's filesystem
TypeImage Type = "image"
) )
// Mount represents a mount (volume). // Mount represents a mount (volume).
@ -34,6 +36,7 @@ type Mount struct {
BindOptions *BindOptions `json:",omitempty"` BindOptions *BindOptions `json:",omitempty"`
VolumeOptions *VolumeOptions `json:",omitempty"` VolumeOptions *VolumeOptions `json:",omitempty"`
ImageOptions *ImageOptions `json:",omitempty"`
TmpfsOptions *TmpfsOptions `json:",omitempty"` TmpfsOptions *TmpfsOptions `json:",omitempty"`
ClusterOptions *ClusterOptions `json:",omitempty"` ClusterOptions *ClusterOptions `json:",omitempty"`
} }
@ -100,6 +103,10 @@ type VolumeOptions struct {
DriverConfig *Driver `json:",omitempty"` DriverConfig *Driver `json:",omitempty"`
} }
type ImageOptions struct {
Subpath string `json:",omitempty"`
}
// Driver represents a volume driver. // Driver represents a volume driver.
type Driver struct { type Driver struct {
Name string `json:",omitempty"` Name string `json:",omitempty"`

View File

@ -3,10 +3,9 @@ import (
"context" "context"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"fmt"
"io" "io"
"strings" "strings"
"github.com/pkg/errors"
) )
// AuthHeader is the name of the header used to send encoded registry // AuthHeader is the name of the header used to send encoded registry
@ -98,7 +97,7 @@ func decodeAuthConfigFromReader(rdr io.Reader) (*AuthConfig, error) {
} }
func invalid(err error) error { func invalid(err error) error {
return errInvalidParameter{errors.Wrap(err, "invalid X-Registry-Auth header")} return errInvalidParameter{fmt.Errorf("invalid X-Registry-Auth header: %w", err)}
} }
type errInvalidParameter struct{ error } type errInvalidParameter struct{ error }

View File

@ -170,8 +170,10 @@ type BuildCache struct {
// BuildCachePruneOptions hold parameters to prune the build cache // BuildCachePruneOptions hold parameters to prune the build cache
type BuildCachePruneOptions struct { type BuildCachePruneOptions struct {
All bool All bool
KeepStorage int64 ReservedSpace int64
MaxUsedSpace int64
MinFreeSpace int64
Filters filters.Args Filters filters.Args
// FIXME(thaJeztah): add new options; see https://github.com/moby/moby/issues/48639 KeepStorage int64 // Deprecated: deprecated in API 1.48.
} }

View File

@ -21,7 +21,19 @@ func (cli *Client) BuildCachePrune(ctx context.Context, opts types.BuildCachePru
if opts.All { if opts.All {
query.Set("all", "1") query.Set("all", "1")
} }
if opts.KeepStorage != 0 {
query.Set("keep-storage", strconv.Itoa(int(opts.KeepStorage))) query.Set("keep-storage", strconv.Itoa(int(opts.KeepStorage)))
}
if opts.ReservedSpace != 0 {
query.Set("reserved-space", strconv.Itoa(int(opts.ReservedSpace)))
}
if opts.MaxUsedSpace != 0 {
query.Set("max-used-space", strconv.Itoa(int(opts.MaxUsedSpace)))
}
if opts.MinFreeSpace != 0 {
query.Set("min-free-space", strconv.Itoa(int(opts.MinFreeSpace)))
}
f, err := filters.ToJSON(opts.Filters) f, err := filters.ToJSON(opts.Filters)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "prune could not marshal filters option") return nil, errors.Wrap(err, "prune could not marshal filters option")

View File

@ -6,11 +6,11 @@ import (
"github.com/docker/docker/api/types/checkpoint" "github.com/docker/docker/api/types/checkpoint"
) )
type apiClientExperimental interface { // CheckpointAPIClient defines API client methods for the checkpoints.
CheckpointAPIClient //
} // Experimental: checkpoint and restore is still an experimental feature,
// and only available if the daemon is running with experimental features
// CheckpointAPIClient defines API client methods for the checkpoints // enabled.
type CheckpointAPIClient interface { type CheckpointAPIClient interface {
CheckpointCreate(ctx context.Context, container string, options checkpoint.CreateOptions) error CheckpointCreate(ctx context.Context, container string, options checkpoint.CreateOptions) error
CheckpointDelete(ctx context.Context, container string, options checkpoint.DeleteOptions) error CheckpointDelete(ctx context.Context, container string, options checkpoint.DeleteOptions) error

View File

@ -7,8 +7,13 @@ import (
) )
// CheckpointCreate creates a checkpoint from the given container with the given name // CheckpointCreate creates a checkpoint from the given container with the given name
func (cli *Client) CheckpointCreate(ctx context.Context, container string, options checkpoint.CreateOptions) error { func (cli *Client) CheckpointCreate(ctx context.Context, containerID string, options checkpoint.CreateOptions) error {
resp, err := cli.post(ctx, "/containers/"+container+"/checkpoints", nil, options, nil) containerID, err := trimID("container", containerID)
if err != nil {
return err
}
resp, err := cli.post(ctx, "/containers/"+containerID+"/checkpoints", nil, options, nil)
ensureReaderClosed(resp) ensureReaderClosed(resp)
return err return err
} }

View File

@ -9,6 +9,11 @@ import (
// CheckpointDelete deletes the checkpoint with the given name from the given container // CheckpointDelete deletes the checkpoint with the given name from the given container
func (cli *Client) CheckpointDelete(ctx context.Context, containerID string, options checkpoint.DeleteOptions) error { func (cli *Client) CheckpointDelete(ctx context.Context, containerID string, options checkpoint.DeleteOptions) error {
containerID, err := trimID("container", containerID)
if err != nil {
return err
}
query := url.Values{} query := url.Values{}
if options.CheckpointDir != "" { if options.CheckpointDir != "" {
query.Set("dir", options.CheckpointDir) query.Set("dir", options.CheckpointDir)

View File

@ -99,6 +99,9 @@ const DummyHost = "api.moby.localhost"
// recent version before negotiation was introduced. // recent version before negotiation was introduced.
const fallbackAPIVersion = "1.24" const fallbackAPIVersion = "1.24"
// Ensure that Client always implements APIClient.
var _ APIClient = &Client{}
// Client is the API client that performs all operations // Client is the API client that performs all operations
// against a docker server. // against a docker server.
type Client struct { type Client struct {
@ -304,8 +307,7 @@ func (cli *Client) getAPIPath(ctx context.Context, p string, query url.Values) s
var apiPath string var apiPath string
_ = cli.checkVersion(ctx) _ = cli.checkVersion(ctx)
if cli.version != "" { if cli.version != "" {
v := strings.TrimPrefix(cli.version, "v") apiPath = path.Join(cli.basePath, "/v"+strings.TrimPrefix(cli.version, "v"), p)
apiPath = path.Join(cli.basePath, "/v"+v, p)
} else { } else {
apiPath = path.Join(cli.basePath, p) apiPath = path.Join(cli.basePath, p)
} }
@ -450,6 +452,10 @@ func (cli *Client) dialerFromTransport() func(context.Context, string, string) (
// //
// ["docker dial-stdio"]: https://github.com/docker/cli/pull/1014 // ["docker dial-stdio"]: https://github.com/docker/cli/pull/1014
func (cli *Client) Dialer() func(context.Context) (net.Conn, error) { func (cli *Client) Dialer() func(context.Context) (net.Conn, error) {
return cli.dialer()
}
func (cli *Client) dialer() func(context.Context) (net.Conn, error) {
return func(ctx context.Context) (net.Conn, error) { return func(ctx context.Context) (net.Conn, error) {
if dialFn := cli.dialerFromTransport(); dialFn != nil { if dialFn := cli.dialerFromTransport(); dialFn != nil {
return dialFn(ctx, cli.proto, cli.addr) return dialFn(ctx, cli.proto, cli.addr)

View File

@ -20,17 +20,23 @@ import (
) )
// CommonAPIClient is the common methods between stable and experimental versions of APIClient. // CommonAPIClient is the common methods between stable and experimental versions of APIClient.
type CommonAPIClient interface { //
// Deprecated: use [APIClient] instead. This type will be an alias for [APIClient] in the next release, and removed after.
type CommonAPIClient = stableAPIClient
// APIClient is an interface that clients that talk with a docker server must implement.
type APIClient interface {
stableAPIClient
CheckpointAPIClient // CheckpointAPIClient is still experimental.
}
type stableAPIClient interface {
ConfigAPIClient ConfigAPIClient
ContainerAPIClient ContainerAPIClient
DistributionAPIClient DistributionAPIClient
ImageAPIClient ImageAPIClient
NodeAPIClient
NetworkAPIClient NetworkAPIClient
PluginAPIClient PluginAPIClient
ServiceAPIClient
SwarmAPIClient
SecretAPIClient
SystemAPIClient SystemAPIClient
VolumeAPIClient VolumeAPIClient
ClientVersion() string ClientVersion() string
@ -39,9 +45,25 @@ type CommonAPIClient interface {
ServerVersion(ctx context.Context) (types.Version, error) ServerVersion(ctx context.Context) (types.Version, error)
NegotiateAPIVersion(ctx context.Context) NegotiateAPIVersion(ctx context.Context)
NegotiateAPIVersionPing(types.Ping) NegotiateAPIVersionPing(types.Ping)
DialHijack(ctx context.Context, url, proto string, meta map[string][]string) (net.Conn, error) HijackDialer
Dialer() func(context.Context) (net.Conn, error) Dialer() func(context.Context) (net.Conn, error)
Close() error Close() error
SwarmManagementAPIClient
}
// SwarmManagementAPIClient defines all methods for managing Swarm-specific
// objects.
type SwarmManagementAPIClient interface {
SwarmAPIClient
NodeAPIClient
ServiceAPIClient
SecretAPIClient
ConfigAPIClient
}
// HijackDialer defines methods for a hijack dialer.
type HijackDialer interface {
DialHijack(ctx context.Context, url, proto string, meta map[string][]string) (net.Conn, error)
} }
// ContainerAPIClient defines API client methods for the containers // ContainerAPIClient defines API client methods for the containers
@ -93,7 +115,10 @@ type ImageAPIClient interface {
ImageCreate(ctx context.Context, parentReference string, options image.CreateOptions) (io.ReadCloser, error) ImageCreate(ctx context.Context, parentReference string, options image.CreateOptions) (io.ReadCloser, error)
ImageHistory(ctx context.Context, image string, opts image.HistoryOptions) ([]image.HistoryResponseItem, error) ImageHistory(ctx context.Context, image string, opts image.HistoryOptions) ([]image.HistoryResponseItem, error)
ImageImport(ctx context.Context, source image.ImportSource, ref string, options image.ImportOptions) (io.ReadCloser, error) ImageImport(ctx context.Context, source image.ImportSource, ref string, options image.ImportOptions) (io.ReadCloser, error)
// Deprecated: Use [Client.ImageInspect] instead.
// Raw response can be obtained by [ImageInspectWithRawResponse] option.
ImageInspectWithRaw(ctx context.Context, image string) (image.InspectResponse, []byte, error) ImageInspectWithRaw(ctx context.Context, image string) (image.InspectResponse, []byte, error)
ImageInspect(ctx context.Context, image string, _ ...ImageInspectOption) (image.InspectResponse, error)
ImageList(ctx context.Context, options image.ListOptions) ([]image.Summary, error) ImageList(ctx context.Context, options image.ListOptions) ([]image.Summary, error)
ImageLoad(ctx context.Context, input io.Reader, opts image.LoadOptions) (image.LoadResponse, error) ImageLoad(ctx context.Context, input io.Reader, opts image.LoadOptions) (image.LoadResponse, error)
ImagePull(ctx context.Context, ref string, options image.PullOptions) (io.ReadCloser, error) ImagePull(ctx context.Context, ref string, options image.PullOptions) (io.ReadCloser, error)

View File

@ -11,8 +11,9 @@ import (
// ConfigInspectWithRaw returns the config information with raw data // ConfigInspectWithRaw returns the config information with raw data
func (cli *Client) ConfigInspectWithRaw(ctx context.Context, id string) (swarm.Config, []byte, error) { func (cli *Client) ConfigInspectWithRaw(ctx context.Context, id string) (swarm.Config, []byte, error) {
if id == "" { id, err := trimID("contig", id)
return swarm.Config{}, nil, objectNotFoundError{object: "config", id: id} if err != nil {
return swarm.Config{}, nil, err
} }
if err := cli.NewVersionError(ctx, "1.30", "config inspect"); err != nil { if err := cli.NewVersionError(ctx, "1.30", "config inspect"); err != nil {
return swarm.Config{}, nil, err return swarm.Config{}, nil, err

View File

@ -4,6 +4,10 @@ import "context"
// ConfigRemove removes a config. // ConfigRemove removes a config.
func (cli *Client) ConfigRemove(ctx context.Context, id string) error { func (cli *Client) ConfigRemove(ctx context.Context, id string) error {
id, err := trimID("config", id)
if err != nil {
return err
}
if err := cli.NewVersionError(ctx, "1.30", "config remove"); err != nil { if err := cli.NewVersionError(ctx, "1.30", "config remove"); err != nil {
return err return err
} }

View File

@ -9,6 +9,10 @@ import (
// ConfigUpdate attempts to update a config // ConfigUpdate attempts to update a config
func (cli *Client) ConfigUpdate(ctx context.Context, id string, version swarm.Version, config swarm.ConfigSpec) error { func (cli *Client) ConfigUpdate(ctx context.Context, id string, version swarm.Version, config swarm.ConfigSpec) error {
id, err := trimID("config", id)
if err != nil {
return err
}
if err := cli.NewVersionError(ctx, "1.30", "config update"); err != nil { if err := cli.NewVersionError(ctx, "1.30", "config update"); err != nil {
return err return err
} }

View File

@ -33,7 +33,12 @@ import (
// //
// You can use github.com/docker/docker/pkg/stdcopy.StdCopy to demultiplex this // You can use github.com/docker/docker/pkg/stdcopy.StdCopy to demultiplex this
// stream. // stream.
func (cli *Client) ContainerAttach(ctx context.Context, container string, options container.AttachOptions) (types.HijackedResponse, error) { func (cli *Client) ContainerAttach(ctx context.Context, containerID string, options container.AttachOptions) (types.HijackedResponse, error) {
containerID, err := trimID("container", containerID)
if err != nil {
return types.HijackedResponse{}, err
}
query := url.Values{} query := url.Values{}
if options.Stream { if options.Stream {
query.Set("stream", "1") query.Set("stream", "1")
@ -54,7 +59,7 @@ func (cli *Client) ContainerAttach(ctx context.Context, container string, option
query.Set("logs", "1") query.Set("logs", "1")
} }
return cli.postHijacked(ctx, "/containers/"+container+"/attach", query, nil, http.Header{ return cli.postHijacked(ctx, "/containers/"+containerID+"/attach", query, nil, http.Header{
"Content-Type": {"text/plain"}, "Content-Type": {"text/plain"},
}) })
} }

View File

@ -12,7 +12,12 @@ import (
) )
// ContainerCommit applies changes to a container and creates a new tagged image. // ContainerCommit applies changes to a container and creates a new tagged image.
func (cli *Client) ContainerCommit(ctx context.Context, container string, options container.CommitOptions) (types.IDResponse, error) { func (cli *Client) ContainerCommit(ctx context.Context, containerID string, options container.CommitOptions) (types.IDResponse, error) {
containerID, err := trimID("container", containerID)
if err != nil {
return types.IDResponse{}, err
}
var repository, tag string var repository, tag string
if options.Reference != "" { if options.Reference != "" {
ref, err := reference.ParseNormalizedNamed(options.Reference) ref, err := reference.ParseNormalizedNamed(options.Reference)
@ -32,7 +37,7 @@ func (cli *Client) ContainerCommit(ctx context.Context, container string, option
} }
query := url.Values{} query := url.Values{}
query.Set("container", container) query.Set("container", containerID)
query.Set("repo", repository) query.Set("repo", repository)
query.Set("tag", tag) query.Set("tag", tag)
query.Set("comment", options.Comment) query.Set("comment", options.Comment)

View File

@ -16,11 +16,15 @@ import (
// ContainerStatPath returns stat information about a path inside the container filesystem. // 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, path string) (container.PathStat, error) {
containerID, err := trimID("container", containerID)
if err != nil {
return container.PathStat{}, err
}
query := url.Values{} query := url.Values{}
query.Set("path", filepath.ToSlash(path)) // Normalize the paths used in the API. query.Set("path", filepath.ToSlash(path)) // Normalize the paths used in the API.
urlStr := "/containers/" + containerID + "/archive" response, err := cli.head(ctx, "/containers/"+containerID+"/archive", query, nil)
response, err := cli.head(ctx, urlStr, query, nil)
defer ensureReaderClosed(response) defer ensureReaderClosed(response)
if err != nil { if err != nil {
return container.PathStat{}, err return container.PathStat{}, err
@ -31,6 +35,11 @@ func (cli *Client) ContainerStatPath(ctx context.Context, containerID, path stri
// CopyToContainer copies content into the container filesystem. // CopyToContainer copies content into the container filesystem.
// Note that `content` must be a Reader for a TAR archive // 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 container.CopyToContainerOptions) error { func (cli *Client) CopyToContainer(ctx context.Context, containerID, dstPath string, content io.Reader, options container.CopyToContainerOptions) error {
containerID, err := trimID("container", containerID)
if err != nil {
return err
}
query := url.Values{} query := url.Values{}
query.Set("path", filepath.ToSlash(dstPath)) // Normalize the paths used in the API. query.Set("path", filepath.ToSlash(dstPath)) // Normalize the paths used in the API.
// Do not allow for an existing directory to be overwritten by a non-directory and vice versa. // Do not allow for an existing directory to be overwritten by a non-directory and vice versa.
@ -42,9 +51,7 @@ func (cli *Client) CopyToContainer(ctx context.Context, containerID, dstPath str
query.Set("copyUIDGID", "true") query.Set("copyUIDGID", "true")
} }
apiPath := "/containers/" + containerID + "/archive" response, err := cli.putRaw(ctx, "/containers/"+containerID+"/archive", query, content, nil)
response, err := cli.putRaw(ctx, apiPath, query, content, nil)
defer ensureReaderClosed(response) defer ensureReaderClosed(response)
if err != nil { if err != nil {
return err return err
@ -56,11 +63,15 @@ func (cli *Client) CopyToContainer(ctx context.Context, containerID, dstPath str
// CopyFromContainer gets the content from the container and returns it as a Reader // 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. // 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, srcPath string) (io.ReadCloser, container.PathStat, error) {
containerID, err := trimID("container", containerID)
if err != nil {
return nil, container.PathStat{}, err
}
query := make(url.Values, 1) query := make(url.Values, 1)
query.Set("path", filepath.ToSlash(srcPath)) // Normalize the paths used in the API. query.Set("path", filepath.ToSlash(srcPath)) // Normalize the paths used in the API.
apiPath := "/containers/" + containerID + "/archive" response, err := cli.get(ctx, "/containers/"+containerID+"/archive", query, nil)
response, err := cli.get(ctx, apiPath, query, nil)
if err != nil { if err != nil {
return nil, container.PathStat{}, err return nil, container.PathStat{}, err
} }

View File

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

View File

@ -11,8 +11,11 @@ import (
) )
// ContainerExecCreate creates a new exec configuration to run an exec process. // ContainerExecCreate creates a new exec configuration to run an exec process.
func (cli *Client) ContainerExecCreate(ctx context.Context, container string, options container.ExecOptions) (types.IDResponse, error) { func (cli *Client) ContainerExecCreate(ctx context.Context, containerID string, options container.ExecOptions) (types.IDResponse, error) {
var response types.IDResponse containerID, err := trimID("container", containerID)
if err != nil {
return types.IDResponse{}, err
}
// Make sure we negotiated (if the client is configured to do so), // Make sure we negotiated (if the client is configured to do so),
// as code below contains API-version specific handling of options. // as code below contains API-version specific handling of options.
@ -20,21 +23,23 @@ func (cli *Client) ContainerExecCreate(ctx context.Context, container string, op
// Normally, version-negotiation (if enabled) would not happen until // Normally, version-negotiation (if enabled) would not happen until
// the API request is made. // the API request is made.
if err := cli.checkVersion(ctx); err != nil { if err := cli.checkVersion(ctx); err != nil {
return response, err return types.IDResponse{}, err
} }
if err := cli.NewVersionError(ctx, "1.25", "env"); len(options.Env) != 0 && err != nil { if err := cli.NewVersionError(ctx, "1.25", "env"); len(options.Env) != 0 && err != nil {
return response, err return types.IDResponse{}, err
} }
if versions.LessThan(cli.ClientVersion(), "1.42") { if versions.LessThan(cli.ClientVersion(), "1.42") {
options.ConsoleSize = nil options.ConsoleSize = nil
} }
resp, err := cli.post(ctx, "/containers/"+container+"/exec", nil, options, nil) resp, err := cli.post(ctx, "/containers/"+containerID+"/exec", nil, options, nil)
defer ensureReaderClosed(resp) defer ensureReaderClosed(resp)
if err != nil { if err != nil {
return response, err return types.IDResponse{}, err
} }
var response types.IDResponse
err = json.NewDecoder(resp.body).Decode(&response) err = json.NewDecoder(resp.body).Decode(&response)
return response, err return response, err
} }

View File

@ -10,6 +10,11 @@ import (
// and returns them as an io.ReadCloser. It's up to the caller // and returns them as an io.ReadCloser. It's up to the caller
// to close the stream. // 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) (io.ReadCloser, error) {
containerID, err := trimID("container", containerID)
if err != nil {
return nil, err
}
serverResp, err := cli.get(ctx, "/containers/"+containerID+"/export", url.Values{}, nil) serverResp, err := cli.get(ctx, "/containers/"+containerID+"/export", url.Values{}, nil)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -12,9 +12,11 @@ import (
// ContainerInspect returns the container information. // ContainerInspect returns the container information.
func (cli *Client) ContainerInspect(ctx context.Context, containerID string) (container.InspectResponse, error) { func (cli *Client) ContainerInspect(ctx context.Context, containerID string) (container.InspectResponse, error) {
if containerID == "" { containerID, err := trimID("container", containerID)
return container.InspectResponse{}, objectNotFoundError{object: "container", id: containerID} if err != nil {
return container.InspectResponse{}, err
} }
serverResp, err := cli.get(ctx, "/containers/"+containerID+"/json", nil, nil) serverResp, err := cli.get(ctx, "/containers/"+containerID+"/json", nil, nil)
defer ensureReaderClosed(serverResp) defer ensureReaderClosed(serverResp)
if err != nil { if err != nil {
@ -28,9 +30,11 @@ func (cli *Client) ContainerInspect(ctx context.Context, containerID string) (co
// ContainerInspectWithRaw returns the container information and its raw representation. // ContainerInspectWithRaw returns the container information and its raw representation.
func (cli *Client) ContainerInspectWithRaw(ctx context.Context, containerID string, getSize bool) (container.InspectResponse, []byte, error) { func (cli *Client) ContainerInspectWithRaw(ctx context.Context, containerID string, getSize bool) (container.InspectResponse, []byte, error) {
if containerID == "" { containerID, err := trimID("container", containerID)
return container.InspectResponse{}, nil, objectNotFoundError{object: "container", id: containerID} if err != nil {
return container.InspectResponse{}, nil, err
} }
query := url.Values{} query := url.Values{}
if getSize { if getSize {
query.Set("size", "1") query.Set("size", "1")

View File

@ -7,6 +7,11 @@ import (
// ContainerKill terminates the container process but does not remove the container from the docker host. // ContainerKill terminates the container process but does not remove the container from the docker host.
func (cli *Client) ContainerKill(ctx context.Context, containerID, signal string) error { func (cli *Client) ContainerKill(ctx context.Context, containerID, signal string) error {
containerID, err := trimID("container", containerID)
if err != nil {
return err
}
query := url.Values{} query := url.Values{}
if signal != "" { if signal != "" {
query.Set("signal", signal) query.Set("signal", signal)

View File

@ -33,7 +33,12 @@ import (
// //
// You can use github.com/docker/docker/pkg/stdcopy.StdCopy to demultiplex this // You can use github.com/docker/docker/pkg/stdcopy.StdCopy to demultiplex this
// stream. // stream.
func (cli *Client) ContainerLogs(ctx context.Context, container string, options container.LogsOptions) (io.ReadCloser, error) { func (cli *Client) ContainerLogs(ctx context.Context, containerID string, options container.LogsOptions) (io.ReadCloser, error) {
containerID, err := trimID("container", containerID)
if err != nil {
return nil, err
}
query := url.Values{} query := url.Values{}
if options.ShowStdout { if options.ShowStdout {
query.Set("stdout", "1") query.Set("stdout", "1")
@ -72,7 +77,7 @@ func (cli *Client) ContainerLogs(ctx context.Context, container string, options
} }
query.Set("tail", options.Tail) query.Set("tail", options.Tail)
resp, err := cli.get(ctx, "/containers/"+container+"/logs", query, nil) resp, err := cli.get(ctx, "/containers/"+containerID+"/logs", query, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -4,6 +4,11 @@ import "context"
// ContainerPause pauses the main process of a given container without terminating it. // ContainerPause pauses the main process of a given container without terminating it.
func (cli *Client) ContainerPause(ctx context.Context, containerID string) error { func (cli *Client) ContainerPause(ctx context.Context, containerID string) error {
containerID, err := trimID("container", containerID)
if err != nil {
return err
}
resp, err := cli.post(ctx, "/containers/"+containerID+"/pause", nil, nil, nil) resp, err := cli.post(ctx, "/containers/"+containerID+"/pause", nil, nil, nil)
ensureReaderClosed(resp) ensureReaderClosed(resp)
return err return err

View File

@ -9,6 +9,11 @@ import (
// ContainerRemove kills and removes a container from the docker host. // ContainerRemove kills and removes a container from the docker host.
func (cli *Client) ContainerRemove(ctx context.Context, containerID string, options container.RemoveOptions) error { func (cli *Client) ContainerRemove(ctx context.Context, containerID string, options container.RemoveOptions) error {
containerID, err := trimID("container", containerID)
if err != nil {
return err
}
query := url.Values{} query := url.Values{}
if options.RemoveVolumes { if options.RemoveVolumes {
query.Set("v", "1") query.Set("v", "1")

View File

@ -7,6 +7,11 @@ import (
// ContainerRename changes the name of a given container. // 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, newContainerName string) error {
containerID, err := trimID("container", containerID)
if err != nil {
return err
}
query := url.Values{} query := url.Values{}
query.Set("name", newContainerName) query.Set("name", newContainerName)
resp, err := cli.post(ctx, "/containers/"+containerID+"/rename", query, nil, nil) resp, err := cli.post(ctx, "/containers/"+containerID+"/rename", query, nil, nil)

View File

@ -10,11 +10,19 @@ import (
// ContainerResize changes the size of the tty for a container. // ContainerResize changes the size of the tty for a container.
func (cli *Client) ContainerResize(ctx context.Context, containerID string, options container.ResizeOptions) error { func (cli *Client) ContainerResize(ctx context.Context, containerID string, options container.ResizeOptions) error {
containerID, err := trimID("container", containerID)
if err != nil {
return err
}
return cli.resize(ctx, "/containers/"+containerID, options.Height, options.Width) return cli.resize(ctx, "/containers/"+containerID, options.Height, options.Width)
} }
// ContainerExecResize changes the size of the tty for an exec process running inside a container. // ContainerExecResize changes the size of the tty for an exec process running inside a container.
func (cli *Client) ContainerExecResize(ctx context.Context, execID string, options container.ResizeOptions) error { func (cli *Client) ContainerExecResize(ctx context.Context, execID string, options container.ResizeOptions) error {
execID, err := trimID("exec", execID)
if err != nil {
return err
}
return cli.resize(ctx, "/exec/"+execID, options.Height, options.Width) return cli.resize(ctx, "/exec/"+execID, options.Height, options.Width)
} }

View File

@ -13,6 +13,11 @@ import (
// It makes the daemon wait for the container to be up again for // It makes the daemon wait for the container to be up again for
// a specific amount of time, given the timeout. // a specific amount of time, given the timeout.
func (cli *Client) ContainerRestart(ctx context.Context, containerID string, options container.StopOptions) error { func (cli *Client) ContainerRestart(ctx context.Context, containerID string, options container.StopOptions) error {
containerID, err := trimID("container", containerID)
if err != nil {
return err
}
query := url.Values{} query := url.Values{}
if options.Timeout != nil { if options.Timeout != nil {
query.Set("t", strconv.Itoa(*options.Timeout)) query.Set("t", strconv.Itoa(*options.Timeout))

View File

@ -9,6 +9,11 @@ import (
// ContainerStart sends a request to the docker daemon to start a container. // ContainerStart sends a request to the docker daemon to start a container.
func (cli *Client) ContainerStart(ctx context.Context, containerID string, options container.StartOptions) error { func (cli *Client) ContainerStart(ctx context.Context, containerID string, options container.StartOptions) error {
containerID, err := trimID("container", containerID)
if err != nil {
return err
}
query := url.Values{} query := url.Values{}
if len(options.CheckpointID) != 0 { if len(options.CheckpointID) != 0 {
query.Set("checkpoint", options.CheckpointID) query.Set("checkpoint", options.CheckpointID)

View File

@ -10,6 +10,11 @@ import (
// ContainerStats returns near realtime stats for a given container. // ContainerStats returns near realtime stats for a given container.
// It's up to the caller to close the io.ReadCloser returned. // It's up to the caller to close the io.ReadCloser returned.
func (cli *Client) ContainerStats(ctx context.Context, containerID string, stream bool) (container.StatsResponseReader, error) { func (cli *Client) ContainerStats(ctx context.Context, containerID string, stream bool) (container.StatsResponseReader, error) {
containerID, err := trimID("container", containerID)
if err != nil {
return container.StatsResponseReader{}, err
}
query := url.Values{} query := url.Values{}
query.Set("stream", "0") query.Set("stream", "0")
if stream { if stream {
@ -30,6 +35,11 @@ func (cli *Client) ContainerStats(ctx context.Context, containerID string, strea
// ContainerStatsOneShot gets a single stat entry from a container. // ContainerStatsOneShot gets a single stat entry from a container.
// It differs from `ContainerStats` in that the API should not wait to prime the stats // It differs from `ContainerStats` in that the API should not wait to prime the stats
func (cli *Client) ContainerStatsOneShot(ctx context.Context, containerID string) (container.StatsResponseReader, error) { func (cli *Client) ContainerStatsOneShot(ctx context.Context, containerID string) (container.StatsResponseReader, error) {
containerID, err := trimID("container", containerID)
if err != nil {
return container.StatsResponseReader{}, err
}
query := url.Values{} query := url.Values{}
query.Set("stream", "0") query.Set("stream", "0")
query.Set("one-shot", "1") query.Set("one-shot", "1")

View File

@ -17,6 +17,11 @@ import (
// otherwise the engine default. A negative timeout value can be specified, // otherwise the engine default. A negative timeout value can be specified,
// meaning no timeout, i.e. no forceful termination is performed. // meaning no timeout, i.e. no forceful termination is performed.
func (cli *Client) ContainerStop(ctx context.Context, containerID string, options container.StopOptions) error { func (cli *Client) ContainerStop(ctx context.Context, containerID string, options container.StopOptions) error {
containerID, err := trimID("container", containerID)
if err != nil {
return err
}
query := url.Values{} query := url.Values{}
if options.Timeout != nil { if options.Timeout != nil {
query.Set("t", strconv.Itoa(*options.Timeout)) query.Set("t", strconv.Itoa(*options.Timeout))

View File

@ -11,7 +11,11 @@ import (
// ContainerTop shows process information from within a container. // ContainerTop shows process information from within a container.
func (cli *Client) ContainerTop(ctx context.Context, containerID string, arguments []string) (container.ContainerTopOKBody, error) { func (cli *Client) ContainerTop(ctx context.Context, containerID string, arguments []string) (container.ContainerTopOKBody, error) {
var response container.ContainerTopOKBody containerID, err := trimID("container", containerID)
if err != nil {
return container.ContainerTopOKBody{}, err
}
query := url.Values{} query := url.Values{}
if len(arguments) > 0 { if len(arguments) > 0 {
query.Set("ps_args", strings.Join(arguments, " ")) query.Set("ps_args", strings.Join(arguments, " "))
@ -20,9 +24,10 @@ func (cli *Client) ContainerTop(ctx context.Context, containerID string, argumen
resp, err := cli.get(ctx, "/containers/"+containerID+"/top", query, nil) resp, err := cli.get(ctx, "/containers/"+containerID+"/top", query, nil)
defer ensureReaderClosed(resp) defer ensureReaderClosed(resp)
if err != nil { if err != nil {
return response, err return container.ContainerTopOKBody{}, err
} }
var response container.ContainerTopOKBody
err = json.NewDecoder(resp.body).Decode(&response) err = json.NewDecoder(resp.body).Decode(&response)
return response, err return response, err
} }

View File

@ -4,6 +4,11 @@ import "context"
// ContainerUnpause resumes the process execution within a container // ContainerUnpause resumes the process execution within a container
func (cli *Client) ContainerUnpause(ctx context.Context, containerID string) error { func (cli *Client) ContainerUnpause(ctx context.Context, containerID string) error {
containerID, err := trimID("container", containerID)
if err != nil {
return err
}
resp, err := cli.post(ctx, "/containers/"+containerID+"/unpause", nil, nil, nil) resp, err := cli.post(ctx, "/containers/"+containerID+"/unpause", nil, nil, nil)
ensureReaderClosed(resp) ensureReaderClosed(resp)
return err return err

View File

@ -9,13 +9,18 @@ import (
// ContainerUpdate updates the resources of a container. // ContainerUpdate updates the resources of a container.
func (cli *Client) ContainerUpdate(ctx context.Context, containerID string, updateConfig container.UpdateConfig) (container.ContainerUpdateOKBody, error) { func (cli *Client) ContainerUpdate(ctx context.Context, containerID string, updateConfig container.UpdateConfig) (container.ContainerUpdateOKBody, error) {
var response container.ContainerUpdateOKBody containerID, err := trimID("container", containerID)
if err != nil {
return container.ContainerUpdateOKBody{}, err
}
serverResp, err := cli.post(ctx, "/containers/"+containerID+"/update", nil, updateConfig, nil) serverResp, err := cli.post(ctx, "/containers/"+containerID+"/update", nil, updateConfig, nil)
defer ensureReaderClosed(serverResp) defer ensureReaderClosed(serverResp)
if err != nil { if err != nil {
return response, err return container.ContainerUpdateOKBody{}, err
} }
var response container.ContainerUpdateOKBody
err = json.NewDecoder(serverResp.body).Decode(&response) err = json.NewDecoder(serverResp.body).Decode(&response)
return response, err return response, err
} }

View File

@ -33,6 +33,12 @@ func (cli *Client) ContainerWait(ctx context.Context, containerID string, condit
resultC := make(chan container.WaitResponse) resultC := make(chan container.WaitResponse)
errC := make(chan error, 1) errC := make(chan error, 1)
containerID, err := trimID("container", containerID)
if err != nil {
errC <- err
return resultC, errC
}
// Make sure we negotiated (if the client is configured to do so), // Make sure we negotiated (if the client is configured to do so),
// as code below contains API-version specific handling of options. // as code below contains API-version specific handling of options.
// //

View File

@ -2,11 +2,11 @@ package client // import "github.com/docker/docker/client"
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"github.com/docker/docker/api/types/versions" "github.com/docker/docker/api/types/versions"
"github.com/docker/docker/errdefs" "github.com/docker/docker/errdefs"
"github.com/pkg/errors"
) )
// errConnectionFailed implements an error returned when connection failed. // errConnectionFailed implements an error returned when connection failed.
@ -29,10 +29,18 @@ func IsErrConnectionFailed(err error) bool {
} }
// ErrorConnectionFailed returns an error with host in the error message when connection to docker daemon failed. // ErrorConnectionFailed returns an error with host in the error message when connection to docker daemon failed.
//
// Deprecated: this function was only used internally, and will be removed in the next release.
func ErrorConnectionFailed(host string) error { func ErrorConnectionFailed(host string) error {
return connectionFailed(host)
}
// connectionFailed returns an error with host in the error message when connection
// to docker daemon failed.
func connectionFailed(host string) error {
var err error var err error
if host == "" { if host == "" {
err = fmt.Errorf("Cannot connect to the Docker daemon. Is the docker daemon running on this host?") err = errors.New("Cannot connect to the Docker daemon. Is the docker daemon running on this host?")
} else { } else {
err = fmt.Errorf("Cannot connect to the Docker daemon at %s. Is the docker daemon running?", host) err = fmt.Errorf("Cannot connect to the Docker daemon at %s. Is the docker daemon running?", host)
} }

View File

@ -25,12 +25,17 @@ func (cli *Client) postHijacked(ctx context.Context, path string, query url.Valu
if err != nil { if err != nil {
return types.HijackedResponse{}, err return types.HijackedResponse{}, err
} }
conn, mediaType, err := cli.setupHijackConn(req, "tcp") conn, mediaType, err := setupHijackConn(cli.dialer(), req, "tcp")
if err != nil { if err != nil {
return types.HijackedResponse{}, err return types.HijackedResponse{}, err
} }
return types.NewHijackedResponse(conn, mediaType), err if versions.LessThan(cli.ClientVersion(), "1.42") {
// Prior to 1.42, Content-Type is always set to raw-stream and not relevant
mediaType = ""
}
return types.NewHijackedResponse(conn, mediaType), nil
} }
// DialHijack returns a hijacked connection with negotiated protocol proto. // DialHijack returns a hijacked connection with negotiated protocol proto.
@ -41,16 +46,15 @@ func (cli *Client) DialHijack(ctx context.Context, url, proto string, meta map[s
} }
req = cli.addHeaders(req, meta) req = cli.addHeaders(req, meta)
conn, _, err := cli.setupHijackConn(req, proto) conn, _, err := setupHijackConn(cli.Dialer(), req, proto)
return conn, err return conn, err
} }
func (cli *Client) setupHijackConn(req *http.Request, proto string) (_ net.Conn, _ string, retErr error) { func setupHijackConn(dialer func(context.Context) (net.Conn, error), req *http.Request, proto string) (_ net.Conn, _ string, retErr error) {
ctx := req.Context() ctx := req.Context()
req.Header.Set("Connection", "Upgrade") req.Header.Set("Connection", "Upgrade")
req.Header.Set("Upgrade", proto) req.Header.Set("Upgrade", proto)
dialer := cli.Dialer()
conn, err := dialer(ctx) conn, err := dialer(ctx)
if err != nil { if err != nil {
return nil, "", errors.Wrap(err, "cannot connect to the Docker daemon. Is 'docker daemon' running on this host?") return nil, "", errors.Wrap(err, "cannot connect to the Docker daemon. Is 'docker daemon' running on this host?")
@ -96,13 +100,7 @@ func (cli *Client) setupHijackConn(req *http.Request, proto string) (_ net.Conn,
hc.r.Reset(nil) hc.r.Reset(nil)
} }
var mediaType string return conn, resp.Header.Get("Content-Type"), nil
if versions.GreaterThanOrEqualTo(cli.ClientVersion(), "1.42") {
// Prior to 1.42, Content-Type is always set to raw-stream and not relevant
mediaType = resp.Header.Get("Content-Type")
}
return conn, mediaType, nil
} }
// hijackedConn wraps a net.Conn and is returned by setupHijackConn in the case // hijackedConn wraps a net.Conn and is returned by setupHijackConn in the case

View File

@ -4,29 +4,106 @@ import (
"bytes" "bytes"
"context" "context"
"encoding/json" "encoding/json"
"fmt"
"io" "io"
"net/url"
"github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/image"
) )
// ImageInspectWithRaw returns the image information and its raw representation. // ImageInspectOption is a type representing functional options for the image inspect operation.
func (cli *Client) ImageInspectWithRaw(ctx context.Context, imageID string) (image.InspectResponse, []byte, error) { type ImageInspectOption interface {
Apply(*imageInspectOpts) error
}
type imageInspectOptionFunc func(opt *imageInspectOpts) error
func (f imageInspectOptionFunc) Apply(o *imageInspectOpts) error {
return f(o)
}
// ImageInspectWithRawResponse instructs the client to additionally store the
// raw inspect response in the provided buffer.
func ImageInspectWithRawResponse(raw *bytes.Buffer) ImageInspectOption {
return imageInspectOptionFunc(func(opts *imageInspectOpts) error {
opts.raw = raw
return nil
})
}
// ImageInspectWithManifests sets manifests API option for the image inspect operation.
// This option is only available for API version 1.48 and up.
// With this option set, the image inspect operation response will have the
// [image.InspectResponse.Manifests] field populated if the server is multi-platform capable.
func ImageInspectWithManifests(manifests bool) ImageInspectOption {
return imageInspectOptionFunc(func(clientOpts *imageInspectOpts) error {
clientOpts.apiOptions.Manifests = manifests
return nil
})
}
// ImageInspectWithAPIOpts sets the API options for the image inspect operation.
func ImageInspectWithAPIOpts(opts image.InspectOptions) ImageInspectOption {
return imageInspectOptionFunc(func(clientOpts *imageInspectOpts) error {
clientOpts.apiOptions = opts
return nil
})
}
type imageInspectOpts struct {
raw *bytes.Buffer
apiOptions image.InspectOptions
}
// ImageInspect returns the image information.
func (cli *Client) ImageInspect(ctx context.Context, imageID string, inspectOpts ...ImageInspectOption) (image.InspectResponse, error) {
if imageID == "" { if imageID == "" {
return image.InspectResponse{}, nil, objectNotFoundError{object: "image", id: imageID} return image.InspectResponse{}, objectNotFoundError{object: "image", id: imageID}
}
serverResp, err := cli.get(ctx, "/images/"+imageID+"/json", nil, nil)
defer ensureReaderClosed(serverResp)
if err != nil {
return image.InspectResponse{}, nil, err
} }
body, err := io.ReadAll(serverResp.body) var opts imageInspectOpts
for _, opt := range inspectOpts {
if err := opt.Apply(&opts); err != nil {
return image.InspectResponse{}, fmt.Errorf("error applying image inspect option: %w", err)
}
}
query := url.Values{}
if opts.apiOptions.Manifests {
if err := cli.NewVersionError(ctx, "1.48", "manifests"); err != nil {
return image.InspectResponse{}, err
}
query.Set("manifests", "1")
}
serverResp, err := cli.get(ctx, "/images/"+imageID+"/json", query, nil)
defer ensureReaderClosed(serverResp)
if err != nil { if err != nil {
return image.InspectResponse{}, nil, err return image.InspectResponse{}, err
}
buf := opts.raw
if buf == nil {
buf = &bytes.Buffer{}
}
if _, err := io.Copy(buf, serverResp.body); err != nil {
return image.InspectResponse{}, err
} }
var response image.InspectResponse var response image.InspectResponse
rdr := bytes.NewReader(body) err = json.Unmarshal(buf.Bytes(), &response)
err = json.NewDecoder(rdr).Decode(&response) return response, err
return response, body, err }
// ImageInspectWithRaw returns the image information and its raw representation.
//
// Deprecated: Use [Client.ImageInspect] instead.
// Raw response can be obtained by [ImageInspectWithRawResponse] option.
func (cli *Client) ImageInspectWithRaw(ctx context.Context, imageID string) (image.InspectResponse, []byte, error) {
var buf bytes.Buffer
resp, err := cli.ImageInspect(ctx, imageID, ImageInspectWithRawResponse(&buf))
if err != nil {
return image.InspectResponse{}, nil, err
}
return resp, buf.Bytes(), err
} }

View File

@ -1,10 +0,0 @@
package client // import "github.com/docker/docker/client"
// APIClient is an interface that clients that talk with a docker server must implement.
type APIClient interface {
CommonAPIClient
apiClientExperimental
}
// Ensure that Client always implements APIClient.
var _ APIClient = &Client{}

View File

@ -8,6 +8,16 @@ import (
// NetworkConnect connects a container to an existent network in the docker host. // 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, containerID string, config *network.EndpointSettings) error {
networkID, err := trimID("network", networkID)
if err != nil {
return err
}
containerID, err = trimID("container", containerID)
if err != nil {
return err
}
nc := network.ConnectOptions{ nc := network.ConnectOptions{
Container: containerID, Container: containerID,
EndpointConfig: config, EndpointConfig: config,

View File

@ -8,6 +8,16 @@ import (
// NetworkDisconnect disconnects a container from an existent network in the docker host. // 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, containerID string, force bool) error {
networkID, err := trimID("network", networkID)
if err != nil {
return err
}
containerID, err = trimID("container", containerID)
if err != nil {
return err
}
nd := network.DisconnectOptions{ nd := network.DisconnectOptions{
Container: containerID, Container: containerID,
Force: force, Force: force,

View File

@ -18,8 +18,9 @@ func (cli *Client) NetworkInspect(ctx context.Context, networkID string, options
// NetworkInspectWithRaw returns the information for a specific network configured in the docker host and its raw representation. // NetworkInspectWithRaw returns the information for a specific network configured in the docker host and its raw representation.
func (cli *Client) NetworkInspectWithRaw(ctx context.Context, networkID string, options network.InspectOptions) (network.Inspect, []byte, error) { func (cli *Client) NetworkInspectWithRaw(ctx context.Context, networkID string, options network.InspectOptions) (network.Inspect, []byte, error) {
if networkID == "" { networkID, err := trimID("network", networkID)
return network.Inspect{}, nil, objectNotFoundError{object: "network", id: networkID} if err != nil {
return network.Inspect{}, nil, err
} }
query := url.Values{} query := url.Values{}
if options.Verbose { if options.Verbose {

View File

@ -4,6 +4,10 @@ import "context"
// NetworkRemove removes an existent network from the docker host. // 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) error {
networkID, err := trimID("network", networkID)
if err != nil {
return err
}
resp, err := cli.delete(ctx, "/networks/"+networkID, nil, nil) resp, err := cli.delete(ctx, "/networks/"+networkID, nil, nil)
defer ensureReaderClosed(resp) defer ensureReaderClosed(resp)
return err return err

View File

@ -11,8 +11,9 @@ import (
// NodeInspectWithRaw returns the node information. // NodeInspectWithRaw returns the node information.
func (cli *Client) NodeInspectWithRaw(ctx context.Context, nodeID string) (swarm.Node, []byte, error) { func (cli *Client) NodeInspectWithRaw(ctx context.Context, nodeID string) (swarm.Node, []byte, error) {
if nodeID == "" { nodeID, err := trimID("node", nodeID)
return swarm.Node{}, nil, objectNotFoundError{object: "node", id: nodeID} if err != nil {
return swarm.Node{}, nil, err
} }
serverResp, err := cli.get(ctx, "/nodes/"+nodeID, nil, nil) serverResp, err := cli.get(ctx, "/nodes/"+nodeID, nil, nil)
defer ensureReaderClosed(serverResp) defer ensureReaderClosed(serverResp)

View File

@ -9,6 +9,11 @@ import (
// NodeRemove removes a Node. // NodeRemove removes a Node.
func (cli *Client) NodeRemove(ctx context.Context, nodeID string, options types.NodeRemoveOptions) error { func (cli *Client) NodeRemove(ctx context.Context, nodeID string, options types.NodeRemoveOptions) error {
nodeID, err := trimID("node", nodeID)
if err != nil {
return err
}
query := url.Values{} query := url.Values{}
if options.Force { if options.Force {
query.Set("force", "1") query.Set("force", "1")

View File

@ -9,6 +9,11 @@ import (
// NodeUpdate updates a Node. // NodeUpdate updates a Node.
func (cli *Client) NodeUpdate(ctx context.Context, nodeID string, version swarm.Version, node swarm.NodeSpec) error { func (cli *Client) NodeUpdate(ctx context.Context, nodeID string, version swarm.Version, node swarm.NodeSpec) error {
nodeID, err := trimID("node", nodeID)
if err != nil {
return err
}
query := url.Values{} query := url.Values{}
query.Set("version", version.String()) query.Set("version", version.String())
resp, err := cli.post(ctx, "/nodes/"+nodeID+"/update", query, node, nil) resp, err := cli.post(ctx, "/nodes/"+nodeID+"/update", query, node, nil)

View File

@ -6,6 +6,7 @@ import (
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"time" "time"
"github.com/docker/go-connections/sockets" "github.com/docker/go-connections/sockets"
@ -194,8 +195,8 @@ func WithTLSClientConfigFromEnv() Opt {
// (see [WithAPIVersionNegotiation]). // (see [WithAPIVersionNegotiation]).
func WithVersion(version string) Opt { func WithVersion(version string) Opt {
return func(c *Client) error { return func(c *Client) error {
if version != "" { if v := strings.TrimPrefix(version, "v"); v != "" {
c.version = version c.version = v
c.manualOverride = true c.manualOverride = true
} }
return nil return nil

View File

@ -9,6 +9,10 @@ import (
// PluginDisable disables a plugin // PluginDisable disables a plugin
func (cli *Client) PluginDisable(ctx context.Context, name string, options types.PluginDisableOptions) error { func (cli *Client) PluginDisable(ctx context.Context, name string, options types.PluginDisableOptions) error {
name, err := trimID("plugin", name)
if err != nil {
return err
}
query := url.Values{} query := url.Values{}
if options.Force { if options.Force {
query.Set("force", "1") query.Set("force", "1")

View File

@ -10,6 +10,10 @@ import (
// PluginEnable enables a plugin // PluginEnable enables a plugin
func (cli *Client) PluginEnable(ctx context.Context, name string, options types.PluginEnableOptions) error { func (cli *Client) PluginEnable(ctx context.Context, name string, options types.PluginEnableOptions) error {
name, err := trimID("plugin", name)
if err != nil {
return err
}
query := url.Values{} query := url.Values{}
query.Set("timeout", strconv.Itoa(options.Timeout)) query.Set("timeout", strconv.Itoa(options.Timeout))

View File

@ -11,8 +11,9 @@ import (
// PluginInspectWithRaw inspects an existing plugin // PluginInspectWithRaw inspects an existing plugin
func (cli *Client) PluginInspectWithRaw(ctx context.Context, name string) (*types.Plugin, []byte, error) { func (cli *Client) PluginInspectWithRaw(ctx context.Context, name string) (*types.Plugin, []byte, error) {
if name == "" { name, err := trimID("plugin", name)
return nil, nil, objectNotFoundError{object: "plugin", id: name} if err != nil {
return nil, nil, err
} }
resp, err := cli.get(ctx, "/plugins/"+name+"/json", nil, nil) resp, err := cli.get(ctx, "/plugins/"+name+"/json", nil, nil)
defer ensureReaderClosed(resp) defer ensureReaderClosed(resp)

View File

@ -10,6 +10,10 @@ import (
// PluginPush pushes a plugin to a registry // PluginPush pushes a plugin to a registry
func (cli *Client) PluginPush(ctx context.Context, name string, registryAuth string) (io.ReadCloser, error) { func (cli *Client) PluginPush(ctx context.Context, name string, registryAuth string) (io.ReadCloser, error) {
name, err := trimID("plugin", name)
if err != nil {
return nil, err
}
resp, err := cli.post(ctx, "/plugins/"+name+"/push", nil, nil, http.Header{ resp, err := cli.post(ctx, "/plugins/"+name+"/push", nil, nil, http.Header{
registry.AuthHeader: {registryAuth}, registry.AuthHeader: {registryAuth},
}) })

View File

@ -9,6 +9,11 @@ import (
// PluginRemove removes a plugin // PluginRemove removes a plugin
func (cli *Client) PluginRemove(ctx context.Context, name string, options types.PluginRemoveOptions) error { func (cli *Client) PluginRemove(ctx context.Context, name string, options types.PluginRemoveOptions) error {
name, err := trimID("plugin", name)
if err != nil {
return err
}
query := url.Values{} query := url.Values{}
if options.Force { if options.Force {
query.Set("force", "1") query.Set("force", "1")

View File

@ -6,6 +6,11 @@ import (
// PluginSet modifies settings for an existing plugin // PluginSet modifies settings for an existing plugin
func (cli *Client) PluginSet(ctx context.Context, name string, args []string) error { func (cli *Client) PluginSet(ctx context.Context, name string, args []string) error {
name, err := trimID("plugin", name)
if err != nil {
return err
}
resp, err := cli.post(ctx, "/plugins/"+name+"/set", nil, args, nil) resp, err := cli.post(ctx, "/plugins/"+name+"/set", nil, args, nil)
ensureReaderClosed(resp) ensureReaderClosed(resp)
return err return err

View File

@ -13,7 +13,12 @@ import (
) )
// PluginUpgrade upgrades a plugin // PluginUpgrade upgrades a plugin
func (cli *Client) PluginUpgrade(ctx context.Context, name string, options types.PluginInstallOptions) (rc io.ReadCloser, err error) { func (cli *Client) PluginUpgrade(ctx context.Context, name string, options types.PluginInstallOptions) (io.ReadCloser, error) {
name, err := trimID("plugin", name)
if err != nil {
return nil, err
}
if err := cli.NewVersionError(ctx, "1.26", "plugin upgrade"); err != nil { if err := cli.NewVersionError(ctx, "1.26", "plugin upgrade"); err != nil {
return nil, err return nil, err
} }

View File

@ -154,21 +154,24 @@ func (cli *Client) doRequest(req *http.Request) (serverResponse, error) {
return serverResp, err return serverResp, err
} }
if uErr, ok := err.(*url.Error); ok { var uErr *url.Error
if nErr, ok := uErr.Err.(*net.OpError); ok { if errors.As(err, &uErr) {
var nErr *net.OpError
if errors.As(uErr.Err, &nErr) {
if os.IsPermission(nErr.Err) { if os.IsPermission(nErr.Err) {
return serverResp, errConnectionFailed{errors.Wrapf(err, "permission denied while trying to connect to the Docker daemon socket at %v", cli.host)} return serverResp, errConnectionFailed{errors.Wrapf(err, "permission denied while trying to connect to the Docker daemon socket at %v", cli.host)}
} }
} }
} }
if nErr, ok := err.(net.Error); ok { var nErr net.Error
if errors.As(err, &nErr) {
// FIXME(thaJeztah): any net.Error should be considered a connection error (but we should include the original error)? // FIXME(thaJeztah): any net.Error should be considered a connection error (but we should include the original error)?
if nErr.Timeout() { if nErr.Timeout() {
return serverResp, ErrorConnectionFailed(cli.host) return serverResp, connectionFailed(cli.host)
} }
if strings.Contains(nErr.Error(), "connection refused") || strings.Contains(nErr.Error(), "dial unix") { if strings.Contains(nErr.Error(), "connection refused") || strings.Contains(nErr.Error(), "dial unix") {
return serverResp, ErrorConnectionFailed(cli.host) return serverResp, connectionFailed(cli.host)
} }
} }
@ -234,8 +237,35 @@ func (cli *Client) checkResponseErr(serverResp serverResponse) error {
if err := json.Unmarshal(body, &errorResponse); err != nil { if err := json.Unmarshal(body, &errorResponse); err != nil {
return errors.Wrap(err, "Error reading JSON") return errors.Wrap(err, "Error reading JSON")
} }
daemonErr = errors.New(strings.TrimSpace(errorResponse.Message)) if errorResponse.Message == "" {
// Error-message is empty, which means that we successfully parsed the
// JSON-response (no error produced), but it didn't contain an error
// message. This could either be because the response was empty, or
// the response was valid JSON, but not with the expected schema
// ([types.ErrorResponse]).
//
// We cannot use "strict" JSON handling (json.NewDecoder with DisallowUnknownFields)
// due to the API using an open schema (we must anticipate fields
// being added to [types.ErrorResponse] in the future, and not
// reject those responses.
//
// For these cases, we construct an error with the status-code
// returned, but we could consider returning (a truncated version
// of) the actual response as-is.
//
// TODO(thaJeztah): consider adding a log.Debug to allow clients to debug the actual response when enabling debug logging.
daemonErr = fmt.Errorf(`API returned a %d (%s) but provided no error-message`,
serverResp.statusCode,
http.StatusText(serverResp.statusCode),
)
} else { } else {
daemonErr = errors.New(strings.TrimSpace(errorResponse.Message))
}
} else {
// Fall back to returning the response as-is for API versions < 1.24
// that didn't support JSON error responses, and for situations
// where a plain text error is returned. This branch may also catch
// situations where a proxy is involved, returning a HTML response.
daemonErr = errors.New(strings.TrimSpace(string(body))) daemonErr = errors.New(strings.TrimSpace(string(body)))
} }
return errors.Wrap(daemonErr, "Error response from daemon") return errors.Wrap(daemonErr, "Error response from daemon")

View File

@ -11,11 +11,12 @@ import (
// SecretInspectWithRaw returns the secret information with raw data // SecretInspectWithRaw returns the secret information with raw data
func (cli *Client) SecretInspectWithRaw(ctx context.Context, id string) (swarm.Secret, []byte, error) { func (cli *Client) SecretInspectWithRaw(ctx context.Context, id string) (swarm.Secret, []byte, error) {
if err := cli.NewVersionError(ctx, "1.25", "secret inspect"); err != nil { id, err := trimID("secret", id)
if err != nil {
return swarm.Secret{}, nil, err return swarm.Secret{}, nil, err
} }
if id == "" { if err := cli.NewVersionError(ctx, "1.25", "secret inspect"); err != nil {
return swarm.Secret{}, nil, objectNotFoundError{object: "secret", id: id} return swarm.Secret{}, nil, err
} }
resp, err := cli.get(ctx, "/secrets/"+id, nil, nil) resp, err := cli.get(ctx, "/secrets/"+id, nil, nil)
defer ensureReaderClosed(resp) defer ensureReaderClosed(resp)

View File

@ -4,6 +4,10 @@ import "context"
// SecretRemove removes a secret. // SecretRemove removes a secret.
func (cli *Client) SecretRemove(ctx context.Context, id string) error { func (cli *Client) SecretRemove(ctx context.Context, id string) error {
id, err := trimID("secret", id)
if err != nil {
return err
}
if err := cli.NewVersionError(ctx, "1.25", "secret remove"); err != nil { if err := cli.NewVersionError(ctx, "1.25", "secret remove"); err != nil {
return err return err
} }

View File

@ -9,6 +9,10 @@ import (
// SecretUpdate attempts to update a secret. // SecretUpdate attempts to update a secret.
func (cli *Client) SecretUpdate(ctx context.Context, id string, version swarm.Version, secret swarm.SecretSpec) error { func (cli *Client) SecretUpdate(ctx context.Context, id string, version swarm.Version, secret swarm.SecretSpec) error {
id, err := trimID("secret", id)
if err != nil {
return err
}
if err := cli.NewVersionError(ctx, "1.25", "secret update"); err != nil { if err := cli.NewVersionError(ctx, "1.25", "secret update"); err != nil {
return err return err
} }

View File

@ -14,9 +14,11 @@ import (
// ServiceInspectWithRaw returns the service information and the raw data. // ServiceInspectWithRaw returns the service information and the raw data.
func (cli *Client) ServiceInspectWithRaw(ctx context.Context, serviceID string, opts types.ServiceInspectOptions) (swarm.Service, []byte, error) { func (cli *Client) ServiceInspectWithRaw(ctx context.Context, serviceID string, opts types.ServiceInspectOptions) (swarm.Service, []byte, error) {
if serviceID == "" { serviceID, err := trimID("service", serviceID)
return swarm.Service{}, nil, objectNotFoundError{object: "service", id: serviceID} if err != nil {
return swarm.Service{}, nil, err
} }
query := url.Values{} query := url.Values{}
query.Set("insertDefaults", fmt.Sprintf("%v", opts.InsertDefaults)) query.Set("insertDefaults", fmt.Sprintf("%v", opts.InsertDefaults))
serverResp, err := cli.get(ctx, "/services/"+serviceID, query, nil) serverResp, err := cli.get(ctx, "/services/"+serviceID, query, nil)

View File

@ -14,6 +14,11 @@ import (
// ServiceLogs returns the logs generated by a service in an io.ReadCloser. // ServiceLogs returns the logs generated by a service in an io.ReadCloser.
// It's up to the caller to close the stream. // It's up to the caller to close the stream.
func (cli *Client) ServiceLogs(ctx context.Context, serviceID string, options container.LogsOptions) (io.ReadCloser, error) { func (cli *Client) ServiceLogs(ctx context.Context, serviceID string, options container.LogsOptions) (io.ReadCloser, error) {
serviceID, err := trimID("service", serviceID)
if err != nil {
return nil, err
}
query := url.Values{} query := url.Values{}
if options.ShowStdout { if options.ShowStdout {
query.Set("stdout", "1") query.Set("stdout", "1")

View File

@ -4,6 +4,11 @@ import "context"
// ServiceRemove kills and removes a service. // ServiceRemove kills and removes a service.
func (cli *Client) ServiceRemove(ctx context.Context, serviceID string) error { func (cli *Client) ServiceRemove(ctx context.Context, serviceID string) error {
serviceID, err := trimID("service", serviceID)
if err != nil {
return err
}
resp, err := cli.delete(ctx, "/services/"+serviceID, nil, nil) resp, err := cli.delete(ctx, "/services/"+serviceID, nil, nil)
defer ensureReaderClosed(resp) defer ensureReaderClosed(resp)
return err return err

View File

@ -16,7 +16,10 @@ import (
// It should be the value as set *before* the update. You can find this value in the Meta field // It should be the value as set *before* the update. You can find this value in the Meta field
// of swarm.Service, which can be found using ServiceInspectWithRaw. // of swarm.Service, which can be found using ServiceInspectWithRaw.
func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error) { func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error) {
response := swarm.ServiceUpdateResponse{} serviceID, err := trimID("service", serviceID)
if err != nil {
return swarm.ServiceUpdateResponse{}, err
}
// Make sure we negotiated (if the client is configured to do so), // Make sure we negotiated (if the client is configured to do so),
// as code below contains API-version specific handling of options. // as code below contains API-version specific handling of options.
@ -24,7 +27,7 @@ func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version
// Normally, version-negotiation (if enabled) would not happen until // Normally, version-negotiation (if enabled) would not happen until
// the API request is made. // the API request is made.
if err := cli.checkVersion(ctx); err != nil { if err := cli.checkVersion(ctx); err != nil {
return response, err return swarm.ServiceUpdateResponse{}, err
} }
query := url.Values{} query := url.Values{}
@ -39,7 +42,7 @@ func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version
query.Set("version", version.String()) query.Set("version", version.String())
if err := validateServiceSpec(service); err != nil { if err := validateServiceSpec(service); err != nil {
return response, err return swarm.ServiceUpdateResponse{}, err
} }
// ensure that the image is tagged // ensure that the image is tagged
@ -74,9 +77,10 @@ func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version
resp, err := cli.post(ctx, "/services/"+serviceID+"/update", query, service, headers) resp, err := cli.post(ctx, "/services/"+serviceID+"/update", query, service, headers)
defer ensureReaderClosed(resp) defer ensureReaderClosed(resp)
if err != nil { if err != nil {
return response, err return swarm.ServiceUpdateResponse{}, err
} }
response := swarm.ServiceUpdateResponse{}
err = json.NewDecoder(resp.body).Decode(&response) err = json.NewDecoder(resp.body).Decode(&response)
if resolveWarning != "" { if resolveWarning != "" {
response.Warnings = append(response.Warnings, resolveWarning) response.Warnings = append(response.Warnings, resolveWarning)

View File

@ -11,9 +11,11 @@ import (
// TaskInspectWithRaw returns the task information and its raw representation. // TaskInspectWithRaw returns the task information and its raw representation.
func (cli *Client) TaskInspectWithRaw(ctx context.Context, taskID string) (swarm.Task, []byte, error) { func (cli *Client) TaskInspectWithRaw(ctx context.Context, taskID string) (swarm.Task, []byte, error) {
if taskID == "" { taskID, err := trimID("task", taskID)
return swarm.Task{}, nil, objectNotFoundError{object: "task", id: taskID} if err != nil {
return swarm.Task{}, nil, err
} }
serverResp, err := cli.get(ctx, "/tasks/"+taskID, nil, nil) serverResp, err := cli.get(ctx, "/tasks/"+taskID, nil, nil)
defer ensureReaderClosed(serverResp) defer ensureReaderClosed(serverResp)
if err != nil { if err != nil {

View File

@ -4,6 +4,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/url" "net/url"
"strings"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
"github.com/docker/docker/errdefs" "github.com/docker/docker/errdefs"
@ -13,6 +14,23 @@ import (
var headerRegexp = lazyregexp.New(`\ADocker/.+\s\((.+)\)\z`) var headerRegexp = lazyregexp.New(`\ADocker/.+\s\((.+)\)\z`)
type emptyIDError string
func (e emptyIDError) InvalidParameter() {}
func (e emptyIDError) Error() string {
return "invalid " + string(e) + " name or ID: value is empty"
}
// trimID trims the given object-ID / name, returning an error if it's empty.
func trimID(objType, id string) (string, error) {
id = strings.TrimSpace(id)
if len(id) == 0 {
return "", emptyIDError(objType)
}
return id, nil
}
// getDockerOS returns the operating system based on the server header from the daemon. // getDockerOS returns the operating system based on the server header from the daemon.
func getDockerOS(serverHeader string) string { func getDockerOS(serverHeader string) string {
var osType string var osType string

View File

@ -17,8 +17,9 @@ func (cli *Client) VolumeInspect(ctx context.Context, volumeID string) (volume.V
// VolumeInspectWithRaw returns the information about a specific volume in the docker host and its raw representation // VolumeInspectWithRaw returns the information about a specific volume in the docker host and its raw representation
func (cli *Client) VolumeInspectWithRaw(ctx context.Context, volumeID string) (volume.Volume, []byte, error) { func (cli *Client) VolumeInspectWithRaw(ctx context.Context, volumeID string) (volume.Volume, []byte, error) {
if volumeID == "" { volumeID, err := trimID("volume", volumeID)
return volume.Volume{}, nil, objectNotFoundError{object: "volume", id: volumeID} if err != nil {
return volume.Volume{}, nil, err
} }
var vol volume.Volume var vol volume.Volume

View File

@ -9,6 +9,11 @@ import (
// VolumeRemove removes a volume from the docker host. // VolumeRemove removes a volume from the docker host.
func (cli *Client) VolumeRemove(ctx context.Context, volumeID string, force bool) error { func (cli *Client) VolumeRemove(ctx context.Context, volumeID string, force bool) error {
volumeID, err := trimID("volume", volumeID)
if err != nil {
return err
}
query := url.Values{} query := url.Values{}
if force { if force {
// Make sure we negotiated (if the client is configured to do so), // Make sure we negotiated (if the client is configured to do so),

View File

@ -11,6 +11,10 @@ import (
// VolumeUpdate updates a volume. This only works for Cluster Volumes, and // VolumeUpdate updates a volume. This only works for Cluster Volumes, and
// only some fields can be updated. // only some fields can be updated.
func (cli *Client) VolumeUpdate(ctx context.Context, volumeID string, version swarm.Version, options volume.UpdateOptions) error { func (cli *Client) VolumeUpdate(ctx context.Context, volumeID string, version swarm.Version, options volume.UpdateOptions) error {
volumeID, err := trimID("volume", volumeID)
if err != nil {
return err
}
if err := cli.NewVersionError(ctx, "1.42", "volume update"); err != nil { if err := cli.NewVersionError(ctx, "1.42", "volume update"); err != nil {
return err return err
} }

View File

@ -14,7 +14,9 @@ func (e errNotFound) Unwrap() error {
return e.error return e.error
} }
// NotFound is a helper to create an error of the class with the same name from any error type // NotFound creates an [ErrNotFound] error from the given error.
// It returns the error as-is if it is either nil (no error) or already implements
// [ErrNotFound],
func NotFound(err error) error { func NotFound(err error) error {
if err == nil || IsNotFound(err) { if err == nil || IsNotFound(err) {
return err return err
@ -34,7 +36,9 @@ func (e errInvalidParameter) Unwrap() error {
return e.error return e.error
} }
// InvalidParameter is a helper to create an error of the class with the same name from any error type // InvalidParameter creates an [ErrInvalidParameter] error from the given error.
// It returns the error as-is if it is either nil (no error) or already implements
// [ErrInvalidParameter],
func InvalidParameter(err error) error { func InvalidParameter(err error) error {
if err == nil || IsInvalidParameter(err) { if err == nil || IsInvalidParameter(err) {
return err return err
@ -54,7 +58,9 @@ func (e errConflict) Unwrap() error {
return e.error return e.error
} }
// Conflict is a helper to create an error of the class with the same name from any error type // Conflict creates an [ErrConflict] error from the given error.
// It returns the error as-is if it is either nil (no error) or already implements
// [ErrConflict],
func Conflict(err error) error { func Conflict(err error) error {
if err == nil || IsConflict(err) { if err == nil || IsConflict(err) {
return err return err
@ -74,7 +80,9 @@ func (e errUnauthorized) Unwrap() error {
return e.error return e.error
} }
// Unauthorized is a helper to create an error of the class with the same name from any error type // Unauthorized creates an [ErrUnauthorized] error from the given error.
// It returns the error as-is if it is either nil (no error) or already implements
// [ErrUnauthorized],
func Unauthorized(err error) error { func Unauthorized(err error) error {
if err == nil || IsUnauthorized(err) { if err == nil || IsUnauthorized(err) {
return err return err
@ -94,7 +102,9 @@ func (e errUnavailable) Unwrap() error {
return e.error return e.error
} }
// Unavailable is a helper to create an error of the class with the same name from any error type // Unavailable creates an [ErrUnavailable] error from the given error.
// It returns the error as-is if it is either nil (no error) or already implements
// [ErrUnavailable],
func Unavailable(err error) error { func Unavailable(err error) error {
if err == nil || IsUnavailable(err) { if err == nil || IsUnavailable(err) {
return err return err
@ -114,7 +124,9 @@ func (e errForbidden) Unwrap() error {
return e.error return e.error
} }
// Forbidden is a helper to create an error of the class with the same name from any error type // Forbidden creates an [ErrForbidden] error from the given error.
// It returns the error as-is if it is either nil (no error) or already implements
// [ErrForbidden],
func Forbidden(err error) error { func Forbidden(err error) error {
if err == nil || IsForbidden(err) { if err == nil || IsForbidden(err) {
return err return err
@ -134,7 +146,9 @@ func (e errSystem) Unwrap() error {
return e.error return e.error
} }
// System is a helper to create an error of the class with the same name from any error type // System creates an [ErrSystem] error from the given error.
// It returns the error as-is if it is either nil (no error) or already implements
// [ErrSystem],
func System(err error) error { func System(err error) error {
if err == nil || IsSystem(err) { if err == nil || IsSystem(err) {
return err return err
@ -154,7 +168,9 @@ func (e errNotModified) Unwrap() error {
return e.error return e.error
} }
// NotModified is a helper to create an error of the class with the same name from any error type // NotModified creates an [ErrNotModified] error from the given error.
// It returns the error as-is if it is either nil (no error) or already implements
// [NotModified],
func NotModified(err error) error { func NotModified(err error) error {
if err == nil || IsNotModified(err) { if err == nil || IsNotModified(err) {
return err return err
@ -174,7 +190,9 @@ func (e errNotImplemented) Unwrap() error {
return e.error return e.error
} }
// NotImplemented is a helper to create an error of the class with the same name from any error type // NotImplemented creates an [ErrNotImplemented] error from the given error.
// It returns the error as-is if it is either nil (no error) or already implements
// [ErrNotImplemented],
func NotImplemented(err error) error { func NotImplemented(err error) error {
if err == nil || IsNotImplemented(err) { if err == nil || IsNotImplemented(err) {
return err return err
@ -194,7 +212,9 @@ func (e errUnknown) Unwrap() error {
return e.error return e.error
} }
// Unknown is a helper to create an error of the class with the same name from any error type // Unknown creates an [ErrUnknown] error from the given error.
// It returns the error as-is if it is either nil (no error) or already implements
// [ErrUnknown],
func Unknown(err error) error { func Unknown(err error) error {
if err == nil || IsUnknown(err) { if err == nil || IsUnknown(err) {
return err return err
@ -214,7 +234,9 @@ func (e errCancelled) Unwrap() error {
return e.error return e.error
} }
// Cancelled is a helper to create an error of the class with the same name from any error type // Cancelled creates an [ErrCancelled] error from the given error.
// It returns the error as-is if it is either nil (no error) or already implements
// [ErrCancelled],
func Cancelled(err error) error { func Cancelled(err error) error {
if err == nil || IsCancelled(err) { if err == nil || IsCancelled(err) {
return err return err
@ -234,7 +256,9 @@ func (e errDeadline) Unwrap() error {
return e.error return e.error
} }
// Deadline is a helper to create an error of the class with the same name from any error type // Deadline creates an [ErrDeadline] error from the given error.
// It returns the error as-is if it is either nil (no error) or already implements
// [ErrDeadline],
func Deadline(err error) error { func Deadline(err error) error {
if err == nil || IsDeadline(err) { if err == nil || IsDeadline(err) {
return err return err
@ -254,7 +278,9 @@ func (e errDataLoss) Unwrap() error {
return e.error return e.error
} }
// DataLoss is a helper to create an error of the class with the same name from any error type // DataLoss creates an [ErrDataLoss] error from the given error.
// It returns the error as-is if it is either nil (no error) or already implements
// [ErrDataLoss],
func DataLoss(err error) error { func DataLoss(err error) error {
if err == nil || IsDataLoss(err) { if err == nil || IsDataLoss(err) {
return err return err

View File

@ -39,79 +39,79 @@ func getImplementer(err error) error {
} }
} }
// IsNotFound returns if the passed in error is an ErrNotFound // IsNotFound returns if the passed in error is an [ErrNotFound],
func IsNotFound(err error) bool { func IsNotFound(err error) bool {
_, ok := getImplementer(err).(ErrNotFound) _, ok := getImplementer(err).(ErrNotFound)
return ok return ok
} }
// IsInvalidParameter returns if the passed in error is an ErrInvalidParameter // IsInvalidParameter returns if the passed in error is an [ErrInvalidParameter].
func IsInvalidParameter(err error) bool { func IsInvalidParameter(err error) bool {
_, ok := getImplementer(err).(ErrInvalidParameter) _, ok := getImplementer(err).(ErrInvalidParameter)
return ok return ok
} }
// IsConflict returns if the passed in error is an ErrConflict // IsConflict returns if the passed in error is an [ErrConflict].
func IsConflict(err error) bool { func IsConflict(err error) bool {
_, ok := getImplementer(err).(ErrConflict) _, ok := getImplementer(err).(ErrConflict)
return ok return ok
} }
// IsUnauthorized returns if the passed in error is an ErrUnauthorized // IsUnauthorized returns if the passed in error is an [ErrUnauthorized].
func IsUnauthorized(err error) bool { func IsUnauthorized(err error) bool {
_, ok := getImplementer(err).(ErrUnauthorized) _, ok := getImplementer(err).(ErrUnauthorized)
return ok return ok
} }
// IsUnavailable returns if the passed in error is an ErrUnavailable // IsUnavailable returns if the passed in error is an [ErrUnavailable].
func IsUnavailable(err error) bool { func IsUnavailable(err error) bool {
_, ok := getImplementer(err).(ErrUnavailable) _, ok := getImplementer(err).(ErrUnavailable)
return ok return ok
} }
// IsForbidden returns if the passed in error is an ErrForbidden // IsForbidden returns if the passed in error is an [ErrForbidden].
func IsForbidden(err error) bool { func IsForbidden(err error) bool {
_, ok := getImplementer(err).(ErrForbidden) _, ok := getImplementer(err).(ErrForbidden)
return ok return ok
} }
// IsSystem returns if the passed in error is an ErrSystem // IsSystem returns if the passed in error is an [ErrSystem].
func IsSystem(err error) bool { func IsSystem(err error) bool {
_, ok := getImplementer(err).(ErrSystem) _, ok := getImplementer(err).(ErrSystem)
return ok return ok
} }
// IsNotModified returns if the passed in error is a NotModified error // IsNotModified returns if the passed in error is an [ErrNotModified].
func IsNotModified(err error) bool { func IsNotModified(err error) bool {
_, ok := getImplementer(err).(ErrNotModified) _, ok := getImplementer(err).(ErrNotModified)
return ok return ok
} }
// IsNotImplemented returns if the passed in error is an ErrNotImplemented // IsNotImplemented returns if the passed in error is an [ErrNotImplemented].
func IsNotImplemented(err error) bool { func IsNotImplemented(err error) bool {
_, ok := getImplementer(err).(ErrNotImplemented) _, ok := getImplementer(err).(ErrNotImplemented)
return ok return ok
} }
// IsUnknown returns if the passed in error is an ErrUnknown // IsUnknown returns if the passed in error is an [ErrUnknown].
func IsUnknown(err error) bool { func IsUnknown(err error) bool {
_, ok := getImplementer(err).(ErrUnknown) _, ok := getImplementer(err).(ErrUnknown)
return ok return ok
} }
// IsCancelled returns if the passed in error is an ErrCancelled // IsCancelled returns if the passed in error is an [ErrCancelled].
func IsCancelled(err error) bool { func IsCancelled(err error) bool {
_, ok := getImplementer(err).(ErrCancelled) _, ok := getImplementer(err).(ErrCancelled)
return ok return ok
} }
// IsDeadline returns if the passed in error is an ErrDeadline // IsDeadline returns if the passed in error is an [ErrDeadline].
func IsDeadline(err error) bool { func IsDeadline(err error) bool {
_, ok := getImplementer(err).(ErrDeadline) _, ok := getImplementer(err).(ErrDeadline)
return ok return ok
} }
// IsDataLoss returns if the passed in error is an ErrDataLoss // IsDataLoss returns if the passed in error is an [ErrDataLoss].
func IsDataLoss(err error) bool { func IsDataLoss(err error) bool {
_, ok := getImplementer(err).(ErrDataLoss) _, ok := getImplementer(err).(ErrDataLoss)
return ok return ok

View File

@ -5,15 +5,13 @@ import (
) )
const ( const (
// Deprecated: copy value locally
SeTakeOwnershipPrivilege = "SeTakeOwnershipPrivilege" SeTakeOwnershipPrivilege = "SeTakeOwnershipPrivilege"
) )
// TODO(thaJeztah): these magic consts need a source of reference, and should be defined in a canonical location
const ( const (
// Deprecated: copy value locally
ContainerAdministratorSidString = "S-1-5-93-2-1" ContainerAdministratorSidString = "S-1-5-93-2-1"
// Deprecated: copy value locally
ContainerUserSidString = "S-1-5-93-2-2" ContainerUserSidString = "S-1-5-93-2-2"
) )

View File

@ -2,10 +2,6 @@ package ioutils // import "github.com/docker/docker/pkg/ioutils"
import ( import (
"context" "context"
// make sure crypto.SHA256, crypto.sha512 and crypto.SHA384 are registered
// TODO remove once https://github.com/opencontainers/go-digest/pull/64 is merged.
_ "crypto/sha256"
_ "crypto/sha512"
"io" "io"
"runtime/debug" "runtime/debug"
"sync/atomic" "sync/atomic"

2
vendor/modules.txt vendored
View File

@ -55,7 +55,7 @@ github.com/docker/distribution/registry/client/transport
github.com/docker/distribution/registry/storage/cache github.com/docker/distribution/registry/storage/cache
github.com/docker/distribution/registry/storage/cache/memory github.com/docker/distribution/registry/storage/cache/memory
github.com/docker/distribution/uuid github.com/docker/distribution/uuid
# github.com/docker/docker v27.0.2-0.20250110234321-69687190936d+incompatible # github.com/docker/docker v27.0.2-0.20250206180949-6c3797923dcb+incompatible
## explicit ## explicit
github.com/docker/docker/api github.com/docker/docker/api
github.com/docker/docker/api/types github.com/docker/docker/api/types