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,7 +101,6 @@ func runCreate(dockerCli command.Cli, options createOptions) error {
Config: ipamCfg, Config: ipamCfg,
Options: options.ipamOpt.GetAll(), Options: options.ipamOpt.GetAll(),
}, },
CheckDuplicate: true,
Internal: options.internal, Internal: options.internal,
EnableIPv6: options.ipv6, EnableIPv6: options.ipv6,
Attachable: options.attachable, Attachable: options.attachable,

View File

@ -13,7 +13,7 @@ require (
github.com/creack/pty v1.1.18 github.com/creack/pty v1.1.18
github.com/distribution/reference v0.0.0-20230830145923-e42074f83a9c github.com/distribution/reference v0.0.0-20230830145923-e42074f83a9c
github.com/docker/distribution v2.8.2+incompatible 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/docker-credential-helpers v0.8.0
github.com/docker/go-connections v0.4.0 github.com/docker/go-connections v0.4.0
github.com/docker/go-units v0.5.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.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 h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= 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.20230921123131-d3afa80b96bf+incompatible h1:4TeclqMx5eFwUUQiHqXCB3fMBPxTWVdrILSKISy3IKc=
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/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 h1:YQFtbBQb4VrpoPxhFuzEBPQ9E16qz5SpHLS+uswaCp8=
github.com/docker/docker-credential-helpers v0.8.0/go.mod h1:UGFXcuoQ5TxPiB54nHOZ32AWRqQdECoh/Mg0AlEYb40= 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= github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=

View File

@ -1363,16 +1363,16 @@ definitions:
EndpointsConfig: EndpointsConfig:
description: | description: |
A mapping of network name to endpoint configuration for that network. 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" type: "object"
additionalProperties: additionalProperties:
$ref: "#/definitions/EndpointSettings" $ref: "#/definitions/EndpointSettings"
example: example:
# putting an example here, instead of using the example values from # putting an example here, instead of using the example values from
# /definitions/EndpointSettings, because containers/create currently # /definitions/EndpointSettings, because EndpointSettings contains
# does not support attaching to multiple networks, so the example request # operational data returned when inspecting a container that we don't
# would be confusing if it showed that multiple networks can be contained # accept here.
# 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)
EndpointsConfig: EndpointsConfig:
isolated_nw: isolated_nw:
IPAMConfig: IPAMConfig:
@ -1387,6 +1387,7 @@ definitions:
Aliases: Aliases:
- "server_x" - "server_x"
- "server_y" - "server_y"
database_nw: {}
NetworkSettings: NetworkSettings:
description: "NetworkSettings exposes the network settings in the API" description: "NetworkSettings exposes the network settings in the API"
@ -6439,6 +6440,7 @@ paths:
Aliases: Aliases:
- "server_x" - "server_x"
- "server_y" - "server_y"
database_nw: {}
required: true required: true
responses: responses:
@ -9962,13 +9964,7 @@ paths:
type: "string" type: "string"
CheckDuplicate: CheckDuplicate:
description: | description: |
Check for networks with duplicate names. Since Network is Deprecated: CheckDuplicate is now always enabled.
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.
type: "boolean" type: "boolean"
Driver: Driver:
description: "Name of the network driver plugin to use." description: "Name of the network driver plugin to use."
@ -10042,6 +10038,10 @@ paths:
responses: responses:
200: 200:
description: "No error" description: "No error"
400:
description: "bad parameter"
schema:
$ref: "#/definitions/ErrorResponse"
403: 403:
description: "Operation not supported for swarm scoped networks" description: "Operation not supported for swarm scoped networks"
schema: 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" 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 { if ipam == nil {
return 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())) 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 { if ipRangeErrs := validateIPRange(cfg.IPRange, subnet, subnetFamily); len(ipRangeErrs) > 0 {
errs = append(errs, ipRangeErrs...) errs = append(errs, ipRangeErrs...)
} }

View File

