vendor: github.com/moby/moby/api, client 0769fe708773 (master)

full diff: 4ca8aedf92...0769fe7087

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn
2025-10-06 12:54:54 +02:00
parent 6ddff81bee
commit f81816ef88
175 changed files with 1514 additions and 1830 deletions

View File

@ -1,12 +1,10 @@
package stdcopy
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"sync"
)
// StdType is the type of standard stream
@ -28,71 +26,6 @@ const (
startingBufLen = 32*1024 + stdWriterPrefixLen + 1
)
var bufPool = &sync.Pool{New: func() any { return bytes.NewBuffer(nil) }}
// stdWriter is wrapper of io.Writer with extra customized info.
type stdWriter struct {
io.Writer
prefix byte
}
// Write sends the buffer to the underlying writer.
// It inserts the prefix header before the buffer,
// so [StdCopy] knows where to multiplex the output.
//
// It implements [io.Writer].
func (w *stdWriter) Write(p []byte) (int, error) {
if w == nil || w.Writer == nil {
return 0, errors.New("writer not instantiated")
}
if p == nil {
return 0, nil
}
header := [stdWriterPrefixLen]byte{stdWriterFdIndex: w.prefix}
binary.BigEndian.PutUint32(header[stdWriterSizeIndex:], uint32(len(p)))
buf := bufPool.Get().(*bytes.Buffer)
buf.Write(header[:])
buf.Write(p)
n, err := w.Writer.Write(buf.Bytes())
n -= stdWriterPrefixLen
if n < 0 {
n = 0
}
buf.Reset()
bufPool.Put(buf)
return n, err
}
// NewStdWriter instantiates a new writer using a custom format to multiplex
// multiple streams to a single writer. All messages written using this writer
// are encapsulated using a custom format, and written to the underlying
// stream "w".
//
// Writers created through NewStdWriter allow for multiple write streams
// (e.g., stdout ([Stdout]) and stderr ([Stderr]) to be multiplexed into a
// single connection. "streamType" indicates the type of stream to encapsulate,
// commonly, [Stdout] or [Stderr]. The [Systemerr] stream can be used to
// include server-side errors in the stream. Information on this stream
// is returned as an error by [StdCopy] and terminates processing the
// stream.
//
// The [Stdin] stream is present for completeness and should generally
// NOT be used. It is output on [Stdout] when reading the stream with
// [StdCopy].
//
// All streams must share the same underlying [io.Writer] to ensure proper
// multiplexing. Each call to NewStdWriter wraps that shared writer with
// a header indicating the target stream.
func NewStdWriter(w io.Writer, streamType StdType) io.Writer {
return &stdWriter{
Writer: w,
prefix: byte(streamType),
}
}
// StdCopy is a modified version of [io.Copy] to de-multiplex messages
// from "multiplexedSource" and copy them to destination streams
// "destOut" and "destErr".

View File

