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

View File

@ -24,7 +24,7 @@ type fakeClient struct {
imagesPruneFunc func(pruneFilter filters.Args) (image.PruneReport, error)
imageLoadFunc func(input io.Reader, options image.LoadOptions) (image.LoadResponse, 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)
imageHistoryFunc func(img string, options image.HistoryOptions) ([]image.HistoryResponseItem, 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
}
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 {
return cli.imageInspectFunc(img)
}
return image.InspectResponse{}, nil, nil
return image.InspectResponse{}, nil
}
func (cli *fakeClient) ImageImport(_ context.Context, source image.ImportSource, ref string,

View File

@ -4,6 +4,7 @@
package image
import (
"bytes"
"context"
"github.com/docker/cli/cli"
@ -11,6 +12,8 @@ import (
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/cli/command/inspect"
flagsHelper "github.com/docker/cli/cli/flags"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/client"
"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 {
apiClient := dockerCLI.Client()
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
args []string
imageCount int
imageInspectFunc func(img string) (image.InspectResponse, []byte, error)
imageInspectFunc func(img string) (image.InspectResponse, error)
}{
{
name: "simple",
args: []string{"image"},
imageCount: 1,
imageInspectFunc: func(img string) (image.InspectResponse, []byte, error) {
imageInspectFunc: func(img string) (image.InspectResponse, error) {
imageInspectInvocationCount++
assert.Check(t, is.Equal("image", img))
return image.InspectResponse{}, nil, nil
return image.InspectResponse{}, nil
},
},
{
name: "format",
imageCount: 1,
args: []string{"--format='{{.ID}}'", "image"},
imageInspectFunc: func(img string) (image.InspectResponse, []byte, error) {
imageInspectFunc: func(img string) (image.InspectResponse, error) {
imageInspectInvocationCount++
return image.InspectResponse{ID: img}, nil, nil
return image.InspectResponse{ID: img}, nil
},
},
{
name: "simple-many",
args: []string{"image1", "image2"},
imageCount: 2,
imageInspectFunc: func(img string) (image.InspectResponse, []byte, error) {
imageInspectFunc: func(img string) (image.InspectResponse, error) {
imageInspectInvocationCount++
if imageInspectInvocationCount == 1 {
assert.Check(t, is.Equal("image1", img))
} else {
assert.Check(t, is.Equal("image2", img))
}
return image.InspectResponse{}, nil, nil
return image.InspectResponse{}, nil
},
},
}

View File

@ -4,6 +4,7 @@
package system
import (
"bytes"
"context"
"fmt"
"strings"
@ -13,7 +14,9 @@ import (
"github.com/docker/cli/cli/command/inspect"
flagsHelper "github.com/docker/cli/cli/flags"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/client"
"github.com/docker/docker/errdefs"
"github.com/pkg/errors"
"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 {
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
}
func (c *fakeClient) ImageInspectWithRaw(context.Context, string) (image.InspectResponse, []byte, error) {
return image.InspectResponse{}, []byte{}, nil
func (c *fakeClient) ImageInspect(context.Context, string, ...client.ImageInspectOption) (image.InspectResponse, error) {
return image.InspectResponse{}, nil
}
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 {
_, _, err := apiClient.ImageInspectWithRaw(ctx, imageName)
_, err := apiClient.ImageInspect(ctx, imageName)
return err
}

View File

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

View File

@ -13,7 +13,7 @@ require (
github.com/distribution/reference v0.6.0
github.com/docker/cli-docs-tool v0.9.0
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/go-connections 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.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/docker v27.0.2-0.20250110234321-69687190936d+incompatible h1:JqToo31hcYA8YAJwnaF1aq7WUhFRcMNPnkKCUzGFGJM=
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 h1:R6uFQLKwpUW5K3rRtO6PZ5Bhx4CsuiBla2lV+bZQT+Y=
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/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
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,
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
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
@ -212,6 +212,7 @@ definitions:
- `bind` a mount of a file or directory from the host into the container.
- `volume` a docker volume with the given `Name`.
- `image` a docker image
- `tmpfs` a `tmpfs`.
- `npipe` a named pipe from the host into the container.
- `cluster` a Swarm cluster volume
@ -219,6 +220,7 @@ definitions:
enum:
- "bind"
- "volume"
- "image"
- "tmpfs"
- "npipe"
- "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.
- `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.
- `npipe` Mounts a named pipe from the host into the container. Must exist prior to creating the container.
- `cluster` a Swarm cluster volume
@ -357,6 +360,7 @@ definitions:
enum:
- "bind"
- "volume"
- "image"
- "tmpfs"
- "npipe"
- "cluster"
@ -431,6 +435,14 @@ definitions:
description: "Source path inside the volume. Must be relative without any back traversals."
type: "string"
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:
description: "Optional configuration for the `tmpfs` type."
type: "object"
@ -2004,6 +2016,21 @@ definitions:
compatibility.
x-nullable: true
$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:
description: |
List of image names/tags in the local image cache that reference this
@ -5299,6 +5326,551 @@ definitions:
type: "string"
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:
description: "OK response to ContainerWait operation"
type: "object"
@ -7757,99 +8329,7 @@ paths:
200:
description: "no error"
schema:
type: "object"
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
$ref: "#/definitions/ContainerStatsResponse"
404:
description: "no such container"
schema:
@ -8994,10 +9474,29 @@ paths:
operationId: "BuildPrune"
parameters:
- 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"
description: "Amount of disk space in bytes to keep for cache"
type: "integer"
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"
in: "query"
type: "boolean"
@ -9064,7 +9563,13 @@ paths:
parameters:
- name: "fromImage"
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"
- name: "fromSrc"
in: "query"
@ -9158,6 +9663,12 @@ paths:
description: "Image name or id"
type: "string"
required: true
- name: "manifests"
in: "query"
description: "Include Manifests in the image summary."
type: "boolean"
default: false
required: false
tags: ["Image"]
/images/{name}/history:
get:
@ -10826,9 +11337,7 @@ paths:
description: "Optional custom IP scheme for the network."
$ref: "#/definitions/IPAM"
EnableIPv4:
description: |
Enable IPv4 on the network.
To disable IPv4, the daemon must be started with experimental features enabled.
description: "Enable IPv4 on the network."
type: "boolean"
example: true
EnableIPv6:

View File

@ -148,7 +148,15 @@ type PidsStats struct {
}
// 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
Read time.Time `json:"read"`
PreRead time.Time `json:"preread"`
@ -162,20 +170,8 @@ type Stats struct {
StorageStats StorageStats `json:"storage_stats,omitempty"`
// Shared stats
CPUStats CPUStats `json:"cpu_stats,omitempty"`
PreCPUStats CPUStats `json:"precpu_stats,omitempty"` // "Pre"="Previous"
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"`
CPUStats CPUStats `json:"cpu_stats,omitempty"`
PreCPUStats CPUStats `json:"precpu_stats,omitempty"` // "Pre"="Previous"
MemoryStats MemoryStats `json:"memory_stats,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
// compatibility.
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
}
type InspectOptions struct {
// Manifests returns the image manifests.
Manifests bool
}
// SaveOptions holds parameters to save images.
type SaveOptions struct {
// Platforms selects the platforms to save if the image is a

View File

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

View File

@ -3,10 +3,9 @@ import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"strings"
"github.com/pkg/errors"
)
// 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 {
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 }

View File

@ -169,9 +169,11 @@ type BuildCache struct {
// BuildCachePruneOptions hold parameters to prune the build cache
type BuildCachePruneOptions struct {
All bool
KeepStorage int64
Filters filters.Args
All bool
ReservedSpace int64
MaxUsedSpace int64
MinFreeSpace int64
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 {
query.Set("all", "1")
}
query.Set("keep-storage", strconv.Itoa(int(opts.KeepStorage)))
if opts.KeepStorage != 0 {
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)
if err != nil {
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"
)
type apiClientExperimental interface {
CheckpointAPIClient
}
// CheckpointAPIClient defines API client methods for the checkpoints
// CheckpointAPIClient defines API client methods for the checkpoints.
//
// Experimental: checkpoint and restore is still an experimental feature,
// and only available if the daemon is running with experimental features
// enabled.
type CheckpointAPIClient interface {
CheckpointCreate(ctx context.Context, container string, options checkpoint.CreateOptions) 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
func (cli *Client) CheckpointCreate(ctx context.Context, container string, options checkpoint.CreateOptions) error {
resp, err := cli.post(ctx, "/containers/"+container+"/checkpoints", nil, options, nil)
func (cli *Client) CheckpointCreate(ctx context.Context, containerID string, options checkpoint.CreateOptions) error {
containerID, err := trimID("container", containerID)
if err != nil {
return err
}
resp, err := cli.post(ctx, "/containers/"+containerID+"/checkpoints", nil, options, nil)
ensureReaderClosed(resp)
return err
}

View File

@ -9,6 +9,11 @@ import (
// 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 {
containerID, err := trimID("container", containerID)
if err != nil {
return err
}
query := url.Values{}
if options.CheckpointDir != "" {
query.Set("dir", options.CheckpointDir)

View File

@ -99,6 +99,9 @@ const DummyHost = "api.moby.localhost"
// recent version before negotiation was introduced.
const fallbackAPIVersion = "1.24"
// Ensure that Client always implements APIClient.
var _ APIClient = &Client{}
// Client is the API client that performs all operations
// against a docker server.
type Client struct {
@ -304,8 +307,7 @@ func (cli *Client) getAPIPath(ctx context.Context, p string, query url.Values) s
var apiPath string
_ = cli.checkVersion(ctx)
if cli.version != "" {
v := strings.TrimPrefix(cli.version, "v")
apiPath = path.Join(cli.basePath, "/v"+v, p)
apiPath = path.Join(cli.basePath, "/v"+strings.TrimPrefix(cli.version, "v"), p)
} else {
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
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) {
if dialFn := cli.dialerFromTransport(); dialFn != nil {
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.
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
ContainerAPIClient
DistributionAPIClient
ImageAPIClient
NodeAPIClient
NetworkAPIClient
PluginAPIClient
ServiceAPIClient
SwarmAPIClient
SecretAPIClient
SystemAPIClient
VolumeAPIClient
ClientVersion() string
@ -39,9 +45,25 @@ type CommonAPIClient interface {
ServerVersion(ctx context.Context) (types.Version, error)
NegotiateAPIVersion(ctx context.Context)
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)
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
@ -93,7 +115,10 @@ type ImageAPIClient interface {
ImageCreate(ctx context.Context, parentReference string, options image.CreateOptions) (io.ReadCloser, 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)
// Deprecated: Use [Client.ImageInspect] instead.
// Raw response can be obtained by [ImageInspectWithRawResponse] option.
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)
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)

View File

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

View File

@ -4,6 +4,10 @@ import "context"
// ConfigRemove removes a config.
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 {
return err
}

View File

@ -9,6 +9,10 @@ import (
// ConfigUpdate attempts to update a config
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 {
return err
}

View File

@ -33,7 +33,12 @@ import (
//
// You can use github.com/docker/docker/pkg/stdcopy.StdCopy to demultiplex this
// 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{}
if options.Stream {
query.Set("stream", "1")
@ -54,7 +59,7 @@ func (cli *Client) ContainerAttach(ctx context.Context, container string, option
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"},
})
}

View File

@ -12,7 +12,12 @@ import (
)
// 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
if 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.Set("container", container)
query.Set("container", containerID)
query.Set("repo", repository)
query.Set("tag", tag)
query.Set("comment", options.Comment)

View File

@ -16,11 +16,15 @@ import (
// ContainerStatPath returns stat information about a path inside the container filesystem.
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.Set("path", filepath.ToSlash(path)) // Normalize the paths used in the API.
urlStr := "/containers/" + containerID + "/archive"
response, err := cli.head(ctx, urlStr, query, nil)
response, err := cli.head(ctx, "/containers/"+containerID+"/archive", query, nil)
defer ensureReaderClosed(response)
if err != nil {
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.
// 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 {
containerID, err := trimID("container", containerID)
if err != nil {
return err
}
query := url.Values{}
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.
@ -42,9 +51,7 @@ func (cli *Client) CopyToContainer(ctx context.Context, containerID, dstPath str
query.Set("copyUIDGID", "true")
}
apiPath := "/containers/" + containerID + "/archive"
response, err := cli.putRaw(ctx, apiPath, query, content, nil)
response, err := cli.putRaw(ctx, "/containers/"+containerID+"/archive", query, content, nil)
defer ensureReaderClosed(response)
if err != nil {
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
// 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) {
containerID, err := trimID("container", containerID)
if err != nil {
return nil, container.PathStat{}, err
}
query := make(url.Values, 1)
query.Set("path", filepath.ToSlash(srcPath)) // Normalize the paths used in the API.
apiPath := "/containers/" + containerID + "/archive"
response, err := cli.get(ctx, apiPath, query, nil)
response, err := cli.get(ctx, "/containers/"+containerID+"/archive", query, nil)
if err != nil {
return nil, container.PathStat{}, err
}

View File

@ -10,14 +10,21 @@ import (
// ContainerDiff shows differences in a container filesystem since it was started.
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)
defer ensureReaderClosed(serverResp)
if err != nil {
return changes, err
return nil, err
}
var changes []container.FilesystemChange
err = json.NewDecoder(serverResp.body).Decode(&changes)
if err != nil {
return nil, err
}
return changes, err
}

View File

@ -11,8 +11,11 @@ import (
)
// 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) {
var response types.IDResponse
func (cli *Client) ContainerExecCreate(ctx context.Context, containerID string, options container.ExecOptions) (types.IDResponse, error) {
containerID, err := trimID("container", containerID)
if err != nil {
return types.IDResponse{}, err
}
// Make sure we negotiated (if the client is configured to do so),
// 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
// the API request is made.
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 {
return response, err
return types.IDResponse{}, err
}
if versions.LessThan(cli.ClientVersion(), "1.42") {
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)
if err != nil {
return response, err
return types.IDResponse{}, err
}
var response types.IDResponse
err = json.NewDecoder(resp.body).Decode(&response)
return response, err
}

View File

@ -10,6 +10,11 @@ import (
// and returns them as an io.ReadCloser. It's up to the caller
// to close the stream.
func (cli *Client) ContainerExport(ctx context.Context, containerID string) (io.ReadCloser, error) {
containerID, err := trimID("container", containerID)
if err != nil {
return nil, err
}
serverResp, err := cli.get(ctx, "/containers/"+containerID+"/export", url.Values{}, nil)
if err != nil {
return nil, err

View File

@ -12,9 +12,11 @@ import (
// ContainerInspect returns the container information.
func (cli *Client) ContainerInspect(ctx context.Context, containerID string) (container.InspectResponse, error) {
if containerID == "" {
return container.InspectResponse{}, objectNotFoundError{object: "container", id: containerID}
containerID, err := trimID("container", containerID)
if err != nil {
return container.InspectResponse{}, err
}
serverResp, err := cli.get(ctx, "/containers/"+containerID+"/json", nil, nil)
defer ensureReaderClosed(serverResp)
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.
func (cli *Client) ContainerInspectWithRaw(ctx context.Context, containerID string, getSize bool) (container.InspectResponse, []byte, error) {
if containerID == "" {
return container.InspectResponse{}, nil, objectNotFoundError{object: "container", id: containerID}
containerID, err := trimID("container", containerID)
if err != nil {
return container.InspectResponse{}, nil, err
}
query := url.Values{}
if getSize {
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.
func (cli *Client) ContainerKill(ctx context.Context, containerID, signal string) error {
containerID, err := trimID("container", containerID)
if err != nil {
return err
}
query := url.Values{}
if 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
// 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{}
if options.ShowStdout {
query.Set("stdout", "1")
@ -72,7 +77,7 @@ func (cli *Client) ContainerLogs(ctx context.Context, container string, options
}
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 {
return nil, err
}

View File

@ -4,6 +4,11 @@ import "context"
// ContainerPause pauses the main process of a given container without terminating it.
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)
ensureReaderClosed(resp)
return err

View File

@ -9,6 +9,11 @@ import (
// ContainerRemove kills and removes a container from the docker host.
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{}
if options.RemoveVolumes {
query.Set("v", "1")

View File

@ -7,6 +7,11 @@ import (
// ContainerRename changes the name of a given container.
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.Set("name", newContainerName)
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.
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)
}
// 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 {
execID, err := trimID("exec", execID)
if err != nil {
return err
}
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
// a specific amount of time, given the timeout.
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{}
if options.Timeout != nil {
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.
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{}
if len(options.CheckpointID) != 0 {
query.Set("checkpoint", options.CheckpointID)

View File

@ -10,6 +10,11 @@ import (
// ContainerStats returns near realtime stats for a given container.
// 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) {
containerID, err := trimID("container", containerID)
if err != nil {
return container.StatsResponseReader{}, err
}
query := url.Values{}
query.Set("stream", "0")
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.
// 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) {
containerID, err := trimID("container", containerID)
if err != nil {
return container.StatsResponseReader{}, err
}
query := url.Values{}
query.Set("stream", "0")
query.Set("one-shot", "1")

View File

@ -17,6 +17,11 @@ import (
// otherwise the engine default. A negative timeout value can be specified,
// meaning no timeout, i.e. no forceful termination is performed.
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{}
if options.Timeout != nil {
query.Set("t", strconv.Itoa(*options.Timeout))

View File

@ -11,7 +11,11 @@ import (
// ContainerTop shows process information from within a container.
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{}
if len(arguments) > 0 {
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)
defer ensureReaderClosed(resp)
if err != nil {
return response, err
return container.ContainerTopOKBody{}, err
}
var response container.ContainerTopOKBody
err = json.NewDecoder(resp.body).Decode(&response)
return response, err
}

View File

@ -4,6 +4,11 @@ import "context"
// ContainerUnpause resumes the process execution within a container
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)
ensureReaderClosed(resp)
return err

View File

@ -9,13 +9,18 @@ import (
// ContainerUpdate updates the resources of a container.
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)
defer ensureReaderClosed(serverResp)
if err != nil {
return response, err
return container.ContainerUpdateOKBody{}, err
}
var response container.ContainerUpdateOKBody
err = json.NewDecoder(serverResp.body).Decode(&response)
return response, err
}

View File

@ -33,6 +33,12 @@ func (cli *Client) ContainerWait(ctx context.Context, containerID string, condit
resultC := make(chan container.WaitResponse)
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),
// 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 (
"context"
"errors"
"fmt"
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/errdefs"
"github.com/pkg/errors"
)
// 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.
//
// Deprecated: this function was only used internally, and will be removed in the next release.
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
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 {
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 {
return types.HijackedResponse{}, err
}
conn, mediaType, err := cli.setupHijackConn(req, "tcp")
conn, mediaType, err := setupHijackConn(cli.dialer(), req, "tcp")
if err != nil {
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.
@ -41,16 +46,15 @@ func (cli *Client) DialHijack(ctx context.Context, url, proto string, meta map[s
}
req = cli.addHeaders(req, meta)
conn, _, err := cli.setupHijackConn(req, proto)
conn, _, err := setupHijackConn(cli.Dialer(), req, proto)
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()
req.Header.Set("Connection", "Upgrade")
req.Header.Set("Upgrade", proto)
dialer := cli.Dialer()
conn, err := dialer(ctx)
if err != nil {
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)
}
var mediaType string
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
return conn, resp.Header.Get("Content-Type"), nil
}
// hijackedConn wraps a net.Conn and is returned by setupHijackConn in the case

View File

@ -4,29 +4,106 @@ import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/url"
"github.com/docker/docker/api/types/image"
)
// ImageInspectWithRaw returns the image information and its raw representation.
func (cli *Client) ImageInspectWithRaw(ctx context.Context, imageID string) (image.InspectResponse, []byte, error) {
// ImageInspectOption is a type representing functional options for the image inspect operation.
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 == "" {
return image.InspectResponse{}, nil, 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
return image.InspectResponse{}, objectNotFoundError{object: "image", id: imageID}
}
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 {
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
rdr := bytes.NewReader(body)
err = json.NewDecoder(rdr).Decode(&response)
return response, body, err
err = json.Unmarshal(buf.Bytes(), &response)
return response, 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.
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{
Container: containerID,
EndpointConfig: config,

View File

@ -8,6 +8,16 @@ import (
// 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 {
networkID, err := trimID("network", networkID)
if err != nil {
return err
}
containerID, err = trimID("container", containerID)
if err != nil {
return err
}
nd := network.DisconnectOptions{
Container: containerID,
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.
func (cli *Client) NetworkInspectWithRaw(ctx context.Context, networkID string, options network.InspectOptions) (network.Inspect, []byte, error) {
if networkID == "" {
return network.Inspect{}, nil, objectNotFoundError{object: "network", id: networkID}
networkID, err := trimID("network", networkID)
if err != nil {
return network.Inspect{}, nil, err
}
query := url.Values{}
if options.Verbose {

View File

@ -4,6 +4,10 @@ import "context"
// NetworkRemove removes an existent network from the docker host.
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)
defer ensureReaderClosed(resp)
return err

View File

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

View File

@ -9,6 +9,11 @@ import (
// NodeRemove removes a Node.
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{}
if options.Force {
query.Set("force", "1")

View File

@ -9,6 +9,11 @@ import (
// NodeUpdate updates a Node.
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.Set("version", version.String())
resp, err := cli.post(ctx, "/nodes/"+nodeID+"/update", query, node, nil)

View File

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

View File

@ -9,6 +9,10 @@ import (
// PluginDisable disables a plugin
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{}
if options.Force {
query.Set("force", "1")

View File

@ -10,6 +10,10 @@ import (
// PluginEnable enables a plugin
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.Set("timeout", strconv.Itoa(options.Timeout))

View File

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

View File

@ -10,6 +10,10 @@ import (
// PluginPush pushes a plugin to a registry
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{
registry.AuthHeader: {registryAuth},
})

View File

@ -9,6 +9,11 @@ import (
// PluginRemove removes a plugin
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{}
if options.Force {
query.Set("force", "1")

View File

@ -6,6 +6,11 @@ import (
// PluginSet modifies settings for an existing plugin
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)
ensureReaderClosed(resp)
return err

View File

@ -13,7 +13,12 @@ import (
)
// 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 {
return nil, err
}

View File

@ -154,21 +154,24 @@ func (cli *Client) doRequest(req *http.Request) (serverResponse, error) {
return serverResp, err
}
if uErr, ok := err.(*url.Error); ok {
if nErr, ok := uErr.Err.(*net.OpError); ok {
var uErr *url.Error
if errors.As(err, &uErr) {
var nErr *net.OpError
if errors.As(uErr.Err, &nErr) {
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)}
}
}
}
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)?
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") {
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 {
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 {
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)))
}
return errors.Wrap(daemonErr, "Error response from daemon")

View File

@ -11,11 +11,12 @@ import (
// SecretInspectWithRaw returns the secret information with raw data
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
}
if id == "" {
return swarm.Secret{}, nil, objectNotFoundError{object: "secret", id: id}
if err := cli.NewVersionError(ctx, "1.25", "secret inspect"); err != nil {
return swarm.Secret{}, nil, err
}
resp, err := cli.get(ctx, "/secrets/"+id, nil, nil)
defer ensureReaderClosed(resp)

View File

@ -4,6 +4,10 @@ import "context"
// SecretRemove removes a secret.
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 {
return err
}

View File

@ -9,6 +9,10 @@ import (
// SecretUpdate attempts to update a secret.
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 {
return err
}

View File

@ -14,9 +14,11 @@ import (
// 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) {
if serviceID == "" {
return swarm.Service{}, nil, objectNotFoundError{object: "service", id: serviceID}
serviceID, err := trimID("service", serviceID)
if err != nil {
return swarm.Service{}, nil, err
}
query := url.Values{}
query.Set("insertDefaults", fmt.Sprintf("%v", opts.InsertDefaults))
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.
// 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) {
serviceID, err := trimID("service", serviceID)
if err != nil {
return nil, err
}
query := url.Values{}
if options.ShowStdout {
query.Set("stdout", "1")

View File

@ -4,6 +4,11 @@ import "context"
// ServiceRemove kills and removes a service.
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)
defer ensureReaderClosed(resp)
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
// 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) {
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),
// 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
// the API request is made.
if err := cli.checkVersion(ctx); err != nil {
return response, err
return swarm.ServiceUpdateResponse{}, err
}
query := url.Values{}
@ -39,7 +42,7 @@ func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version
query.Set("version", version.String())
if err := validateServiceSpec(service); err != nil {
return response, err
return swarm.ServiceUpdateResponse{}, err
}
// 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)
defer ensureReaderClosed(resp)
if err != nil {
return response, err
return swarm.ServiceUpdateResponse{}, err
}
response := swarm.ServiceUpdateResponse{}
err = json.NewDecoder(resp.body).Decode(&response)
if resolveWarning != "" {
response.Warnings = append(response.Warnings, resolveWarning)

View File

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

View File

@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"net/url"
"strings"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/errdefs"
@ -13,6 +14,23 @@ import (
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.
func getDockerOS(serverHeader string) 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
func (cli *Client) VolumeInspectWithRaw(ctx context.Context, volumeID string) (volume.Volume, []byte, error) {
if volumeID == "" {
return volume.Volume{}, nil, objectNotFoundError{object: "volume", id: volumeID}
volumeID, err := trimID("volume", volumeID)
if err != nil {
return volume.Volume{}, nil, err
}
var vol volume.Volume

View File

@ -9,6 +9,11 @@ import (
// VolumeRemove removes a volume from the docker host.
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{}
if force {
// 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
// only some fields can be updated.
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 {
return err
}

View File

@ -14,7 +14,9 @@ func (e errNotFound) Unwrap() 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 {
if err == nil || IsNotFound(err) {
return err
@ -34,7 +36,9 @@ func (e errInvalidParameter) Unwrap() 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 {
if err == nil || IsInvalidParameter(err) {
return err
@ -54,7 +58,9 @@ func (e errConflict) Unwrap() 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 {
if err == nil || IsConflict(err) {
return err
@ -74,7 +80,9 @@ func (e errUnauthorized) Unwrap() 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 {
if err == nil || IsUnauthorized(err) {
return err
@ -94,7 +102,9 @@ func (e errUnavailable) Unwrap() 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 {
if err == nil || IsUnavailable(err) {
return err
@ -114,7 +124,9 @@ func (e errForbidden) Unwrap() 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 {
if err == nil || IsForbidden(err) {
return err
@ -134,7 +146,9 @@ func (e errSystem) Unwrap() 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 {
if err == nil || IsSystem(err) {
return err
@ -154,7 +168,9 @@ func (e errNotModified) Unwrap() 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 {
if err == nil || IsNotModified(err) {
return err
@ -174,7 +190,9 @@ func (e errNotImplemented) Unwrap() 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 {
if err == nil || IsNotImplemented(err) {
return err
@ -194,7 +212,9 @@ func (e errUnknown) Unwrap() 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 {
if err == nil || IsUnknown(err) {
return err
@ -214,7 +234,9 @@ func (e errCancelled) Unwrap() 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 {
if err == nil || IsCancelled(err) {
return err
@ -234,7 +256,9 @@ func (e errDeadline) Unwrap() 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 {
if err == nil || IsDeadline(err) {
return err
@ -254,7 +278,9 @@ func (e errDataLoss) Unwrap() 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 {
if err == nil || IsDataLoss(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 {
_, ok := getImplementer(err).(ErrNotFound)
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 {
_, ok := getImplementer(err).(ErrInvalidParameter)
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 {
_, ok := getImplementer(err).(ErrConflict)
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 {
_, ok := getImplementer(err).(ErrUnauthorized)
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 {
_, ok := getImplementer(err).(ErrUnavailable)
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 {
_, ok := getImplementer(err).(ErrForbidden)
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 {
_, ok := getImplementer(err).(ErrSystem)
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 {
_, ok := getImplementer(err).(ErrNotModified)
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 {
_, ok := getImplementer(err).(ErrNotImplemented)
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 {
_, ok := getImplementer(err).(ErrUnknown)
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 {
_, ok := getImplementer(err).(ErrCancelled)
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 {
_, ok := getImplementer(err).(ErrDeadline)
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 {
_, ok := getImplementer(err).(ErrDataLoss)
return ok

View File

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

View File

@ -2,10 +2,6 @@ package ioutils // import "github.com/docker/docker/pkg/ioutils"
import (
"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"
"runtime/debug"
"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/memory
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
github.com/docker/docker/api
github.com/docker/docker/api/types