@ -9,46 +9,12 @@ type Address struct {
PrefixLen int 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 // PeerInfo represents one peer of an overlay network
type PeerInfo struct { type PeerInfo struct {
Name string Name string
IP 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 // Task carries the information about one backend task
type Task struct { type Task struct {
Name string Name string
@ -65,25 +31,6 @@ type ServiceInfo struct {
Tasks []Task 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 // 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 // Carries the networking configs specified in the `docker run` and `docker network connect` commands
type NetworkingConfig struct { type NetworkingConfig struct {

View File

@ -443,14 +443,9 @@ type EndpointResource struct {
// NetworkCreate is the expected body of the "create network" http request message // NetworkCreate is the expected body of the "create network" http request message
type NetworkCreate struct { type NetworkCreate struct {
// Check for networks with duplicate names. // Deprecated: CheckDuplicate is deprecated since API v1.44, but it defaults to true when sent by the client
// Network is primarily keyed based on a random ID and not on the name. // package to older daemons.
// Network name is strictly a user-friendly alias to the network CheckDuplicate bool `json:",omitempty"`
// 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
Driver string Driver string
Scope string Scope string
EnableIPv6 bool EnableIPv6 bool

View File

@ -13,7 +13,7 @@ import (
// BuildCachePrune requests the daemon to delete unused cache data // BuildCachePrune requests the daemon to delete unused cache data
func (cli *Client) BuildCachePrune(ctx context.Context, opts types.BuildCachePruneOptions) (*types.BuildCachePruneReport, error) { 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 return nil, err
} }

View File

@ -254,13 +254,21 @@ func (cli *Client) Close() error {
return nil 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. // getAPIPath returns the versioned request path to call the API.
// It appends the query parameters to the path if they are not empty. // 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 { func (cli *Client) getAPIPath(ctx context.Context, p string, query url.Values) string {
var apiPath string var apiPath string
if cli.negotiateVersion && !cli.negotiated { cli.checkVersion(ctx)
cli.NegotiateAPIVersion(ctx)
}
if cli.version != "" { if cli.version != "" {
v := strings.TrimPrefix(cli.version, "v") v := strings.TrimPrefix(cli.version, "v")
apiPath = path.Join(cli.basePath, "/v"+v, p) apiPath = path.Join(cli.basePath, "/v"+v, p)

View File

@ -11,7 +11,7 @@ import (
// ConfigCreate creates a new config. // ConfigCreate creates a new config.
func (cli *Client) ConfigCreate(ctx context.Context, config swarm.ConfigSpec) (types.ConfigCreateResponse, error) { func (cli *Client) ConfigCreate(ctx context.Context, config swarm.ConfigSpec) (types.ConfigCreateResponse, error) {
var response types.ConfigCreateResponse 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 return response, err
} }
resp, err := cli.post(ctx, "/configs/create", nil, config, nil) 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 == "" { if id == "" {
return swarm.Config{}, nil, objectNotFoundError{object: "config", id: 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 return swarm.Config{}, nil, err
} }
resp, err := cli.get(ctx, "/configs/"+id, nil, nil) resp, err := cli.get(ctx, "/configs/"+id, nil, nil)

View File

@ -12,7 +12,7 @@ import (
// ConfigList returns the list of configs. // ConfigList returns the list of configs.
func (cli *Client) ConfigList(ctx context.Context, options types.ConfigListOptions) ([]swarm.Config, error) { 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 return nil, err
} }
query := url.Values{} query := url.Values{}

View File

@ -4,7 +4,7 @@ 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 {
if err := cli.NewVersionError("1.30", "config remove"); err != nil { if err := cli.NewVersionError(ctx, "1.30", "config remove"); err != nil {
return err return err
} }
resp, err := cli.delete(ctx, "/configs/"+id, nil, nil) resp, err := cli.delete(ctx, "/configs/"+id, nil, nil)

View File

@ -9,7 +9,7 @@ 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 {
if err := cli.NewVersionError("1.30", "config update"); err != nil { if err := cli.NewVersionError(ctx, "1.30", "config update"); err != nil {
return err return err
} }
query := url.Values{} 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) { 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 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 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 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 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) { func (cli *Client) ContainerExecCreate(ctx context.Context, container string, config types.ExecConfig) (types.IDResponse, error) {
var response types.IDResponse 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 return response, err
} }
if versions.LessThan(cli.ClientVersion(), "1.42") { 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) { func (cli *Client) ContainersPrune(ctx context.Context, pruneFilters filters.Args) (types.ContainersPruneReport, error) {
var report types.ContainersPruneReport 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 return report, err
} }

View File

@ -17,9 +17,17 @@ func (cli *Client) ContainerRestart(ctx context.Context, containerID string, opt
if options.Timeout != nil { if options.Timeout != nil {
query.Set("t", strconv.Itoa(*options.Timeout)) query.Set("t", strconv.Itoa(*options.Timeout))
} }
if options.Signal != "" && versions.GreaterThanOrEqualTo(cli.version, "1.42") { 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) query.Set("signal", options.Signal)
} }
}
resp, err := cli.post(ctx, "/containers/"+containerID+"/restart", query, nil, nil) resp, err := cli.post(ctx, "/containers/"+containerID+"/restart", query, nil, nil)
ensureReaderClosed(resp) ensureReaderClosed(resp)
return err return err

View File

@ -21,9 +21,17 @@ func (cli *Client) ContainerStop(ctx context.Context, containerID string, option
if options.Timeout != nil { if options.Timeout != nil {
query.Set("t", strconv.Itoa(*options.Timeout)) query.Set("t", strconv.Itoa(*options.Timeout))
} }
if options.Signal != "" && versions.GreaterThanOrEqualTo(cli.version, "1.42") { 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) query.Set("signal", options.Signal)
} }
}
resp, err := cli.post(ctx, "/containers/"+containerID+"/stop", query, nil, nil) resp, err := cli.post(ctx, "/containers/"+containerID+"/stop", query, nil, nil)
ensureReaderClosed(resp) ensureReaderClosed(resp)
return err return err

View File

@ -30,6 +30,12 @@ const containerWaitErrorMsgLimit = 2 * 1024 /* Max: 2KiB */
// synchronize ContainerWait with other calls, such as specifying a // synchronize ContainerWait with other calls, such as specifying a
// "next-exit" condition before issuing a ContainerStart request. // "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) { 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") { if versions.LessThan(cli.ClientVersion(), "1.30") {
return cli.legacyContainerWait(ctx, containerID) 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} 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 return distributionInspect, err
} }

View File

@ -1,6 +1,7 @@
package client // import "github.com/docker/docker/client" package client // import "github.com/docker/docker/client"
import ( import (
"context"
"fmt" "fmt"
"github.com/docker/docker/api/types/versions" "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) return fmt.Sprintf("Error: No such %s: %s", e.object, e.id)
} }
// NewVersionError returns an error if the APIVersion required // NewVersionError returns an error if the APIVersion required is less than the
// if less than the current supported version // current supported version.
func (cli *Client) NewVersionError(APIrequired, feature string) error { //
// 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) { if cli.version != "" && versions.LessThan(cli.version, APIrequired) {
return fmt.Errorf("%q requires API version %s, but the Docker daemon API version is %s", feature, APIrequired, cli.version) return 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 // The Body in the response implements an io.ReadCloser and it's up to the caller to
// close it. // close it.
func (cli *Client) ImageBuild(ctx context.Context, buildContext io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) { 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 { if err != nil {
return types.ImageBuildResponse{}, err return types.ImageBuildResponse{}, err
} }
@ -43,7 +43,7 @@ func (cli *Client) ImageBuild(ctx context.Context, buildContext io.Reader, optio
}, nil }, 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{ query := url.Values{
"t": options.Tags, "t": options.Tags,
"securityopt": options.SecurityOpt, "securityopt": options.SecurityOpt,
@ -73,7 +73,7 @@ func (cli *Client) imageBuildOptionsToQuery(options types.ImageBuildOptions) (ur
} }
if options.Squash { 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 return query, err
} }
query.Set("squash", "1") query.Set("squash", "1")
@ -123,7 +123,7 @@ func (cli *Client) imageBuildOptionsToQuery(options types.ImageBuildOptions) (ur
query.Set("session", options.SessionID) query.Set("session", options.SessionID)
} }
if options.Platform != "" { 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 return query, err
} }
query.Set("platform", strings.ToLower(options.Platform)) query.Set("platform", strings.ToLower(options.Platform))

View File

@ -12,6 +12,13 @@ import (
// ImageList returns a list of images in the docker host. // ImageList returns a list of images in the docker host.
func (cli *Client) ImageList(ctx context.Context, options types.ImageListOptions) ([]types.ImageSummary, error) { 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 var images []types.ImageSummary
query := url.Values{} query := url.Values{}

View File

@ -13,7 +13,7 @@ import (
func (cli *Client) ImagesPrune(ctx context.Context, pruneFilters filters.Args) (types.ImagesPruneReport, error) { func (cli *Client) ImagesPrune(ctx context.Context, pruneFilters filters.Args) (types.ImagesPruneReport, error) {
var report types.ImagesPruneReport 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 return report, err
} }

View File

@ -5,14 +5,26 @@ import (
"encoding/json" "encoding/json"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/versions"
) )
// NetworkCreate creates a new network in the docker host. // NetworkCreate creates a new network in the docker host.
func (cli *Client) NetworkCreate(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error) { 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{ networkCreateRequest := types.NetworkCreateRequest{
NetworkCreate: options, NetworkCreate: options,
Name: name, 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 var response types.NetworkCreateResponse
serverResp, err := cli.post(ctx, "/networks/create", nil, networkCreateRequest, nil) serverResp, err := cli.post(ctx, "/networks/create", nil, networkCreateRequest, nil)
defer ensureReaderClosed(serverResp) defer ensureReaderClosed(serverResp)

View File

@ -13,7 +13,7 @@ import (
func (cli *Client) NetworksPrune(ctx context.Context, pruneFilters filters.Args) (types.NetworksPruneReport, error) { func (cli *Client) NetworksPrune(ctx context.Context, pruneFilters filters.Args) (types.NetworksPruneReport, error) {
var report types.NetworksPruneReport 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 return report, err
} }

View File

@ -14,7 +14,7 @@ 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) (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 return nil, err
} }
query := url.Values{} query := url.Values{}

View File

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

View File

@ -12,7 +12,7 @@ import (
// SecretList returns the list of secrets. // SecretList returns the list of secrets.
func (cli *Client) SecretList(ctx context.Context, options types.SecretListOptions) ([]swarm.Secret, error) { 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 return nil, err
} }
query := url.Values{} query := url.Values{}

View File

@ -4,7 +4,7 @@ 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 {
if err := cli.NewVersionError("1.25", "secret remove"); err != nil { if err := cli.NewVersionError(ctx, "1.25", "secret remove"); err != nil {
return err return err
} }
resp, err := cli.delete(ctx, "/secrets/"+id, nil, nil) resp, err := cli.delete(ctx, "/secrets/"+id, nil, nil)

View File

@ -9,7 +9,7 @@ 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 {
if err := cli.NewVersionError("1.25", "secret update"); err != nil { if err := cli.NewVersionError(ctx, "1.25", "secret update"); err != nil {
return err return err
} }
query := url.Values{} 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) { func (cli *Client) ServiceCreate(ctx context.Context, service swarm.ServiceSpec, options types.ServiceCreateOptions) (types.ServiceCreateResponse, error) {
var response types.ServiceCreateResponse 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 // 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) { if service.TaskTemplate.ContainerSpec == nil && (service.TaskTemplate.Runtime == "" || service.TaskTemplate.Runtime == swarm.RuntimeContainer) {
service.TaskTemplate.ContainerSpec = &swarm.ContainerSpec{} 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 // 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) (types.ServiceUpdateResponse, error) { 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 ( var (
query = url.Values{} query = url.Values{}
response = types.ServiceUpdateResponse{} response = types.ServiceUpdateResponse{}

View File

@ -13,7 +13,7 @@ import (
func (cli *Client) VolumesPrune(ctx context.Context, pruneFilters filters.Args) (types.VolumesPruneReport, error) { func (cli *Client) VolumesPrune(ctx context.Context, pruneFilters filters.Args) (types.VolumesPruneReport, error) {
var report types.VolumesPruneReport 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 return report, err
} }

View File

@ -10,8 +10,14 @@ import (
// VolumeRemove removes a volume from the docker host. // VolumeRemove removes a volume from the docker host.
func (cli *Client) VolumeRemove(ctx context.Context, volumeID string, force bool) error { func (cli *Client) VolumeRemove(ctx context.Context, volumeID string, force bool) error {
query := url.Values{} 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") query.Set("force", "1")
} }
} }

View File

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

View File

@ -4,7 +4,6 @@ import (
"context" "context"
"crypto/tls" "crypto/tls"
"encoding/json" "encoding/json"
"io"
"net/http" "net/http"
"net/url" "net/url"
"strings" "strings"
@ -15,12 +14,8 @@ import (
) )
// v1PingResult contains the information returned when pinging a registry. It // v1PingResult contains the information returned when pinging a registry. It
// indicates the registry's version and whether the registry claims to be a // indicates whether the registry claims to be a standalone registry.
// standalone registry.
type v1PingResult struct { 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 is set to true if the registry indicates it is a
// standalone registry in the X-Docker-Registry-Standalone // standalone registry in the X-Docker-Registry-Standalone
// header // header
@ -47,39 +42,30 @@ func newV1Endpoint(index *registry.IndexInfo, headers http.Header) (*v1Endpoint,
return nil, err return nil, err
} }
err = validateEndpoint(endpoint) if endpoint.String() == IndexServer {
if err != nil { // Skip the check, we know this one is valid
return nil, err // (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 // Try HTTPS ping to registry
endpoint.URL.Scheme = "https" endpoint.URL.Scheme = "https"
if _, err := endpoint.ping(); err != nil { if _, err := endpoint.ping(); err != nil {
if endpoint.IsSecure { if endpoint.IsSecure {
// If registry is secure and HTTPS failed, show user the error and tell them about `--insecure-registry` // 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 fall back to HTTP. // in case that's what they need. DO NOT accept unknown CA certificates, and DO NOT fall back 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) 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) log.G(context.TODO()).WithError(err).Debugf("error from registry %q marked as insecure - insecurely falling back to HTTP", endpoint)
endpoint.URL.Scheme = "http" endpoint.URL.Scheme = "http"
if _, err2 := endpoint.ping(); err2 != nil {
var err2 error return nil, invalidParamf("invalid registry endpoint %q. HTTPS attempt: %v. HTTP attempt: %v", endpoint, err, err2)
if _, err2 = endpoint.ping(); err2 == nil { }
return nil
} }
return invalidParamf("invalid registry endpoint %q. HTTPS attempt: %v. HTTP attempt: %v", endpoint, err, err2) return endpoint, nil
}
return nil
} }
// trimV1Address trims the "v1" version suffix off the address and returns // trimV1Address trims the "v1" version suffix off the address and returns
@ -130,8 +116,8 @@ func (e *v1Endpoint) ping() (v1PingResult, error) {
return v1PingResult{}, nil return v1PingResult{}, nil
} }
log.G(context.TODO()).Debugf("attempting v1 ping for registry endpoint %s", e)
pingURL := e.String() + "_ping" 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) req, err := http.NewRequest(http.MethodGet, pingURL, nil)
if err != nil { if err != nil {
return v1PingResult{}, invalidParam(err) return v1PingResult{}, invalidParam(err)
@ -144,9 +130,14 @@ func (e *v1Endpoint) ping() (v1PingResult, error) {
defer resp.Body.Close() defer resp.Body.Close()
jsonString, err := io.ReadAll(resp.Body) if v := resp.Header.Get("X-Docker-Registry-Standalone"); v != "" {
if err != nil { info := v1PingResult{}
return v1PingResult{}, invalidParamWrapf(err, "error while reading response from %s", pingURL) // 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 // If the header is absent, we assume true for compatibility with earlier
@ -154,24 +145,11 @@ func (e *v1Endpoint) ping() (v1PingResult, error) {
info := v1PingResult{ info := v1PingResult{
Standalone: true, 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") log.G(context.TODO()).WithError(err).Debug("error unmarshaling _ping response")
// don't stop here. Just assume sane defaults // 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) log.G(context.TODO()).Debugf("v1PingResult.Standalone: %t", info.Standalone)
return info, nil return info, nil
} }

View File

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