@ -4,6 +4,7 @@ import (
"time"
dockerspec "github.com/moby/docker-image-spec/specs-go/v1"
"github.com/moby/moby/api/types/network"
)
// MinimumDuration puts a minimum on user configured duration.
@ -28,7 +29,7 @@ type Config struct {
AttachStdin bool // Attach the standard input, makes possible user interaction
AttachStdout bool // Attach the standard output
AttachStderr bool // Attach the standard error
ExposedPorts PortSet `json:",omitempty"` // List of exposed ports
ExposedPorts network.PortSet `json:",omitempty"` // List of exposed ports
Tty bool // Attach standard streams to a tty, including stdin if it is not closed.
OpenStdin bool // Open stdin
StdinOnce bool // If true, close stdin after the 1 attached client disconnects.

View File

@ -3,6 +3,7 @@ package container
import (
"errors"
"fmt"
"net/netip"
"strings"
"github.com/docker/go-units"
@ -420,7 +421,7 @@ type HostConfig struct {
ContainerIDFile string // File (path) where the containerId is written
LogConfig LogConfig // Configuration of the logs for this container
NetworkMode NetworkMode // Network mode to use for the container
PortBindings PortMap // Port mapping between the exposed port (container) and the host
PortBindings network.PortMap // Port mapping between the exposed port (container) and the host
RestartPolicy RestartPolicy // Restart policy to be used for the container
AutoRemove bool // Automatically remove container when it exits
VolumeDriver string // Name of the volume driver used to mount volumes
@ -432,7 +433,7 @@ type HostConfig struct {
CapAdd []string // List of kernel capabilities to add to the container
CapDrop []string // List of kernel capabilities to remove from the container
CgroupnsMode CgroupnsMode // Cgroup namespace mode to use for the container
DNS []string `json:"Dns"` // List of DNS server to lookup
DNS []netip.Addr `json:"Dns"` // List of DNS server to lookup
DNSOptions []string `json:"DnsOptions"` // List of DNSOption to look for
DNSSearch []string `json:"DnsSearch"` // List of DNSSearch to look for
ExtraHosts []string // List of extra hosts

View File

@ -1,24 +0,0 @@
package container
import "github.com/docker/go-connections/nat"
// PortRangeProto is a string containing port number and protocol in the format "80/tcp",
// or a port range and protocol in the format "80-83/tcp".
//
// It is currently an alias for [nat.Port] but may become a concrete type in a future release.
type PortRangeProto = nat.Port
// PortSet is a collection of structs indexed by [HostPort].
//
// It is currently an alias for [nat.PortSet] but may become a concrete type in a future release.
type PortSet = nat.PortSet
// PortBinding represents a binding between a Host IP address and a [HostPort].
//
// It is currently an alias for [nat.PortBinding] but may become a concrete type in a future release.
type PortBinding = nat.PortBinding
// PortMap is a collection of [PortBinding] indexed by [HostPort].
//
// It is currently an alias for [nat.PortMap] but may become a concrete type in a future release.
type PortMap = nat.PortMap

View File

@ -6,10 +6,13 @@ import (
// NetworkSettings exposes the network settings in the api
type NetworkSettings struct {
SandboxID string // SandboxID uniquely represents a container's network stack
SandboxKey string // SandboxKey identifies the sandbox
Ports PortMap // Ports is a collection of PortBinding indexed by Port
Networks map[string]*network.EndpointSettings
SandboxID string // SandboxID uniquely represents a container's network stack
SandboxKey string // SandboxKey identifies the sandbox
// Ports is a collection of [network.PortBinding] indexed by [network.Port]
Ports network.PortMap
Networks map[string]*network.EndpointSettings
}
// NetworkSettingsSummary provides a summary of container's networks

View File

@ -5,6 +5,10 @@ package container
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/netip"
)
// PortSummary Describes a port-mapping between the container and the host.
//
// Example: {"PrivatePort":8080,"PublicPort":80,"Type":"tcp"}
@ -13,7 +17,7 @@ package container
type PortSummary struct {
// Host IP address that the container's port is mapped to
IP string `json:"IP,omitempty"`
IP netip.Addr `json:"IP,omitempty"`
// Port on the container
// Required: true

View File

@ -1,24 +0,0 @@
package filters
import "fmt"
// invalidFilter indicates that the provided filter or its value is invalid
type invalidFilter struct {
Filter string
Value []string
}
func (e invalidFilter) Error() string {
msg := "invalid filter"
if e.Filter != "" {
msg += " '" + e.Filter
if e.Value != nil {
msg = fmt.Sprintf("%s=%s", msg, e.Value)
}
msg += "'"
}
return msg
}
// InvalidParameter marks this error as ErrInvalidParameter
func (e invalidFilter) InvalidParameter() {}

View File

@ -1,302 +0,0 @@
/*
Package filters provides tools for encoding a mapping of keys to a set of
multiple values.
*/
package filters
import (
"encoding/json"
"regexp"
"strings"
)
// Args stores a mapping of keys to a set of multiple values.
type Args struct {
fields map[string]map[string]bool
}
// KeyValuePair are used to initialize a new Args
type KeyValuePair struct {
Key string
Value string
}
// Arg creates a new KeyValuePair for initializing Args
func Arg(key, value string) KeyValuePair {
return KeyValuePair{Key: key, Value: value}
}
// NewArgs returns a new Args populated with the initial args
func NewArgs(initialArgs ...KeyValuePair) Args {
args := Args{fields: map[string]map[string]bool{}}
for _, arg := range initialArgs {
args.Add(arg.Key, arg.Value)
}
return args
}
// Keys returns all the keys in list of Args
func (args Args) Keys() []string {
keys := make([]string, 0, len(args.fields))
for k := range args.fields {
keys = append(keys, k)
}
return keys
}
// MarshalJSON returns a JSON byte representation of the Args
func (args Args) MarshalJSON() ([]byte, error) {
if len(args.fields) == 0 {
return []byte("{}"), nil
}
return json.Marshal(args.fields)
}
// ToJSON returns the Args as a JSON encoded string
func ToJSON(a Args) (string, error) {
if a.Len() == 0 {
return "", nil
}
buf, err := json.Marshal(a)
return string(buf), err
}
// FromJSON decodes a JSON encoded string into Args
func FromJSON(p string) (Args, error) {
args := NewArgs()
if p == "" {
return args, nil
}
raw := []byte(p)
err := json.Unmarshal(raw, &args)
if err == nil {
return args, nil
}
// Fallback to parsing arguments in the legacy slice format
deprecated := map[string][]string{}
if legacyErr := json.Unmarshal(raw, &deprecated); legacyErr != nil {
return args, &invalidFilter{}
}
args.fields = deprecatedArgs(deprecated)
return args, nil
}
// UnmarshalJSON populates the Args from JSON encode bytes
func (args Args) UnmarshalJSON(raw []byte) error {
return json.Unmarshal(raw, &args.fields)
}
// Get returns the list of values associated with the key
func (args Args) Get(key string) []string {
values := args.fields[key]
if values == nil {
return make([]string, 0)
}
slice := make([]string, 0, len(values))
for key := range values {
slice = append(slice, key)
}
return slice
}
// Add a new value to the set of values
func (args Args) Add(key, value string) {
if _, ok := args.fields[key]; ok {
args.fields[key][value] = true
} else {
args.fields[key] = map[string]bool{value: true}
}
}
// Del removes a value from the set
func (args Args) Del(key, value string) {
if _, ok := args.fields[key]; ok {
delete(args.fields[key], value)
if len(args.fields[key]) == 0 {
delete(args.fields, key)
}
}
}
// Len returns the number of keys in the mapping
func (args Args) Len() int {
return len(args.fields)
}
// MatchKVList returns true if all the pairs in sources exist as key=value
// pairs in the mapping at key, or if there are no values at key.
func (args Args) MatchKVList(key string, sources map[string]string) bool {
fieldValues := args.fields[key]
// do not filter if there is no filter set or cannot determine filter
if len(fieldValues) == 0 {
return true
}
if len(sources) == 0 {
return false
}
for value := range fieldValues {
testK, testV, hasValue := strings.Cut(value, "=")
v, ok := sources[testK]
if !ok {
return false
}
if hasValue && testV != v {
return false
}
}
return true
}
// Match returns true if any of the values at key match the source string
func (args Args) Match(field, source string) bool {
if args.ExactMatch(field, source) {
return true
}
fieldValues := args.fields[field]
for name2match := range fieldValues {
match, err := regexp.MatchString(name2match, source)
if err != nil {
continue
}
if match {
return true
}
}
return false
}
// GetBoolOrDefault returns a boolean value of the key if the key is present
// and is interpretable as a boolean value. Otherwise the default value is returned.
// 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) {
fieldValues, ok := args.fields[key]
if !ok {
return defaultValue, nil
}
if len(fieldValues) == 0 {
return defaultValue, &invalidFilter{key, nil}
}
isFalse := fieldValues["0"] || fieldValues["false"]
isTrue := fieldValues["1"] || fieldValues["true"]
if isFalse == isTrue {
// Either no or conflicting truthy/falsy value were provided
return defaultValue, &invalidFilter{key, args.Get(key)}
}
return isTrue, nil
}
// ExactMatch returns true if the source matches exactly one of the values.
func (args Args) ExactMatch(key, source string) bool {
fieldValues, ok := args.fields[key]
// do not filter if there is no filter set or cannot determine filter
if !ok || len(fieldValues) == 0 {
return true
}
// try to match full name value to avoid O(N) regular expression matching
return fieldValues[source]
}
// UniqueExactMatch returns true if there is only one value and the source
// matches exactly the value.
func (args Args) UniqueExactMatch(key, source string) bool {
fieldValues := args.fields[key]
// do not filter if there is no filter set or cannot determine filter
if len(fieldValues) == 0 {
return true
}
if len(args.fields[key]) != 1 {
return false
}
// try to match full name value to avoid O(N) regular expression matching
return fieldValues[source]
}
// FuzzyMatch returns true if the source matches exactly one value, or the
// source has one of the values as a prefix.
func (args Args) FuzzyMatch(key, source string) bool {
if args.ExactMatch(key, source) {
return true
}
fieldValues := args.fields[key]
for prefix := range fieldValues {
if strings.HasPrefix(source, prefix) {
return true
}
}
return false
}
// Contains returns true if the key exists in the mapping
func (args Args) Contains(field string) bool {
_, ok := args.fields[field]
return ok
}
// Validate compared the set of accepted keys against the keys in the mapping.
// An error is returned if any mapping keys are not in the accepted set.
func (args Args) Validate(accepted map[string]bool) error {
for name := range args.fields {
if !accepted[name] {
return &invalidFilter{name, nil}
}
}
return nil
}
// WalkValues iterates over the list of values for a key in the mapping and calls
// op() for each value. If op returns an error the iteration stops and the
// error is returned.
func (args Args) WalkValues(field string, op func(value string) error) error {
if _, ok := args.fields[field]; !ok {
return nil
}
for v := range args.fields[field] {
if err := op(v); err != nil {
return err
}
}
return nil
}
// Clone returns a copy of args.
func (args Args) Clone() (newArgs Args) {
newArgs.fields = make(map[string]map[string]bool, len(args.fields))
for k, m := range args.fields {
var mm map[string]bool
if m != nil {
mm = make(map[string]bool, len(m))
for kk, v := range m {
mm[kk] = v
}
}
newArgs.fields[k] = mm
}
return newArgs
}
func deprecatedArgs(d map[string][]string) map[string]map[string]bool {
m := map[string]map[string]bool{}
for k, v := range d {
values := map[string]bool{}
for _, vv := range v {
values[vv] = true
}
m[k] = values
}
return m
}

View File

@ -2,7 +2,6 @@ package image
import (
dockerspec "github.com/moby/docker-image-spec/specs-go/v1"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/storage"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
@ -43,16 +42,9 @@ type InspectResponse struct {
// 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
// importing the image. This field is omitted if not set.
Comment string `json:",omitempty"`
// Created is the date and time at which the image was created, formatted in
// RFC 3339 nano-seconds (time.RFC3339Nano).
@ -61,30 +53,10 @@ type InspectResponse struct {
// 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
// This field is omitted if not set.
Author string `json:",omitempty"`
Config *dockerspec.DockerOCIImageConfig
// Architecture is the hardware CPU architecture that the image runs on.
@ -103,12 +75,6 @@ type InspectResponse struct {
// 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 `json:"GraphDriver,omitempty"`

View File

@ -1,10 +1,8 @@
package network
import (
"errors"
"fmt"
"maps"
"net"
"net/netip"
"slices"
)
@ -28,11 +26,11 @@ type EndpointSettings struct {
// Operational data
NetworkID string
EndpointID string
Gateway string
IPAddress string
Gateway netip.Addr
IPAddress netip.Addr
IPPrefixLen int
IPv6Gateway string
GlobalIPv6Address string
IPv6Gateway netip.Addr
GlobalIPv6Address netip.Addr
GlobalIPv6PrefixLen int
// DNSNames holds all the (non fully qualified) DNS names associated to this endpoint. First entry is used to
// generate PTR records.
@ -57,9 +55,9 @@ func (es *EndpointSettings) Copy() *EndpointSettings {
// EndpointIPAMConfig represents IPAM configurations for the endpoint
type EndpointIPAMConfig struct {
IPv4Address string `json:",omitempty"`
IPv6Address string `json:",omitempty"`
LinkLocalIPs []string `json:",omitempty"`
IPv4Address netip.Addr `json:",omitempty"`
IPv6Address netip.Addr `json:",omitempty"`
LinkLocalIPs []netip.Addr `json:",omitempty"`
}
// Copy makes a copy of the endpoint ipam config
@ -71,76 +69,3 @@ func (cfg *EndpointIPAMConfig) Copy() *EndpointIPAMConfig {
cfgCopy.LinkLocalIPs = slices.Clone(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 errJoin(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 errJoin(errs...)
}

View File

@ -5,6 +5,10 @@ package network
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/netip"
)
// EndpointResource contains network resources allocated and used for a container in a network.
//
// swagger:model EndpointResource
@ -24,8 +28,8 @@ type EndpointResource struct {
// IPv4 address
// Example: 172.19.0.2/16
IPv4Address string `json:"IPv4Address"`
IPv4Address netip.Prefix `json:"IPv4Address"`
// IPv6 address
IPv6Address string `json:"IPv6Address"`
IPv6Address netip.Prefix `json:"IPv6Address"`
}

View File

@ -1,10 +1,7 @@
package network
import (
"errors"
"fmt"
"net/netip"
"strings"
)
// IPAM represents IP Address Management
@ -16,160 +13,10 @@ type IPAM struct {
// IPAMConfig represents IPAM configurations
type IPAMConfig struct {
Subnet string `json:",omitempty"`
IPRange string `json:",omitempty"`
Gateway string `json:",omitempty"`
AuxAddress map[string]string `json:"AuxiliaryAddresses,omitempty"`
Subnet netip.Prefix `json:",omitempty"`
IPRange netip.Prefix `json:",omitempty"`
Gateway netip.Addr `json:",omitempty"`
AuxAddress map[string]netip.Addr `json:"AuxiliaryAddresses,omitempty"`
}
type SubnetStatuses = map[netip.Prefix]SubnetStatus
type ipFamily string
const (
ip4 ipFamily = "IPv4"
ip6 ipFamily = "IPv6"
)
// 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, enableIPv6 bool) error {
if ipam == nil {
return nil
}
var errs []error
for _, cfg := range ipam.Config {
subnet, err := netip.ParsePrefix(cfg.Subnet)
if err != nil {
errs = append(errs, fmt.Errorf("invalid subnet %s: invalid CIDR block notation", cfg.Subnet))
continue
}
subnetFamily := ip4
if subnet.Addr().Is6() {
subnetFamily = ip6
}
if !enableIPv6 && subnetFamily == ip6 {
continue
}
if subnet != subnet.Masked() {
errs = append(errs, fmt.Errorf("invalid subnet %s: it should be %s", subnet, subnet.Masked()))
}
if ipRangeErrs := validateIPRange(cfg.IPRange, subnet, subnetFamily); len(ipRangeErrs) > 0 {
errs = append(errs, ipRangeErrs...)
}
if err := validateAddress(cfg.Gateway, subnet, subnetFamily); err != nil {
errs = append(errs, fmt.Errorf("invalid gateway %s: %w", cfg.Gateway, err))
}
for auxName, aux := range cfg.AuxAddress {
if err := validateAddress(aux, subnet, subnetFamily); err != nil {
errs = append(errs, fmt.Errorf("invalid auxiliary address %s: %w", auxName, err))
}
}
}
if err := errJoin(errs...); err != nil {
return fmt.Errorf("invalid network config:\n%w", err)
}
return nil
}
func validateIPRange(ipRange string, subnet netip.Prefix, subnetFamily ipFamily) []error {
if ipRange == "" {
return nil
}
prefix, err := netip.ParsePrefix(ipRange)
if err != nil {
return []error{fmt.Errorf("invalid ip-range %s: invalid CIDR block notation", ipRange)}
}
family := ip4
if prefix.Addr().Is6() {
family = ip6
}
if family != subnetFamily {
return []error{fmt.Errorf("invalid ip-range %s: parent subnet is an %s block", ipRange, subnetFamily)}
}
var errs []error
if prefix.Bits() < subnet.Bits() {
errs = append(errs, fmt.Errorf("invalid ip-range %s: CIDR block is bigger than its parent subnet %s", ipRange, subnet))
}
if prefix != prefix.Masked() {
errs = append(errs, fmt.Errorf("invalid ip-range %s: it should be %s", prefix, prefix.Masked()))
}
if !subnet.Overlaps(prefix) {
errs = append(errs, fmt.Errorf("invalid ip-range %s: parent subnet %s doesn't contain ip-range", ipRange, subnet))
}
return errs
}
func validateAddress(address string, subnet netip.Prefix, subnetFamily ipFamily) error {
if address == "" {
return nil
}
addr, err := netip.ParseAddr(address)
if err != nil {
return errors.New("invalid address")
}
family := ip4
if addr.Is6() {
family = ip6
}
if family != subnetFamily {
return fmt.Errorf("parent subnet is an %s block", subnetFamily)
}
if !subnet.Contains(addr) {
return fmt.Errorf("parent subnet %s doesn't contain this address", subnet)
}
return nil
}
func errJoin(errs ...error) error {
n := 0
for _, err := range errs {
if err != nil {
n++
}
}
if n == 0 {
return nil
}
e := &joinError{
errs: make([]error, 0, n),
}
for _, err := range errs {
if err != nil {
e.errs = append(e.errs, err)
}
}
return e
}
type joinError struct {
errs []error
}
func (e *joinError) Error() string {
if len(e.errs) == 1 {
return strings.TrimSpace(e.errs[0].Error())
}
stringErrs := make([]string, 0, len(e.errs))
for _, subErr := range e.errs {
stringErrs = append(stringErrs, strings.ReplaceAll(subErr.Error(), "\n", "\n\t"))
}
return "* " + strings.Join(stringErrs, "\n* ")
}
func (e *joinError) Unwrap() []error {
return e.errs
}

View File

@ -30,14 +30,6 @@ type CreateRequest struct {
Labels map[string]string // Labels holds metadata specific to the network being created.
}
// ServiceInfo represents service parameters with the list of service's tasks
type ServiceInfo struct {
VIP string
Ports []string
LocalLBIndex int
Tasks []Task
}
// 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

@ -5,6 +5,10 @@ package network
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/netip"
)
// PeerInfo represents one peer of an overlay network.
//
// swagger:model PeerInfo
@ -16,5 +20,5 @@ type PeerInfo struct {
// IP-address of the peer-node in the Swarm cluster.
// Example: 10.133.77.91
IP string `json:"IP"`
IP netip.Addr `json:"IP"`
}

346
vendor/github.com/moby/moby/api/types/network/port.go generated vendored Normal file
View File

@ -0,0 +1,346 @@
package network
import (
"errors"
"fmt"
"iter"
"net/netip"
"strconv"
"strings"
"unique"
)
// IPProtocol represents a network protocol for a port.
type IPProtocol string
const (
TCP IPProtocol = "tcp"
UDP IPProtocol = "udp"
SCTP IPProtocol = "sctp"
)
// Sentinel port proto value for zero Port and PortRange values.
var protoZero unique.Handle[IPProtocol]
// Port is a type representing a single port number and protocol in the format "<portnum>/[<proto>]".
//
// The zero port value, i.e. Port{}, is invalid; use [ParsePort] to create a valid Port value.
type Port struct {
num uint16
proto unique.Handle[IPProtocol]
}
// ParsePort parses s as a [Port].
//
// It normalizes the provided protocol such that "80/tcp", "80/TCP", and "80/tCp" are equivalent.
// If a port number is provided, but no protocol, the default ("tcp") protocol is returned.
func ParsePort(s string) (Port, error) {
if s == "" {
return Port{}, errors.New("invalid port: value is empty")
}
port, proto, _ := strings.Cut(s, "/")
portNum, err := parsePortNumber(port)
if err != nil {
return Port{}, fmt.Errorf("invalid port '%s': %w", port, err)
}
normalizedPortProto := normalizePortProto(proto)
return Port{num: portNum, proto: normalizedPortProto}, nil
}
// MustParsePort calls [ParsePort](s) and panics on error.
//
// It is intended for use in tests with hard-coded strings.
func MustParsePort(s string) Port {
p, err := ParsePort(s)
if err != nil {
panic(err)
}
return p
}
// PortFrom returns a [Port] with the given number and protocol.
//
// If no protocol is specified (i.e. proto == ""), then PortFrom returns Port{}, false.
func PortFrom(num uint16, proto IPProtocol) (p Port, ok bool) {
if proto == "" {
return Port{}, false
}
normalized := normalizePortProto(string(proto))
return Port{num: num, proto: normalized}, true
}
// Num returns p's port number.
func (p Port) Num() uint16 {
return p.num
}
// Proto returns p's network protocol.
func (p Port) Proto() IPProtocol {
return p.proto.Value()
}
// IsZero reports whether p is the zero value.
func (p Port) IsZero() bool {
return p.proto == protoZero
}
// IsValid reports whether p is an initialized valid port (not the zero value).
func (p Port) IsValid() bool {
return p.proto != protoZero
}
// String returns a string representation of the port in the format "<portnum>/<proto>".
// If the port is the zero value, it returns "invalid port".
func (p Port) String() string {
switch p.proto {
case protoZero:
return "invalid port"
default:
return string(p.AppendTo(nil))
}
}
// AppendText implements [encoding.TextAppender] interface.
// It is the same as [Port.AppendTo] but returns an error to satisfy the interface.
func (p Port) AppendText(b []byte) ([]byte, error) {
return p.AppendTo(b), nil
}
// AppendTo appends a text encoding of p to b and returns the extended buffer.
func (p Port) AppendTo(b []byte) []byte {
if p.IsZero() {
return b
}
return fmt.Appendf(b, "%d/%s", p.num, p.proto.Value())
}
// MarshalText implements [encoding.TextMarshaler] interface.
func (p Port) MarshalText() ([]byte, error) {
return p.AppendText(nil)
}
// UnmarshalText implements [encoding.TextUnmarshaler] interface.
func (p *Port) UnmarshalText(text []byte) error {
if len(text) == 0 {
*p = Port{}
return nil
}
port, err := ParsePort(string(text))
if err != nil {
return err
}
*p = port
return nil
}
// Range returns a [PortRange] representing the single port.
func (p Port) Range() PortRange {
return PortRange{start: p.num, end: p.num, proto: p.proto}
}
// PortSet is a collection of structs indexed by [Port].
type PortSet = map[Port]struct{}
// PortBinding represents a binding between a Host IP address and a Host Port.
type PortBinding struct {
// HostIP is the host IP Address
HostIP netip.Addr `json:"HostIp"`
// HostPort is the host port number
HostPort string `json:"HostPort"`
}
// PortMap is a collection of [PortBinding] indexed by [Port].
type PortMap = map[Port][]PortBinding
// PortRange represents a range of port numbers and a protocol in the format "8000-9000/tcp".
//
// The zero port range value, i.e. PortRange{}, is invalid; use [ParsePortRange] to create a valid PortRange value.
type PortRange struct {
start uint16
end uint16
proto unique.Handle[IPProtocol]
}
// ParsePortRange parses s as a [PortRange].
//
// It normalizes the provided protocol such that "80-90/tcp", "80-90/TCP", and "80-90/tCp" are equivalent.
// If a port number range is provided, but no protocol, the default ("tcp") protocol is returned.
func ParsePortRange(s string) (PortRange, error) {
if s == "" {
return PortRange{}, errors.New("invalid port range: value is empty")
}
portRange, proto, _ := strings.Cut(s, "/")
start, end, ok := strings.Cut(portRange, "-")
startVal, err := parsePortNumber(start)
if err != nil {
return PortRange{}, fmt.Errorf("invalid start port '%s': %w", start, err)
}
portProto := normalizePortProto(proto)
if !ok || start == end {
return PortRange{start: startVal, end: startVal, proto: portProto}, nil
}
endVal, err := parsePortNumber(end)
if err != nil {
return PortRange{}, fmt.Errorf("invalid end port '%s': %w", end, err)
}
if endVal < startVal {
return PortRange{}, errors.New("invalid port range: " + s)
}
return PortRange{start: startVal, end: endVal, proto: portProto}, nil
}
// MustParsePortRange calls [ParsePortRange](s) and panics on error.
// It is intended for use in tests with hard-coded strings.
func MustParsePortRange(s string) PortRange {
pr, err := ParsePortRange(s)
if err != nil {
panic(err)
}
return pr
}
// PortRangeFrom returns a [PortRange] with the given start and end port numbers and protocol.
//
// If end < start or no protocol is specified (i.e. proto == ""), then PortRangeFrom returns PortRange{}, false.
func PortRangeFrom(start, end uint16, proto IPProtocol) (pr PortRange, ok bool) {
if end < start || proto == "" {
return PortRange{}, false
}
normalized := normalizePortProto(string(proto))
return PortRange{start: start, end: end, proto: normalized}, true
}
// Start returns pr's start port number.
func (pr PortRange) Start() uint16 {
return pr.start
}
// End returns pr's end port number.
func (pr PortRange) End() uint16 {
return pr.end
}
// Proto returns pr's network protocol.
func (pr PortRange) Proto() IPProtocol {
return pr.proto.Value()
}
// IsZero reports whether pr is the zero value.
func (pr PortRange) IsZero() bool {
return pr.proto == protoZero
}
// IsValid reports whether pr is an initialized valid port range (not the zero value).
func (pr PortRange) IsValid() bool {
return pr.proto != protoZero
}
// String returns a string representation of the port range in the format "<start>-<end>/<proto>" or "<portnum>/<proto>" if start == end.
// If the port range is the zero value, it returns "invalid port range".
func (pr PortRange) String() string {
switch pr.proto {
case protoZero:
return "invalid port range"
default:
return string(pr.AppendTo(nil))
}
}
// AppendText implements [encoding.TextAppender] interface.
// It is the same as [PortRange.AppendTo] but returns an error to satisfy the interface.
func (pr PortRange) AppendText(b []byte) ([]byte, error) {
return pr.AppendTo(b), nil
}
// AppendTo appends a text encoding of pr to b and returns the extended buffer.
func (pr PortRange) AppendTo(b []byte) []byte {
if pr.IsZero() {
return b
}
if pr.start == pr.end {
return fmt.Appendf(b, "%d/%s", pr.start, pr.proto.Value())
}
return fmt.Appendf(b, "%d-%d/%s", pr.start, pr.end, pr.proto.Value())
}
// MarshalText implements [encoding.TextMarshaler] interface.
func (pr PortRange) MarshalText() ([]byte, error) {
return pr.AppendText(nil)
}
// UnmarshalText implements [encoding.TextUnmarshaler] interface.
func (pr *PortRange) UnmarshalText(text []byte) error {
if len(text) == 0 {
*pr = PortRange{}
return nil
}
portRange, err := ParsePortRange(string(text))
if err != nil {
return err
}
*pr = portRange
return nil
}
// Range returns pr.
func (pr PortRange) Range() PortRange {
return pr
}
// All returns an iterator over all the individual ports in the range.
//
// For example:
//
// for port := range pr.All() {
// // ...
// }
func (pr PortRange) All() iter.Seq[Port] {
return func(yield func(Port) bool) {
for i := uint32(pr.Start()); i <= uint32(pr.End()); i++ {
if !yield(Port{num: uint16(i), proto: pr.proto}) {
return
}
}
}
}
// parsePortNumber parses rawPort into an int, unwrapping strconv errors
// and returning a single "out of range" error for any value outside 065535.
func parsePortNumber(rawPort string) (uint16, error) {
if rawPort == "" {
return 0, errors.New("value is empty")
}
port, err := strconv.ParseUint(rawPort, 10, 16)
if err != nil {
var numErr *strconv.NumError
if errors.As(err, &numErr) {
err = numErr.Err
}
return 0, err
}
return uint16(port), nil
}
// normalizePortProto normalizes the protocol string such that "tcp", "TCP", and "tCp" are equivalent.
// If proto is not specified, it defaults to "tcp".
func normalizePortProto(proto string) unique.Handle[IPProtocol] {
if proto == "" {
return unique.Make(TCP)
}
proto = strings.ToLower(proto)
return unique.Make(IPProtocol(proto))
}

View File

@ -0,0 +1,28 @@
// Code generated by go-swagger; DO NOT EDIT.
package network
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/netip"
)
// ServiceInfo represents service parameters with the list of service's tasks
//
// swagger:model ServiceInfo
type ServiceInfo struct {
// v IP
VIP netip.Addr `json:"VIP"`
// ports
Ports []string `json:"Ports"`
// local l b index
LocalLBIndex int `json:"LocalLBIndex"`
// tasks
Tasks []Task `json:"Tasks"`
}

View File

@ -5,6 +5,10 @@ package network
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/netip"
)
// Task carries the information about one backend task
//
// swagger:model Task
@ -17,7 +21,7 @@ type Task struct {
EndpointID string `json:"EndpointID"`
// endpoint IP
EndpointIP string `json:"EndpointIP"`
EndpointIP netip.Addr `json:"EndpointIP"`
// info
Info map[string]string `json:"Info"`

View File

@ -51,8 +51,11 @@ type Config struct {
// Required: true
Description string `json:"Description"`
// Docker Version used to create the plugin
// Example: 17.06.0-ce
// Docker Version used to create the plugin.
//
// Depending on how the plugin was created, this field may be empty or omitted.
//
// Deprecated: this field is no longer set, and will be removed in the next API version.
DockerVersion string `json:"DockerVersion,omitempty"`
// documentation

View File

@ -1,48 +1,16 @@
package registry
import (
"encoding/json"
"net"
"net/netip"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
// ServiceConfig stores daemon registry services configuration.
type ServiceConfig struct {
InsecureRegistryCIDRs []*NetIPNet `json:"InsecureRegistryCIDRs"`
InsecureRegistryCIDRs []netip.Prefix `json:"InsecureRegistryCIDRs"`
IndexConfigs map[string]*IndexInfo `json:"IndexConfigs"`
Mirrors []string
// ExtraFields is for internal use to include deprecated fields on older API versions.
ExtraFields map[string]any `json:"-"`
}
// NetIPNet is the net.IPNet type, which can be marshalled and
// unmarshalled to JSON
type NetIPNet net.IPNet
// String returns the CIDR notation of ipnet
func (ipnet *NetIPNet) String() string {
return (*net.IPNet)(ipnet).String()
}
// MarshalJSON returns the JSON representation of the IPNet
func (ipnet *NetIPNet) MarshalJSON() ([]byte, error) {
return json.Marshal((*net.IPNet)(ipnet).String())
}
// UnmarshalJSON sets the IPNet from a byte array of JSON
func (ipnet *NetIPNet) UnmarshalJSON(b []byte) error {
var ipnetStr string
if err := json.Unmarshal(b, &ipnetStr); err != nil {
return err
}
_, cidr, err := net.ParseCIDR(ipnetStr)
if err != nil {
return err
}
*ipnet = NetIPNet(*cidr)
return nil
}
// IndexInfo contains information about a registry

View File

@ -1,6 +1,7 @@
package swarm
import (
"net/netip"
"time"
"github.com/moby/moby/api/types/container"
@ -14,7 +15,7 @@ import (
// TODO: `domain` is not supported yet.
type DNSConfig struct {
// Nameservers specifies the IP addresses of the name servers
Nameservers []string `json:",omitempty"`
Nameservers []netip.Addr `json:",omitempty"`
// Search specifies the search list for host-name lookup
Search []string `json:",omitempty"`
// Options allows certain internal resolver variables to be modified

View File

@ -1,6 +1,8 @@
package swarm
import (
"net/netip"
"github.com/moby/moby/api/types/network"
)
@ -30,7 +32,7 @@ const (
// PortConfig represents the config of a port.
type PortConfig struct {
Name string `json:",omitempty"`
Protocol PortConfigProtocol `json:",omitempty"`
Protocol network.IPProtocol `json:",omitempty"`
// TargetPort is the port inside the container
TargetPort uint32 `json:",omitempty"`
// PublishedPort is the port on the swarm hosts
@ -52,24 +54,14 @@ const (
PortConfigPublishModeHost PortConfigPublishMode = "host"
)
// PortConfigProtocol represents the protocol of a port.
type PortConfigProtocol string
const (
// TODO(stevvooe): These should be used generally, not just for PortConfig.
// PortConfigProtocolTCP TCP
PortConfigProtocolTCP PortConfigProtocol = "tcp"
// PortConfigProtocolUDP UDP
PortConfigProtocolUDP PortConfigProtocol = "udp"
// PortConfigProtocolSCTP SCTP
PortConfigProtocolSCTP PortConfigProtocol = "sctp"
)
// EndpointVirtualIP represents the virtual ip of a port.
type EndpointVirtualIP struct {
NetworkID string `json:",omitempty"`
Addr string `json:",omitempty"`
// Addr is the virtual ip address.
// This field accepts CIDR notation, for example `10.0.0.1/24`, to maintain backwards
// compatibility, but only the IP address is used.
Addr netip.Prefix `json:",omitempty"`
}
// Network represents a network.
@ -103,8 +95,12 @@ type NetworkAttachmentConfig struct {
// NetworkAttachment represents a network attachment.
type NetworkAttachment struct {
Network Network `json:",omitempty"`
Addresses []string `json:",omitempty"`
Network Network `json:",omitempty"`
// Addresses contains the IP addresses associated with the endpoint in the network.
// This field accepts CIDR notation, for example `10.0.0.1/24`, to maintain backwards
// compatibility, but only the IP address is used.
Addresses []netip.Prefix `json:",omitempty"`
}
// IPAMOptions represents ipam options.
@ -115,7 +111,7 @@ type IPAMOptions struct {
// IPAMConfig represents ipam configuration.
type IPAMConfig struct {
Subnet string `json:",omitempty"`
Range string `json:",omitempty"`
Gateway string `json:",omitempty"`
Subnet netip.Prefix `json:",omitempty"`
Range netip.Prefix `json:",omitempty"`
Gateway netip.Addr `json:",omitempty"`
}

View File

@ -1,6 +1,7 @@
package swarm
import (
"net/netip"
"time"
)
@ -12,7 +13,7 @@ type ClusterInfo struct {
Spec Spec
TLSInfo TLSInfo
RootRotationInProgress bool
DefaultAddrPool []string
DefaultAddrPool []netip.Prefix
SubnetSize uint32
DataPathPort uint32
}
@ -159,7 +160,7 @@ type InitRequest struct {
Spec Spec
AutoLockManagers bool
Availability NodeAvailability
DefaultAddrPool []string
DefaultAddrPool []netip.Prefix
SubnetSize uint32
}

View File

@ -1,6 +1,8 @@
package system
import (
"net/netip"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/registry"
"github.com/moby/moby/api/types/swarm"
@ -137,16 +139,11 @@ type PluginsInfo struct {
type Commit struct {
// ID is the actual commit ID or version of external tool.
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 `json:",omitempty"`
}
// NetworkAddressPool is a temp struct used by [Info] struct.
type NetworkAddressPool struct {
Base string
Base netip.Prefix
Size int
}

View File

@ -11,6 +11,15 @@ const (
// MediaTypeMultiplexedStream is vendor specific MIME-Type set for stdin/stdout/stderr multiplexed streams
MediaTypeMultiplexedStream = "application/vnd.docker.multiplexed-stream"
// MediaTypeJSON is the MIME-Type for JSON objects
MediaTypeJSON = "application/json"
// MediaTypeNDJson is the MIME-Type for Newline Delimited JSON objects streams
MediaTypeNDJSON = "application/x-ndjson"
// MediaTypeJsonSequence is the MIME-Type for JSON Text Sequences (RFC7464)
MediaTypeJSONSequence = "application/json-seq"
)
// Ping contains response of Engine API:

View File

@ -5,9 +5,11 @@ import (
"net/url"
)
type BuildCancelOptions struct{}
// BuildCancel requests the daemon to cancel the ongoing build request
// with the given id.
func (cli *Client) BuildCancel(ctx context.Context, id string) error {
func (cli *Client) BuildCancel(ctx context.Context, id string, _ BuildCancelOptions) error {
query := url.Values{}
query.Set("id", id)

View File

@ -8,7 +8,6 @@ import (
"strconv"
"github.com/moby/moby/api/types/build"
"github.com/moby/moby/api/types/filters"
"github.com/moby/moby/api/types/versions"
)
@ -18,11 +17,17 @@ type BuildCachePruneOptions struct {
ReservedSpace int64
MaxUsedSpace int64
MinFreeSpace int64
Filters filters.Args
Filters Filters
}
// BuildCachePruneResult holds the result from the BuildCachePrune method.
type BuildCachePruneResult struct {
Report build.CachePruneReport
}
// BuildCachePrune requests the daemon to delete unused cache data.
func (cli *Client) BuildCachePrune(ctx context.Context, opts BuildCachePruneOptions) (*build.CachePruneReport, error) {
func (cli *Client) BuildCachePrune(ctx context.Context, opts BuildCachePruneOptions) (BuildCachePruneResult, error) {
var out BuildCachePruneResult
query := url.Values{}
if opts.All {
query.Set("all", "1")
@ -43,23 +48,20 @@ func (cli *Client) BuildCachePrune(ctx context.Context, opts BuildCachePruneOpti
if opts.MinFreeSpace != 0 {
query.Set("min-free-space", strconv.Itoa(int(opts.MinFreeSpace)))
}
f, err := filters.ToJSON(opts.Filters)
if err != nil {
return nil, fmt.Errorf("prune could not marshal filters option: %w", err)
}
query.Set("filters", f)
opts.Filters.updateURLValues(query)
resp, err := cli.post(ctx, "/build/prune", query, nil, nil)
defer ensureReaderClosed(resp)
if err != nil {
return nil, err
return BuildCachePruneResult{}, err
}
report := build.CachePruneReport{}
if err := json.NewDecoder(resp.Body).Decode(&report); err != nil {
return nil, fmt.Errorf("error retrieving disk usage: %w", err)
return BuildCachePruneResult{}, fmt.Errorf("error retrieving disk usage: %w", err)
}
return &report, nil
out.Report = report
return out, nil
}

View File

@ -54,6 +54,7 @@ import (
"sync/atomic"
"time"
cerrdefs "github.com/containerd/errdefs"
"github.com/docker/go-connections/sockets"
"github.com/moby/moby/api/types"
"github.com/moby/moby/api/types/versions"
@ -99,12 +100,10 @@ const DummyHost = "api.moby.localhost"
// This version may be lower than the version of the api library module used.
const MaxAPIVersion = "1.52"
// fallbackAPIVersion is the version to fallback to if API-version negotiation
// fails. This version is the highest version of the API before API-version
// negotiation was introduced. If negotiation fails (or no API version was
// included in the API response), we assume the API server uses the most
// recent version before negotiation was introduced.
const fallbackAPIVersion = "1.24"
// fallbackAPIVersion is the version to fall back to if API-version negotiation
// fails. API versions below this version are not supported by the client,
// and not considered when negotiating.
const fallbackAPIVersion = "1.44"
// Ensure that Client always implements APIClient.
var _ APIClient = &Client{}
@ -275,7 +274,7 @@ func (cli *Client) checkVersion(ctx context.Context) error {
if err != nil {
return err
}
cli.negotiateAPIVersionPing(ping.APIVersion)
return cli.negotiateAPIVersion(ping.APIVersion)
}
return nil
}
@ -311,8 +310,7 @@ func (cli *Client) ClientVersion() string {
// If the API server's ping response does not contain an API version, or if the
// client did not get a successful ping response, it assumes it is connected with
// an old daemon that does not support API version negotiation, in which case it
// downgrades to the latest version of the API before version negotiation was
// added (1.24).
// downgrades to the lowest supported API version.
func (cli *Client) NegotiateAPIVersion(ctx context.Context) {
if !cli.manualOverride {
// Avoid concurrent modification of version-related fields
@ -324,7 +322,8 @@ func (cli *Client) NegotiateAPIVersion(ctx context.Context) {
// FIXME(thaJeztah): Ping returns an error when failing to connect to the API; we should not swallow the error here, and instead returning it.
return
}
cli.negotiateAPIVersionPing(ping.APIVersion)
// FIXME(thaJeztah): we should not swallow the error here, and instead returning it.
_ = cli.negotiateAPIVersion(ping.APIVersion)
}
}
@ -337,28 +336,30 @@ func (cli *Client) NegotiateAPIVersion(ctx context.Context) {
// ([EnvOverrideAPIVersion]) environment variable, or if the client is initialized
// with a fixed version ([WithVersion]), no negotiation is performed.
//
// If the API server's ping response does not contain an API version, we assume
// we are connected with an old daemon without API version negotiation support,
// and downgrade to the latest version of the API before version negotiation was
// added (1.24).
// If the API server's ping response does not contain an API version, it falls
// back to the oldest API version supported.
func (cli *Client) NegotiateAPIVersionPing(pingResponse types.Ping) {
if !cli.manualOverride {
// Avoid concurrent modification of version-related fields
cli.negotiateLock.Lock()
defer cli.negotiateLock.Unlock()
cli.negotiateAPIVersionPing(pingResponse.APIVersion)
// FIXME(thaJeztah): we should not swallow the error here, and instead returning it.
_ = cli.negotiateAPIVersion(pingResponse.APIVersion)
}
}
// negotiateAPIVersionPing queries the API and updates the version to match the
// API version from the ping response.
func (cli *Client) negotiateAPIVersionPing(pingVersion string) {
// negotiateAPIVersion updates the version to match the API version from
// the ping response. It falls back to the lowest version supported if the
// API version is empty, or returns an error if the API version is lower than
// the lowest supported API version, in which case the version is not modified.
func (cli *Client) negotiateAPIVersion(pingVersion string) error {
pingVersion = strings.TrimPrefix(pingVersion, "v")
// default to the latest version before versioning headers existed
if pingVersion == "" {
// TODO(thaJeztah): consider returning an error on empty value or not falling back; see https://github.com/moby/moby/pull/51119#discussion_r2413148487
pingVersion = fallbackAPIVersion
} else if versions.LessThan(pingVersion, fallbackAPIVersion) {
return cerrdefs.ErrInvalidArgument.WithMessage(fmt.Sprintf("API version %s is not supported by this client: the minimum supported API version is %s", pingVersion, fallbackAPIVersion))
}
// if the client is not initialized with a version, start with the latest supported version
@ -376,6 +377,7 @@ func (cli *Client) negotiateAPIVersionPing(pingVersion string) {
if cli.negotiateVersion {
cli.negotiated.Store(true)
}
return nil
}
// DaemonHost returns the host address used by the client

View File

@ -6,10 +6,8 @@ import (
"net"
"github.com/moby/moby/api/types"
"github.com/moby/moby/api/types/build"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/events"
"github.com/moby/moby/api/types/filters"
"github.com/moby/moby/api/types/image"
"github.com/moby/moby/api/types/network"
"github.com/moby/moby/api/types/plugin"
@ -90,7 +88,7 @@ type ContainerAPIClient interface {
ContainerWait(ctx context.Context, container string, condition container.WaitCondition) (<-chan container.WaitResponse, <-chan error)
CopyFromContainer(ctx context.Context, container, srcPath string) (io.ReadCloser, container.PathStat, error)
CopyToContainer(ctx context.Context, container, path string, content io.Reader, options CopyToContainerOptions) error
ContainersPrune(ctx context.Context, pruneFilters filters.Args) (container.PruneReport, error)
ContainersPrune(ctx context.Context, pruneFilters Filters) (container.PruneReport, error)
}
type ExecAPIClient interface {
@ -109,18 +107,18 @@ type DistributionAPIClient interface {
// ImageAPIClient defines API client methods for the images
type ImageAPIClient interface {
ImageBuild(ctx context.Context, context io.Reader, options ImageBuildOptions) (ImageBuildResponse, error)
BuildCachePrune(ctx context.Context, opts BuildCachePruneOptions) (*build.CachePruneReport, error)
BuildCancel(ctx context.Context, id string) error
BuildCachePrune(ctx context.Context, opts BuildCachePruneOptions) (BuildCachePruneResult, error)
BuildCancel(ctx context.Context, id string, opts BuildCancelOptions) error
ImageCreate(ctx context.Context, parentReference string, options ImageCreateOptions) (io.ReadCloser, error)
ImageImport(ctx context.Context, source ImageImportSource, ref string, options ImageImportOptions) (io.ReadCloser, error)
ImageList(ctx context.Context, options ImageListOptions) ([]image.Summary, error)
ImagePull(ctx context.Context, ref string, options ImagePullOptions) (io.ReadCloser, error)
ImagePull(ctx context.Context, ref string, options ImagePullOptions) (ImagePullResponse, error)
ImagePush(ctx context.Context, ref string, options ImagePushOptions) (io.ReadCloser, error)
ImageRemove(ctx context.Context, image string, options ImageRemoveOptions) ([]image.DeleteResponse, error)
ImageSearch(ctx context.Context, term string, options ImageSearchOptions) ([]registry.SearchResult, 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) (image.PruneReport, error)
ImageInspect(ctx context.Context, image string, _ ...ImageInspectOption) (image.InspectResponse, error)
ImageHistory(ctx context.Context, image string, _ ...ImageHistoryOption) ([]image.HistoryResponseItem, error)
@ -137,7 +135,7 @@ type NetworkAPIClient interface {
NetworkInspectWithRaw(ctx context.Context, network string, options NetworkInspectOptions) (network.Inspect, []byte, error)
NetworkList(ctx context.Context, options NetworkListOptions) ([]network.Summary, error)
NetworkRemove(ctx context.Context, network string) error
NetworksPrune(ctx context.Context, pruneFilter filters.Args) (network.PruneReport, error)
NetworksPrune(ctx context.Context, pruneFilter Filters) (network.PruneReport, error)
}
// NodeAPIClient defines API client methods for the nodes
@ -150,7 +148,7 @@ type NodeAPIClient interface {
// PluginAPIClient defines API client methods for the plugins
type PluginAPIClient interface {
PluginList(ctx context.Context, filter filters.Args) (plugin.ListResponse, error)
PluginList(ctx context.Context, filter Filters) (plugin.ListResponse, error)
PluginRemove(ctx context.Context, name string, options PluginRemoveOptions) error
PluginEnable(ctx context.Context, name string, options PluginEnableOptions) error
PluginDisable(ctx context.Context, name string, options PluginDisableOptions) error
@ -202,7 +200,7 @@ type VolumeAPIClient interface {
VolumeInspectWithRaw(ctx context.Context, volumeID string) (volume.Volume, []byte, error)
VolumeList(ctx context.Context, options VolumeListOptions) (volume.ListResponse, error)
VolumeRemove(ctx context.Context, volumeID string, force bool) error
VolumesPrune(ctx context.Context, pruneFilter filters.Args) (volume.PruneReport, error)
VolumesPrune(ctx context.Context, pruneFilter Filters) (volume.PruneReport, error)
VolumeUpdate(ctx context.Context, volumeID string, version swarm.Version, options volume.UpdateOptions) error
}

View File

@ -5,22 +5,13 @@ import (
"encoding/json"
"net/url"
"github.com/moby/moby/api/types/filters"
"github.com/moby/moby/api/types/swarm"
)
// ConfigList returns the list of configs.
func (cli *Client) ConfigList(ctx context.Context, options ConfigListOptions) ([]swarm.Config, error) {
query := url.Values{}
if options.Filters.Len() > 0 {
filterJSON, err := filters.ToJSON(options.Filters)
if err != nil {
return nil, err
}
query.Set("filters", filterJSON)
}
options.Filters.updateURLValues(query)
resp, err := cli.get(ctx, "/configs", query, nil)
defer ensureReaderClosed(resp)

View File

@ -3,7 +3,6 @@ package client
import (
"context"
"encoding/json"
"errors"
"net/url"
"path"
"sort"
@ -25,58 +24,28 @@ func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config
var response container.CreateResponse
// 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.
if err := cli.checkVersion(ctx); err != nil {
return response, err
}
if err := cli.NewVersionError(ctx, "1.25", "stop timeout"); config.StopTimeout != nil && err != nil {
return response, err
}
if err := cli.NewVersionError(ctx, "1.41", "specify container image platform"); platform != nil && err != nil {
return response, err
}
if err := cli.NewVersionError(ctx, "1.44", "specify health-check start interval"); config.Healthcheck != nil && config.Healthcheck.StartInterval != 0 && err != nil {
return response, err
}
if err := cli.NewVersionError(ctx, "1.44", "specify mac-address per network"); hasEndpointSpecificMacAddress(networkingConfig) && err != nil {
return response, err
}
if hostConfig != nil {
if versions.LessThan(cli.ClientVersion(), "1.25") {
// When using API 1.24 and under, the client is responsible for removing the container
hostConfig.AutoRemove = false
}
if platform != nil && platform.OS == "linux" && versions.LessThan(cli.ClientVersion(), "1.42") {
// When using API under 1.42, the Linux daemon doesn't respect the ConsoleSize
hostConfig.ConsoleSize = [2]uint{0, 0}
}
if versions.LessThan(cli.ClientVersion(), "1.44") {
for _, m := range hostConfig.Mounts {
if m.BindOptions != nil {
// ReadOnlyNonRecursive can be safely ignored when API < 1.44
if m.BindOptions.ReadOnlyForceRecursive {
return response, errors.New("bind-recursive=readonly requires API v1.44 or later")
}
if m.BindOptions.NonRecursive && versions.LessThan(cli.ClientVersion(), "1.40") {
return response, errors.New("bind-recursive=disabled requires API v1.40 or later")
}
}
}
}
hostConfig.CapAdd = normalizeCapabilities(hostConfig.CapAdd)
hostConfig.CapDrop = normalizeCapabilities(hostConfig.CapDrop)
}
// Since API 1.44, the container-wide MacAddress is deprecated and triggers a WARNING if it's specified.
if versions.GreaterThanOrEqualTo(cli.ClientVersion(), "1.44") {
config.MacAddress = "" //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44.
// FIXME(thaJeztah): remove this once we updated our (integration) tests;
// some integration tests depend on this to test old API versions; see https://github.com/moby/moby/pull/51120#issuecomment-3376224865
if config.MacAddress != "" { //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44.
// 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.
if err := cli.checkVersion(ctx); err != nil {
return response, err
}
if versions.GreaterThanOrEqualTo(cli.ClientVersion(), "1.44") {
// Since API 1.44, the container-wide MacAddress is deprecated and triggers a WARNING if it's specified.
//
// FIXME(thaJeztah): remove the field from the API
config.MacAddress = "" //nolint:staticcheck // ignore SA1019: field is deprecated, but still used on API < v1.44.
}
}
query := url.Values{}

View File

@ -6,7 +6,6 @@ import (
"net/http"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/versions"
)
// ExecCreateOptions is a small subset of the Config struct that holds the configuration
@ -32,22 +31,6 @@ func (cli *Client) ContainerExecCreate(ctx context.Context, containerID string,
return container.ExecCreateResponse{}, err
}
// 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.
if err := cli.checkVersion(ctx); err != nil {
return container.ExecCreateResponse{}, err
}
if err := cli.NewVersionError(ctx, "1.25", "env"); len(options.Env) != 0 && err != nil {
return container.ExecCreateResponse{}, err
}
if versions.LessThan(cli.ClientVersion(), "1.42") {
options.ConsoleSize = nil
}
req := container.ExecCreateRequest{
User: options.User,
Privileged: options.Privileged,
@ -86,19 +69,6 @@ type ExecStartOptions struct {
// ContainerExecStart starts an exec process already created in the docker host.
func (cli *Client) ContainerExecStart(ctx context.Context, execID string, config ExecStartOptions) 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.
if err := cli.checkVersion(ctx); err != nil {
return err
}
if versions.LessThan(cli.ClientVersion(), "1.42") {
config.ConsoleSize = nil
}
req := container.ExecStartRequest{
Detach: config.Detach,
Tty: config.Tty,
@ -133,9 +103,6 @@ type ExecAttachOptions = ExecStartOptions
//
// [stdcopy.StdCopy]: https://pkg.go.dev/github.com/moby/moby/api/pkg/stdcopy#StdCopy
func (cli *Client) ContainerExecAttach(ctx context.Context, execID string, config ExecAttachOptions) (HijackedResponse, error) {
if versions.LessThan(cli.ClientVersion(), "1.42") {
config.ConsoleSize = nil
}
req := container.ExecStartRequest{
Detach: config.Detach,
Tty: config.Tty,

View File

@ -7,7 +7,6 @@ import (
"strconv"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/filters"
)
// ContainerListOptions holds parameters to list containers with.
@ -18,7 +17,7 @@ type ContainerListOptions struct {
Since string
Before string
Limit int
Filters filters.Args
Filters Filters
}
// ContainerList returns the list of containers in the docker host.
@ -45,13 +44,7 @@ func (cli *Client) ContainerList(ctx context.Context, options ContainerListOptio
query.Set("size", "1")
}
if options.Filters.Len() > 0 {
filterJSON, err := filters.ToJSON(options.Filters)
if err != nil {
return nil, err
}
query.Set("filters", filterJSON)
}
options.Filters.updateURLValues(query)
resp, err := cli.get(ctx, "/containers/json", query, nil)
defer ensureReaderClosed(resp)

View File

@ -4,17 +4,15 @@ import (
"context"
"encoding/json"
"fmt"
"net/url"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/filters"
)
// ContainersPrune requests the daemon to delete unused data
func (cli *Client) ContainersPrune(ctx context.Context, pruneFilters filters.Args) (container.PruneReport, error) {
query, err := getFiltersQuery(pruneFilters)
if err != nil {
return container.PruneReport{}, err
}
func (cli *Client) ContainersPrune(ctx context.Context, pruneFilters Filters) (container.PruneReport, error) {
query := url.Values{}
pruneFilters.updateURLValues(query)
resp, err := cli.post(ctx, "/containers/prune", query, nil, nil)
defer ensureReaderClosed(resp)

View File

@ -4,8 +4,6 @@ import (
"context"
"net/url"
"strconv"
"github.com/moby/moby/api/types/versions"
)
// ContainerRestart stops, and starts a container again.
@ -22,17 +20,7 @@ func (cli *Client) ContainerRestart(ctx context.Context, containerID string, opt
query.Set("t", strconv.Itoa(*options.Timeout))
}
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.
if err := cli.checkVersion(ctx); err != nil {
return err
}
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)
defer ensureReaderClosed(resp)

View File

@ -4,8 +4,6 @@ import (
"context"
"net/url"
"strconv"
"github.com/moby/moby/api/types/versions"
)
// ContainerStopOptions holds the options to stop or restart a container.
@ -44,17 +42,7 @@ func (cli *Client) ContainerStop(ctx context.Context, containerID string, option
query.Set("t", strconv.Itoa(*options.Timeout))
}
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.
if err := cli.checkVersion(ctx); err != nil {
return err
}
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)
defer ensureReaderClosed(resp)

View File

@ -9,7 +9,6 @@ import (
"net/url"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/versions"
)
const containerWaitErrorMsgLimit = 2 * 1024 /* Max: 2KiB */
@ -41,19 +40,6 @@ func (cli *Client) ContainerWait(ctx context.Context, containerID string, condit
return resultC, errC
}
// Make sure we negotiated (if the client is configured to do so),
// as code below contains API-version specific handling of options.
//
// Normally, version-negotiation (if enabled) would not happen until
// the API request is made.
if err := cli.checkVersion(ctx); err != nil {
errC <- err
return resultC, errC
}
if versions.LessThan(cli.ClientVersion(), "1.30") {
return cli.legacyContainerWait(ctx, containerID)
}
query := url.Values{}
if condition != "" {
query.Set("condition", string(condition))

46
vendor/github.com/moby/moby/client/filters.go generated vendored Normal file
View File

@ -0,0 +1,46 @@
package client
import (
"encoding/json"
"net/url"
)
// Filters describes a predicate for an API request.
//
// Each entry in the map is a filter term.
// Each term is evaluated against the set of values.
// A filter term is satisfied if any one of the values in the set is a match.
// An item matches the filters when all terms are satisfied.
//
// Like all other map types in Go, the zero value is empty and read-only.
type Filters map[string]map[string]bool
// Add appends values to the value-set of term.
//
// The receiver f is returned for chaining.
//
// f := make(Filters).Add("name", "foo", "bar").Add("status", "exited")
func (f Filters) Add(term string, values ...string) Filters {
if _, ok := f[term]; !ok {
f[term] = make(map[string]bool)
}
for _, v := range values {
f[term][v] = true
}
return f
}
// updateURLValues sets the "filters" key in values to the marshalled value of
// f, replacing any existing values. When f is empty, any existing "filters" key
// is removed.
func (f Filters) updateURLValues(values url.Values) {
if len(f) > 0 {
b, err := json.Marshal(f)
if err != nil {
panic(err) // Marshaling builtin types should never fail
}
values.Set("filters", string(b))
} else {
values.Del("filters")
}
}

View File

@ -9,7 +9,6 @@ import (
"net/url"
"time"
"github.com/moby/moby/api/types/versions"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)
@ -28,11 +27,6 @@ func (cli *Client) postHijacked(ctx context.Context, path string, query url.Valu
return HijackedResponse{}, 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 NewHijackedResponse(conn, mediaType), nil
}

View File

@ -42,7 +42,7 @@ func (cli *Client) ImageBuild(ctx context.Context, buildContext io.Reader, optio
}, nil
}
func (cli *Client) imageBuildOptionsToQuery(ctx context.Context, options ImageBuildOptions) (url.Values, error) {
func (cli *Client) imageBuildOptionsToQuery(_ context.Context, options ImageBuildOptions) (url.Values, error) {
query := url.Values{}
if len(options.Tags) > 0 {
query["t"] = options.Tags
@ -79,9 +79,7 @@ func (cli *Client) imageBuildOptionsToQuery(ctx context.Context, options ImageBu
}
if options.Squash {
if err := cli.NewVersionError(ctx, "1.25", "squash"); err != nil {
return query, err
}
// TODO(thaJeztah): squash is experimental, and deprecated when using BuildKit?
query.Set("squash", "1")
}
@ -157,9 +155,6 @@ func (cli *Client) imageBuildOptionsToQuery(ctx context.Context, options ImageBu
query.Set("session", options.SessionID)
}
if options.Platform != "" {
if err := cli.NewVersionError(ctx, "1.32", "platform"); err != nil {
return query, err
}
query.Set("platform", strings.ToLower(options.Platform))
}
if options.BuildID != "" {

View File

@ -5,7 +5,6 @@ import (
"encoding/json"
"net/url"
"github.com/moby/moby/api/types/filters"
"github.com/moby/moby/api/types/image"
"github.com/moby/moby/api/types/versions"
)
@ -19,32 +18,28 @@ import (
func (cli *Client) ImageList(ctx context.Context, options ImageListOptions) ([]image.Summary, error) {
var images []image.Summary
// 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.
if err := cli.checkVersion(ctx); err != nil {
return images, err
}
query := url.Values{}
if options.Filters.Len() > 0 {
filterJSON, err := filters.ToJSON(options.Filters)
if err != nil {
return images, err
}
query.Set("filters", filterJSON)
}
options.Filters.updateURLValues(query)
if options.All {
query.Set("all", "1")
}
if options.SharedSize && versions.GreaterThanOrEqualTo(cli.version, "1.42") {
if options.SharedSize {
query.Set("shared-size", "1")
}
if options.Manifests && versions.GreaterThanOrEqualTo(cli.version, "1.47") {
query.Set("manifests", "1")
if options.Manifests {
// 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.
if err := cli.checkVersion(ctx); err != nil {
return images, err
}
if versions.GreaterThanOrEqualTo(cli.version, "1.47") {
query.Set("manifests", "1")
}
}
resp, err := cli.get(ctx, "/images/json", query, nil)

View File

@ -1,7 +1,5 @@
package client
import "github.com/moby/moby/api/types/filters"
// ImageListOptions holds parameters to list images with.
type ImageListOptions struct {
// All controls whether all images in the graph are filtered, or just
@ -9,7 +7,7 @@ type ImageListOptions struct {
All bool
// Filters is a JSON-encoded set of filter arguments.
Filters filters.Args
Filters Filters
// SharedSize indicates whether the shared size of images should be computed.
SharedSize bool

View File

@ -4,17 +4,15 @@ import (
"context"
"encoding/json"
"fmt"
"net/url"
"github.com/moby/moby/api/types/filters"
"github.com/moby/moby/api/types/image"
)
// ImagesPrune requests the daemon to delete unused data
func (cli *Client) ImagesPrune(ctx context.Context, pruneFilters filters.Args) (image.PruneReport, error) {
query, err := getFiltersQuery(pruneFilters)
if err != nil {
return image.PruneReport{}, err
}
func (cli *Client) ImagesPrune(ctx context.Context, pruneFilters Filters) (image.PruneReport, error) {
query := url.Values{}
pruneFilters.updateURLValues(query)
resp, err := cli.post(ctx, "/images/prune", query, nil, nil)
defer ensureReaderClosed(resp)

View File

@ -2,19 +2,84 @@ package client
import (
"context"
"encoding/json"
"errors"
"io"
"iter"
"net/url"
"strings"
"sync"
cerrdefs "github.com/containerd/errdefs"
"github.com/distribution/reference"
"github.com/moby/moby/client/pkg/jsonmessage"
)
func newImagePullResponse(rc io.ReadCloser) ImagePullResponse {
if rc == nil {
panic("nil io.ReadCloser")
}
return ImagePullResponse{
rc: rc,
close: sync.OnceValue(rc.Close),
}
}
type ImagePullResponse struct {
rc io.ReadCloser
close func() error
}
// Read implements io.ReadCloser
func (r ImagePullResponse) Read(p []byte) (n int, err error) {
if r.rc == nil {
return 0, io.EOF
}
return r.rc.Read(p)
}
// Close implements io.ReadCloser
func (r ImagePullResponse) Close() error {
if r.close == nil {
return nil
}
return r.close()
}
// JSONMessages decodes the response stream as a sequence of JSONMessages.
// if stream ends or context is cancelled, the underlying [io.Reader] is closed.
func (r ImagePullResponse) JSONMessages(ctx context.Context) iter.Seq2[jsonmessage.JSONMessage, error] {
context.AfterFunc(ctx, func() {
_ = r.Close()
})
dec := json.NewDecoder(r)
return func(yield func(jsonmessage.JSONMessage, error) bool) {
defer r.Close()
for {
var jm jsonmessage.JSONMessage
err := dec.Decode(&jm)
if errors.Is(err, io.EOF) {
break
}
if ctx.Err() != nil {
yield(jm, ctx.Err())
return
}
if !yield(jm, err) {
return
}
}
}
}
// ImagePull requests the docker host to pull an image from a remote registry.
// It executes the privileged function if the operation is unauthorized
// and it tries one more time.
// It's up to the caller to handle the [io.ReadCloser] and close it.
func (cli *Client) ImagePull(ctx context.Context, refStr string, options ImagePullOptions) (io.ReadCloser, error) {
// Callers can use [ImagePullResponse.JSONMessages] to monitor pull progress as
// a sequence of JSONMessages, [ImagePullResponse.Close] does not need to be
// called in this case. Or, use the [io.Reader] interface and call
// [ImagePullResponse.Close] after processing.
func (cli *Client) ImagePull(ctx context.Context, refStr string, options ImagePullOptions) (ImagePullResponse, error) {
// FIXME(vdemeester): there is currently used in a few way in docker/docker
// - if not in trusted content, ref is used to pass the whole reference, and tag is empty
// - if in trusted content, ref is used to pass the reference name, and tag for the digest
@ -23,7 +88,7 @@ func (cli *Client) ImagePull(ctx context.Context, refStr string, options ImagePu
ref, err := reference.ParseNormalizedNamed(refStr)
if err != nil {
return nil, err
return ImagePullResponse{}, err
}
query := url.Values{}
@ -40,9 +105,10 @@ func (cli *Client) ImagePull(ctx context.Context, refStr string, options ImagePu
resp, err = cli.tryImageCreate(ctx, query, options.PrivilegeFunc)
}
if err != nil {
return nil, err
return ImagePullResponse{}, err
}
return resp.Body, nil
return newImagePullResponse(resp.Body), nil
}
// getAPITagFromNamedRef returns a tag from the specified reference.

View File

@ -1,6 +1,8 @@
package client
import "context"
import (
"context"
)
// ImagePullOptions holds information to pull images.
type ImagePullOptions struct {

View File

@ -8,7 +8,6 @@ import (
"strconv"
cerrdefs "github.com/containerd/errdefs"
"github.com/moby/moby/api/types/filters"
"github.com/moby/moby/api/types/registry"
)
@ -22,13 +21,7 @@ func (cli *Client) ImageSearch(ctx context.Context, term string, options ImageSe
query.Set("limit", strconv.Itoa(options.Limit))
}
if options.Filters.Len() > 0 {
filterJSON, err := filters.ToJSON(options.Filters)
if err != nil {
return results, err
}
query.Set("filters", filterJSON)
}
options.Filters.updateURLValues(query)
resp, err := cli.tryImageSearch(ctx, query, options.RegistryAuth)
defer ensureReaderClosed(resp)

View File

@ -2,8 +2,6 @@ package client
import (
"context"
"github.com/moby/moby/api/types/filters"
)
// ImageSearchOptions holds parameters to search images with.
@ -17,6 +15,6 @@ type ImageSearchOptions struct {
//
// For details, refer to [github.com/moby/moby/api/types/registry.RequestAuthConfig].
PrivilegeFunc func(context.Context) (string, error)
Filters filters.Args
Filters Filters
Limit int
}

View File

@ -0,0 +1,50 @@
package internal
import (
"encoding/json"
"io"
"slices"
"github.com/moby/moby/api/types"
)
const rs = 0x1E
type DecoderFn func(v any) error
// NewJSONStreamDecoder builds adequate DecoderFn to read json records formatted with specified content-type
func NewJSONStreamDecoder(r io.Reader, contentType string) DecoderFn {
switch contentType {
case types.MediaTypeJSONSequence:
return json.NewDecoder(NewRSFilterReader(r)).Decode
case types.MediaTypeJSON, types.MediaTypeNDJSON:
fallthrough
default:
return json.NewDecoder(r).Decode
}
}
// RSFilterReader wraps an io.Reader and filters out ASCII RS characters
type RSFilterReader struct {
reader io.Reader
buffer []byte
}
// NewRSFilterReader creates a new RSFilterReader that filters out RS characters
func NewRSFilterReader(r io.Reader) *RSFilterReader {
return &RSFilterReader{
reader: r,
buffer: make([]byte, 4096), // Internal buffer for reading chunks
}
}
// Read implements the io.Reader interface, filtering out RS characters
func (r *RSFilterReader) Read(p []byte) (n int, err error) {
if len(p) == 0 {
return 0, nil
}
n, err = r.reader.Read(p)
filtered := slices.DeleteFunc(p[:n], func(b byte) bool { return b == rs })
return len(filtered), err
}

View File

@ -5,21 +5,11 @@ import (
"encoding/json"
"github.com/moby/moby/api/types/network"
"github.com/moby/moby/api/types/versions"
)
// NetworkCreate creates a new network in the docker host.
func (cli *Client) NetworkCreate(ctx context.Context, name string, options NetworkCreateOptions) (network.CreateResponse, 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.
if err := cli.checkVersion(ctx); err != nil {
return network.CreateResponse{}, err
}
networkCreateRequest := network.CreateRequest{
req := network.CreateRequest{
Name: name,
Driver: options.Driver,
Scope: options.Scope,
@ -35,27 +25,6 @@ func (cli *Client) NetworkCreate(ctx context.Context, name string, options Netwo
Labels: options.Labels,
}
var req any
if versions.LessThan(cli.version, "1.44") {
// CheckDuplicate is removed in API v1.44, and no longer used by
// daemons supporting that API version (v25.0.0-beta.1 and up)
// regardless of the API version used, but it must be set to true
// when sent to older daemons.
//
// TODO(thaJeztah) remove this once daemon versions v24.0 and lower are no
// longer expected to be used (when Mirantis Container Runtime v23
// is EOL); https://github.com/moby/moby/blob/v2.0.0-beta.0/project/BRANCHES-AND-TAGS.md
req = struct {
network.CreateRequest
CheckDuplicate bool
}{
CreateRequest: networkCreateRequest,
CheckDuplicate: true,
}
} else {
req = networkCreateRequest
}
resp, err := cli.post(ctx, "/networks/create", nil, req, nil)
defer ensureReaderClosed(resp)
if err != nil {

View File

@ -5,20 +5,13 @@ import (
"encoding/json"
"net/url"
"github.com/moby/moby/api/types/filters"
"github.com/moby/moby/api/types/network"
)
// NetworkList returns the list of networks configured in the docker host.
func (cli *Client) NetworkList(ctx context.Context, options NetworkListOptions) ([]network.Summary, error) {
query := url.Values{}
if options.Filters.Len() > 0 {
filterJSON, err := filters.ToJSON(options.Filters)
if err != nil {
return nil, err
}
query.Set("filters", filterJSON)
}
options.Filters.updateURLValues(query)
var networkResources []network.Summary
resp, err := cli.get(ctx, "/networks", query, nil)
defer ensureReaderClosed(resp)

View File

@ -1,8 +1,6 @@
package client
import "github.com/moby/moby/api/types/filters"
// NetworkListOptions holds parameters to filter the list of networks with.
type NetworkListOptions struct {
Filters filters.Args
Filters Filters
}

View File

@ -4,17 +4,15 @@ import (
"context"
"encoding/json"
"fmt"
"net/url"
"github.com/moby/moby/api/types/filters"
"github.com/moby/moby/api/types/network"
)
// NetworksPrune requests the daemon to delete unused networks
func (cli *Client) NetworksPrune(ctx context.Context, pruneFilters filters.Args) (network.PruneReport, error) {
query, err := getFiltersQuery(pruneFilters)
if err != nil {
return network.PruneReport{}, err
}
func (cli *Client) NetworksPrune(ctx context.Context, pruneFilters Filters) (network.PruneReport, error) {
query := url.Values{}
pruneFilters.updateURLValues(query)
resp, err := cli.post(ctx, "/networks/prune", query, nil, nil)
defer ensureReaderClosed(resp)

View File

@ -5,23 +5,13 @@ import (
"encoding/json"
"net/url"
"github.com/moby/moby/api/types/filters"
"github.com/moby/moby/api/types/swarm"
)
// NodeList returns the list of nodes.
func (cli *Client) NodeList(ctx context.Context, options NodeListOptions) ([]swarm.Node, error) {
query := url.Values{}
if options.Filters.Len() > 0 {
filterJSON, err := filters.ToJSON(options.Filters)
if err != nil {
return nil, err
}
query.Set("filters", filterJSON)
}
options.Filters.updateURLValues(query)
resp, err := cli.get(ctx, "/nodes", query, nil)
defer ensureReaderClosed(resp)
if err != nil {

View File

@ -2,8 +2,10 @@ package jsonmessage
import (
"encoding/json"
"errors"
"fmt"
"io"
"iter"
"strings"
"time"
@ -187,9 +189,32 @@ func (jm *JSONMessage) Display(out io.Writer, isTerminal bool) error {
return nil
}
type JSONMessagesStream iter.Seq2[JSONMessage, error]
// DisplayJSONMessagesStream reads a JSON message stream from in, and writes
// each [JSONMessage] to out. It returns an error if an invalid JSONMessage
// is received, or if a JSONMessage containers a non-zero [JSONMessage.Error].
// each [JSONMessage] to out.
// see DisplayJSONMessages for details
func DisplayJSONMessagesStream(in io.Reader, out io.Writer, terminalFd uintptr, isTerminal bool, auxCallback func(JSONMessage)) error {
var dec = json.NewDecoder(in)
var f JSONMessagesStream = func(yield func(JSONMessage, error) bool) {
for {
var jm JSONMessage
err := dec.Decode(&jm)
if errors.Is(err, io.EOF) {
break
}
if !yield(jm, err) {
return
}
}
}
return DisplayJSONMessages(f, out, terminalFd, isTerminal, auxCallback)
}
// DisplayJSONMessages writes each [JSONMessage] from stream to out.
// It returns an error if an invalid JSONMessage is received, or if
// a JSONMessage containers a non-zero [JSONMessage.Error].
//
// Presentation of the JSONMessage depends on whether a terminal is attached,
// and on the terminal width. Progress bars ([JSONProgress]) are suppressed
@ -203,19 +228,12 @@ func (jm *JSONMessage) Display(out io.Writer, isTerminal bool) error {
// - auxCallback allows handling the [JSONMessage.Aux] field. It is
// called if a JSONMessage contains an Aux field, in which case
// DisplayJSONMessagesStream does not present the JSONMessage.
func DisplayJSONMessagesStream(in io.Reader, out io.Writer, terminalFd uintptr, isTerminal bool, auxCallback func(JSONMessage)) error {
var (
dec = json.NewDecoder(in)
ids = make(map[string]uint)
)
func DisplayJSONMessages(messages JSONMessagesStream, out io.Writer, terminalFd uintptr, isTerminal bool, auxCallback func(JSONMessage)) error {
var ids = make(map[string]uint)
for {
for jm, err := range messages {
var diff uint
var jm JSONMessage
if err := dec.Decode(&jm); err != nil {
if err == io.EOF {
break
}
if err != nil {
return err
}

View File

@ -5,22 +5,15 @@ import (
"encoding/json"
"net/url"
"github.com/moby/moby/api/types/filters"
"github.com/moby/moby/api/types/plugin"
)
// PluginList returns the installed plugins
func (cli *Client) PluginList(ctx context.Context, filter filters.Args) (plugin.ListResponse, error) {
func (cli *Client) PluginList(ctx context.Context, filter Filters) (plugin.ListResponse, error) {
var plugins plugin.ListResponse
query := url.Values{}
if filter.Len() > 0 {
filterJSON, err := filters.ToJSON(filter)
if err != nil {
return plugins, err
}
query.Set("filters", filterJSON)
}
filter.updateURLValues(query)
resp, err := cli.get(ctx, "/plugins", query, nil)
defer ensureReaderClosed(resp)
if err != nil {

View File

@ -303,9 +303,8 @@ func checkResponseErr(serverResp *http.Response) (retErr error) {
daemonErr = errors.New(strings.TrimSpace(errorResponse.Message))
}
} else {
// Fall back to returning the response as-is for API versions < 1.24
// that didn't support JSON error responses, and for situations
// where a plain text error is returned. This branch may also catch
// Fall back to returning the response as-is 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)))
}

View File

@ -5,7 +5,6 @@ import (
"encoding/json"
"net/url"
"github.com/moby/moby/api/types/filters"
"github.com/moby/moby/api/types/swarm"
)
@ -13,15 +12,7 @@ import (
func (cli *Client) SecretList(ctx context.Context, options SecretListOptions) ([]swarm.Secret, error) {
query := url.Values{}
if options.Filters.Len() > 0 {
filterJSON, err := filters.ToJSON(options.Filters)
if err != nil {
return nil, err
}
query.Set("filters", filterJSON)
}
options.Filters.updateURLValues(query)
resp, err := cli.get(ctx, "/secrets", query, nil)
defer ensureReaderClosed(resp)
if err != nil {

View File

@ -1,8 +1,6 @@
package client
import "github.com/moby/moby/api/types/filters"
// SecretListOptions holds parameters to list secrets
type SecretListOptions struct {
Filters filters.Args
Filters Filters
}

View File

@ -11,7 +11,6 @@ import (
"github.com/distribution/reference"
"github.com/moby/moby/api/types/registry"
"github.com/moby/moby/api/types/swarm"
"github.com/moby/moby/api/types/versions"
"github.com/opencontainers/go-digest"
)
@ -19,21 +18,12 @@ import (
func (cli *Client) ServiceCreate(ctx context.Context, service swarm.ServiceSpec, options ServiceCreateOptions) (swarm.ServiceCreateResponse, error) {
var response swarm.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.
if err := cli.checkVersion(ctx); err != nil {
return response, err
}
// 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{}
}
if err := validateServiceSpec(service, cli.version); err != nil {
if err := validateServiceSpec(service); err != nil {
return response, err
}
@ -172,7 +162,7 @@ func digestWarning(image string) string {
return fmt.Sprintf("image %s could not be accessed on a registry to record\nits digest. Each node will access %s independently,\npossibly leading to different nodes running different\nversions of the image.\n", image, image)
}
func validateServiceSpec(s swarm.ServiceSpec, apiVersion string) error {
func validateServiceSpec(s swarm.ServiceSpec) error {
if s.TaskTemplate.ContainerSpec != nil && s.TaskTemplate.PluginSpec != nil {
return errors.New("must not specify both a container spec and a plugin spec in the task template")
}
@ -182,18 +172,5 @@ func validateServiceSpec(s swarm.ServiceSpec, apiVersion string) error {
if s.TaskTemplate.ContainerSpec != nil && (s.TaskTemplate.Runtime != "" && s.TaskTemplate.Runtime != swarm.RuntimeContainer) {
return errors.New("mismatched runtime with container spec")
}
if s.TaskTemplate.ContainerSpec != nil && apiVersion != "" && versions.LessThan(apiVersion, "1.44") {
for _, m := range s.TaskTemplate.ContainerSpec.Mounts {
if m.BindOptions != nil {
if m.BindOptions.NonRecursive && versions.LessThan(apiVersion, "1.40") {
return errors.New("bind-recursive=disabled requires API v1.40 or later")
}
// ReadOnlyNonRecursive can be safely ignored when API < 1.44
if m.BindOptions.ReadOnlyForceRecursive && versions.LessThan(apiVersion, "1.44") {
return errors.New("bind-recursive=readonly requires API v1.44 or later")
}
}
}
}
return nil
}

View File

@ -5,7 +5,6 @@ import (
"encoding/json"
"net/url"
"github.com/moby/moby/api/types/filters"
"github.com/moby/moby/api/types/swarm"
)
@ -13,14 +12,7 @@ import (
func (cli *Client) ServiceList(ctx context.Context, options ServiceListOptions) ([]swarm.Service, error) {
query := url.Values{}
if options.Filters.Len() > 0 {
filterJSON, err := filters.ToJSON(options.Filters)
if err != nil {
return nil, err
}
query.Set("filters", filterJSON)
}
options.Filters.updateURLValues(query)
if options.Status {
query.Set("status", "true")

View File

@ -20,15 +20,7 @@ func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version
return swarm.ServiceUpdateResponse{}, err
}
// Make sure we negotiated (if the client is configured to do so),
// as code below contains API-version specific handling of options.
//
// Normally, version-negotiation (if enabled) would not happen until
// the API request is made.
if err := cli.checkVersion(ctx); err != nil {
return swarm.ServiceUpdateResponse{}, err
}
if err := validateServiceSpec(service, cli.version); err != nil {
if err := validateServiceSpec(service); err != nil {
return swarm.ServiceUpdateResponse{}, err
}

View File

@ -1,8 +1,6 @@
package client
import "github.com/moby/moby/api/types/filters"
// ConfigListOptions holds parameters to list configs
type ConfigListOptions struct {
Filters filters.Args
Filters Filters
}

View File

@ -1,8 +1,6 @@
package client
import "github.com/moby/moby/api/types/filters"
// NodeListOptions holds parameters to list nodes with.
type NodeListOptions struct {
Filters filters.Args
Filters Filters
}

View File

@ -1,10 +1,8 @@
package client
import "github.com/moby/moby/api/types/filters"
// ServiceListOptions holds parameters to list services with.
type ServiceListOptions struct {
Filters filters.Args
Filters Filters
// Status indicates whether the server should include the service task
// count of running and desired tasks.

View File

@ -1,8 +1,6 @@
package client
import "github.com/moby/moby/api/types/filters"
// TaskListOptions holds parameters to list tasks with.
type TaskListOptions struct {
Filters filters.Args
Filters Filters
}

View File

@ -2,12 +2,13 @@ package client
import (
"context"
"encoding/json"
"net/http"
"net/url"
"time"
"github.com/moby/moby/api/types"
"github.com/moby/moby/api/types/events"
"github.com/moby/moby/api/types/filters"
"github.com/moby/moby/client/internal"
"github.com/moby/moby/client/internal/timestamp"
)
@ -15,7 +16,7 @@ import (
type EventsListOptions struct {
Since string
Until string
Filters filters.Args
Filters Filters
}
// Events returns a stream of events in the daemon. It's up to the caller to close the stream
@ -37,7 +38,10 @@ func (cli *Client) Events(ctx context.Context, options EventsListOptions) (<-cha
return
}
resp, err := cli.get(ctx, "/events", query, nil)
headers := http.Header{}
headers.Add("Accept", types.MediaTypeJSONSequence)
headers.Add("Accept", types.MediaTypeNDJSON)
resp, err := cli.get(ctx, "/events", query, headers)
if err != nil {
close(started)
errs <- err
@ -45,7 +49,8 @@ func (cli *Client) Events(ctx context.Context, options EventsListOptions) (<-cha
}
defer resp.Body.Close()
decoder := json.NewDecoder(resp.Body)
contentType := resp.Header.Get("Content-Type")
decoder := internal.NewJSONStreamDecoder(resp.Body, contentType)
close(started)
for {
@ -55,7 +60,7 @@ func (cli *Client) Events(ctx context.Context, options EventsListOptions) (<-cha
return
default:
var event events.Message
if err := decoder.Decode(&event); err != nil {
if err := decoder(&event); err != nil {
errs <- err
return
}
@ -94,13 +99,7 @@ func buildEventsQueryParams(options EventsListOptions) (url.Values, error) {
query.Set("until", ts)
}
if options.Filters.Len() > 0 {
filterJSON, err := filters.ToJSON(options.Filters)
if err != nil {
return nil, err
}
query.Set("filters", filterJSON)
}
options.Filters.updateURLValues(query)
return query, nil
}

View File

@ -5,7 +5,6 @@ import (
"encoding/json"
"net/url"
"github.com/moby/moby/api/types/filters"
"github.com/moby/moby/api/types/swarm"
)
@ -13,14 +12,7 @@ import (
func (cli *Client) TaskList(ctx context.Context, options TaskListOptions) ([]swarm.Task, error) {
query := url.Values{}
if options.Filters.Len() > 0 {
filterJSON, err := filters.ToJSON(options.Filters)
if err != nil {
return nil, err
}
query.Set("filters", filterJSON)
}
options.Filters.updateURLValues(query)
resp, err := cli.get(ctx, "/tasks", query, nil)
defer ensureReaderClosed(resp)

View File

@ -3,11 +3,9 @@ package client
import (
"encoding/json"
"fmt"
"net/url"
"strings"
cerrdefs "github.com/containerd/errdefs"
"github.com/moby/moby/api/types/filters"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
@ -28,20 +26,6 @@ func trimID(objType, id string) (string, error) {
return id, nil
}
// getFiltersQuery returns a url query with "filters" query term, based on the
// filters provided.
func getFiltersQuery(f filters.Args) (url.Values, error) {
query := url.Values{}
if f.Len() > 0 {
filterJSON, err := filters.ToJSON(f)
if err != nil {
return query, err
}
query.Set("filters", filterJSON)
}
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) {

View File

@ -5,7 +5,6 @@ import (
"encoding/json"
"net/url"
"github.com/moby/moby/api/types/filters"
"github.com/moby/moby/api/types/volume"
)
@ -13,13 +12,7 @@ import (
func (cli *Client) VolumeList(ctx context.Context, options VolumeListOptions) (volume.ListResponse, error) {
query := url.Values{}
if options.Filters.Len() > 0 {
filterJSON, err := filters.ToJSON(options.Filters)
if err != nil {
return volume.ListResponse{}, err
}
query.Set("filters", filterJSON)
}
options.Filters.updateURLValues(query)
resp, err := cli.get(ctx, "/volumes", query, nil)
defer ensureReaderClosed(resp)
if err != nil {

View File

@ -1,8 +1,6 @@
package client
import "github.com/moby/moby/api/types/filters"
// VolumeListOptions holds parameters to list volumes.
type VolumeListOptions struct {
Filters filters.Args
Filters Filters
}

View File

@ -4,17 +4,15 @@ import (
"context"
"encoding/json"
"fmt"
"net/url"
"github.com/moby/moby/api/types/filters"
"github.com/moby/moby/api/types/volume"
)
// VolumesPrune requests the daemon to delete unused data
func (cli *Client) VolumesPrune(ctx context.Context, pruneFilters filters.Args) (volume.PruneReport, error) {
query, err := getFiltersQuery(pruneFilters)
if err != nil {
return volume.PruneReport{}, err
}
func (cli *Client) VolumesPrune(ctx context.Context, pruneFilters Filters) (volume.PruneReport, error) {
query := url.Values{}
pruneFilters.updateURLValues(query)
resp, err := cli.post(ctx, "/volumes/prune", query, nil, nil)
defer ensureReaderClosed(resp)