vendor: github.com/docker/docker/v28.0.0-rc.1

full diff: https://github.com/docker/docker/compare/v27.5.1..v28.0.0-rc.1

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn 2025-01-18 11:18:08 +01:00
parent 70a5e266d1
commit b195b80ddf
No known key found for this signature in database
GPG Key ID: 76698F39D527CE8C
182 changed files with 2914 additions and 3093 deletions

View File

@ -835,7 +835,7 @@ func remoteDigestWithMoby(ctx context.Context, d *driver.DriverHandle, name stri
if err != nil { if err != nil {
return "", err return "", err
} }
img, _, err := api.ImageInspectWithRaw(ctx, name) img, err := api.ImageInspect(ctx, name)
if err != nil { if err != nil {
return "", err return "", err
} }

View File

@ -41,7 +41,7 @@ import (
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
dockeropts "github.com/docker/cli/opts" dockeropts "github.com/docker/cli/opts"
"github.com/docker/docker/api/types/versions" "github.com/docker/docker/api/types/versions"
"github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/atomicwriter"
"github.com/moby/buildkit/client" "github.com/moby/buildkit/client"
"github.com/moby/buildkit/exporter/containerimage/exptypes" "github.com/moby/buildkit/exporter/containerimage/exptypes"
"github.com/moby/buildkit/frontend/subrequests" "github.com/moby/buildkit/frontend/subrequests"
@ -745,7 +745,7 @@ func writeMetadataFile(filename string, dt interface{}) error {
if err != nil { if err != nil {
return err return err
} }
return ioutils.AtomicWriteFile(filename, b, 0644) return atomicwriter.WriteFile(filename, b, 0644)
} }
func decodeExporterResponse(exporterResponse map[string]string) map[string]interface{} { func decodeExporterResponse(exporterResponse map[string]string) map[string]interface{} {

View File

@ -105,8 +105,9 @@ func (d *Driver) create(ctx context.Context, l progress.SubLogger) error {
}); err != nil { }); err != nil {
// image pulling failed, check if it exists in local image store. // image pulling failed, check if it exists in local image store.
// if not, return pulling error. otherwise log it. // if not, return pulling error. otherwise log it.
_, _, errInspect := d.DockerAPI.ImageInspectWithRaw(ctx, imageName) _, errInspect := d.DockerAPI.ImageInspect(ctx, imageName)
if errInspect != nil { found := errInspect == nil
if !found {
return err return err
} }
l.Wrap("pulling failed, using local image "+imageName, func() error { return nil }) l.Wrap("pulling failed, using local image "+imageName, func() error { return nil })

4
go.mod
View File

@ -19,7 +19,7 @@ require (
github.com/distribution/reference v0.6.0 github.com/distribution/reference v0.6.0
github.com/docker/cli v27.5.1+incompatible github.com/docker/cli v27.5.1+incompatible
github.com/docker/cli-docs-tool v0.9.0 github.com/docker/cli-docs-tool v0.9.0
github.com/docker/docker v27.5.1+incompatible github.com/docker/docker v28.0.0-rc.1+incompatible
github.com/docker/go-units v0.5.0 github.com/docker/go-units v0.5.0
github.com/gofrs/flock v0.12.1 github.com/gofrs/flock v0.12.1
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
@ -173,7 +173,7 @@ require (
golang.org/x/oauth2 v0.23.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect
golang.org/x/time v0.6.0 // indirect golang.org/x/time v0.6.0 // indirect
golang.org/x/tools v0.27.0 // indirect golang.org/x/tools v0.27.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20241021214115-324edc3d5d38 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/klog/v2 v2.130.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect

8
go.sum
View File

@ -129,8 +129,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.5.1+incompatible h1:4PYU5dnBYqRQi0294d1FBECqT9ECWeQAIfE8q4YnPY8= github.com/docker/docker v28.0.0-rc.1+incompatible h1:xUbdsVxJIFvyZ+958MzyyIT7VuHO4Ecao9hKhl7kGUc=
github.com/docker/docker v27.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v28.0.0-rc.1+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=
@ -572,8 +572,8 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 h1:T6rh4haD3GVYsgEfWExoCZA2o2FmbNyKpTuAxbEFPTg= google.golang.org/genproto/googleapis/api v0.0.0-20241021214115-324edc3d5d38 h1:2oV8dfuIkM1Ti7DwXc0BJfnwr9csz4TDXI9EmiI+Rbw=
google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:wp2WsuBYj6j8wUdo3ToZsdxxixbvQNAHqVJrTgi5E5M= google.golang.org/genproto/googleapis/api v0.0.0-20241021214115-324edc3d5d38/go.mod h1:vuAjtvlwkDKF6L1GQ0SokiRLCGFfeBUXWr/aFFkHACc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 h1:zciRKQ4kBpFgpfC5QQCVtnnNAcLIqweL7plyZRQHVpI= google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 h1:zciRKQ4kBpFgpfC5QQCVtnnNAcLIqweL7plyZRQHVpI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
google.golang.org/grpc v1.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=

View File

@ -8,7 +8,7 @@ import (
"sync" "sync"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/atomicwriter"
"github.com/moby/buildkit/cmd/buildkitd/config" "github.com/moby/buildkit/cmd/buildkitd/config"
"github.com/pelletier/go-toml" "github.com/pelletier/go-toml"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -106,7 +106,7 @@ func (c *Config) MkdirAll(dir string, perm os.FileMode) error {
// AtomicWriteFile writes data to a file within the config dir atomically // AtomicWriteFile writes data to a file within the config dir atomically
func (c *Config) AtomicWriteFile(filename string, data []byte, perm os.FileMode) error { func (c *Config) AtomicWriteFile(filename string, data []byte, perm os.FileMode) error {
f := filepath.Join(c.dir, filename) f := filepath.Join(c.dir, filename)
if err := ioutils.AtomicWriteFile(f, data, perm); err != nil { if err := atomicwriter.WriteFile(f, data, perm); err != nil {
return err return err
} }
if c.chowner == nil { if c.chowner == nil {

View File

@ -7,6 +7,7 @@ import (
"github.com/docker/buildx/util/progress" "github.com/docker/buildx/util/progress"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/docker/api/types/image"
dockerclient "github.com/docker/docker/client" dockerclient "github.com/docker/docker/client"
) )
@ -52,7 +53,7 @@ func (c *Client) LoadImage(ctx context.Context, name string, status progress.Wri
w.mu.Unlock() w.mu.Unlock()
} }
resp, err := dapi.ImageLoad(ctx, pr, false) resp, err := dapi.ImageLoad(ctx, pr, image.LoadOptions{})
defer close(done) defer close(done)
if err != nil { if err != nil {
handleErr(err) handleErr(err)

View File

@ -3,7 +3,7 @@ package api // import "github.com/docker/docker/api"
// Common constants for daemon and client. // Common constants for daemon and client.
const ( const (
// DefaultVersion of the current REST API. // DefaultVersion of the current REST API.
DefaultVersion = "1.47" DefaultVersion = "1.48"
// MinSupportedAPIVersion is the minimum API version that can be supported // MinSupportedAPIVersion is the minimum API version that can be supported
// by the API server, specified as "major.minor". Note that the daemon // by the API server, specified as "major.minor". Note that the daemon

File diff suppressed because it is too large Load Diff

View File

@ -11,7 +11,7 @@ import (
"github.com/docker/docker/api/types/registry" "github.com/docker/docker/api/types/registry"
) )
// NewHijackedResponse intializes a HijackedResponse type // NewHijackedResponse initializes a [HijackedResponse] type.
func NewHijackedResponse(conn net.Conn, mediaType string) HijackedResponse { func NewHijackedResponse(conn net.Conn, mediaType string) HijackedResponse {
return HijackedResponse{Conn: conn, Reader: bufio.NewReader(conn), mediaType: mediaType} return HijackedResponse{Conn: conn, Reader: bufio.NewReader(conn), mediaType: mediaType}
} }
@ -129,14 +129,6 @@ type ImageBuildResponse struct {
OSType string OSType string
} }
// RequestPrivilegeFunc is a function interface that
// clients can supply to retry operations after
// getting an authorization error.
// This function returns the registry authentication
// header value in base 64 format, or an error
// if the privilege request fails.
type RequestPrivilegeFunc func(context.Context) (string, error)
// NodeListOptions holds parameters to list nodes with. // NodeListOptions holds parameters to list nodes with.
type NodeListOptions struct { type NodeListOptions struct {
Filters filters.Args Filters filters.Args
@ -235,11 +227,18 @@ type PluginDisableOptions struct {
// PluginInstallOptions holds parameters to install a plugin. // PluginInstallOptions holds parameters to install a plugin.
type PluginInstallOptions struct { type PluginInstallOptions struct {
Disabled bool Disabled bool
AcceptAllPermissions bool AcceptAllPermissions bool
RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry
RemoteRef string // RemoteRef is the plugin name on the registry RemoteRef string // RemoteRef is the plugin name on the registry
PrivilegeFunc RequestPrivilegeFunc
// PrivilegeFunc is a function that clients can supply to retry operations
// after getting an authorization error. This function returns the registry
// authentication header value in base64 encoded format, or an error if the
// privilege request fails.
//
// For details, refer to [github.com/docker/docker/api/types/registry.RequestAuthConfig].
PrivilegeFunc func(context.Context) (string, error)
AcceptPermissionsFunc func(context.Context, PluginPrivileges) (bool, error) AcceptPermissionsFunc func(context.Context, PluginPrivileges) (bool, error)
Args []string Args []string
} }

View File

@ -4,6 +4,10 @@ import (
"io" "io"
"os" "os"
"time" "time"
"github.com/docker/docker/api/types/mount"
"github.com/docker/docker/api/types/storage"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
) )
// PruneReport contains the response for Engine API: // PruneReport contains the response for Engine API:
@ -42,3 +46,132 @@ type StatsResponseReader struct {
Body io.ReadCloser `json:"body"` Body io.ReadCloser `json:"body"`
OSType string `json:"ostype"` OSType string `json:"ostype"`
} }
// MountPoint represents a mount point configuration inside the container.
// This is used for reporting the mountpoints in use by a container.
type MountPoint struct {
// Type is the type of mount, see `Type<foo>` definitions in
// github.com/docker/docker/api/types/mount.Type
Type mount.Type `json:",omitempty"`
// Name is the name reference to the underlying data defined by `Source`
// e.g., the volume name.
Name string `json:",omitempty"`
// Source is the source location of the mount.
//
// For volumes, this contains the storage location of the volume (within
// `/var/lib/docker/volumes/`). For bind-mounts, and `npipe`, this contains
// the source (host) part of the bind-mount. For `tmpfs` mount points, this
// field is empty.
Source string
// Destination is the path relative to the container root (`/`) where the
// Source is mounted inside the container.
Destination string
// Driver is the volume driver used to create the volume (if it is a volume).
Driver string `json:",omitempty"`
// Mode is a comma separated list of options supplied by the user when
// creating the bind/volume mount.
//
// The default is platform-specific (`"z"` on Linux, empty on Windows).
Mode string
// RW indicates whether the mount is mounted writable (read-write).
RW bool
// Propagation describes how mounts are propagated from the host into the
// mount point, and vice-versa. Refer to the Linux kernel documentation
// for details:
// https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt
//
// This field is not used on Windows.
Propagation mount.Propagation
}
// State stores container's running state
// it's part of ContainerJSONBase and returned by "inspect" command
type State struct {
Status string // String representation of the container state. Can be one of "created", "running", "paused", "restarting", "removing", "exited", or "dead"
Running bool
Paused bool
Restarting bool
OOMKilled bool
Dead bool
Pid int
ExitCode int
Error string
StartedAt string
FinishedAt string
Health *Health `json:",omitempty"`
}
// Summary contains response of Engine API:
// GET "/containers/json"
type Summary struct {
ID string `json:"Id"`
Names []string
Image string
ImageID string
Command string
Created int64
Ports []Port
SizeRw int64 `json:",omitempty"`
SizeRootFs int64 `json:",omitempty"`
Labels map[string]string
State string
Status string
HostConfig struct {
NetworkMode string `json:",omitempty"`
Annotations map[string]string `json:",omitempty"`
}
NetworkSettings *NetworkSettingsSummary
Mounts []MountPoint
}
// ContainerJSONBase contains response of Engine API GET "/containers/{name:.*}/json"
// for API version 1.18 and older.
//
// TODO(thaJeztah): combine ContainerJSONBase and InspectResponse into a single struct.
// The split between ContainerJSONBase (ContainerJSONBase) and InspectResponse (InspectResponse)
// was done in commit 6deaa58ba5f051039643cedceee97c8695e2af74 (https://github.com/moby/moby/pull/13675).
// ContainerJSONBase contained all fields for API < 1.19, and InspectResponse
// held fields that were added in API 1.19 and up. Given that the minimum
// supported API version is now 1.24, we no longer use the separate type.
type ContainerJSONBase struct {
ID string `json:"Id"`
Created string
Path string
Args []string
State *State
Image string
ResolvConfPath string
HostnamePath string
HostsPath string
LogPath string
Name string
RestartCount int
Driver string
Platform string
MountLabel string
ProcessLabel string
AppArmorProfile string
ExecIDs []string
HostConfig *HostConfig
GraphDriver storage.DriverData
SizeRw *int64 `json:",omitempty"`
SizeRootFs *int64 `json:",omitempty"`
}
// InspectResponse is the response for the GET "/containers/{name:.*}/json"
// endpoint.
type InspectResponse struct {
*ContainerJSONBase
Mounts []MountPoint
Config *Config
NetworkSettings *NetworkSettings
// ImageManifestDescriptor is the descriptor of a platform-specific manifest of the image used to create the container.
ImageManifestDescriptor *ocispec.Descriptor `json:",omitempty"`
}

View File

@ -0,0 +1,26 @@
package container
import "time"
// Health states
const (
NoHealthcheck = "none" // Indicates there is no healthcheck
Starting = "starting" // Starting indicates that the container is not yet ready
Healthy = "healthy" // Healthy indicates that the container is running correctly
Unhealthy = "unhealthy" // Unhealthy indicates that the container has a problem
)
// Health stores information about the container's healthcheck results
type Health struct {
Status string // Status is one of [Starting], [Healthy] or [Unhealthy].
FailingStreak int // FailingStreak is the number of consecutive failures
Log []*HealthcheckResult // Log contains the last few results (oldest first)
}
// HealthcheckResult stores information about a single run of a healthcheck probe
type HealthcheckResult struct {
Start time.Time // Start is the time this check started
End time.Time // End is the time this check ended
ExitCode int // ExitCode meanings: 0=healthy, 1=unhealthy, 2=reserved (considered unhealthy), else=error running probe
Output string // Output from last check
}

View File

@ -0,0 +1,56 @@
package container
import (
"github.com/docker/docker/api/types/network"
"github.com/docker/go-connections/nat"
)
// NetworkSettings exposes the network settings in the api
type NetworkSettings struct {
NetworkSettingsBase
DefaultNetworkSettings
Networks map[string]*network.EndpointSettings
}
// NetworkSettingsBase holds networking state for a container when inspecting it.
type NetworkSettingsBase struct {
Bridge string // Bridge contains the name of the default bridge interface iff it was set through the daemon --bridge flag.
SandboxID string // SandboxID uniquely represents a container's network stack
SandboxKey string // SandboxKey identifies the sandbox
Ports nat.PortMap // Ports is a collection of PortBinding indexed by Port
// HairpinMode specifies if hairpin NAT should be enabled on the virtual interface
//
// Deprecated: This field is never set and will be removed in a future release.
HairpinMode bool
// LinkLocalIPv6Address is an IPv6 unicast address using the link-local prefix
//
// Deprecated: This field is never set and will be removed in a future release.
LinkLocalIPv6Address string
// LinkLocalIPv6PrefixLen is the prefix length of an IPv6 unicast address
//
// Deprecated: This field is never set and will be removed in a future release.
LinkLocalIPv6PrefixLen int
SecondaryIPAddresses []network.Address // Deprecated: This field is never set and will be removed in a future release.
SecondaryIPv6Addresses []network.Address // Deprecated: This field is never set and will be removed in a future release.
}
// DefaultNetworkSettings holds network information
// during the 2 release deprecation period.
// It will be removed in Docker 1.11.
type DefaultNetworkSettings struct {
EndpointID string // EndpointID uniquely represents a service endpoint in a Sandbox
Gateway string // Gateway holds the gateway address for the network
GlobalIPv6Address string // GlobalIPv6Address holds network's global IPv6 address
GlobalIPv6PrefixLen int // GlobalIPv6PrefixLen represents mask length of network's global IPv6 address
IPAddress string // IPAddress holds the IPv4 address for the network
IPPrefixLen int // IPPrefixLen represents mask length of network's IPv4 address
IPv6Gateway string // IPv6Gateway holds gateway address specific for IPv6
MacAddress string // MacAddress holds the MAC address for the network
}
// NetworkSettingsSummary provides a summary of container's networks
// in /containers/json
type NetworkSettingsSummary struct {
Networks map[string]*network.EndpointSettings
}

View File

@ -1,4 +1,4 @@
package types package container
// This file was generated by the swagger tool. // This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command // Editing this file might prove futile when you re-run the swagger generate command

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"`
@ -162,20 +170,8 @@ type Stats struct {
StorageStats StorageStats `json:"storage_stats,omitempty"` StorageStats StorageStats `json:"storage_stats,omitempty"`
// Shared stats // Shared stats
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"`
} Networks map[string]NetworkStats `json:"networks,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"`
} }

View File

@ -22,16 +22,3 @@ func (e invalidFilter) Error() string {
// InvalidParameter marks this error as ErrInvalidParameter // InvalidParameter marks this error as ErrInvalidParameter
func (e invalidFilter) InvalidParameter() {} func (e invalidFilter) InvalidParameter() {}
// unreachableCode is an error indicating that the code path was not expected to be reached.
type unreachableCode struct {
Filter string
Value []string
}
// System marks this error as ErrSystem
func (e unreachableCode) System() {}
func (e unreachableCode) Error() string {
return fmt.Sprintf("unreachable code reached for filter: %q with values: %s", e.Filter, e.Value)
}

View File

@ -200,7 +200,6 @@ func (args Args) Match(field, source string) bool {
// Error is not nil only if the filter values are not valid boolean or are conflicting. // Error is not nil only if the filter values are not valid boolean or are conflicting.
func (args Args) GetBoolOrDefault(key string, defaultValue bool) (bool, error) { func (args Args) GetBoolOrDefault(key string, defaultValue bool) (bool, error) {
fieldValues, ok := args.fields[key] fieldValues, ok := args.fields[key]
if !ok { if !ok {
return defaultValue, nil return defaultValue, nil
} }
@ -211,20 +210,11 @@ func (args Args) GetBoolOrDefault(key string, defaultValue bool) (bool, error) {
isFalse := fieldValues["0"] || fieldValues["false"] isFalse := fieldValues["0"] || fieldValues["false"]
isTrue := fieldValues["1"] || fieldValues["true"] isTrue := fieldValues["1"] || fieldValues["true"]
if isFalse == isTrue {
conflicting := isFalse && isTrue // Either no or conflicting truthy/falsy value were provided
invalid := !isFalse && !isTrue
if conflicting || invalid {
return defaultValue, &invalidFilter{key, args.Get(key)} return defaultValue, &invalidFilter{key, args.Get(key)}
} else if isFalse {
return false, nil
} else if isTrue {
return true, nil
} }
return isTrue, nil
// This code shouldn't be reached.
return defaultValue, &unreachableCode{Filter: key, Value: args.Get(key)}
} }
// ExactMatch returns true if the source matches exactly one of the values. // ExactMatch returns true if the source matches exactly one of the values.

View File

@ -0,0 +1,140 @@
package image
import (
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/storage"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
// RootFS returns Image's RootFS description including the layer IDs.
type RootFS struct {
Type string `json:",omitempty"`
Layers []string `json:",omitempty"`
}
// InspectResponse contains response of Engine API:
// GET "/images/{name:.*}/json"
type InspectResponse struct {
// ID is the content-addressable ID of an image.
//
// This identifier is a content-addressable digest calculated from the
// image's configuration (which includes the digests of layers used by
// the image).
//
// Note that this digest differs from the `RepoDigests` below, which
// holds digests of image manifests that reference the image.
ID string `json:"Id"`
// RepoTags is a list of image names/tags in the local image cache that
// reference this image.
//
// Multiple image tags can refer to the same image, and this list may be
// empty if no tags reference the image, in which case the image is
// "untagged", in which case it can still be referenced by its ID.
RepoTags []string
// RepoDigests is a list of content-addressable digests of locally available
// image manifests that the image is referenced from. Multiple manifests can
// refer to the same image.
//
// These digests are usually only available if the image was either pulled
// from a registry, or if the image was pushed to a registry, which is when
// the manifest is generated and its digest calculated.
RepoDigests []string
// Parent is the ID of the parent image.
//
// Depending on how the image was created, this field may be empty and
// is only set for images that were built/created locally. This field
// is empty if the image was pulled from an image registry.
Parent string
// Comment is an optional message that can be set when committing or
// importing the image.
Comment string
// Created is the date and time at which the image was created, formatted in
// RFC 3339 nano-seconds (time.RFC3339Nano).
//
// This information is only available if present in the image,
// and omitted otherwise.
Created string `json:",omitempty"`
// Container is the ID of the container that was used to create the image.
//
// Depending on how the image was created, this field may be empty.
//
// Deprecated: this field is omitted in API v1.45, but kept for backward compatibility.
Container string `json:",omitempty"`
// ContainerConfig is an optional field containing the configuration of the
// container that was last committed when creating the image.
//
// Previous versions of Docker builder used this field to store build cache,
// and it is not in active use anymore.
//
// Deprecated: this field is omitted in API v1.45, but kept for backward compatibility.
ContainerConfig *container.Config `json:",omitempty"`
// DockerVersion is the version of Docker that was used to build the image.
//
// Depending on how the image was created, this field may be empty.
DockerVersion string
// Author is the name of the author that was specified when committing the
// image, or as specified through MAINTAINER (deprecated) in the Dockerfile.
Author string
Config *container.Config
// Architecture is the hardware CPU architecture that the image runs on.
Architecture string
// Variant is the CPU architecture variant (presently ARM-only).
Variant string `json:",omitempty"`
// OS is the Operating System the image is built to run on.
Os string
// OsVersion is the version of the Operating System the image is built to
// run on (especially for Windows).
OsVersion string `json:",omitempty"`
// Size is the total size of the image including all layers it is composed of.
Size int64
// VirtualSize is the total size of the image including all layers it is
// composed of.
//
// Deprecated: this field is omitted in API v1.44, but kept for backward compatibility. Use Size instead.
VirtualSize int64 `json:"VirtualSize,omitempty"`
// GraphDriver holds information about the storage driver used to store the
// container's and image's filesystem.
GraphDriver storage.DriverData
// RootFS contains information about the image's RootFS, including the
// layer IDs.
RootFS RootFS
// Metadata of the image in the local cache.
//
// This information is local to the daemon, and not part of the image itself.
Metadata Metadata
// Descriptor is the OCI descriptor of the image target.
// It's only set if the daemon provides a multi-platform image store.
//
// 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

@ -38,7 +38,7 @@ type PullOptions struct {
// authentication header value in base64 encoded format, or an error if the // authentication header value in base64 encoded format, or an error if the
// privilege request fails. // privilege request fails.
// //
// Also see [github.com/docker/docker/api/types.RequestPrivilegeFunc]. // For details, refer to [github.com/docker/docker/api/types/registry.RequestAuthConfig].
PrivilegeFunc func(context.Context) (string, error) PrivilegeFunc func(context.Context) (string, error)
Platform string Platform string
} }
@ -53,7 +53,7 @@ type PushOptions struct {
// authentication header value in base64 encoded format, or an error if the // authentication header value in base64 encoded format, or an error if the
// privilege request fails. // privilege request fails.
// //
// Also see [github.com/docker/docker/api/types.RequestPrivilegeFunc]. // For details, refer to [github.com/docker/docker/api/types/registry.RequestAuthConfig].
PrivilegeFunc func(context.Context) (string, error) PrivilegeFunc func(context.Context) (string, error)
// Platform is an optional field that selects a specific platform to push // Platform is an optional field that selects a specific platform to push
@ -86,3 +86,31 @@ type RemoveOptions struct {
Force bool Force bool
PruneChildren bool PruneChildren bool
} }
// HistoryOptions holds parameters to get image history.
type HistoryOptions struct {
// Platform from the manifest list to use for history.
Platform *ocispec.Platform
}
// LoadOptions holds parameters to load images.
type LoadOptions struct {
// Quiet suppresses progress output
Quiet bool
// Platforms selects the platforms to load if the image is a
// multi-platform image and has multiple variants.
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
// multi-platform image and has multiple variants.
Platforms []ocispec.Platform
}

View File

@ -1,5 +1,7 @@
package image package image
import ocispec "github.com/opencontainers/image-spec/specs-go/v1"
type Summary struct { type Summary struct {
// Number of containers using this image. Includes both stopped and running // Number of containers using this image. Includes both stopped and running
@ -42,6 +44,13 @@ type Summary struct {
// Required: true // Required: true
ParentID string `json:"ParentId"` ParentID string `json:"ParentId"`
// Descriptor is the OCI descriptor of the image target.
// It's only set if the daemon provides a multi-platform image store.
//
// 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 // Manifests is a list of image manifests available in this image. It
// provides a more detailed view of the platform-specific image manifests or // provides a more detailed view of the platform-specific image manifests or
// other image-attached data like build attestations. // other image-attached data like build attestations.

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

@ -19,6 +19,12 @@ type EndpointSettings struct {
// generated address). // generated address).
MacAddress string MacAddress string
DriverOpts map[string]string DriverOpts map[string]string
// GwPriority determines which endpoint will provide the default gateway
// for the container. The endpoint with the highest priority will be used.
// If multiple endpoints have the same priority, they are lexicographically
// sorted based on their network name, and the one that sorts first is picked.
GwPriority int
// Operational data // Operational data
NetworkID string NetworkID string
EndpointID string EndpointID string

View File

@ -33,6 +33,7 @@ type CreateRequest struct {
type CreateOptions struct { type CreateOptions struct {
Driver string // Driver is the driver-name used to create the network (e.g. `bridge`, `overlay`) Driver string // Driver is the driver-name used to create the network (e.g. `bridge`, `overlay`)
Scope string // Scope describes the level at which the network exists (e.g. `swarm` for cluster-wide or `local` for machine level). Scope string // Scope describes the level at which the network exists (e.g. `swarm` for cluster-wide or `local` for machine level).
EnableIPv4 *bool `json:",omitempty"` // EnableIPv4 represents whether to enable IPv4.
EnableIPv6 *bool `json:",omitempty"` // EnableIPv6 represents whether to enable IPv6. EnableIPv6 *bool `json:",omitempty"` // EnableIPv6 represents whether to enable IPv6.
IPAM *IPAM // IPAM is the network's IP Address Management. IPAM *IPAM // IPAM is the network's IP Address Management.
Internal bool // Internal represents if the network is used internal only. Internal bool // Internal represents if the network is used internal only.
@ -76,7 +77,8 @@ type Inspect struct {
Created time.Time // Created is the time the network created Created time.Time // Created is the time the network created
Scope string // Scope describes the level at which the network exists (e.g. `swarm` for cluster-wide or `local` for machine level) Scope string // Scope describes the level at which the network exists (e.g. `swarm` for cluster-wide or `local` for machine level)
Driver string // Driver is the Driver name used to create the network (e.g. `bridge`, `overlay`) Driver string // Driver is the Driver name used to create the network (e.g. `bridge`, `overlay`)
EnableIPv6 bool // EnableIPv6 represents whether to enable IPv6 EnableIPv4 bool // EnableIPv4 represents whether IPv4 is enabled
EnableIPv6 bool // EnableIPv6 represents whether IPv6 is enabled
IPAM IPAM // IPAM is the network's IP Address Management IPAM IPAM // IPAM is the network's IP Address Management
Internal bool // Internal represents if the network is used internal only Internal bool // Internal represents if the network is used internal only
Attachable bool // Attachable represents if the global scope is manually attachable by regular containers from workers in swarm mode. Attachable bool // Attachable represents if the global scope is manually attachable by regular containers from workers in swarm mode.

View File

@ -1,17 +1,29 @@
package registry // import "github.com/docker/docker/api/types/registry" package registry // import "github.com/docker/docker/api/types/registry"
import ( import (
"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
// authorization credentials for registry operations (push/pull). // authorization credentials for registry operations (push/pull).
const AuthHeader = "X-Registry-Auth" const AuthHeader = "X-Registry-Auth"
// RequestAuthConfig is a function interface that clients can supply
// to retry operations after getting an authorization error.
//
// The function must return the [AuthHeader] value ([AuthConfig]), encoded
// in base64url format ([RFC4648, section 5]), which can be decoded by
// [DecodeAuthConfig].
//
// It must return an error if the privilege request fails.
//
// [RFC4648, section 5]: https://tools.ietf.org/html/rfc4648#section-5
type RequestAuthConfig func(context.Context) (string, error)
// AuthConfig contains authorization information for connecting to a Registry. // AuthConfig contains authorization information for connecting to a Registry.
type AuthConfig struct { type AuthConfig struct {
Username string `json:"username,omitempty"` Username string `json:"username,omitempty"`
@ -85,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

@ -9,11 +9,29 @@ import (
// ServiceConfig stores daemon registry services configuration. // ServiceConfig stores daemon registry services configuration.
type ServiceConfig struct { type ServiceConfig struct {
AllowNondistributableArtifactsCIDRs []*NetIPNet AllowNondistributableArtifactsCIDRs []*NetIPNet `json:"AllowNondistributableArtifactsCIDRs,omitempty"` // Deprecated: non-distributable artifacts are deprecated and enabled by default. This field will be removed in the next release.
AllowNondistributableArtifactsHostnames []string AllowNondistributableArtifactsHostnames []string `json:"AllowNondistributableArtifactsHostnames,omitempty"` // Deprecated: non-distributable artifacts are deprecated and enabled by default. This field will be removed in the next release.
InsecureRegistryCIDRs []*NetIPNet `json:"InsecureRegistryCIDRs"`
IndexConfigs map[string]*IndexInfo `json:"IndexConfigs"` InsecureRegistryCIDRs []*NetIPNet `json:"InsecureRegistryCIDRs"`
Mirrors []string IndexConfigs map[string]*IndexInfo `json:"IndexConfigs"`
Mirrors []string
}
// MarshalJSON implements a custom marshaler to include legacy fields
// in API responses.
func (sc ServiceConfig) MarshalJSON() ([]byte, error) {
tmp := map[string]interface{}{
"InsecureRegistryCIDRs": sc.InsecureRegistryCIDRs,
"IndexConfigs": sc.IndexConfigs,
"Mirrors": sc.Mirrors,
}
if sc.AllowNondistributableArtifactsCIDRs != nil {
tmp["AllowNondistributableArtifactsCIDRs"] = nil
}
if sc.AllowNondistributableArtifactsHostnames != nil {
tmp["AllowNondistributableArtifactsHostnames"] = nil
}
return json.Marshal(tmp)
} }
// NetIPNet is the net.IPNet type, which can be marshalled and // NetIPNet is the net.IPNet type, which can be marshalled and

View File

@ -10,11 +10,12 @@ import (
type SearchOptions struct { type SearchOptions struct {
RegistryAuth string RegistryAuth string
// PrivilegeFunc is a [types.RequestPrivilegeFunc] the client can // PrivilegeFunc is a function that clients can supply to retry operations
// supply to retry operations after getting an authorization error. // after getting an authorization error. This function returns the registry
// authentication header value in base64 encoded format, or an error if the
// privilege request fails.
// //
// It must return the registry authentication header value in base64 // For details, refer to [github.com/docker/docker/api/types/registry.RequestAuthConfig].
// format, or an error if the privilege request fails.
PrivilegeFunc func(context.Context) (string, error) PrivilegeFunc func(context.Context) (string, error)
Filters filters.Args Filters filters.Args
Limit int Limit int

View File

@ -1,13 +1,13 @@
package types package storage
// This file was generated by the swagger tool. // This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command // Editing this file might prove futile when you re-run the swagger generate command
// GraphDriverData Information about the storage driver used to store the container's and // DriverData Information about the storage driver used to store the container's and
// image's filesystem. // image's filesystem.
// //
// swagger:model GraphDriverData // swagger:model DriverData
type GraphDriverData struct { type DriverData struct {
// Low-level storage metadata, provided as key/value pairs. // Low-level storage metadata, provided as key/value pairs.
// //

View File

@ -29,8 +29,8 @@ type Info struct {
CPUSet bool CPUSet bool
PidsLimit bool PidsLimit bool
IPv4Forwarding bool IPv4Forwarding bool
BridgeNfIptables bool BridgeNfIptables bool `json:"BridgeNfIptables"` // Deprecated: netfilter module is now loaded on-demand and no longer during daemon startup, making this field obsolete. This field is always false and will be removed in the next release.
BridgeNfIP6tables bool `json:"BridgeNfIp6tables"` BridgeNfIP6tables bool `json:"BridgeNfIp6tables"` // Deprecated: netfilter module is now loaded on-demand and no longer during daemon startup, making this field obsolete. This field is always false and will be removed in the next release.
Debug bool Debug bool
NFd int NFd int
OomKillDisable bool OomKillDisable bool
@ -137,8 +137,13 @@ type PluginsInfo struct {
// Commit holds the Git-commit (SHA1) that a binary was built from, as reported // Commit holds the Git-commit (SHA1) that a binary was built from, as reported
// in the version-string of external tools, such as containerd, or runC. // in the version-string of external tools, such as containerd, or runC.
type Commit struct { type Commit struct {
ID string // ID is the actual commit ID of external tool. // ID is the actual commit ID or version of external tool.
Expected string // Expected is the commit ID of external tool expected by dockerd as set at build time. ID string
// Expected is the commit ID of external tool expected by dockerd as set at build time.
//
// Deprecated: this field is no longer used in API v1.49, but kept for backward-compatibility with older API versions.
Expected string
} }
// NetworkAddressPool is a temp struct used by [Info] struct. // NetworkAddressPool is a temp struct used by [Info] struct.

View File

@ -6,11 +6,8 @@ import (
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/mount"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/swarm" "github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/volume" "github.com/docker/docker/api/types/volume"
"github.com/docker/go-connections/nat"
) )
const ( const (
@ -21,145 +18,6 @@ const (
MediaTypeMultiplexedStream = "application/vnd.docker.multiplexed-stream" MediaTypeMultiplexedStream = "application/vnd.docker.multiplexed-stream"
) )
// RootFS returns Image's RootFS description including the layer IDs.
type RootFS struct {
Type string `json:",omitempty"`
Layers []string `json:",omitempty"`
}
// ImageInspect contains response of Engine API:
// GET "/images/{name:.*}/json"
type ImageInspect struct {
// ID is the content-addressable ID of an image.
//
// This identifier is a content-addressable digest calculated from the
// image's configuration (which includes the digests of layers used by
// the image).
//
// Note that this digest differs from the `RepoDigests` below, which
// holds digests of image manifests that reference the image.
ID string `json:"Id"`
// RepoTags is a list of image names/tags in the local image cache that
// reference this image.
//
// Multiple image tags can refer to the same image, and this list may be
// empty if no tags reference the image, in which case the image is
// "untagged", in which case it can still be referenced by its ID.
RepoTags []string
// RepoDigests is a list of content-addressable digests of locally available
// image manifests that the image is referenced from. Multiple manifests can
// refer to the same image.
//
// These digests are usually only available if the image was either pulled
// from a registry, or if the image was pushed to a registry, which is when
// the manifest is generated and its digest calculated.
RepoDigests []string
// Parent is the ID of the parent image.
//
// Depending on how the image was created, this field may be empty and
// is only set for images that were built/created locally. This field
// is empty if the image was pulled from an image registry.
Parent string
// Comment is an optional message that can be set when committing or
// importing the image.
Comment string
// Created is the date and time at which the image was created, formatted in
// RFC 3339 nano-seconds (time.RFC3339Nano).
//
// This information is only available if present in the image,
// and omitted otherwise.
Created string `json:",omitempty"`
// Container is the ID of the container that was used to create the image.
//
// Depending on how the image was created, this field may be empty.
//
// Deprecated: this field is omitted in API v1.45, but kept for backward compatibility.
Container string `json:",omitempty"`
// ContainerConfig is an optional field containing the configuration of the
// container that was last committed when creating the image.
//
// Previous versions of Docker builder used this field to store build cache,
// and it is not in active use anymore.
//
// Deprecated: this field is omitted in API v1.45, but kept for backward compatibility.
ContainerConfig *container.Config `json:",omitempty"`
// DockerVersion is the version of Docker that was used to build the image.
//
// Depending on how the image was created, this field may be empty.
DockerVersion string
// Author is the name of the author that was specified when committing the
// image, or as specified through MAINTAINER (deprecated) in the Dockerfile.
Author string
Config *container.Config
// Architecture is the hardware CPU architecture that the image runs on.
Architecture string
// Variant is the CPU architecture variant (presently ARM-only).
Variant string `json:",omitempty"`
// OS is the Operating System the image is built to run on.
Os string
// OsVersion is the version of the Operating System the image is built to
// run on (especially for Windows).
OsVersion string `json:",omitempty"`
// Size is the total size of the image including all layers it is composed of.
Size int64
// VirtualSize is the total size of the image including all layers it is
// composed of.
//
// Deprecated: this field is omitted in API v1.44, but kept for backward compatibility. Use Size instead.
VirtualSize int64 `json:"VirtualSize,omitempty"`
// GraphDriver holds information about the storage driver used to store the
// container's and image's filesystem.
GraphDriver GraphDriverData
// RootFS contains information about the image's RootFS, including the
// layer IDs.
RootFS RootFS
// Metadata of the image in the local cache.
//
// This information is local to the daemon, and not part of the image itself.
Metadata image.Metadata
}
// Container contains response of Engine API:
// GET "/containers/json"
type Container struct {
ID string `json:"Id"`
Names []string
Image string
ImageID string
Command string
Created int64
Ports []Port
SizeRw int64 `json:",omitempty"`
SizeRootFs int64 `json:",omitempty"`
Labels map[string]string
State string
Status string
HostConfig struct {
NetworkMode string `json:",omitempty"`
Annotations map[string]string `json:",omitempty"`
}
NetworkSettings *SummaryNetworkSettings
Mounts []MountPoint
}
// Ping contains response of Engine API: // Ping contains response of Engine API:
// GET "/_ping" // GET "/_ping"
type Ping struct { type Ping struct {
@ -205,176 +63,6 @@ type Version struct {
BuildTime string `json:",omitempty"` BuildTime string `json:",omitempty"`
} }
// HealthcheckResult stores information about a single run of a healthcheck probe
type HealthcheckResult struct {
Start time.Time // Start is the time this check started
End time.Time // End is the time this check ended
ExitCode int // ExitCode meanings: 0=healthy, 1=unhealthy, 2=reserved (considered unhealthy), else=error running probe
Output string // Output from last check
}
// Health states
const (
NoHealthcheck = "none" // Indicates there is no healthcheck
Starting = "starting" // Starting indicates that the container is not yet ready
Healthy = "healthy" // Healthy indicates that the container is running correctly
Unhealthy = "unhealthy" // Unhealthy indicates that the container has a problem
)
// Health stores information about the container's healthcheck results
type Health struct {
Status string // Status is one of Starting, Healthy or Unhealthy
FailingStreak int // FailingStreak is the number of consecutive failures
Log []*HealthcheckResult // Log contains the last few results (oldest first)
}
// ContainerState stores container's running state
// it's part of ContainerJSONBase and will return by "inspect" command
type ContainerState struct {
Status string // String representation of the container state. Can be one of "created", "running", "paused", "restarting", "removing", "exited", or "dead"
Running bool
Paused bool
Restarting bool
OOMKilled bool
Dead bool
Pid int
ExitCode int
Error string
StartedAt string
FinishedAt string
Health *Health `json:",omitempty"`
}
// ContainerJSONBase contains response of Engine API:
// GET "/containers/{name:.*}/json"
type ContainerJSONBase struct {
ID string `json:"Id"`
Created string
Path string
Args []string
State *ContainerState
Image string
ResolvConfPath string
HostnamePath string
HostsPath string
LogPath string
Node *ContainerNode `json:",omitempty"` // Deprecated: Node was only propagated by Docker Swarm standalone API. It sill be removed in the next release.
Name string
RestartCount int
Driver string
Platform string
MountLabel string
ProcessLabel string
AppArmorProfile string
ExecIDs []string
HostConfig *container.HostConfig
GraphDriver GraphDriverData
SizeRw *int64 `json:",omitempty"`
SizeRootFs *int64 `json:",omitempty"`
}
// ContainerJSON is newly used struct along with MountPoint
type ContainerJSON struct {
*ContainerJSONBase
Mounts []MountPoint
Config *container.Config
NetworkSettings *NetworkSettings
}
// NetworkSettings exposes the network settings in the api
type NetworkSettings struct {
NetworkSettingsBase
DefaultNetworkSettings
Networks map[string]*network.EndpointSettings
}
// SummaryNetworkSettings provides a summary of container's networks
// in /containers/json
type SummaryNetworkSettings struct {
Networks map[string]*network.EndpointSettings
}
// NetworkSettingsBase holds networking state for a container when inspecting it.
type NetworkSettingsBase struct {
Bridge string // Bridge contains the name of the default bridge interface iff it was set through the daemon --bridge flag.
SandboxID string // SandboxID uniquely represents a container's network stack
SandboxKey string // SandboxKey identifies the sandbox
Ports nat.PortMap // Ports is a collection of PortBinding indexed by Port
// HairpinMode specifies if hairpin NAT should be enabled on the virtual interface
//
// Deprecated: This field is never set and will be removed in a future release.
HairpinMode bool
// LinkLocalIPv6Address is an IPv6 unicast address using the link-local prefix
//
// Deprecated: This field is never set and will be removed in a future release.
LinkLocalIPv6Address string
// LinkLocalIPv6PrefixLen is the prefix length of an IPv6 unicast address
//
// Deprecated: This field is never set and will be removed in a future release.
LinkLocalIPv6PrefixLen int
SecondaryIPAddresses []network.Address // Deprecated: This field is never set and will be removed in a future release.
SecondaryIPv6Addresses []network.Address // Deprecated: This field is never set and will be removed in a future release.
}
// DefaultNetworkSettings holds network information
// during the 2 release deprecation period.
// It will be removed in Docker 1.11.
type DefaultNetworkSettings struct {
EndpointID string // EndpointID uniquely represents a service endpoint in a Sandbox
Gateway string // Gateway holds the gateway address for the network
GlobalIPv6Address string // GlobalIPv6Address holds network's global IPv6 address
GlobalIPv6PrefixLen int // GlobalIPv6PrefixLen represents mask length of network's global IPv6 address
IPAddress string // IPAddress holds the IPv4 address for the network
IPPrefixLen int // IPPrefixLen represents mask length of network's IPv4 address
IPv6Gateway string // IPv6Gateway holds gateway address specific for IPv6
MacAddress string // MacAddress holds the MAC address for the network
}
// MountPoint represents a mount point configuration inside the container.
// This is used for reporting the mountpoints in use by a container.
type MountPoint struct {
// Type is the type of mount, see `Type<foo>` definitions in
// github.com/docker/docker/api/types/mount.Type
Type mount.Type `json:",omitempty"`
// Name is the name reference to the underlying data defined by `Source`
// e.g., the volume name.
Name string `json:",omitempty"`
// Source is the source location of the mount.
//
// For volumes, this contains the storage location of the volume (within
// `/var/lib/docker/volumes/`). For bind-mounts, and `npipe`, this contains
// the source (host) part of the bind-mount. For `tmpfs` mount points, this
// field is empty.
Source string
// Destination is the path relative to the container root (`/`) where the
// Source is mounted inside the container.
Destination string
// Driver is the volume driver used to create the volume (if it is a volume).
Driver string `json:",omitempty"`
// Mode is a comma separated list of options supplied by the user when
// creating the bind/volume mount.
//
// The default is platform-specific (`"z"` on Linux, empty on Windows).
Mode string
// RW indicates whether the mount is mounted writable (read-write).
RW bool
// Propagation describes how mounts are propagated from the host into the
// mount point, and vice-versa. Refer to the Linux kernel documentation
// for details:
// https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt
//
// This field is not used on Windows.
Propagation mount.Propagation
}
// DiskUsageObject represents an object type used for disk usage query filtering. // DiskUsageObject represents an object type used for disk usage query filtering.
type DiskUsageObject string type DiskUsageObject string
@ -401,7 +89,7 @@ type DiskUsageOptions struct {
type DiskUsage struct { type DiskUsage struct {
LayersSize int64 LayersSize int64
Images []*image.Summary Images []*image.Summary
Containers []*Container Containers []*container.Summary
Volumes []*volume.Volume Volumes []*volume.Volume
BuildCache []*BuildCache BuildCache []*BuildCache
BuilderSize int64 `json:",omitempty"` // Deprecated: deprecated in API 1.38, and no longer used since API 1.40. BuilderSize int64 `json:",omitempty"` // Deprecated: deprecated in API 1.38, and no longer used since API 1.40.
@ -481,9 +169,11 @@ 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
Filters filters.Args 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

@ -1,210 +1,109 @@
package types package types
import ( import (
"context"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/events"
"github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/network" "github.com/docker/docker/api/types/storage"
"github.com/docker/docker/api/types/registry"
"github.com/docker/docker/api/types/volume"
) )
// ImagesPruneReport contains the response for Engine API: // ContainerJSONBase contains response of Engine API GET "/containers/{name:.*}/json"
// POST "/images/prune" // for API version 1.18 and older.
// //
// Deprecated: use [image.PruneReport]. // Deprecated: use [container.InspectResponse] or [container.ContainerJSONBase]. It will be removed in the next release.
type ImagesPruneReport = image.PruneReport type ContainerJSONBase = container.ContainerJSONBase
// VolumesPruneReport contains the response for Engine API: // ContainerJSON is the response for the GET "/containers/{name:.*}/json"
// POST "/volumes/prune". // endpoint.
// //
// Deprecated: use [volume.PruneReport]. // Deprecated: use [container.InspectResponse]. It will be removed in the next release.
type VolumesPruneReport = volume.PruneReport type ContainerJSON = container.InspectResponse
// NetworkCreateRequest is the request message sent to the server for network create call. // Container contains response of Engine API:
// GET "/containers/json"
// //
// Deprecated: use [network.CreateRequest]. // Deprecated: use [container.Summary].
type NetworkCreateRequest = network.CreateRequest type Container = container.Summary
// NetworkCreate is the expected body of the "create network" http request message // ContainerState stores container's running state
// //
// Deprecated: use [network.CreateOptions]. // Deprecated: use [container.State].
type NetworkCreate = network.CreateOptions type ContainerState = container.State
// NetworkListOptions holds parameters to filter the list of networks with. // NetworkSettings exposes the network settings in the api.
// //
// Deprecated: use [network.ListOptions]. // Deprecated: use [container.NetworkSettings].
type NetworkListOptions = network.ListOptions type NetworkSettings = container.NetworkSettings
// NetworkCreateResponse is the response message sent by the server for network create call. // NetworkSettingsBase holds networking state for a container when inspecting it.
// //
// Deprecated: use [network.CreateResponse]. // Deprecated: use [container.NetworkSettingsBase].
type NetworkCreateResponse = network.CreateResponse type NetworkSettingsBase = container.NetworkSettingsBase
// NetworkInspectOptions holds parameters to inspect network. // DefaultNetworkSettings holds network information
// during the 2 release deprecation period.
// It will be removed in Docker 1.11.
// //
// Deprecated: use [network.InspectOptions]. // Deprecated: use [container.DefaultNetworkSettings].
type NetworkInspectOptions = network.InspectOptions type DefaultNetworkSettings = container.DefaultNetworkSettings
// NetworkConnect represents the data to be used to connect a container to the network // SummaryNetworkSettings provides a summary of container's networks
// in /containers/json.
// //
// Deprecated: use [network.ConnectOptions]. // Deprecated: use [container.NetworkSettingsSummary].
type NetworkConnect = network.ConnectOptions type SummaryNetworkSettings = container.NetworkSettingsSummary
// NetworkDisconnect represents the data to be used to disconnect a container from the network // Health states
// const (
// Deprecated: use [network.DisconnectOptions]. NoHealthcheck = container.NoHealthcheck // Deprecated: use [container.NoHealthcheck].
type NetworkDisconnect = network.DisconnectOptions Starting = container.Starting // Deprecated: use [container.Starting].
Healthy = container.Healthy // Deprecated: use [container.Healthy].
Unhealthy = container.Unhealthy // Deprecated: use [container.Unhealthy].
)
// EndpointResource contains network resources allocated and used for a container in a network. // Health stores information about the container's healthcheck results.
// //
// Deprecated: use [network.EndpointResource]. // Deprecated: use [container.Health].
type EndpointResource = network.EndpointResource type Health = container.Health
// NetworkResource is the body of the "get network" http response message/ // HealthcheckResult stores information about a single run of a healthcheck probe.
// //
// Deprecated: use [network.Inspect] or [network.Summary] (for list operations). // Deprecated: use [container.HealthcheckResult].
type NetworkResource = network.Inspect type HealthcheckResult = container.HealthcheckResult
// NetworksPruneReport contains the response for Engine API: // MountPoint represents a mount point configuration inside the container.
// POST "/networks/prune" // This is used for reporting the mountpoints in use by a container.
// //
// Deprecated: use [network.PruneReport]. // Deprecated: use [container.MountPoint].
type NetworksPruneReport = network.PruneReport type MountPoint = container.MountPoint
// ExecConfig is a small subset of the Config struct that holds the configuration // Port An open port on a container
// for the exec feature of docker.
// //
// Deprecated: use [container.ExecOptions]. // Deprecated: use [container.Port].
type ExecConfig = container.ExecOptions type Port = container.Port
// ExecStartCheck is a temp struct used by execStart // GraphDriverData Information about the storage driver used to store the container's and
// Config fields is part of ExecConfig in runconfig package // image's filesystem.
// //
// Deprecated: use [container.ExecStartOptions] or [container.ExecAttachOptions]. // Deprecated: use [storage.DriverData].
type ExecStartCheck = container.ExecStartOptions type GraphDriverData = storage.DriverData
// ContainerExecInspect holds information returned by exec inspect. // RootFS returns Image's RootFS description including the layer IDs.
// //
// Deprecated: use [container.ExecInspect]. // Deprecated: use [image.RootFS].
type ContainerExecInspect = container.ExecInspect type RootFS = image.RootFS
// ContainersPruneReport contains the response for Engine API: // ImageInspect contains response of Engine API:
// POST "/containers/prune" // GET "/images/{name:.*}/json"
// //
// Deprecated: use [container.PruneReport]. // Deprecated: use [image.InspectResponse].
type ContainersPruneReport = container.PruneReport type ImageInspect = image.InspectResponse
// ContainerPathStat is used to encode the header from // RequestPrivilegeFunc is a function interface that clients can supply to
// GET "/containers/{name:.*}/archive" // retry operations after getting an authorization error.
// "Name" is the file or directory name. // This function returns the registry authentication header value in base64
// format, or an error if the privilege request fails.
// //
// Deprecated: use [container.PathStat]. // Deprecated: moved to [github.com/docker/docker/api/types/registry.RequestAuthConfig].
type ContainerPathStat = container.PathStat type RequestPrivilegeFunc func(context.Context) (string, error)
// CopyToContainerOptions holds information
// about files to copy into a container.
//
// Deprecated: use [container.CopyToContainerOptions],
type CopyToContainerOptions = container.CopyToContainerOptions
// ContainerStats contains response of Engine API:
// GET "/stats"
//
// Deprecated: use [container.StatsResponseReader].
type ContainerStats = container.StatsResponseReader
// ThrottlingData stores CPU throttling stats of one running container.
// Not used on Windows.
//
// Deprecated: use [container.ThrottlingData].
type ThrottlingData = container.ThrottlingData
// CPUUsage stores All CPU stats aggregated since container inception.
//
// Deprecated: use [container.CPUUsage].
type CPUUsage = container.CPUUsage
// CPUStats aggregates and wraps all CPU related info of container
//
// Deprecated: use [container.CPUStats].
type CPUStats = container.CPUStats
// MemoryStats aggregates all memory stats since container inception on Linux.
// Windows returns stats for commit and private working set only.
//
// Deprecated: use [container.MemoryStats].
type MemoryStats = container.MemoryStats
// BlkioStatEntry is one small entity to store a piece of Blkio stats
// Not used on Windows.
//
// Deprecated: use [container.BlkioStatEntry].
type BlkioStatEntry = container.BlkioStatEntry
// BlkioStats stores All IO service stats for data read and write.
// This is a Linux specific structure as the differences between expressing
// block I/O on Windows and Linux are sufficiently significant to make
// little sense attempting to morph into a combined structure.
//
// Deprecated: use [container.BlkioStats].
type BlkioStats = container.BlkioStats
// StorageStats is the disk I/O stats for read/write on Windows.
//
// Deprecated: use [container.StorageStats].
type StorageStats = container.StorageStats
// NetworkStats aggregates the network stats of one container
//
// Deprecated: use [container.NetworkStats].
type NetworkStats = container.NetworkStats
// PidsStats contains the stats of a container's pids
//
// Deprecated: use [container.PidsStats].
type PidsStats = container.PidsStats
// Stats is Ultimate struct aggregating all types of stats of one container
//
// Deprecated: use [container.Stats].
type Stats = container.Stats
// StatsJSON is newly used Networks
//
// Deprecated: use [container.StatsResponse].
type StatsJSON = container.StatsResponse
// EventsOptions holds parameters to filter events with.
//
// Deprecated: use [events.ListOptions].
type EventsOptions = events.ListOptions
// ImageSearchOptions holds parameters to search images with.
//
// Deprecated: use [registry.SearchOptions].
type ImageSearchOptions = registry.SearchOptions
// ImageImportSource holds source information for ImageImport
//
// Deprecated: use [image.ImportSource].
type ImageImportSource image.ImportSource
// ImageLoadResponse returns information to the client about a load process.
//
// Deprecated: use [image.LoadResponse].
type ImageLoadResponse = image.LoadResponse
// ContainerNode stores information about the node that a container
// is running on. It's only used by the Docker Swarm standalone API.
//
// Deprecated: ContainerNode was used for the classic Docker Swarm standalone API. It will be removed in the next release.
type ContainerNode struct {
ID string
IPAddress string `json:"IP"`
Addr string
Name string
Cpus int
Memory int64
Labels map[string]string
}

View File

@ -17,13 +17,23 @@ func (cli *Client) BuildCachePrune(ctx context.Context, opts types.BuildCachePru
return nil, err return nil, err
} }
report := types.BuildCachePruneReport{}
query := url.Values{} query := url.Values{}
if opts.All { if opts.All {
query.Set("all", "1") 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) 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")
@ -37,6 +47,7 @@ func (cli *Client) BuildCachePrune(ctx context.Context, opts types.BuildCachePru
return nil, err return nil, err
} }
report := types.BuildCachePruneReport{}
if err := json.NewDecoder(serverResp.body).Decode(&report); err != nil { if err := json.NewDecoder(serverResp.body).Decode(&report); err != nil {
return nil, errors.Wrap(err, "error retrieving disk usage") return nil, errors.Wrap(err, "error retrieving disk usage")
} }

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
@ -56,10 +78,10 @@ type ContainerAPIClient interface {
ContainerExecResize(ctx context.Context, execID string, options container.ResizeOptions) error ContainerExecResize(ctx context.Context, execID string, options container.ResizeOptions) error
ContainerExecStart(ctx context.Context, execID string, options container.ExecStartOptions) error ContainerExecStart(ctx context.Context, execID string, options container.ExecStartOptions) error
ContainerExport(ctx context.Context, container string) (io.ReadCloser, error) ContainerExport(ctx context.Context, container string) (io.ReadCloser, error)
ContainerInspect(ctx context.Context, container string) (types.ContainerJSON, error) ContainerInspect(ctx context.Context, container string) (container.InspectResponse, error)
ContainerInspectWithRaw(ctx context.Context, container string, getSize bool) (types.ContainerJSON, []byte, error) ContainerInspectWithRaw(ctx context.Context, container string, getSize bool) (container.InspectResponse, []byte, error)
ContainerKill(ctx context.Context, container, signal string) error ContainerKill(ctx context.Context, container, signal string) error
ContainerList(ctx context.Context, options container.ListOptions) ([]types.Container, error) ContainerList(ctx context.Context, options container.ListOptions) ([]container.Summary, error)
ContainerLogs(ctx context.Context, container string, options container.LogsOptions) (io.ReadCloser, error) ContainerLogs(ctx context.Context, container string, options container.LogsOptions) (io.ReadCloser, error)
ContainerPause(ctx context.Context, container string) error ContainerPause(ctx context.Context, container string) error
ContainerRemove(ctx context.Context, container string, options container.RemoveOptions) error ContainerRemove(ctx context.Context, container string, options container.RemoveOptions) error
@ -91,16 +113,19 @@ type ImageAPIClient interface {
BuildCachePrune(ctx context.Context, opts types.BuildCachePruneOptions) (*types.BuildCachePruneReport, error) BuildCachePrune(ctx context.Context, opts types.BuildCachePruneOptions) (*types.BuildCachePruneReport, error)
BuildCancel(ctx context.Context, id string) error BuildCancel(ctx context.Context, id string) error
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) ([]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)
ImageInspectWithRaw(ctx context.Context, image string) (types.ImageInspect, []byte, 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) ImageList(ctx context.Context, options image.ListOptions) ([]image.Summary, error)
ImageLoad(ctx context.Context, input io.Reader, quiet bool) (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)
ImagePush(ctx context.Context, ref string, options image.PushOptions) (io.ReadCloser, error) ImagePush(ctx context.Context, ref string, options image.PushOptions) (io.ReadCloser, error)
ImageRemove(ctx context.Context, image string, options image.RemoveOptions) ([]image.DeleteResponse, error) ImageRemove(ctx context.Context, image string, options image.RemoveOptions) ([]image.DeleteResponse, error)
ImageSave(ctx context.Context, images []string, opts image.SaveOptions) (io.ReadCloser, error)
ImageSearch(ctx context.Context, term string, options registry.SearchOptions) ([]registry.SearchResult, error) ImageSearch(ctx context.Context, term string, options registry.SearchOptions) ([]registry.SearchResult, error)
ImageSave(ctx context.Context, images []string) (io.ReadCloser, error)
ImageTag(ctx context.Context, image, ref string) error ImageTag(ctx context.Context, image, ref string) error
ImagesPrune(ctx context.Context, pruneFilter filters.Args) (image.PruneReport, error) ImagesPrune(ctx context.Context, pruneFilter filters.Args) (image.PruneReport, 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

@ -5,6 +5,8 @@ import (
"encoding/json" "encoding/json"
"net/url" "net/url"
"path" "path"
"sort"
"strings"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network" "github.com/docker/docker/api/types/network"
@ -12,12 +14,6 @@ import (
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
) )
type configWrapper struct {
*container.Config
HostConfig *container.HostConfig
NetworkingConfig *network.NetworkingConfig
}
// ContainerCreate creates a new container based on the given configuration. // ContainerCreate creates a new container based on the given configuration.
// It can be associated with a name, but it's not mandatory. // It can be associated with a name, but it's not mandatory.
func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error) { func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error) {
@ -58,6 +54,9 @@ func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config
// When using API under 1.42, the Linux daemon doesn't respect the ConsoleSize // When using API under 1.42, the Linux daemon doesn't respect the ConsoleSize
hostConfig.ConsoleSize = [2]uint{0, 0} hostConfig.ConsoleSize = [2]uint{0, 0}
} }
hostConfig.CapAdd = normalizeCapabilities(hostConfig.CapAdd)
hostConfig.CapDrop = normalizeCapabilities(hostConfig.CapDrop)
} }
// Since API 1.44, the container-wide MacAddress is deprecated and will trigger a WARNING if it's specified. // Since API 1.44, the container-wide MacAddress is deprecated and will trigger a WARNING if it's specified.
@ -74,7 +73,7 @@ func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config
query.Set("name", containerName) query.Set("name", containerName)
} }
body := configWrapper{ body := container.CreateRequest{
Config: config, Config: config,
HostConfig: hostConfig, HostConfig: hostConfig,
NetworkingConfig: networkingConfig, NetworkingConfig: networkingConfig,
@ -114,3 +113,42 @@ func hasEndpointSpecificMacAddress(networkingConfig *network.NetworkingConfig) b
} }
return false return false
} }
// allCapabilities is a magic value for "all capabilities"
const allCapabilities = "ALL"
// normalizeCapabilities normalizes capabilities to their canonical form,
// removes duplicates, and sorts the results.
//
// It is similar to [github.com/docker/docker/oci/caps.NormalizeLegacyCapabilities],
// but performs no validation based on supported capabilities.
func normalizeCapabilities(caps []string) []string {
var normalized []string
unique := make(map[string]struct{})
for _, c := range caps {
c = normalizeCap(c)
if _, ok := unique[c]; ok {
continue
}
unique[c] = struct{}{}
normalized = append(normalized, c)
}
sort.Strings(normalized)
return normalized
}
// normalizeCap normalizes a capability to its canonical format by upper-casing
// and adding a "CAP_" prefix (if not yet present). It also accepts the "ALL"
// magic-value.
func normalizeCap(cap string) string {
cap = strings.ToUpper(cap)
if cap == allCapabilities {
return cap
}
if !strings.HasPrefix(cap, "CAP_") {
cap = "CAP_" + cap
}
return cap
}

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

@ -7,30 +7,34 @@ import (
"io" "io"
"net/url" "net/url"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container"
) )
// ContainerInspect returns the container information. // ContainerInspect returns the container information.
func (cli *Client) ContainerInspect(ctx context.Context, containerID string) (types.ContainerJSON, error) { func (cli *Client) ContainerInspect(ctx context.Context, containerID string) (container.InspectResponse, error) {
if containerID == "" { containerID, err := trimID("container", containerID)
return types.ContainerJSON{}, 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 {
return types.ContainerJSON{}, err return container.InspectResponse{}, err
} }
var response types.ContainerJSON var response container.InspectResponse
err = json.NewDecoder(serverResp.body).Decode(&response) err = json.NewDecoder(serverResp.body).Decode(&response)
return response, err return response, err
} }
// 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) (types.ContainerJSON, []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 types.ContainerJSON{}, 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")
@ -38,15 +42,15 @@ func (cli *Client) ContainerInspectWithRaw(ctx context.Context, containerID stri
serverResp, err := cli.get(ctx, "/containers/"+containerID+"/json", query, nil) serverResp, err := cli.get(ctx, "/containers/"+containerID+"/json", query, nil)
defer ensureReaderClosed(serverResp) defer ensureReaderClosed(serverResp)
if err != nil { if err != nil {
return types.ContainerJSON{}, nil, err return container.InspectResponse{}, nil, err
} }
body, err := io.ReadAll(serverResp.body) body, err := io.ReadAll(serverResp.body)
if err != nil { if err != nil {
return types.ContainerJSON{}, nil, err return container.InspectResponse{}, nil, err
} }
var response types.ContainerJSON var response container.InspectResponse
rdr := bytes.NewReader(body) rdr := bytes.NewReader(body)
err = json.NewDecoder(rdr).Decode(&response) err = json.NewDecoder(rdr).Decode(&response)
return response, body, err return response, body, err

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

@ -6,13 +6,12 @@ import (
"net/url" "net/url"
"strconv" "strconv"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
) )
// ContainerList returns the list of containers in the docker host. // ContainerList returns the list of containers in the docker host.
func (cli *Client) ContainerList(ctx context.Context, options container.ListOptions) ([]types.Container, error) { func (cli *Client) ContainerList(ctx context.Context, options container.ListOptions) ([]container.Summary, error) {
query := url.Values{} query := url.Values{}
if options.All { if options.All {
@ -51,7 +50,7 @@ func (cli *Client) ContainerList(ctx context.Context, options container.ListOpti
return nil, err return nil, err
} }
var containers []types.Container var containers []container.Summary
err = json.NewDecoder(resp.body).Decode(&containers) err = json.NewDecoder(resp.body).Decode(&containers)
return containers, err return containers, 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) 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

@ -11,25 +11,24 @@ import (
// ContainersPrune requests the daemon to delete unused data // ContainersPrune requests the daemon to delete unused data
func (cli *Client) ContainersPrune(ctx context.Context, pruneFilters filters.Args) (container.PruneReport, error) { func (cli *Client) ContainersPrune(ctx context.Context, pruneFilters filters.Args) (container.PruneReport, error) {
var report container.PruneReport
if err := cli.NewVersionError(ctx, "1.25", "container prune"); err != nil { if err := cli.NewVersionError(ctx, "1.25", "container prune"); err != nil {
return report, err return container.PruneReport{}, err
} }
query, err := getFiltersQuery(pruneFilters) query, err := getFiltersQuery(pruneFilters)
if err != nil { if err != nil {
return report, err return container.PruneReport{}, err
} }
serverResp, err := cli.post(ctx, "/containers/prune", query, nil, nil) serverResp, err := cli.post(ctx, "/containers/prune", query, nil, nil)
defer ensureReaderClosed(serverResp) defer ensureReaderClosed(serverResp)
if err != nil { if err != nil {
return report, err return container.PruneReport{}, err
} }
var report container.PruneReport
if err := json.NewDecoder(serverResp.body).Decode(&report); err != nil { if err := json.NewDecoder(serverResp.body).Decode(&report); err != nil {
return report, fmt.Errorf("Error retrieving disk usage: %v", err) return container.PruneReport{}, fmt.Errorf("Error retrieving disk usage: %v", err)
} }
return report, nil return report, nil

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,18 +10,27 @@ 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)
} }
func (cli *Client) resize(ctx context.Context, basePath string, height, width uint) error { func (cli *Client) resize(ctx context.Context, basePath string, height, width uint) error {
// FIXME(thaJeztah): the API / backend accepts uint32, but container.ResizeOptions uses uint.
query := url.Values{} query := url.Values{}
query.Set("h", strconv.Itoa(int(height))) query.Set("h", strconv.FormatUint(uint64(height), 10))
query.Set("w", strconv.Itoa(int(width))) query.Set("w", strconv.FormatUint(uint64(width), 10))
resp, err := cli.post(ctx, basePath+"/resize", query, nil, nil) resp, err := cli.post(ctx, basePath+"/resize", query, nil, nil)
ensureReaderClosed(resp) ensureReaderClosed(resp)

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

@ -12,6 +12,7 @@ import (
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network"
) )
// ImageBuild sends a request to the daemon to build images. // ImageBuild sends a request to the daemon to build images.
@ -44,10 +45,15 @@ func (cli *Client) ImageBuild(ctx context.Context, buildContext io.Reader, optio
} }
func (cli *Client) imageBuildOptionsToQuery(ctx context.Context, options types.ImageBuildOptions) (url.Values, error) { func (cli *Client) imageBuildOptionsToQuery(ctx context.Context, options types.ImageBuildOptions) (url.Values, error) {
query := url.Values{ query := url.Values{}
"t": options.Tags, if len(options.Tags) > 0 {
"securityopt": options.SecurityOpt, query["t"] = options.Tags
"extrahosts": options.ExtraHosts, }
if len(options.SecurityOpt) > 0 {
query["securityopt"] = options.SecurityOpt
}
if len(options.ExtraHosts) > 0 {
query["extrahosts"] = options.ExtraHosts
} }
if options.SuppressOutput { if options.SuppressOutput {
query.Set("q", "1") query.Set("q", "1")
@ -58,9 +64,11 @@ func (cli *Client) imageBuildOptionsToQuery(ctx context.Context, options types.I
if options.NoCache { if options.NoCache {
query.Set("nocache", "1") query.Set("nocache", "1")
} }
if options.Remove { if !options.Remove {
query.Set("rm", "1") // only send value when opting out because the daemon's default is
} else { // to remove intermediate containers after a successful build,
//
// TODO(thaJeztah): deprecate "Remove" option, and provide a "NoRemove" or "Keep" option instead.
query.Set("rm", "0") query.Set("rm", "0")
} }
@ -83,42 +91,70 @@ func (cli *Client) imageBuildOptionsToQuery(ctx context.Context, options types.I
query.Set("isolation", string(options.Isolation)) query.Set("isolation", string(options.Isolation))
} }
query.Set("cpusetcpus", options.CPUSetCPUs) if options.CPUSetCPUs != "" {
query.Set("networkmode", options.NetworkMode) query.Set("cpusetcpus", options.CPUSetCPUs)
query.Set("cpusetmems", options.CPUSetMems)
query.Set("cpushares", strconv.FormatInt(options.CPUShares, 10))
query.Set("cpuquota", strconv.FormatInt(options.CPUQuota, 10))
query.Set("cpuperiod", strconv.FormatInt(options.CPUPeriod, 10))
query.Set("memory", strconv.FormatInt(options.Memory, 10))
query.Set("memswap", strconv.FormatInt(options.MemorySwap, 10))
query.Set("cgroupparent", options.CgroupParent)
query.Set("shmsize", strconv.FormatInt(options.ShmSize, 10))
query.Set("dockerfile", options.Dockerfile)
query.Set("target", options.Target)
ulimitsJSON, err := json.Marshal(options.Ulimits)
if err != nil {
return query, err
} }
query.Set("ulimits", string(ulimitsJSON)) if options.NetworkMode != "" && options.NetworkMode != network.NetworkDefault {
query.Set("networkmode", options.NetworkMode)
buildArgsJSON, err := json.Marshal(options.BuildArgs)
if err != nil {
return query, err
} }
query.Set("buildargs", string(buildArgsJSON)) if options.CPUSetMems != "" {
query.Set("cpusetmems", options.CPUSetMems)
labelsJSON, err := json.Marshal(options.Labels)
if err != nil {
return query, err
} }
query.Set("labels", string(labelsJSON)) if options.CPUShares != 0 {
query.Set("cpushares", strconv.FormatInt(options.CPUShares, 10))
cacheFromJSON, err := json.Marshal(options.CacheFrom) }
if err != nil { if options.CPUQuota != 0 {
return query, err query.Set("cpuquota", strconv.FormatInt(options.CPUQuota, 10))
}
if options.CPUPeriod != 0 {
query.Set("cpuperiod", strconv.FormatInt(options.CPUPeriod, 10))
}
if options.Memory != 0 {
query.Set("memory", strconv.FormatInt(options.Memory, 10))
}
if options.MemorySwap != 0 {
query.Set("memswap", strconv.FormatInt(options.MemorySwap, 10))
}
if options.CgroupParent != "" {
query.Set("cgroupparent", options.CgroupParent)
}
if options.ShmSize != 0 {
query.Set("shmsize", strconv.FormatInt(options.ShmSize, 10))
}
if options.Dockerfile != "" {
query.Set("dockerfile", options.Dockerfile)
}
if options.Target != "" {
query.Set("target", options.Target)
}
if len(options.Ulimits) != 0 {
ulimitsJSON, err := json.Marshal(options.Ulimits)
if err != nil {
return query, err
}
query.Set("ulimits", string(ulimitsJSON))
}
if len(options.BuildArgs) != 0 {
buildArgsJSON, err := json.Marshal(options.BuildArgs)
if err != nil {
return query, err
}
query.Set("buildargs", string(buildArgsJSON))
}
if len(options.Labels) != 0 {
labelsJSON, err := json.Marshal(options.Labels)
if err != nil {
return query, err
}
query.Set("labels", string(labelsJSON))
}
if len(options.CacheFrom) != 0 {
cacheFromJSON, err := json.Marshal(options.CacheFrom)
if err != nil {
return query, err
}
query.Set("cachefrom", string(cacheFromJSON))
} }
query.Set("cachefrom", string(cacheFromJSON))
if options.SessionID != "" { if options.SessionID != "" {
query.Set("session", options.SessionID) query.Set("session", options.SessionID)
} }
@ -131,7 +167,9 @@ func (cli *Client) imageBuildOptionsToQuery(ctx context.Context, options types.I
if options.BuildID != "" { if options.BuildID != "" {
query.Set("buildid", options.BuildID) query.Set("buildid", options.BuildID)
} }
query.Set("version", string(options.Version)) if options.Version != "" {
query.Set("version", string(options.Version))
}
if options.Outputs != nil { if options.Outputs != nil {
outputsJSON, err := json.Marshal(options.Outputs) outputsJSON, err := json.Marshal(options.Outputs)

View File

@ -9,14 +9,27 @@ import (
) )
// ImageHistory returns the changes in an image in history format. // ImageHistory returns the changes in an image in history format.
func (cli *Client) ImageHistory(ctx context.Context, imageID string) ([]image.HistoryResponseItem, error) { func (cli *Client) ImageHistory(ctx context.Context, imageID string, opts image.HistoryOptions) ([]image.HistoryResponseItem, error) {
var history []image.HistoryResponseItem query := url.Values{}
serverResp, err := cli.get(ctx, "/images/"+imageID+"/history", url.Values{}, nil) if opts.Platform != nil {
defer ensureReaderClosed(serverResp) if err := cli.NewVersionError(ctx, "1.48", "platform"); err != nil {
if err != nil { return nil, err
return history, err }
p, err := encodePlatform(opts.Platform)
if err != nil {
return nil, err
}
query.Set("platform", p)
} }
serverResp, err := cli.get(ctx, "/images/"+imageID+"/history", query, nil)
defer ensureReaderClosed(serverResp)
if err != nil {
return nil, err
}
var history []image.HistoryResponseItem
err = json.NewDecoder(serverResp.body).Decode(&history) err = json.NewDecoder(serverResp.body).Decode(&history)
return history, err return history, err
} }

View File

@ -21,10 +21,18 @@ func (cli *Client) ImageImport(ctx context.Context, source image.ImportSource, r
} }
query := url.Values{} query := url.Values{}
query.Set("fromSrc", source.SourceName) if source.SourceName != "" {
query.Set("repo", ref) query.Set("fromSrc", source.SourceName)
query.Set("tag", options.Tag) }
query.Set("message", options.Message) if ref != "" {
query.Set("repo", ref)
}
if options.Tag != "" {
query.Set("tag", options.Tag)
}
if options.Message != "" {
query.Set("message", options.Message)
}
if options.Platform != "" { if options.Platform != "" {
query.Set("platform", strings.ToLower(options.Platform)) query.Set("platform", strings.ToLower(options.Platform))
} }

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" "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) (types.ImageInspect, []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 types.ImageInspect{}, nil, objectNotFoundError{object: "image", id: imageID} return image.InspectResponse{}, objectNotFoundError{object: "image", id: imageID}
} }
serverResp, err := cli.get(ctx, "/images/"+imageID+"/json", nil, nil)
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) defer ensureReaderClosed(serverResp)
if err != nil { if err != nil {
return types.ImageInspect{}, nil, err return image.InspectResponse{}, err
} }
body, err := io.ReadAll(serverResp.body) buf := opts.raw
if err != nil { if buf == nil {
return types.ImageInspect{}, nil, err buf = &bytes.Buffer{}
} }
var response types.ImageInspect if _, err := io.Copy(buf, serverResp.body); err != nil {
rdr := bytes.NewReader(body) return image.InspectResponse{}, err
err = json.NewDecoder(rdr).Decode(&response) }
return response, body, err
var response image.InspectResponse
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

@ -12,13 +12,29 @@ import (
// ImageLoad loads an image in the docker host from the client host. // ImageLoad loads an image in the docker host from the client host.
// It's up to the caller to close the io.ReadCloser in the // It's up to the caller to close the io.ReadCloser in the
// ImageLoadResponse returned by this function. // ImageLoadResponse returned by this function.
func (cli *Client) ImageLoad(ctx context.Context, input io.Reader, quiet bool) (image.LoadResponse, error) { //
v := url.Values{} // Platform is an optional parameter that specifies the platform to load from
v.Set("quiet", "0") // the provided multi-platform image. This is only has effect if the input image
if quiet { // is a multi-platform image.
v.Set("quiet", "1") func (cli *Client) ImageLoad(ctx context.Context, input io.Reader, opts image.LoadOptions) (image.LoadResponse, error) {
query := url.Values{}
query.Set("quiet", "0")
if opts.Quiet {
query.Set("quiet", "1")
} }
resp, err := cli.postRaw(ctx, "/images/load", v, input, http.Header{ if len(opts.Platforms) > 0 {
if err := cli.NewVersionError(ctx, "1.48", "platform"); err != nil {
return image.LoadResponse{}, err
}
p, err := encodePlatforms(opts.Platforms...)
if err != nil {
return image.LoadResponse{}, err
}
query["platform"] = p
}
resp, err := cli.postRaw(ctx, "/images/load", query, input, http.Header{
"Content-Type": {"application/x-tar"}, "Content-Type": {"application/x-tar"},
}) })
if err != nil { if err != nil {

View File

@ -11,25 +11,24 @@ import (
// ImagesPrune requests the daemon to delete unused data // ImagesPrune requests the daemon to delete unused data
func (cli *Client) ImagesPrune(ctx context.Context, pruneFilters filters.Args) (image.PruneReport, error) { func (cli *Client) ImagesPrune(ctx context.Context, pruneFilters filters.Args) (image.PruneReport, error) {
var report image.PruneReport
if err := cli.NewVersionError(ctx, "1.25", "image prune"); err != nil { if err := cli.NewVersionError(ctx, "1.25", "image prune"); err != nil {
return report, err return image.PruneReport{}, err
} }
query, err := getFiltersQuery(pruneFilters) query, err := getFiltersQuery(pruneFilters)
if err != nil { if err != nil {
return report, err return image.PruneReport{}, err
} }
serverResp, err := cli.post(ctx, "/images/prune", query, nil, nil) serverResp, err := cli.post(ctx, "/images/prune", query, nil, nil)
defer ensureReaderClosed(serverResp) defer ensureReaderClosed(serverResp)
if err != nil { if err != nil {
return report, err return image.PruneReport{}, err
} }
var report image.PruneReport
if err := json.NewDecoder(serverResp.body).Decode(&report); err != nil { if err := json.NewDecoder(serverResp.body).Decode(&report); err != nil {
return report, fmt.Errorf("Error retrieving disk usage: %v", err) return image.PruneReport{}, fmt.Errorf("Error retrieving disk usage: %v", err)
} }
return report, nil return report, nil

View File

@ -4,15 +4,28 @@ import (
"context" "context"
"io" "io"
"net/url" "net/url"
"github.com/docker/docker/api/types/image"
) )
// ImageSave retrieves one or more images from the docker host as an io.ReadCloser. // ImageSave retrieves one or more images from the docker host as an io.ReadCloser.
// It's up to the caller to store the images and close the stream. // It's up to the caller to store the images and close the stream.
func (cli *Client) ImageSave(ctx context.Context, imageIDs []string) (io.ReadCloser, error) { func (cli *Client) ImageSave(ctx context.Context, imageIDs []string, opts image.SaveOptions) (io.ReadCloser, error) {
query := url.Values{ query := url.Values{
"names": imageIDs, "names": imageIDs,
} }
if len(opts.Platforms) > 0 {
if err := cli.NewVersionError(ctx, "1.48", "platform"); err != nil {
return nil, err
}
p, err := encodePlatforms(opts.Platforms...)
if err != nil {
return nil, err
}
query["platform"] = p
}
resp, err := cli.get(ctx, "/images/get", query, nil) resp, err := cli.get(ctx, "/images/get", query, nil)
if err != nil { if err != nil {
return nil, err return nil, 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

@ -11,25 +11,24 @@ import (
// NetworksPrune requests the daemon to delete unused networks // NetworksPrune requests the daemon to delete unused networks
func (cli *Client) NetworksPrune(ctx context.Context, pruneFilters filters.Args) (network.PruneReport, error) { func (cli *Client) NetworksPrune(ctx context.Context, pruneFilters filters.Args) (network.PruneReport, error) {
var report network.PruneReport
if err := cli.NewVersionError(ctx, "1.25", "network prune"); err != nil { if err := cli.NewVersionError(ctx, "1.25", "network prune"); err != nil {
return report, err return network.PruneReport{}, err
} }
query, err := getFiltersQuery(pruneFilters) query, err := getFiltersQuery(pruneFilters)
if err != nil { if err != nil {
return report, err return network.PruneReport{}, err
} }
serverResp, err := cli.post(ctx, "/networks/prune", query, nil, nil) serverResp, err := cli.post(ctx, "/networks/prune", query, nil, nil)
defer ensureReaderClosed(serverResp) defer ensureReaderClosed(serverResp)
if err != nil { if err != nil {
return report, err return network.PruneReport{}, err
} }
var report network.PruneReport
if err := json.NewDecoder(serverResp.body).Decode(&report); err != nil { if err := json.NewDecoder(serverResp.body).Decode(&report); err != nil {
return report, fmt.Errorf("Error retrieving network prune report: %v", err) return network.PruneReport{}, fmt.Errorf("Error retrieving network prune report: %v", err)
} }
return report, nil return report, nil

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 {
daemonErr = errors.New(strings.TrimSpace(errorResponse.Message))
}
} else { } 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

@ -1,13 +1,35 @@
package client // import "github.com/docker/docker/client" package client // import "github.com/docker/docker/client"
import ( import (
"encoding/json"
"fmt"
"net/url" "net/url"
"regexp" "strings"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
"github.com/docker/docker/errdefs"
"github.com/docker/docker/internal/lazyregexp"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
) )
var headerRegexp = regexp.MustCompile(`\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 {
@ -32,3 +54,43 @@ func getFiltersQuery(f filters.Args) (url.Values, error) {
} }
return query, nil return query, nil
} }
// encodePlatforms marshals the given platform(s) to JSON format, to
// be used for query-parameters for filtering / selecting platforms.
func encodePlatforms(platform ...ocispec.Platform) ([]string, error) {
if len(platform) == 0 {
return []string{}, nil
}
if len(platform) == 1 {
p, err := encodePlatform(&platform[0])
if err != nil {
return nil, err
}
return []string{p}, nil
}
seen := make(map[string]struct{}, len(platform))
out := make([]string, 0, len(platform))
for i := range platform {
p, err := encodePlatform(&platform[i])
if err != nil {
return nil, err
}
if _, ok := seen[p]; !ok {
out = append(out, p)
seen[p] = struct{}{}
}
}
return out, nil
}
// encodePlatform marshals the given platform to JSON format, to
// be used for query-parameters for filtering / selecting platforms. It
// is used as a helper for encodePlatforms,
func encodePlatform(platform *ocispec.Platform) (string, error) {
p, err := json.Marshal(platform)
if err != nil {
return "", errdefs.InvalidParameter(fmt.Errorf("invalid platform: %v", err))
}
return string(p), nil
}

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

Some files were not shown because too many files have changed in this diff Show More