diff --git a/vendor.mod b/vendor.mod index a55108fc1421..9d96be61fefc 100644 --- a/vendor.mod +++ b/vendor.mod @@ -30,8 +30,8 @@ require ( github.com/google/uuid v1.6.0 github.com/mattn/go-runewidth v0.0.21 github.com/moby/go-archive v0.2.0 - github.com/moby/moby/api v1.54.0 - github.com/moby/moby/client v0.3.0 + github.com/moby/moby/api v1.54.1-0.20260402180246-c4937534f1c7 + github.com/moby/moby/client v0.3.1-0.20260402180246-c4937534f1c7 github.com/moby/patternmatcher v0.6.1 github.com/moby/swarmkit/v2 v2.1.1 github.com/moby/sys/atomicwriter v0.1.0 diff --git a/vendor.sum b/vendor.sum index 93434d909210..ffebe6f3b467 100644 --- a/vendor.sum +++ b/vendor.sum @@ -113,10 +113,10 @@ github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3N github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/go-archive v0.2.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8= github.com/moby/go-archive v0.2.0/go.mod h1:mNeivT14o8xU+5q1YnNrkQVpK+dnNe/K6fHqnTg4qPU= -github.com/moby/moby/api v1.54.0 h1:7kbUgyiKcoBhm0UrWbdrMs7RX8dnwzURKVbZGy2GnL0= -github.com/moby/moby/api v1.54.0/go.mod h1:8mb+ReTlisw4pS6BRzCMts5M49W5M7bKt1cJy/YbAqc= -github.com/moby/moby/client v0.3.0 h1:UUGL5okry+Aomj3WhGt9Aigl3ZOxZGqR7XPo+RLPlKs= -github.com/moby/moby/client v0.3.0/go.mod h1:HJgFbJRvogDQjbM8fqc1MCEm4mIAGMLjXbgwoZp6jCQ= +github.com/moby/moby/api v1.54.1-0.20260402180246-c4937534f1c7 h1:7ggjCbeIHPspMZbiXDXJb2b06m5BduOueyzXpmusYEo= +github.com/moby/moby/api v1.54.1-0.20260402180246-c4937534f1c7/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs= +github.com/moby/moby/client v0.3.1-0.20260402180246-c4937534f1c7 h1:cSY9RrCfyzF4gdTH8PWhHojczcWfV3Bdc594ZUat0pY= +github.com/moby/moby/client v0.3.1-0.20260402180246-c4937534f1c7/go.mod h1:V16/Q7VQci0Mkto86puucoIeRyvUfNJDaISiBQuV+Ow= github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U= github.com/moby/patternmatcher v0.6.1/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/swarmkit/v2 v2.1.1 h1:yvTJ8MMCc3f0qTA44J6R59EZ5yZawdYopkpuLk4+ICU= diff --git a/vendor/github.com/moby/moby/api/types/network/port.go b/vendor/github.com/moby/moby/api/types/network/port.go index 171d9f51d35c..d12d55ab3d71 100644 --- a/vendor/github.com/moby/moby/api/types/network/port.go +++ b/vendor/github.com/moby/moby/api/types/network/port.go @@ -77,8 +77,21 @@ func (p Port) Num() uint16 { return p.num } +// Port returns p's port number as a string. +// +// It returns an empty string for zero-values. +func (p Port) Port() string { + if p.proto == protoZero { + return "" + } + return strconv.Itoa(int(p.num)) +} + // Proto returns p's network protocol. func (p Port) Proto() IPProtocol { + if p.proto == protoZero { + return "" + } return p.proto.Value() } @@ -93,7 +106,8 @@ func (p Port) IsValid() bool { } // String returns a string representation of the port in the format "/". -// If the port is the zero value, it returns "invalid port". +// If the port is the zero value, it returns "invalid port", and users should +// check [PortRange.IsValid] or [PortRange.IsZero] before using this method. func (p Port) String() string { switch p.proto { case protoZero: @@ -232,6 +246,9 @@ func (pr PortRange) End() uint16 { // Proto returns pr's network protocol. func (pr PortRange) Proto() IPProtocol { + if pr.proto == protoZero { + return "" + } return pr.proto.Value() } @@ -245,8 +262,12 @@ func (pr PortRange) IsValid() bool { return pr.proto != protoZero } -// String returns a string representation of the port range in the format "-/" or "/" if start == end. -// If the port range is the zero value, it returns "invalid port range". +// String returns a string representation of the port range in the format +// "-/" or "/" (if start == end). +// +// If the port range is the zero value, it returns "invalid port range", +// and users should check [PortRange.IsValid] or [PortRange.IsZero] before +// using this method. func (pr PortRange) String() string { switch pr.proto { case protoZero: @@ -307,6 +328,11 @@ func (pr PortRange) Range() PortRange { // } func (pr PortRange) All() iter.Seq[Port] { return func(yield func(Port) bool) { + // Do not skip zero values here, because a zero-value means + // "map the port to an ephemeral host port". + // + // For example, "--port 80" is shorthand for "--port 0:80" + // ("--port :80"). for i := uint32(pr.Start()); i <= uint32(pr.End()); i++ { if !yield(Port{num: uint16(i), proto: pr.proto}) { return diff --git a/vendor/github.com/moby/moby/client/README.md b/vendor/github.com/moby/moby/client/README.md index 115e604dbc6a..aed3e641d9a4 100644 --- a/vendor/github.com/moby/moby/client/README.md +++ b/vendor/github.com/moby/moby/client/README.md @@ -23,11 +23,16 @@ import ( ) func main() { - // Create a new client that handles common environment variables - // for configuration (DOCKER_HOST, DOCKER_API_VERSION), and does - // API-version negotiation to allow downgrading the API version - // when connecting with an older daemon version. - apiClient, err := client.New(client.FromEnv) + // Create a new client with "client.FromEnv" (configuring the client + // from commonly used environment variables such as DOCKER_HOST and + // DOCKER_API_VERSION) and set a custom User-Agent. + // + // API-version negotiation is enabled by default to allow downgrading + // the API version when connecting with an older daemon version. + apiClient, err := client.New( + client.FromEnv, + client.WithUserAgent("my-application/1.0.0"), + ) if err != nil { panic(err) } @@ -49,4 +54,4 @@ func main() { } ``` -[Full documentation is available on pkg.go.dev.](https://pkg.go.dev/github.com/moby/moby/client) +Full documentation is available on [pkg.go.dev](https://pkg.go.dev/github.com/moby/moby/client). diff --git a/vendor/github.com/moby/moby/client/build_cancel.go b/vendor/github.com/moby/moby/client/build_cancel.go index f6cfc6bc9155..a31dced97913 100644 --- a/vendor/github.com/moby/moby/client/build_cancel.go +++ b/vendor/github.com/moby/moby/client/build_cancel.go @@ -5,8 +5,10 @@ import ( "net/url" ) +// BuildCancelOptions holds options for [Client.BuildCancel]. type BuildCancelOptions struct{} +// BuildCancelResult holds the result of [Client.BuildCancel]. type BuildCancelResult struct{} // BuildCancel requests the daemon to cancel the ongoing build request diff --git a/vendor/github.com/moby/moby/client/client.go b/vendor/github.com/moby/moby/client/client.go index 2d1e0db79a8f..89ba88ee5c16 100644 --- a/vendor/github.com/moby/moby/client/client.go +++ b/vendor/github.com/moby/moby/client/client.go @@ -59,6 +59,7 @@ import ( "net/http" "net/url" "path" + "runtime" "slices" "strings" "sync" @@ -67,6 +68,7 @@ import ( cerrdefs "github.com/containerd/errdefs" "github.com/docker/go-connections/sockets" + "github.com/moby/moby/client/internal/mod" "github.com/moby/moby/client/pkg/versions" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) @@ -113,6 +115,10 @@ const MaxAPIVersion = "1.54" // below this version are not considered when performing API-version negotiation. const MinAPIVersion = "1.40" +// defaultUserAgent returns the default User-Agent to use if none is set. +// It defaults to "moby-client/ os/arch" +var defaultUserAgent = sync.OnceValue(userAgent) + // Ensure that Client always implements APIClient. var _ APIClient = &Client{} @@ -159,7 +165,9 @@ func CheckRedirect(_ *http.Request, via []*http.Request) error { // NewClientWithOpts initializes a new API client. // -// Deprecated: use New. This function will be removed in the next release. +// Deprecated: use [New]. This function will be removed in the next release. +// +//go:fix inline func NewClientWithOpts(ops ...Opt) (*Client, error) { return New(ops...) } @@ -207,6 +215,9 @@ func New(ops ...Opt) (*Client, error) { cfg := &c.clientConfig for _, op := range ops { + if op == nil { + continue + } if err := op(cfg); err != nil { return nil, err } @@ -431,3 +442,14 @@ func (cli *Client) dialer() func(context.Context) (net.Conn, error) { } } } + +func userAgent() string { + const defaultVersion = "v0.0.0+unknown" + const moduleName = "github.com/moby/moby/client" + + version := defaultVersion + if v := mod.Version(moduleName); v != "" { + version = v + } + return "moby-client/" + version + " " + runtime.GOOS + "/" + runtime.GOARCH +} diff --git a/vendor/github.com/moby/moby/client/client_options.go b/vendor/github.com/moby/moby/client/client_options.go index d92a16a455f7..3992557230c3 100644 --- a/vendor/github.com/moby/moby/client/client_options.go +++ b/vendor/github.com/moby/moby/client/client_options.go @@ -2,6 +2,7 @@ package client import ( "context" + "crypto/tls" "errors" "fmt" "net" @@ -11,6 +12,7 @@ import ( "strings" "time" + cerrdefs "github.com/containerd/errdefs" "github.com/docker/go-connections/sockets" "github.com/docker/go-connections/tlsconfig" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" @@ -193,11 +195,23 @@ func WithUserAgent(ua string) Opt { } // WithHTTPHeaders appends custom HTTP headers to the client's default headers. -// It does not allow for built-in headers (such as "User-Agent", if set) to -// be overridden. Also see [WithUserAgent]. +// It does not allow overriding built-in headers (such as "User-Agent"). +// Also see [WithUserAgent]. +// +// It replaces any existing custom headers. Keys are case-insensitive and +// canonicalized using [http.CanonicalHeaderKey]. If multiple entries map +// to the same canonical key, a [cerrdefs.ErrInvalidArgument] is returned. func WithHTTPHeaders(headers map[string]string) Opt { return func(c *clientConfig) error { - c.customHTTPHeaders = headers + c.customHTTPHeaders = make(map[string]string) + for k, v := range headers { + k = http.CanonicalHeaderKey(k) + _, ok := c.customHTTPHeaders[k] + if ok { + return cerrdefs.ErrInvalidArgument.WithMessage(fmt.Sprintf("duplicate custom HTTP header (%s)", k)) + } + c.customHTTPHeaders[k] = v + } return nil } } @@ -210,56 +224,84 @@ func WithScheme(scheme string) Opt { } } -// WithTLSClientConfig applies a TLS config to the client transport. -func WithTLSClientConfig(cacertPath, certPath, keyPath string) Opt { +// WithTLSClientConfig configures the client's existing HTTP transport to use TLS. +// The minimum TLS version is TLS 1.2. +// +// If caFile is non-empty, it specifies the CA certificate file to use for +// server verification, and replaces the system root pool for that verification. +// If certFile is empty, the system root pool is used. +// +// If either certFile or keyFile is set, both must point to readable files +// containing a valid client certificate and unencrypted private key, or this +// option returns an error. +// +// If both certPath and keyPath are empty, no client certificate is configured. +// The connection will use TLS without client authentication (i.e., not mTLS). +func WithTLSClientConfig(caFile, certFile, keyFile string) Opt { return func(c *clientConfig) error { transport, ok := c.client.Transport.(*http.Transport) if !ok { - return fmt.Errorf("cannot apply tls config to transport: %T", c.client.Transport) + return fmt.Errorf("cannot configure TLS: unsupported HTTP transport %T", c.client.Transport) } config, err := tlsconfig.Client(tlsconfig.Options{ - CAFile: cacertPath, - CertFile: certPath, - KeyFile: keyPath, + CAFile: caFile, + CertFile: certFile, + KeyFile: keyFile, ExclusiveRootPools: true, + MinVersion: tls.VersionTLS12, }) if err != nil { - return fmt.Errorf("failed to create tls config: %w", err) + return fmt.Errorf("configure TLS: %w", err) } transport.TLSClientConfig = config return nil } } -// WithTLSClientConfigFromEnv configures the client's TLS settings with the -// settings in the DOCKER_CERT_PATH ([EnvOverrideCertPath]) and DOCKER_TLS_VERIFY -// ([EnvTLSVerify]) environment variables. If DOCKER_CERT_PATH is not set or empty, -// TLS configuration is not modified. +// WithTLSClientConfigFromEnv configures the client for TLS using the +// DOCKER_CERT_PATH ([EnvOverrideCertPath]) and DOCKER_TLS_VERIFY +// ([EnvTLSVerify]) environment variables. The minimum TLS version is TLS 1.2. // -// WithTLSClientConfigFromEnv uses the following environment variables: +// If DOCKER_CERT_PATH is unset or empty, this option leaves the client +// unchanged. // -// - DOCKER_CERT_PATH ([EnvOverrideCertPath]) to specify the directory from -// which to load the TLS certificates ("ca.pem", "cert.pem", "key.pem"). -// - DOCKER_TLS_VERIFY ([EnvTLSVerify]) to enable or disable TLS verification -// (off by default). +// When DOCKER_CERT_PATH is set, the following files are loaded from that +// directory: +// +// - "ca.pem" as the CA certificate +// - "cert.pem" as the client certificate +// - "key.pem" as the client private key +// +// These files must exist, be readable, and contain valid TLS material, or this +// option returns an error. A client certificate is always loaded from "cert.pem" +// and "key.pem" (mTLS is expected). +// +// If DOCKER_TLS_VERIFY is set to a non-empty value, server certificate +// verification is enabled. In that case, "ca.pem" is added to the system root +// pool used for verification. +// +// If DOCKER_TLS_VERIFY is unset or empty, server certificate verification is +// disabled. func WithTLSClientConfigFromEnv() Opt { return func(c *clientConfig) error { dockerCertPath := os.Getenv(EnvOverrideCertPath) if dockerCertPath == "" { return nil } - tlsc, err := tlsconfig.Client(tlsconfig.Options{ + tlsConfig, err := tlsconfig.Client(tlsconfig.Options{ CAFile: filepath.Join(dockerCertPath, "ca.pem"), CertFile: filepath.Join(dockerCertPath, "cert.pem"), KeyFile: filepath.Join(dockerCertPath, "key.pem"), InsecureSkipVerify: os.Getenv(EnvTLSVerify) == "", + MinVersion: tls.VersionTLS12, }) if err != nil { - return err + return fmt.Errorf("configure TLS from %q: %w", EnvOverrideCertPath+"="+dockerCertPath, err) } + // FIXME(thaJeztah): unlike WithTLSClientConfig, this option replaces the client's http.Client and transport; consider updating just the transport. c.client = &http.Client{ - Transport: &http.Transport{TLSClientConfig: tlsc}, + Transport: &http.Transport{TLSClientConfig: tlsConfig}, CheckRedirect: CheckRedirect, } return nil @@ -295,6 +337,8 @@ func WithAPIVersion(version string) Opt { // WithVersion overrides the client version with the specified one. // // Deprecated: use [WithAPIVersion] instead. +// +//go:fix inline func WithVersion(version string) Opt { return WithAPIVersion(version) } @@ -328,6 +372,8 @@ func WithAPIVersionFromEnv() Opt { // the DOCKER_API_VERSION ([EnvOverrideAPIVersion]) environment variable. // // Deprecated: use [WithAPIVersionFromEnv] instead. +// +//go:fix inline func WithVersionFromEnv() Opt { return WithAPIVersionFromEnv() } @@ -337,8 +383,11 @@ func WithVersionFromEnv() Opt { // to use when making requests. API version negotiation is performed on the first // request; subsequent requests do not re-negotiate. // -// Deprecated: API-version negotiation is now enabled by default. Use [WithAPIVersion] -// or [WithAPIVersionFromEnv] to disable API version negotiation. +// Deprecated: API-version negotiation is now enabled by default and this options +// is now a no-op. +// +// Use [WithAPIVersion] or [WithAPIVersionFromEnv] to set a fixed API version +// instead of using automatic negotiation. func WithAPIVersionNegotiation() Opt { return func(c *clientConfig) error { return nil @@ -348,7 +397,10 @@ func WithAPIVersionNegotiation() Opt { // WithTraceProvider sets the trace provider for the client. // If this is not set then the global trace provider is used. func WithTraceProvider(provider trace.TracerProvider) Opt { - return WithTraceOptions(otelhttp.WithTracerProvider(provider)) + return func(c *clientConfig) error { + c.traceOpts = append(c.traceOpts, otelhttp.WithTracerProvider(provider)) + return nil + } } // WithTraceOptions sets tracing span options for the client. diff --git a/vendor/github.com/moby/moby/client/config_remove.go b/vendor/github.com/moby/moby/client/config_remove.go index c77a4c37862e..5cde5e1439b6 100644 --- a/vendor/github.com/moby/moby/client/config_remove.go +++ b/vendor/github.com/moby/moby/client/config_remove.go @@ -2,10 +2,12 @@ package client import "context" +// ConfigRemoveOptions holds options for [Client.ConfigRemove]. type ConfigRemoveOptions struct { // Add future optional parameters here } +// ConfigRemoveResult holds the result of [Client.ConfigRemove]. type ConfigRemoveResult struct { // Add future fields here } diff --git a/vendor/github.com/moby/moby/client/config_update.go b/vendor/github.com/moby/moby/client/config_update.go index 2651f4b2f89f..31bdd795699d 100644 --- a/vendor/github.com/moby/moby/client/config_update.go +++ b/vendor/github.com/moby/moby/client/config_update.go @@ -13,6 +13,7 @@ type ConfigUpdateOptions struct { Spec swarm.ConfigSpec } +// ConfigUpdateResult holds the result of [Client.ConfigUpdate]. type ConfigUpdateResult struct{} // ConfigUpdate attempts to update a config diff --git a/vendor/github.com/moby/moby/client/container_copy.go b/vendor/github.com/moby/moby/client/container_copy.go index f76511246ca2..b37d1765f441 100644 --- a/vendor/github.com/moby/moby/client/container_copy.go +++ b/vendor/github.com/moby/moby/client/container_copy.go @@ -14,10 +14,12 @@ import ( "github.com/moby/moby/api/types/container" ) +// ContainerStatPathOptions holds options for [Client.ContainerStatPath]. type ContainerStatPathOptions struct { Path string } +// ContainerStatPathResult holds the result of [Client.ContainerStatPath]. type ContainerStatPathResult struct { Stat container.PathStat } @@ -53,6 +55,7 @@ type CopyToContainerOptions struct { CopyUIDGID bool } +// CopyToContainerResult holds the result of [Client.CopyToContainer]. type CopyToContainerResult struct{} // CopyToContainer copies content into the container filesystem. @@ -83,10 +86,12 @@ func (cli *Client) CopyToContainer(ctx context.Context, containerID string, opti return CopyToContainerResult{}, nil } +// CopyFromContainerOptions holds options for [Client.CopyFromContainer]. type CopyFromContainerOptions struct { SourcePath string } +// CopyFromContainerResult holds the result of [Client.CopyFromContainer]. type CopyFromContainerResult struct { Content io.ReadCloser Stat container.PathStat diff --git a/vendor/github.com/moby/moby/client/filters.go b/vendor/github.com/moby/moby/client/filters.go index 347ad5c689a8..3669ae0d4736 100644 --- a/vendor/github.com/moby/moby/client/filters.go +++ b/vendor/github.com/moby/moby/client/filters.go @@ -2,6 +2,7 @@ package client import ( "encoding/json" + "maps" "net/url" ) @@ -35,9 +36,7 @@ func (f Filters) Clone() Filters { out := make(Filters, len(f)) for term, values := range f { inner := make(map[string]bool, len(values)) - for v, ok := range values { - inner[v] = ok - } + maps.Copy(inner, values) out[term] = inner } return out diff --git a/vendor/github.com/moby/moby/client/image_build.go b/vendor/github.com/moby/moby/client/image_build.go index 5062ec5de12d..67ac204aaf41 100644 --- a/vendor/github.com/moby/moby/client/image_build.go +++ b/vendor/github.com/moby/moby/client/image_build.go @@ -23,7 +23,7 @@ func (cli *Client) ImageBuild(ctx context.Context, buildContext io.Reader, optio return ImageBuildResult{}, err } - buf, err := json.Marshal(options.AuthConfigs) + buf, err := json.Marshal(options.AuthConfigs) // #nosec G117 -- ignore "Marshaled struct field "Password" (JSON key "password") matches secret pattern" if err != nil { return ImageBuildResult{}, err } diff --git a/vendor/github.com/moby/moby/client/image_tag.go b/vendor/github.com/moby/moby/client/image_tag.go index 5566f4624f40..37272914f6dc 100644 --- a/vendor/github.com/moby/moby/client/image_tag.go +++ b/vendor/github.com/moby/moby/client/image_tag.go @@ -9,11 +9,13 @@ import ( "github.com/distribution/reference" ) +// ImageTagOptions holds options for [Client.ImageTag]. type ImageTagOptions struct { Source string Target string } +// ImageTagResult holds the result of [Client.ImageTag]. type ImageTagResult struct{} // ImageTag tags an image in the docker host diff --git a/vendor/github.com/moby/moby/client/internal/mod/mod.go b/vendor/github.com/moby/moby/client/internal/mod/mod.go new file mode 100644 index 000000000000..611a9196dd11 --- /dev/null +++ b/vendor/github.com/moby/moby/client/internal/mod/mod.go @@ -0,0 +1,226 @@ +// Package mod provides a small helper to extract a module's version +// from [debug.BuildInfo] without depending on [golang.org/x/mod]. +// +// [golang.org/x/mod]: https://pkg.go.dev/golang.org/x/mod +package mod + +import ( + "fmt" + "runtime/debug" + "strconv" + "strings" + "sync" +) + +var readBuildInfo = sync.OnceValues(debug.ReadBuildInfo) + +// Version returns a best-effort version string for the given module path, +// similar to [mod.Version] in the daemon. +// +// If the module is present in [debug.BuildInfo] dependencies, its version +// is returned. Tagged versions are returned as-is (with "+incompatible" +// stripped). [Pseudo-versions] are normalized to: +// +// +[+meta...][+dirty] +// +// Where "" matches the behavior of [module.PseudoVersionBase] (i.e., +// downgrade to the previous tag for non-prerelease Pseudo-versions). +// +// If the module is replaced (for example via go.work or replace directives), +// or no usable version information is available, Version returns an empty string. +// +// The returned value is intended for display purposes (e.g., in a default +// User-Agent), not for version comparison. +// +// [mod.Version]: https://pkg.go.dev/github.com/moby/moby/v2@v2.0.0-beta.7/daemon/internal/builder-next/worker/mod#Version +// [module.PseudoVersionBase]: https://pkg.go.dev/golang.org/x/mod@v0.34.0/module#PseudoVersionBase +// [Pseudo-versions]: https://cs.opensource.google/go/x/mod/+/refs/tags/v0.34.0:module/pseudo.go;l=5-33 +func Version(name string) string { + bi, ok := readBuildInfo() + if !ok || bi == nil { + return "" + } + return moduleVersion(name, bi) +} + +func moduleVersion(name string, bi *debug.BuildInfo) (modVersion string) { + if bi == nil { + return "" + } + + // Check if we're the main module. + if ok, v := getVersion(name, &bi.Main); ok { + return v + } + + // iterate over all dependencies and find name + for _, dep := range bi.Deps { + if ok, v := getVersion(name, dep); ok { + return v + } + } + + return "" +} + +func getVersion(name string, dep *debug.Module) (bool, string) { + if dep == nil || dep.Path != name { + return false, "" + } + + v := dep.Version + if dep.Replace != nil && dep.Replace.Version != "" { + v = dep.Replace.Version + } + if v == "" || v == "(devel)" { + return true, "" + } + + return true, normalize(v) +} + +// normalize converts a Go module version into a display-friendly form: +// +// - strips "+incompatible" unconditionally +// - if pseudo: vX.Y.Z[-pre][+rev][+meta...][+dirty] +// - if tagged: vX.Y.Z[-pre][+meta...][+dirty] +func normalize(v string) string { + base, metas, dirty := splitMetadata(v) + + out := base + if base2, rev, undoPatch, ok := splitPseudo(base); ok { + if undoPatch { + // Downgrade the patch version that was raised by pseudo-versions: + // + // (2) vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdef123456 + if major, minor, patch, ok := parseSemVer(base2); ok && patch > 0 { + patch-- + base2 = fmt.Sprintf("v%d.%d.%d", major, minor, patch) + } + } + // Go pseudo rev is typically 12, but be defensive. + if len(rev) > 12 { + rev = rev[:12] + } + out = base2 + "+" + rev + } + + // Preserve other metadata (except for "+incompatible"). + for _, m := range metas { + out += m + } + if dirty { + // +dirty goes last + out += "+dirty" + } + return out +} + +func splitMetadata(v string) (base string, metas []string, dirty bool) { + base, meta, ok := strings.Cut(v, "+") + if !ok || meta == "" { + return base, nil, false + } + for m := range strings.SplitSeq(meta, "+") { + // drop incompatible, extract dirty, preserve everything else. + switch m { + case "incompatible", "": + // drop "+incompatible" and empty strings + case "dirty": + dirty = true + default: + metas = append(metas, "+"+m) + } + } + + return base, metas, dirty +} + +// splitPseudo splits a pseudo-version into base + revision, and reports whether +// it is a (Z+1) pseudo that needs patch undo. +// +// Supported (after stripping +incompatible/+dirty metadata): +// +// (1) vX.0.0-yyyymmddhhmmss-abcdef123456 +// (2) vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdef123456 +// (4) vX.Y.Z-pre.0.yyyymmddhhmmss-abcdef123456 +func splitPseudo(v string) (base, rev string, undoPatch bool, ok bool) { + // Split off revision at the last '-'. + last := strings.LastIndexByte(v, '-') + if last < 0 || last+1 >= len(v) { + return "", "", false, false + } + rev = v[last+1:] + left := v[:last] + + // First try the dot-joined timestamp forms: + // ...-0. (release pseudo; undoPatch) + // ....0. (prerelease pseudo; preserve prerelease) + if dot := strings.LastIndexByte(left, '.'); dot > 0 && dot+1 < len(left) { + ts := left[dot+1:] + if isTimestamp(ts) { + prefix := left[:dot] // ends with "-0" or ".0" for forms (2)/(4) + switch { + case strings.HasSuffix(prefix, "-0"): + // (2) vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdef123456 + return prefix[:len(prefix)-2], rev, true, true + case strings.HasSuffix(prefix, ".0"): + // (4) vX.Y.Z-pre.0.yyyymmddhhmmss-abcdef123456 + return prefix[:len(prefix)-2], rev, false, true + } + } + } + + // Fall back to form (1): ...-- + // + // (1) vX.0.0-yyyymmddhhmmss-abcdef123456 + if dash := strings.LastIndexByte(left, '-'); dash > 0 && dash+1 < len(left) { + ts := left[dash+1:] + if isTimestamp(ts) { + return left[:dash], rev, false, true + } + } + + return "", "", false, false +} + +// isTimestamp checks whether s is a timestamp ("yyyymmddhhmmss") +// component in a module version (vX.0.0-yyyymmddhhmmss-abcdef123456). +func isTimestamp(s string) bool { + if len(s) != 14 { + return false + } + for i := range len(s) { + c := s[i] + if c < '0' || c > '9' { + return false + } + } + return true +} + +// parseSemVer parses "vX.Y.Z" into numeric components. +// It intentionally handles only the strict three-segment core form. +func parseSemVer(v string) (major, minor, patch int, ok bool) { + if len(v) < 2 || v[0] != 'v' { + return 0, 0, 0, false + } + parts := strings.Split(v[1:], ".") + if len(parts) != 3 { + return 0, 0, 0, false + } + var err error + major, err = strconv.Atoi(parts[0]) + if err != nil { + return 0, 0, 0, false + } + minor, err = strconv.Atoi(parts[1]) + if err != nil { + return 0, 0, 0, false + } + patch, err = strconv.Atoi(parts[2]) + if err != nil { + return 0, 0, 0, false + } + return major, minor, patch, true +} diff --git a/vendor/github.com/moby/moby/client/node_inspect.go b/vendor/github.com/moby/moby/client/node_inspect.go index cd4ce0119f36..ed482152cc42 100644 --- a/vendor/github.com/moby/moby/client/node_inspect.go +++ b/vendor/github.com/moby/moby/client/node_inspect.go @@ -12,6 +12,7 @@ import ( // NodeInspectOptions holds parameters to inspect nodes with. type NodeInspectOptions struct{} +// NodeInspectResult holds the result of [Client.NodeInspect]. type NodeInspectResult struct { Node swarm.Node Raw json.RawMessage diff --git a/vendor/github.com/moby/moby/client/node_list.go b/vendor/github.com/moby/moby/client/node_list.go index 1a1b57922e72..aec3355e4018 100644 --- a/vendor/github.com/moby/moby/client/node_list.go +++ b/vendor/github.com/moby/moby/client/node_list.go @@ -13,6 +13,7 @@ type NodeListOptions struct { Filters Filters } +// NodeListResult holds the result of [Client.NodeList]. type NodeListResult struct { Items []swarm.Node } diff --git a/vendor/github.com/moby/moby/client/node_remove.go b/vendor/github.com/moby/moby/client/node_remove.go index 56c39d67a616..2a88cf80e3e6 100644 --- a/vendor/github.com/moby/moby/client/node_remove.go +++ b/vendor/github.com/moby/moby/client/node_remove.go @@ -9,6 +9,8 @@ import ( type NodeRemoveOptions struct { Force bool } + +// NodeRemoveResult holds the result of [Client.NodeRemove]. type NodeRemoveResult struct{} // NodeRemove removes a Node. diff --git a/vendor/github.com/moby/moby/client/node_update.go b/vendor/github.com/moby/moby/client/node_update.go index 4bc7c3b69827..24f87a4df559 100644 --- a/vendor/github.com/moby/moby/client/node_update.go +++ b/vendor/github.com/moby/moby/client/node_update.go @@ -13,6 +13,7 @@ type NodeUpdateOptions struct { Spec swarm.NodeSpec } +// NodeUpdateResult holds the result of [Client.NodeUpdate]. type NodeUpdateResult struct{} // NodeUpdate updates a Node. diff --git a/vendor/github.com/moby/moby/client/pkg/jsonmessage/jsonmessage.go b/vendor/github.com/moby/moby/client/pkg/jsonmessage/jsonmessage.go index 0ae74fe72524..e3ac43afd44e 100644 --- a/vendor/github.com/moby/moby/client/pkg/jsonmessage/jsonmessage.go +++ b/vendor/github.com/moby/moby/client/pkg/jsonmessage/jsonmessage.go @@ -20,6 +20,25 @@ var timeNow = time.Now // For overriding in tests. // ensure the formatted time is always the same number of characters. const RFC3339NanoFixed = "2006-01-02T15:04:05.000000000Z07:00" +// DisplayOpt configures behavior for [DisplayStream] and [DisplayMessages]. +// Options are applied in order; if an option returns an error, processing +// stops and the error is returned to the caller. +type DisplayOpt func(*displayOpts) error + +type displayOpts struct { + auxCallback func(jsonstream.Message) +} + +// WithAuxCallback registers a callback that is invoked for auxiliary +// jsonstream messages as they are processed. The callback is optional; +// if not provided, auxiliary messages are ignored. +func WithAuxCallback(fn func(jsonstream.Message)) DisplayOpt { + return func(opts *displayOpts) error { + opts.auxCallback = fn + return nil + } +} + func RenderTUIProgress(p jsonstream.Progress, width uint16) string { var ( pbBox string @@ -37,16 +56,10 @@ func RenderTUIProgress(p jsonstream.Progress, width uint16) string { } } - percentage := int(float64(p.Current)/float64(p.Total)*100) / 2 - if percentage > 50 { - percentage = 50 - } + percentage := min(int(float64(p.Current)/float64(p.Total)*100)/2, 50) if width > 110 { // this number can't be negative gh#7136 - numSpaces := 0 - if 50-percentage > 0 { - numSpaces = 50 - percentage - } + numSpaces := max(50-percentage, 0) pbBox = fmt.Sprintf("[%s>%s] ", strings.Repeat("=", percentage), strings.Repeat(" ", numSpaces)) } @@ -146,10 +159,24 @@ func Display(jm jsonstream.Message, out io.Writer, isTerminal bool, width uint16 type JSONMessagesStream = iter.Seq2[jsonstream.Message, error] -// DisplayJSONMessagesStream reads a JSON message stream from in, and writes -// each [JSONMessage] to out. -// see DisplayJSONMessages for details +// DisplayJSONMessagesStream is like [DisplayStream], but allows the caller to +// explicitly provide the terminal file descriptor and whether out is a terminal. func DisplayJSONMessagesStream(in io.Reader, out io.Writer, terminalFd uintptr, isTerminal bool, auxCallback func(jsonstream.Message)) error { + var opts []DisplayOpt + if auxCallback != nil { + opts = append(opts, WithAuxCallback(auxCallback)) + } + return displayJSONMessagesStream(in, out, terminalFd, isTerminal, opts...) +} + +// DisplayStream reads a JSON message stream from in, and writes each +// [jsonstream.Message] to out. See [DisplayMessages] for details. +func DisplayStream(in io.Reader, out io.Writer, opts ...DisplayOpt) error { + terminalFd, isTerminal := term.GetFdInfo(out) + return displayJSONMessagesStream(in, out, terminalFd, isTerminal, opts...) +} + +func displayJSONMessagesStream(in io.Reader, out io.Writer, terminalFd uintptr, isTerminal bool, opts ...DisplayOpt) error { dec := json.NewDecoder(in) f := func(yield func(jsonstream.Message, error) bool) { for { @@ -164,26 +191,48 @@ func DisplayJSONMessagesStream(in io.Reader, out io.Writer, terminalFd uintptr, } } - return DisplayJSONMessages(f, out, terminalFd, isTerminal, auxCallback) + return displayJSONMessages(f, out, terminalFd, isTerminal, opts...) } -// 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]. +// DisplayJSONMessages is like [DisplayMessages], but allows the caller to +// explicitly provide the terminal file descriptor and whether out is a terminal. +func DisplayJSONMessages(messages iter.Seq2[jsonstream.Message, error], out io.Writer, terminalFd uintptr, isTerminal bool, auxCallback func(jsonstream.Message)) error { + var opts []DisplayOpt + if auxCallback != nil { + opts = append(opts, WithAuxCallback(auxCallback)) + } + return displayJSONMessages(messages, out, terminalFd, isTerminal, opts...) +} + +// DisplayMessages writes each [jsonstream.Message] from stream to out. +// It returns an error if an invalid [jsonstream.Message] is received, or if +// a message contains a non-zero [jsonstream.Message.Error]. // -// Presentation of the JSONMessage depends on whether a terminal is attached, -// and on the terminal width. Progress bars ([JSONProgress]) are suppressed -// on narrower terminals (< 110 characters). +// Presentation of the message depends on whether out is a terminal, and on the +// terminal width. Progress bars ([jsonstream.Progress]) are suppressed on +// narrower terminals (< 110 characters). If out is a terminal, it prints a +// newline ("\n") at the end of each line and moves the cursor while displaying. // -// - isTerminal describes if out is a terminal, in which case it prints -// a newline ("\n") at the end of each line and moves the cursor while -// displaying. -// - terminalFd is the fd of the current terminal (if any), and used -// to get the terminal width. -// - 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 DisplayJSONMessages(messages iter.Seq2[jsonstream.Message, error], out io.Writer, terminalFd uintptr, isTerminal bool, auxCallback func(jsonstream.Message)) error { +// auxCallback allows handling the [jsonstream.Message.Aux] field. It is called +// if a message contains an Aux field, in which case DisplayMessages does not +// present the message. +func DisplayMessages(messages iter.Seq2[jsonstream.Message, error], out io.Writer, opts ...DisplayOpt) error { + terminalFd, isTerminal := term.GetFdInfo(out) + return displayJSONMessages(messages, out, terminalFd, isTerminal, opts...) +} + +func displayJSONMessages(messages iter.Seq2[jsonstream.Message, error], out io.Writer, terminalFd uintptr, isTerminal bool, opts ...DisplayOpt) error { + var cfg displayOpts + for _, opt := range opts { + if opt == nil { + continue + } + if err := opt(&cfg); err != nil { + return err + } + } + auxCallback := cfg.auxCallback + ids := make(map[string]uint) var width uint16 = 200 if isTerminal { diff --git a/vendor/github.com/moby/moby/client/pkg/security/security_opts.go b/vendor/github.com/moby/moby/client/pkg/security/security_opts.go index b123919c1106..3390eaf6ccde 100644 --- a/vendor/github.com/moby/moby/client/pkg/security/security_opts.go +++ b/vendor/github.com/moby/moby/client/pkg/security/security_opts.go @@ -21,7 +21,7 @@ func DecodeOptions(opts []string) []Option { so := make([]Option, 0, len(opts)) for _, opt := range opts { secopt := Option{} - for _, s := range strings.Split(opt, ",") { + for s := range strings.SplitSeq(opt, ",") { k, v, _ := strings.Cut(s, "=") if k == "" { continue diff --git a/vendor/github.com/moby/moby/client/pkg/streamformatter/streamformatter.go b/vendor/github.com/moby/moby/client/pkg/streamformatter/streamformatter.go index 0c142f7089a7..67681d54f4cc 100644 --- a/vendor/github.com/moby/moby/client/pkg/streamformatter/streamformatter.go +++ b/vendor/github.com/moby/moby/client/pkg/streamformatter/streamformatter.go @@ -116,15 +116,9 @@ func rawProgressString(p *jsonstream.Progress) string { } } - percentage := int(float64(p.Current)/float64(p.Total)*100) / 2 - if percentage > 50 { - percentage = 50 - } + percentage := min(int(float64(p.Current)/float64(p.Total)*100)/2, 50) - numSpaces := 0 - if 50-percentage > 0 { - numSpaces = 50 - percentage - } + numSpaces := max(50-percentage, 0) pbBox := fmt.Sprintf("[%s>%s] ", strings.Repeat("=", percentage), strings.Repeat(" ", numSpaces)) var numbersBox string diff --git a/vendor/github.com/moby/moby/client/pkg/versions/compare.go b/vendor/github.com/moby/moby/client/pkg/versions/compare.go index 1a0325c7eda8..fa0ad9b5a3fb 100644 --- a/vendor/github.com/moby/moby/client/pkg/versions/compare.go +++ b/vendor/github.com/moby/moby/client/pkg/versions/compare.go @@ -16,11 +16,8 @@ func compare(v1, v2 string) int { otherTab = strings.Split(v2, ".") ) - maxVer := len(currTab) - if len(otherTab) > maxVer { - maxVer = len(otherTab) - } - for i := 0; i < maxVer; i++ { + maxVer := max(len(otherTab), len(currTab)) + for i := range maxVer { var currInt, otherInt int if len(currTab) > i { diff --git a/vendor/github.com/moby/moby/client/request.go b/vendor/github.com/moby/moby/client/request.go index 7b1ff743dc92..10ed36dc6789 100644 --- a/vendor/github.com/moby/moby/client/request.go +++ b/vendor/github.com/moby/moby/client/request.go @@ -128,7 +128,7 @@ func (cli *Client) sendRequest(ctx context.Context, method, path string, query u // when failing to make a connection, On error, any Response can be ignored. // A non-2xx status code doesn't cause an error. func (cli *Client) doRequest(req *http.Request) (*http.Response, error) { - resp, err := cli.client.Do(req) + resp, err := cli.client.Do(req) // #nosec G704 -- ignore "SSRF via taint analysis"; API client intentionally sends caller-provided requests/URLs. if err == nil { return resp, nil } @@ -317,12 +317,17 @@ func (cli *Client) addHeaders(req *http.Request, headers http.Header) *http.Requ req.Header[http.CanonicalHeaderKey(k)] = v } - if cli.userAgent != nil { - if *cli.userAgent == "" { - req.Header.Del("User-Agent") - } else { - req.Header.Set("User-Agent", *cli.userAgent) + if cli.userAgent == nil { + // No custom User-Agent set: use the default. + if req.Header.Get("User-Agent") == "" { + req.Header.Set("User-Agent", defaultUserAgent()) } + } else if *cli.userAgent == "" { + // User-Agent set to empty value; remove User-Agent. + req.Header.Del("User-Agent") + } else { + // Custom User-Agent set. + req.Header.Set("User-Agent", *cli.userAgent) } return req } @@ -344,7 +349,7 @@ func jsonEncode(data any) (io.Reader, error) { // encoding/json encodes a nil pointer as the JSON document `null`, // irrespective of whether the type implements json.Marshaler or encoding.TextMarshaler. // That is almost certainly not what the caller intended as the request body. - if v := reflect.ValueOf(data); v.Kind() == reflect.Ptr && v.IsNil() { + if v := reflect.ValueOf(data); v.Kind() == reflect.Pointer && v.IsNil() { return http.NoBody, nil } diff --git a/vendor/github.com/moby/moby/client/secret_remove.go b/vendor/github.com/moby/moby/client/secret_remove.go index 8554f3f215c5..42cbfec9e142 100644 --- a/vendor/github.com/moby/moby/client/secret_remove.go +++ b/vendor/github.com/moby/moby/client/secret_remove.go @@ -2,10 +2,12 @@ package client import "context" +// SecretRemoveOptions holds options for [Client.SecretRemove]. type SecretRemoveOptions struct { // Add future optional parameters here } +// SecretRemoveResult holds the result of [Client.SecretRemove]. type SecretRemoveResult struct { // Add future fields here } diff --git a/vendor/github.com/moby/moby/client/secret_update.go b/vendor/github.com/moby/moby/client/secret_update.go index c88ad110604e..d50fba4d4510 100644 --- a/vendor/github.com/moby/moby/client/secret_update.go +++ b/vendor/github.com/moby/moby/client/secret_update.go @@ -13,6 +13,7 @@ type SecretUpdateOptions struct { Spec swarm.SecretSpec } +// SecretUpdateResult holds the result of [Client.SecretUpdate]. type SecretUpdateResult struct{} // SecretUpdate attempts to update a secret. diff --git a/vendor/github.com/moby/moby/client/system_disk_usage.go b/vendor/github.com/moby/moby/client/system_disk_usage.go index 1bb2d0d7efa2..c5df1e1b195c 100644 --- a/vendor/github.com/moby/moby/client/system_disk_usage.go +++ b/vendor/github.com/moby/moby/client/system_disk_usage.go @@ -244,22 +244,15 @@ func imageDiskUsageFromLegacyAPI(du *legacyDiskUsage) ImagesDiskUsage { Items: du.Images, } - var used int64 for _, i := range idu.Items { if i.Containers > 0 { idu.ActiveCount++ - - if i.Size == -1 || i.SharedSize == -1 { - continue - } - used += (i.Size - i.SharedSize) + } else if i.Size != -1 && i.SharedSize != -1 { + // Only count reclaimable size if we have size information + idu.Reclaimable += (i.Size - i.SharedSize) } } - if idu.TotalCount > 0 { - idu.Reclaimable = idu.TotalSize - used - } - return idu } diff --git a/vendor/github.com/moby/moby/client/system_info.go b/vendor/github.com/moby/moby/client/system_info.go index 4c0a2238e17e..b4241742d77d 100644 --- a/vendor/github.com/moby/moby/client/system_info.go +++ b/vendor/github.com/moby/moby/client/system_info.go @@ -9,10 +9,12 @@ import ( "github.com/moby/moby/api/types/system" ) +// InfoOptions holds options for [Client.Info]. type InfoOptions struct { // No options currently; placeholder for future use } +// SystemInfoResult holds the result of [Client.Info]. type SystemInfoResult struct { Info system.Info } diff --git a/vendor/github.com/moby/moby/client/utils.go b/vendor/github.com/moby/moby/client/utils.go index 4415e0dc5a62..1c0d09dfa295 100644 --- a/vendor/github.com/moby/moby/client/utils.go +++ b/vendor/github.com/moby/moby/client/utils.go @@ -136,7 +136,7 @@ func newCancelReadCloser(ctx context.Context, rc io.ReadCloser) io.ReadCloser { rc: rc, close: sync.OnceValue(rc.Close), } - crc.stop = context.AfterFunc(ctx, func() { _ = crc.Close() }) + crc.stop = context.AfterFunc(ctx, func() { _ = crc.close() }) return crc } diff --git a/vendor/modules.txt b/vendor/modules.txt index 6685167ad6ae..fb7b158e6a21 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -167,8 +167,8 @@ github.com/moby/docker-image-spec/specs-go/v1 github.com/moby/go-archive github.com/moby/go-archive/compression github.com/moby/go-archive/tarheader -# github.com/moby/moby/api v1.54.0 -## explicit; go 1.24.0 +# github.com/moby/moby/api v1.54.1-0.20260402180246-c4937534f1c7 +## explicit; go 1.24 github.com/moby/moby/api/pkg/authconfig github.com/moby/moby/api/pkg/stdcopy github.com/moby/moby/api/types @@ -189,10 +189,11 @@ github.com/moby/moby/api/types/storage github.com/moby/moby/api/types/swarm github.com/moby/moby/api/types/system github.com/moby/moby/api/types/volume -# github.com/moby/moby/client v0.3.0 -## explicit; go 1.24.0 +# github.com/moby/moby/client v0.3.1-0.20260402180246-c4937534f1c7 +## explicit; go 1.24 github.com/moby/moby/client github.com/moby/moby/client/internal +github.com/moby/moby/client/internal/mod github.com/moby/moby/client/internal/timestamp github.com/moby/moby/client/pkg/jsonmessage github.com/moby/moby/client/pkg/progress