vendor: github.com/docker/docker d3afa80b96bf (v25.0.0-dev)

full diff: 06499c52e2...d3afa80b96

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn 2023-09-19 14:51:12 +02:00
parent f90890fb48
commit 3e2187b4cb
No known key found for this signature in database
GPG Key ID: 76698F39D527CE8C
42 changed files with 339 additions and 177 deletions

View File

@ -101,14 +101,13 @@ func runCreate(dockerCli command.Cli, options createOptions) error {
Config: ipamCfg,
Options: options.ipamOpt.GetAll(),
},
CheckDuplicate: true,
Internal: options.internal,
EnableIPv6: options.ipv6,
Attachable: options.attachable,
Ingress: options.ingress,
Scope: options.scope,
ConfigOnly: options.configOnly,
Labels: opts.ConvertKVStringsToMap(options.labels.GetAll()),
Internal: options.internal,
EnableIPv6: options.ipv6,
Attachable: options.attachable,
Ingress: options.ingress,
Scope: options.scope,
ConfigOnly: options.configOnly,
Labels: opts.ConvertKVStringsToMap(options.labels.GetAll()),
}
if from := options.configFrom; from != "" {

View File

@ -13,7 +13,7 @@ require (
github.com/creack/pty v1.1.18
github.com/distribution/reference v0.0.0-20230830145923-e42074f83a9c
github.com/docker/distribution v2.8.2+incompatible
github.com/docker/docker v24.0.0-rc.2.0.20230907222536-06499c52e2b1+incompatible // master (v25.0.0-dev)
github.com/docker/docker v24.0.0-rc.2.0.20230921123131-d3afa80b96bf+incompatible // master (v25.0.0-dev)
github.com/docker/docker-credential-helpers v0.8.0
github.com/docker/go-connections v0.4.0
github.com/docker/go-units v0.5.0

View File

@ -54,8 +54,8 @@ github.com/distribution/reference v0.0.0-20230830145923-e42074f83a9c/go.mod h1:B
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v24.0.0-rc.2.0.20230907222536-06499c52e2b1+incompatible h1:wSeDxA5kbar3It3D8VVHH50TVWwEs7sAT+UO3UtFtlU=
github.com/docker/docker v24.0.0-rc.2.0.20230907222536-06499c52e2b1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v24.0.0-rc.2.0.20230921123131-d3afa80b96bf+incompatible h1:4TeclqMx5eFwUUQiHqXCB3fMBPxTWVdrILSKISy3IKc=
github.com/docker/docker v24.0.0-rc.2.0.20230921123131-d3afa80b96bf+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.8.0 h1:YQFtbBQb4VrpoPxhFuzEBPQ9E16qz5SpHLS+uswaCp8=
github.com/docker/docker-credential-helpers v0.8.0/go.mod h1:UGFXcuoQ5TxPiB54nHOZ32AWRqQdECoh/Mg0AlEYb40=
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=

View File

@ -1363,16 +1363,16 @@ definitions:
EndpointsConfig:
description: |
A mapping of network name to endpoint configuration for that network.
The endpoint configuration can be left empty to connect to that
network with no particular endpoint configuration.
type: "object"
additionalProperties:
$ref: "#/definitions/EndpointSettings"
example:
# putting an example here, instead of using the example values from
# /definitions/EndpointSettings, because containers/create currently
# does not support attaching to multiple networks, so the example request
# would be confusing if it showed that multiple networks can be contained
# in the EndpointsConfig.
# TODO remove once we support multiple networks on container create (see https://github.com/moby/moby/blob/07e6b843594e061f82baa5fa23c2ff7d536c2a05/daemon/create.go#L323)
# /definitions/EndpointSettings, because EndpointSettings contains
# operational data returned when inspecting a container that we don't
# accept here.
EndpointsConfig:
isolated_nw:
IPAMConfig:
@ -1387,6 +1387,7 @@ definitions:
Aliases:
- "server_x"
- "server_y"
database_nw: {}
NetworkSettings:
description: "NetworkSettings exposes the network settings in the API"
@ -6439,6 +6440,7 @@ paths:
Aliases:
- "server_x"
- "server_y"
database_nw: {}
required: true
responses:
@ -9962,13 +9964,7 @@ paths:
type: "string"
CheckDuplicate:
description: |
Check for networks with duplicate names. Since Network is
primarily keyed based on a random ID and not on the name, and
network name is strictly a user-friendly alias to the network
which is uniquely identified using ID, there is no guaranteed
way to check for duplicates. CheckDuplicate is there to provide
a best effort checking of any networks which has the same name
but it is not guaranteed to catch all name collisions.
Deprecated: CheckDuplicate is now always enabled.
type: "boolean"
Driver:
description: "Name of the network driver plugin to use."
@ -10042,6 +10038,10 @@ paths:
responses:
200:
description: "No error"
400:
description: "bad parameter"
schema:
$ref: "#/definitions/ErrorResponse"
403:
description: "Operation not supported for swarm scoped networks"
schema:

View File

@ -0,0 +1,135 @@
package network
import (
"errors"
"fmt"
"net"
"github.com/docker/docker/internal/multierror"
)
// EndpointSettings stores the network endpoint details
type EndpointSettings struct {
// Configurations
IPAMConfig *EndpointIPAMConfig
Links []string
Aliases []string
// Operational data
NetworkID string
EndpointID string
Gateway string
IPAddress string
IPPrefixLen int
IPv6Gateway string
GlobalIPv6Address string
GlobalIPv6PrefixLen int
MacAddress string
DriverOpts map[string]string
}
// Copy makes a deep copy of `EndpointSettings`
func (es *EndpointSettings) Copy() *EndpointSettings {
epCopy := *es
if es.IPAMConfig != nil {
epCopy.IPAMConfig = es.IPAMConfig.Copy()
}
if es.Links != nil {
links := make([]string, 0, len(es.Links))
epCopy.Links = append(links, es.Links...)
}
if es.Aliases != nil {
aliases := make([]string, 0, len(es.Aliases))
epCopy.Aliases = append(aliases, es.Aliases...)
}
return &epCopy
}
// EndpointIPAMConfig represents IPAM configurations for the endpoint
type EndpointIPAMConfig struct {
IPv4Address string `json:",omitempty"`
IPv6Address string `json:",omitempty"`
LinkLocalIPs []string `json:",omitempty"`
}
// Copy makes a copy of the endpoint ipam config
func (cfg *EndpointIPAMConfig) Copy() *EndpointIPAMConfig {
cfgCopy := *cfg
cfgCopy.LinkLocalIPs = make([]string, 0, len(cfg.LinkLocalIPs))
cfgCopy.LinkLocalIPs = append(cfgCopy.LinkLocalIPs, cfg.LinkLocalIPs...)
return &cfgCopy
}
// NetworkSubnet describes a user-defined subnet for a specific network. It's only used to validate if an
// EndpointIPAMConfig is valid for a specific network.
type NetworkSubnet interface {
// Contains checks whether the NetworkSubnet contains [addr].
Contains(addr net.IP) bool
// IsStatic checks whether the subnet was statically allocated (ie. user-defined).
IsStatic() bool
}
// IsInRange checks whether static IP addresses are valid in a specific network.
func (cfg *EndpointIPAMConfig) IsInRange(v4Subnets []NetworkSubnet, v6Subnets []NetworkSubnet) error {
var errs []error
if err := validateEndpointIPAddress(cfg.IPv4Address, v4Subnets); err != nil {
errs = append(errs, err)
}
if err := validateEndpointIPAddress(cfg.IPv6Address, v6Subnets); err != nil {
errs = append(errs, err)
}
return multierror.Join(errs...)
}
func validateEndpointIPAddress(epAddr string, ipamSubnets []NetworkSubnet) error {
if epAddr == "" {
return nil
}
var staticSubnet bool
parsedAddr := net.ParseIP(epAddr)
for _, subnet := range ipamSubnets {
if subnet.IsStatic() {
staticSubnet = true
if subnet.Contains(parsedAddr) {
return nil
}
}
}
if staticSubnet {
return fmt.Errorf("no configured subnet or ip-range contain the IP address %s", epAddr)
}
return errors.New("user specified IP address is supported only when connecting to networks with user configured subnets")
}
// Validate checks whether cfg is valid.
func (cfg *EndpointIPAMConfig) Validate() error {
if cfg == nil {
return nil
}
var errs []error
if cfg.IPv4Address != "" {
if addr := net.ParseIP(cfg.IPv4Address); addr == nil || addr.To4() == nil || addr.IsUnspecified() {
errs = append(errs, fmt.Errorf("invalid IPv4 address: %s", cfg.IPv4Address))
}
}
if cfg.IPv6Address != "" {
if addr := net.ParseIP(cfg.IPv6Address); addr == nil || addr.To4() != nil || addr.IsUnspecified() {
errs = append(errs, fmt.Errorf("invalid IPv6 address: %s", cfg.IPv6Address))
}
}
for _, addr := range cfg.LinkLocalIPs {
if parsed := net.ParseIP(addr); parsed == nil || parsed.IsUnspecified() {
errs = append(errs, fmt.Errorf("invalid link-local IP address: %s", addr))
}
}
return multierror.Join(errs...)
}

View File

@ -30,7 +30,30 @@ const (
ip6 ipFamily = "IPv6"
)
func ValidateIPAM(ipam *IPAM, enableIPv6 bool) error {
// HasIPv6Subnets checks whether there's any IPv6 subnets in the ipam parameter. It ignores any invalid Subnet and nil
// ipam.
func HasIPv6Subnets(ipam *IPAM) bool {
if ipam == nil {
return false
}
for _, cfg := range ipam.Config {
subnet, err := netip.ParsePrefix(cfg.Subnet)
if err != nil {
continue
}
if subnet.Addr().Is6() {
return true
}
}
return false
}
// ValidateIPAM checks whether the network's IPAM passed as argument is valid. It returns a joinError of the list of
// errors found.
func ValidateIPAM(ipam *IPAM) error {
if ipam == nil {
return nil
}
@ -51,10 +74,6 @@ func ValidateIPAM(ipam *IPAM, enableIPv6 bool) error {
errs = append(errs, fmt.Errorf("invalid subnet %s: it should be %s", subnet, subnet.Masked()))
}
if !enableIPv6 && subnetFamily == ip6 {
errs = append(errs, fmt.Errorf("invalid subnet %s: IPv6 has not been enabled for this network", subnet))
}
if ipRangeErrs := validateIPRange(cfg.IPRange, subnet, subnetFamily); len(ipRangeErrs) > 0 {
errs = append(errs, ipRangeErrs...)
}

View File

@ -9,46 +9,12 @@ type Address struct {
PrefixLen int
}
// EndpointIPAMConfig represents IPAM configurations for the endpoint
type EndpointIPAMConfig struct {
IPv4Address string `json:",omitempty"`
IPv6Address string `json:",omitempty"`
LinkLocalIPs []string `json:",omitempty"`
}
// Copy makes a copy of the endpoint ipam config
func (cfg *EndpointIPAMConfig) Copy() *EndpointIPAMConfig {
cfgCopy := *cfg
cfgCopy.LinkLocalIPs = make([]string, 0, len(cfg.LinkLocalIPs))
cfgCopy.LinkLocalIPs = append(cfgCopy.LinkLocalIPs, cfg.LinkLocalIPs...)
return &cfgCopy
}
// PeerInfo represents one peer of an overlay network
type PeerInfo struct {
Name string
IP string
}
// EndpointSettings stores the network endpoint details
type EndpointSettings struct {
// Configurations
IPAMConfig *EndpointIPAMConfig
Links []string
Aliases []string
// Operational data
NetworkID string
EndpointID string
Gateway string
IPAddress string
IPPrefixLen int
IPv6Gateway string
GlobalIPv6Address string
GlobalIPv6PrefixLen int
MacAddress string
DriverOpts map[string]string
}
// Task carries the information about one backend task
type Task struct {
Name string
@ -65,25 +31,6 @@ type ServiceInfo struct {
Tasks []Task
}
// Copy makes a deep copy of `EndpointSettings`
func (es *EndpointSettings) Copy() *EndpointSettings {
epCopy := *es
if es.IPAMConfig != nil {
epCopy.IPAMConfig = es.IPAMConfig.Copy()
}
if es.Links != nil {
links := make([]string, 0, len(es.Links))
epCopy.Links = append(links, es.Links...)
}
if es.Aliases != nil {
aliases := make([]string, 0, len(es.Aliases))
epCopy.Aliases = append(aliases, es.Aliases...)
}
return &epCopy
}
// NetworkingConfig represents the container's networking configuration for each of its interfaces
// Carries the networking configs specified in the `docker run` and `docker network connect` commands
type NetworkingConfig struct {

View File

@ -443,14 +443,9 @@ type EndpointResource struct {
// NetworkCreate is the expected body of the "create network" http request message
type NetworkCreate struct {
// Check for networks with duplicate names.
// Network is primarily keyed based on a random ID and not on the name.
// Network name is strictly a user-friendly alias to the network
// which is uniquely identified using ID.
// And there is no guaranteed way to check for duplicates.
// Option CheckDuplicate is there to provide a best effort checking of any networks
// which has the same name but it is not guaranteed to catch all name collisions.
CheckDuplicate bool
// Deprecated: CheckDuplicate is deprecated since API v1.44, but it defaults to true when sent by the client
// package to older daemons.
CheckDuplicate bool `json:",omitempty"`
Driver string
Scope string
EnableIPv6 bool

View File

@ -13,7 +13,7 @@ import (
// BuildCachePrune requests the daemon to delete unused cache data
func (cli *Client) BuildCachePrune(ctx context.Context, opts types.BuildCachePruneOptions) (*types.BuildCachePruneReport, error) {
if err := cli.NewVersionError("1.31", "build prune"); err != nil {
if err := cli.NewVersionError(ctx, "1.31", "build prune"); err != nil {
return nil, err
}

View File

@ -254,13 +254,21 @@ func (cli *Client) Close() error {
return nil
}
// checkVersion manually triggers API version negotiation (if configured).
// This allows for version-dependent code to use the same version as will
// be negotiated when making the actual requests, and for which cases
// we cannot do the negotiation lazily.
func (cli *Client) checkVersion(ctx context.Context) {
if cli.negotiateVersion && !cli.negotiated {
cli.NegotiateAPIVersion(ctx)
}
}
// getAPIPath returns the versioned request path to call the API.
// It appends the query parameters to the path if they are not empty.
func (cli *Client) getAPIPath(ctx context.Context, p string, query url.Values) string {
var apiPath string
if cli.negotiateVersion && !cli.negotiated {
cli.NegotiateAPIVersion(ctx)
}
cli.checkVersion(ctx)
if cli.version != "" {
v := strings.TrimPrefix(cli.version, "v")
apiPath = path.Join(cli.basePath, "/v"+v, p)

View File

@ -11,7 +11,7 @@ import (
// ConfigCreate creates a new config.
func (cli *Client) ConfigCreate(ctx context.Context, config swarm.ConfigSpec) (types.ConfigCreateResponse, error) {
var response types.ConfigCreateResponse
if err := cli.NewVersionError("1.30", "config create"); err != nil {
if err := cli.NewVersionError(ctx, "1.30", "config create"); err != nil {
return response, err
}
resp, err := cli.post(ctx, "/configs/create", nil, config, nil)

View File

@ -14,7 +14,7 @@ func (cli *Client) ConfigInspectWithRaw(ctx context.Context, id string) (swarm.C
if id == "" {
return swarm.Config{}, nil, objectNotFoundError{object: "config", id: id}
}
if err := cli.NewVersionError("1.30", "config inspect"); err != nil {
if err := cli.NewVersionError(ctx, "1.30", "config inspect"); err != nil {
return swarm.Config{}, nil, err
}
resp, err := cli.get(ctx, "/configs/"+id, nil, nil)

View File

@ -12,7 +12,7 @@ import (
// ConfigList returns the list of configs.
func (cli *Client) ConfigList(ctx context.Context, options types.ConfigListOptions) ([]swarm.Config, error) {
if err := cli.NewVersionError("1.30", "config list"); err != nil {
if err := cli.NewVersionError(ctx, "1.30", "config list"); err != nil {
return nil, err
}
query := url.Values{}

View File

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

View File

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

View File

@ -23,13 +23,20 @@ type configWrapper struct {
func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *ocispec.Platform, containerName string) (container.CreateResponse, error) {
var response container.CreateResponse
if err := cli.NewVersionError("1.25", "stop timeout"); config != nil && config.StopTimeout != nil && err != nil {
// Make sure we negotiated (if the client is configured to do so),
// as code below contains API-version specific handling of options.
//
// Normally, version-negotiation (if enabled) would not happen until
// the API request is made.
cli.checkVersion(ctx)
if err := cli.NewVersionError(ctx, "1.25", "stop timeout"); config != nil && config.StopTimeout != nil && err != nil {
return response, err
}
if err := cli.NewVersionError("1.41", "specify container image platform"); platform != nil && err != nil {
if err := cli.NewVersionError(ctx, "1.41", "specify container image platform"); platform != nil && err != nil {
return response, err
}
if err := cli.NewVersionError("1.44", "specify health-check start interval"); config != nil && config.Healthcheck != nil && config.Healthcheck.StartInterval != 0 && err != nil {
if err := cli.NewVersionError(ctx, "1.44", "specify health-check start interval"); config != nil && config.Healthcheck != nil && config.Healthcheck.StartInterval != 0 && err != nil {
return response, err
}

View File

@ -13,7 +13,14 @@ import (
func (cli *Client) ContainerExecCreate(ctx context.Context, container string, config types.ExecConfig) (types.IDResponse, error) {
var response types.IDResponse
if err := cli.NewVersionError("1.25", "env"); len(config.Env) != 0 && err != nil {
// Make sure we negotiated (if the client is configured to do so),
// as code below contains API-version specific handling of options.
//
// Normally, version-negotiation (if enabled) would not happen until
// the API request is made.
cli.checkVersion(ctx)
if err := cli.NewVersionError(ctx, "1.25", "env"); len(config.Env) != 0 && err != nil {
return response, err
}
if versions.LessThan(cli.ClientVersion(), "1.42") {

View File

@ -13,7 +13,7 @@ import (
func (cli *Client) ContainersPrune(ctx context.Context, pruneFilters filters.Args) (types.ContainersPruneReport, error) {
var report types.ContainersPruneReport
if err := cli.NewVersionError("1.25", "container prune"); err != nil {
if err := cli.NewVersionError(ctx, "1.25", "container prune"); err != nil {
return report, err
}

View File

@ -17,8 +17,16 @@ func (cli *Client) ContainerRestart(ctx context.Context, containerID string, opt
if options.Timeout != nil {
query.Set("t", strconv.Itoa(*options.Timeout))
}
if options.Signal != "" && versions.GreaterThanOrEqualTo(cli.version, "1.42") {
query.Set("signal", options.Signal)
if options.Signal != "" {
// Make sure we negotiated (if the client is configured to do so),
// as code below contains API-version specific handling of options.
//
// Normally, version-negotiation (if enabled) would not happen until
// the API request is made.
cli.checkVersion(ctx)
if versions.GreaterThanOrEqualTo(cli.version, "1.42") {
query.Set("signal", options.Signal)
}
}
resp, err := cli.post(ctx, "/containers/"+containerID+"/restart", query, nil, nil)
ensureReaderClosed(resp)

View File

@ -21,8 +21,16 @@ func (cli *Client) ContainerStop(ctx context.Context, containerID string, option
if options.Timeout != nil {
query.Set("t", strconv.Itoa(*options.Timeout))
}
if options.Signal != "" && versions.GreaterThanOrEqualTo(cli.version, "1.42") {
query.Set("signal", options.Signal)
if options.Signal != "" {
// Make sure we negotiated (if the client is configured to do so),
// as code below contains API-version specific handling of options.
//
// Normally, version-negotiation (if enabled) would not happen until
// the API request is made.
cli.checkVersion(ctx)
if versions.GreaterThanOrEqualTo(cli.version, "1.42") {
query.Set("signal", options.Signal)
}
}
resp, err := cli.post(ctx, "/containers/"+containerID+"/stop", query, nil, nil)
ensureReaderClosed(resp)

View File

@ -30,6 +30,12 @@ const containerWaitErrorMsgLimit = 2 * 1024 /* Max: 2KiB */
// synchronize ContainerWait with other calls, such as specifying a
// "next-exit" condition before issuing a ContainerStart request.
func (cli *Client) ContainerWait(ctx context.Context, containerID string, condition container.WaitCondition) (<-chan container.WaitResponse, <-chan error) {
// Make sure we negotiated (if the client is configured to do so),
// as code below contains API-version specific handling of options.
//
// Normally, version-negotiation (if enabled) would not happen until
// the API request is made.
cli.checkVersion(ctx)
if versions.LessThan(cli.ClientVersion(), "1.30") {
return cli.legacyContainerWait(ctx, containerID)
}

View File

@ -17,7 +17,7 @@ func (cli *Client) DistributionInspect(ctx context.Context, image, encodedRegist
return distributionInspect, objectNotFoundError{object: "distribution", id: image}
}
if err := cli.NewVersionError("1.30", "distribution inspect"); err != nil {
if err := cli.NewVersionError(ctx, "1.30", "distribution inspect"); err != nil {
return distributionInspect, err
}

View File

@ -1,6 +1,7 @@
package client // import "github.com/docker/docker/client"
import (
"context"
"fmt"
"github.com/docker/docker/api/types/versions"
@ -48,9 +49,18 @@ func (e objectNotFoundError) Error() string {
return fmt.Sprintf("Error: No such %s: %s", e.object, e.id)
}
// NewVersionError returns an error if the APIVersion required
// if less than the current supported version
func (cli *Client) NewVersionError(APIrequired, feature string) error {
// NewVersionError returns an error if the APIVersion required is less than the
// current supported version.
//
// It performs API-version negotiation if the Client is configured with this
// option, otherwise it assumes the latest API version is used.
func (cli *Client) NewVersionError(ctx context.Context, APIrequired, feature string) error {
// Make sure we negotiated (if the client is configured to do so),
// as code below contains API-version specific handling of options.
//
// Normally, version-negotiation (if enabled) would not happen until
// the API request is made.
cli.checkVersion(ctx)
if cli.version != "" && versions.LessThan(cli.version, APIrequired) {
return fmt.Errorf("%q requires API version %s, but the Docker daemon API version is %s", feature, APIrequired, cli.version)
}

View File

@ -18,7 +18,7 @@ import (
// The Body in the response implements an io.ReadCloser and it's up to the caller to
// close it.
func (cli *Client) ImageBuild(ctx context.Context, buildContext io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) {
query, err := cli.imageBuildOptionsToQuery(options)
query, err := cli.imageBuildOptionsToQuery(ctx, options)
if err != nil {
return types.ImageBuildResponse{}, err
}
@ -43,7 +43,7 @@ func (cli *Client) ImageBuild(ctx context.Context, buildContext io.Reader, optio
}, nil
}
func (cli *Client) imageBuildOptionsToQuery(options types.ImageBuildOptions) (url.Values, error) {
func (cli *Client) imageBuildOptionsToQuery(ctx context.Context, options types.ImageBuildOptions) (url.Values, error) {
query := url.Values{
"t": options.Tags,
"securityopt": options.SecurityOpt,
@ -73,7 +73,7 @@ func (cli *Client) imageBuildOptionsToQuery(options types.ImageBuildOptions) (ur
}
if options.Squash {
if err := cli.NewVersionError("1.25", "squash"); err != nil {
if err := cli.NewVersionError(ctx, "1.25", "squash"); err != nil {
return query, err
}
query.Set("squash", "1")
@ -123,7 +123,7 @@ func (cli *Client) imageBuildOptionsToQuery(options types.ImageBuildOptions) (ur
query.Set("session", options.SessionID)
}
if options.Platform != "" {
if err := cli.NewVersionError("1.32", "platform"); err != nil {
if err := cli.NewVersionError(ctx, "1.32", "platform"); err != nil {
return query, err
}
query.Set("platform", strings.ToLower(options.Platform))

View File

@ -12,6 +12,13 @@ import (
// ImageList returns a list of images in the docker host.
func (cli *Client) ImageList(ctx context.Context, options types.ImageListOptions) ([]types.ImageSummary, error) {
// Make sure we negotiated (if the client is configured to do so),
// as code below contains API-version specific handling of options.
//
// Normally, version-negotiation (if enabled) would not happen until
// the API request is made.
cli.checkVersion(ctx)
var images []types.ImageSummary
query := url.Values{}

View File

@ -13,7 +13,7 @@ import (
func (cli *Client) ImagesPrune(ctx context.Context, pruneFilters filters.Args) (types.ImagesPruneReport, error) {
var report types.ImagesPruneReport
if err := cli.NewVersionError("1.25", "image prune"); err != nil {
if err := cli.NewVersionError(ctx, "1.25", "image prune"); err != nil {
return report, err
}

View File

@ -5,14 +5,26 @@ import (
"encoding/json"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/versions"
)
// NetworkCreate creates a new network in the docker host.
func (cli *Client) NetworkCreate(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error) {
// Make sure we negotiated (if the client is configured to do so),
// as code below contains API-version specific handling of options.
//
// Normally, version-negotiation (if enabled) would not happen until
// the API request is made.
cli.checkVersion(ctx)
networkCreateRequest := types.NetworkCreateRequest{
NetworkCreate: options,
Name: name,
}
if versions.LessThan(cli.version, "1.44") {
networkCreateRequest.CheckDuplicate = true //nolint:staticcheck // ignore SA1019: CheckDuplicate is deprecated since API v1.44.
}
var response types.NetworkCreateResponse
serverResp, err := cli.post(ctx, "/networks/create", nil, networkCreateRequest, nil)
defer ensureReaderClosed(serverResp)

View File

@ -13,7 +13,7 @@ import (
func (cli *Client) NetworksPrune(ctx context.Context, pruneFilters filters.Args) (types.NetworksPruneReport, error) {
var report types.NetworksPruneReport
if err := cli.NewVersionError("1.25", "network prune"); err != nil {
if err := cli.NewVersionError(ctx, "1.25", "network prune"); err != nil {
return report, err
}

View File

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

View File

@ -11,7 +11,7 @@ import (
// SecretCreate creates a new secret.
func (cli *Client) SecretCreate(ctx context.Context, secret swarm.SecretSpec) (types.SecretCreateResponse, error) {
var response types.SecretCreateResponse
if err := cli.NewVersionError("1.25", "secret create"); err != nil {
if err := cli.NewVersionError(ctx, "1.25", "secret create"); err != nil {
return response, err
}
resp, err := cli.post(ctx, "/secrets/create", nil, secret, nil)

View File

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

View File

@ -12,7 +12,7 @@ import (
// SecretList returns the list of secrets.
func (cli *Client) SecretList(ctx context.Context, options types.SecretListOptions) ([]swarm.Secret, error) {
if err := cli.NewVersionError("1.25", "secret list"); err != nil {
if err := cli.NewVersionError(ctx, "1.25", "secret list"); err != nil {
return nil, err
}
query := url.Values{}

View File

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

View File

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

View File

@ -20,6 +20,13 @@ import (
func (cli *Client) ServiceCreate(ctx context.Context, service swarm.ServiceSpec, options types.ServiceCreateOptions) (types.ServiceCreateResponse, error) {
var response types.ServiceCreateResponse
// Make sure we negotiated (if the client is configured to do so),
// as code below contains API-version specific handling of options.
//
// Normally, version-negotiation (if enabled) would not happen until
// the API request is made.
cli.checkVersion(ctx)
// Make sure containerSpec is not nil when no runtime is set or the runtime is set to container
if service.TaskTemplate.ContainerSpec == nil && (service.TaskTemplate.Runtime == "" || service.TaskTemplate.Runtime == swarm.RuntimeContainer) {
service.TaskTemplate.ContainerSpec = &swarm.ContainerSpec{}

View File

@ -16,6 +16,13 @@ import (
// It should be the value as set *before* the update. You can find this value in the Meta field
// of swarm.Service, which can be found using ServiceInspectWithRaw.
func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (types.ServiceUpdateResponse, error) {
// Make sure we negotiated (if the client is configured to do so),
// as code below contains API-version specific handling of options.
//
// Normally, version-negotiation (if enabled) would not happen until
// the API request is made.
cli.checkVersion(ctx)
var (
query = url.Values{}
response = types.ServiceUpdateResponse{}

View File

@ -13,7 +13,7 @@ import (
func (cli *Client) VolumesPrune(ctx context.Context, pruneFilters filters.Args) (types.VolumesPruneReport, error) {
var report types.VolumesPruneReport
if err := cli.NewVersionError("1.25", "volume prune"); err != nil {
if err := cli.NewVersionError(ctx, "1.25", "volume prune"); err != nil {
return report, err
}

View File

@ -10,8 +10,14 @@ import (
// VolumeRemove removes a volume from the docker host.
func (cli *Client) VolumeRemove(ctx context.Context, volumeID string, force bool) error {
query := url.Values{}
if versions.GreaterThanOrEqualTo(cli.version, "1.25") {
if force {
if force {
// Make sure we negotiated (if the client is configured to do so),
// as code below contains API-version specific handling of options.
//
// Normally, version-negotiation (if enabled) would not happen until
// the API request is made.
cli.checkVersion(ctx)
if versions.GreaterThanOrEqualTo(cli.version, "1.25") {
query.Set("force", "1")
}
}

View File

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

View File

@ -4,7 +4,6 @@ import (
"context"
"crypto/tls"
"encoding/json"
"io"
"net/http"
"net/url"
"strings"
@ -15,12 +14,8 @@ import (
)
// v1PingResult contains the information returned when pinging a registry. It
// indicates the registry's version and whether the registry claims to be a
// standalone registry.
// indicates whether the registry claims to be a standalone registry.
type v1PingResult struct {
// Version is the registry version supplied by the registry in an HTTP
// header
Version string `json:"version"`
// Standalone is set to true if the registry indicates it is a
// standalone registry in the X-Docker-Registry-Standalone
// header
@ -47,39 +42,30 @@ func newV1Endpoint(index *registry.IndexInfo, headers http.Header) (*v1Endpoint,
return nil, err
}
err = validateEndpoint(endpoint)
if err != nil {
return nil, err
if endpoint.String() == IndexServer {
// Skip the check, we know this one is valid
// (and we never want to fall back to http in case of error)
return endpoint, nil
}
return endpoint, nil
}
func validateEndpoint(endpoint *v1Endpoint) error {
log.G(context.TODO()).Debugf("pinging registry endpoint %s", endpoint)
// Try HTTPS ping to registry
endpoint.URL.Scheme = "https"
if _, err := endpoint.ping(); err != nil {
if endpoint.IsSecure {
// If registry is secure and HTTPS failed, show user the error and tell them about `--insecure-registry`
// in case that's what they need. DO NOT accept unknown CA certificates, and DO NOT fallback to HTTP.
return invalidParamf("invalid registry endpoint %s: %v. If this private registry supports only HTTP or HTTPS with an unknown CA certificate, please add `--insecure-registry %s` to the daemon's arguments. In the case of HTTPS, if you have access to the registry's CA certificate, no need for the flag; simply place the CA certificate at /etc/docker/certs.d/%s/ca.crt", endpoint, err, endpoint.URL.Host, endpoint.URL.Host)
// in case that's what they need. DO NOT accept unknown CA certificates, and DO NOT fall back to HTTP.
return nil, invalidParamf("invalid registry endpoint %s: %v. If this private registry supports only HTTP or HTTPS with an unknown CA certificate, please add `--insecure-registry %s` to the daemon's arguments. In the case of HTTPS, if you have access to the registry's CA certificate, no need for the flag; simply place the CA certificate at /etc/docker/certs.d/%s/ca.crt", endpoint, err, endpoint.URL.Host, endpoint.URL.Host)
}
// If registry is insecure and HTTPS failed, fallback to HTTP.
// registry is insecure and HTTPS failed, fallback to HTTP.
log.G(context.TODO()).WithError(err).Debugf("error from registry %q marked as insecure - insecurely falling back to HTTP", endpoint)
endpoint.URL.Scheme = "http"
var err2 error
if _, err2 = endpoint.ping(); err2 == nil {
return nil
if _, err2 := endpoint.ping(); err2 != nil {
return nil, invalidParamf("invalid registry endpoint %q. HTTPS attempt: %v. HTTP attempt: %v", endpoint, err, err2)
}
return invalidParamf("invalid registry endpoint %q. HTTPS attempt: %v. HTTP attempt: %v", endpoint, err, err2)
}
return nil
return endpoint, nil
}
// trimV1Address trims the "v1" version suffix off the address and returns
@ -130,8 +116,8 @@ func (e *v1Endpoint) ping() (v1PingResult, error) {
return v1PingResult{}, nil
}
log.G(context.TODO()).Debugf("attempting v1 ping for registry endpoint %s", e)
pingURL := e.String() + "_ping"
log.G(context.TODO()).WithField("url", pingURL).Debug("attempting v1 ping for registry endpoint")
req, err := http.NewRequest(http.MethodGet, pingURL, nil)
if err != nil {
return v1PingResult{}, invalidParam(err)
@ -144,9 +130,14 @@ func (e *v1Endpoint) ping() (v1PingResult, error) {
defer resp.Body.Close()
jsonString, err := io.ReadAll(resp.Body)
if err != nil {
return v1PingResult{}, invalidParamWrapf(err, "error while reading response from %s", pingURL)
if v := resp.Header.Get("X-Docker-Registry-Standalone"); v != "" {
info := v1PingResult{}
// Accepted values are "1", and "true" (case-insensitive).
if v == "1" || strings.EqualFold(v, "true") {
info.Standalone = true
}
log.G(context.TODO()).Debugf("v1PingResult.Standalone (from X-Docker-Registry-Standalone header): %t", info.Standalone)
return info, nil
}
// If the header is absent, we assume true for compatibility with earlier
@ -154,24 +145,11 @@ func (e *v1Endpoint) ping() (v1PingResult, error) {
info := v1PingResult{
Standalone: true,
}
if err := json.Unmarshal(jsonString, &info); err != nil {
if err := json.NewDecoder(resp.Body).Decode(&info); err != nil {
log.G(context.TODO()).WithError(err).Debug("error unmarshaling _ping response")
// don't stop here. Just assume sane defaults
}
if hdr := resp.Header.Get("X-Docker-Registry-Version"); hdr != "" {
info.Version = hdr
}
log.G(context.TODO()).Debugf("v1PingResult.Version: %q", info.Version)
standalone := resp.Header.Get("X-Docker-Registry-Standalone")
// Accepted values are "true" (case-insensitive) and "1".
if strings.EqualFold(standalone, "true") || standalone == "1" {
info.Standalone = true
} else if len(standalone) > 0 {
// there is a header set, and it is not "true" or "1", so assume fails
info.Standalone = false
}
log.G(context.TODO()).Debugf("v1PingResult.Standalone: %t", info.Standalone)
return info, nil
}

View File

@ -9,7 +9,6 @@ import (
"net/http"
"net/http/cookiejar"
"net/url"
"strconv"
"strings"
"sync"
@ -17,7 +16,6 @@ import (
"github.com/docker/docker/api/types/registry"
"github.com/docker/docker/errdefs"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/pkg/errors"
)
@ -208,10 +206,8 @@ func (r *session) searchRepositories(term string, limit int) (*registry.SearchRe
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return nil, errdefs.Unknown(&jsonmessage.JSONError{
Message: "Unexpected status code " + strconv.Itoa(res.StatusCode),
Code: res.StatusCode,
})
// TODO(thaJeztah): return upstream response body for errors (see https://github.com/moby/moby/issues/27286).
return nil, errdefs.Unknown(fmt.Errorf("Unexpected status code %d", res.StatusCode))
}
result := &registry.SearchResults{}
err = json.NewDecoder(res.Body).Decode(result)

2
vendor/modules.txt vendored
View File

@ -55,7 +55,7 @@ github.com/docker/distribution/registry/client/transport
github.com/docker/distribution/registry/storage/cache
github.com/docker/distribution/registry/storage/cache/memory
github.com/docker/distribution/uuid
# github.com/docker/docker v24.0.0-rc.2.0.20230907222536-06499c52e2b1+incompatible
# github.com/docker/docker v24.0.0-rc.2.0.20230921123131-d3afa80b96bf+incompatible
## explicit
github.com/docker/docker/api
github.com/docker/docker/api/types