chore: make deps

This commit is contained in:
2025-11-11 14:18:57 +01:00
parent db7c4042d0
commit 45af67d22d
590 changed files with 22837 additions and 16387 deletions

12
vendor/github.com/docker/cli/AUTHORS generated vendored
View File

@ -63,6 +63,7 @@ Andreas Köhler <andi5.py@gmx.net>
Andres G. Aragoneses <knocte@gmail.com>
Andres Leon Rangel <aleon1220@gmail.com>
Andrew France <andrew@avito.co.uk>
Andrew He <he.andrew.mail@gmail.com>
Andrew Hsu <andrewhsu@docker.com>
Andrew Macpherson <hopscotch23@gmail.com>
Andrew McDonnell <bugs@andrewmcdonnell.net>
@ -86,11 +87,12 @@ Archimedes Trajano <developer@trajano.net>
Arko Dasgupta <arko@tetrate.io>
Arnaud Porterie <icecrime@gmail.com>
Arnaud Rebillout <elboulangero@gmail.com>
Arthur Flageul <arthur.flageul@gmail.com>
Arthur Peka <arthur.peka@outlook.com>
Ashly Mathew <ashly.mathew@sap.com>
Ashwini Oruganti <ashwini.oruganti@gmail.com>
Aslam Ahemad <aslamahemad@gmail.com>
Austin Vazquez <austin.vazquez.dev@gmail.com>
Austin Vazquez <austin.vazquez@docker.com>
Azat Khuyiyakhmetov <shadow_uz@mail.ru>
Bardia Keyoumarsi <bkeyouma@ucsc.edu>
Barnaby Gray <barnaby@pickle.me.uk>
@ -135,10 +137,12 @@ Cao Weiwei <cao.weiwei30@zte.com.cn>
Carlo Mion <mion00@gmail.com>
Carlos Alexandro Becker <caarlos0@gmail.com>
Carlos de Paula <me@carlosedp.com>
carsontham <carsontham@outlook.com>
Carston Schilds <Carston.Schilds@visier.com>
Casey Korver <casey@korver.dev>
Ce Gao <ce.gao@outlook.com>
Cedric Davies <cedricda@microsoft.com>
Cesar Talledo <cesar.talledo@docker.com>
Cezar Sa Espinola <cezarsa@gmail.com>
Chad Faragher <wyckster@hotmail.com>
Chao Wang <wangchao.fnst@cn.fujitsu.com>
@ -220,7 +224,7 @@ David Alvarez <david.alvarez@flyeralarm.com>
David Beitey <david@davidjb.com>
David Calavera <david.calavera@gmail.com>
David Cramer <davcrame@cisco.com>
David Dooling <dooling@gmail.com>
David Dooling <david.dooling@docker.com>
David Gageot <david@gageot.net>
David Karlsson <david.karlsson@docker.com>
David le Blanc <systemmonkey42@users.noreply.github.com>
@ -265,6 +269,7 @@ Eli Uriegas <eli.uriegas@docker.com>
Eli Uriegas <seemethere101@gmail.com>
Elias Faxö <elias.faxo@tre.se>
Elliot Luo <956941328@qq.com>
Eng Zer Jun <engzerjun@gmail.com>
Eric Bode <eric.bode@foundries.io>
Eric Curtin <ericcurtin17@gmail.com>
Eric Engestrom <eric@engestrom.ch>
@ -345,6 +350,7 @@ Henning Sprang <henning.sprang@gmail.com>
Henry N <henrynmail-github@yahoo.de>
Hernan Garcia <hernandanielg@gmail.com>
Hongbin Lu <hongbin034@gmail.com>
Hossein Abbasi <16090309+hsnabszhdn@users.noreply.github.com>
Hu Keping <hukeping@huawei.com>
Huayi Zhang <irachex@gmail.com>
Hugo Chastel <Hugo-C@users.noreply.github.com>
@ -595,6 +601,7 @@ Michael Prokop <github@michael-prokop.at>
Michael Scharf <github@scharf.gr>
Michael Spetsiotis <michael_spets@hotmail.com>
Michael Steinert <mike.steinert@gmail.com>
Michael Tews <michael@tews.dev>
Michael West <mwest@mdsol.com>
Michal Minář <miminar@redhat.com>
Michał Czeraszkiewicz <czerasz@gmail.com>
@ -896,6 +903,7 @@ Wenlong Zhang <zhangwenlong@loongson.cn>
Wenzhi Liang <wenzhi.liang@gmail.com>
Wes Morgan <cap10morgan@gmail.com>
Wewang Xiaorenfine <wang.xiaoren@zte.com.cn>
Will Wang <willww64@gmail.com>
William Henry <whenry@redhat.com>
Xianglin Gao <xlgao@zju.edu.cn>
Xiaodong Liu <liuxiaodong@loongson.cn>

View File

@ -33,4 +33,6 @@ type Metadata struct {
ShortDescription string `json:",omitempty"`
// URL is a pointer to the plugin's homepage.
URL string `json:",omitempty"`
// Hidden hides the plugin in completion and help message output.
Hidden bool `json:",omitempty"`
}

View File

@ -12,7 +12,6 @@ import (
"github.com/fvbommel/sortorder"
"github.com/moby/term"
"github.com/morikuni/aec"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
@ -167,31 +166,6 @@ func (tcmd *TopLevelCommand) Initialize(ops ...command.CLIOption) error {
return tcmd.dockerCli.Initialize(tcmd.opts, ops...)
}
// VisitAll will traverse all commands from the root.
//
// Deprecated: this utility was only used internally and will be removed in the next release.
func VisitAll(root *cobra.Command, fn func(*cobra.Command)) {
visitAll(root, fn)
}
func visitAll(root *cobra.Command, fn func(*cobra.Command)) {
for _, cmd := range root.Commands() {
visitAll(cmd, fn)
}
fn(root)
}
// DisableFlagsInUseLine sets the DisableFlagsInUseLine flag on all
// commands within the tree rooted at cmd.
//
// Deprecated: this utility was only used internally and will be removed in the next release.
func DisableFlagsInUseLine(cmd *cobra.Command) {
visitAll(cmd, func(ccmd *cobra.Command) {
// do not add a `[flags]` to the end of the usage line.
ccmd.DisableFlagsInUseLine = true
})
}
var helpCommand = &cobra.Command{
Use: "help [command]",
Short: "Help about the command",
@ -200,7 +174,7 @@ var helpCommand = &cobra.Command{
RunE: func(c *cobra.Command, args []string) error {
cmd, args, e := c.Root().Find(args)
if cmd == nil || e != nil || len(args) > 0 {
return errors.Errorf("unknown help topic: %v", strings.Join(args, " "))
return fmt.Errorf("unknown help topic: %v", strings.Join(args, " "))
}
helpFunc := cmd.HelpFunc()
helpFunc(cmd, args)
@ -276,11 +250,12 @@ func commandAliases(cmd *cobra.Command) string {
if cmd.HasParent() {
parentPath = cmd.Parent().CommandPath() + " "
}
aliases := cmd.CommandPath()
var aliases strings.Builder
aliases.WriteString(cmd.CommandPath())
for _, alias := range cmd.Aliases {
aliases += ", " + parentPath + alias
aliases.WriteString(", " + parentPath + alias)
}
return aliases
return aliases.String()
}
func topCommands(cmd *cobra.Command) []*cobra.Command {
@ -376,13 +351,10 @@ func orchestratorSubCommands(cmd *cobra.Command) []*cobra.Command {
func allManagementSubCommands(cmd *cobra.Command) []*cobra.Command {
cmds := []*cobra.Command{}
for _, sub := range cmd.Commands() {
if isPlugin(sub) {
if invalidPluginReason(sub) == "" {
cmds = append(cmds, sub)
}
if invalidPluginReason(sub) != "" {
continue
}
if sub.IsAvailableCommand() && sub.HasSubCommands() {
if sub.IsAvailableCommand() && (isPlugin(sub) || sub.HasSubCommands()) {
cmds = append(cmds, sub)
}
}

View File

@ -1,10 +1,11 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.23
//go:build go1.24
package command
import (
"context"
"errors"
"fmt"
"io"
"os"
@ -23,11 +24,8 @@ import (
"github.com/docker/cli/cli/streams"
"github.com/docker/cli/cli/version"
dopts "github.com/docker/cli/opts"
"github.com/docker/docker/api"
"github.com/docker/docker/api/types/build"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client"
"github.com/pkg/errors"
"github.com/moby/moby/api/types/build"
"github.com/moby/moby/client"
"github.com/spf13/cobra"
)
@ -45,12 +43,9 @@ type Cli interface {
Client() client.APIClient
Streams
SetIn(in *streams.In)
Apply(ops ...CLIOption) error
config.Provider
ServerInfo() ServerInfo
DefaultVersion() string
CurrentVersion() string
ContentTrustEnabled() bool
BuildKitEnabled() (bool, error)
ContextStore() store.Store
CurrentContext() string
@ -70,7 +65,6 @@ type DockerCli struct {
err *streams.Out
client client.APIClient
serverInfo ServerInfo
contentTrust bool
contextStore store.Store
currentContext string
init sync.Once
@ -78,6 +72,7 @@ type DockerCli struct {
dockerEndpoint docker.Endpoint
contextStoreConfig *store.Config
initTimeout time.Duration
userAgent string
res telemetryResource
// baseCtx is the base context used for internal operations. In the future
@ -88,17 +83,12 @@ type DockerCli struct {
enableGlobalMeter, enableGlobalTracer bool
}
// DefaultVersion returns [api.DefaultVersion].
func (*DockerCli) DefaultVersion() string {
return api.DefaultVersion
}
// CurrentVersion returns the API version currently negotiated, or the default
// version otherwise.
func (cli *DockerCli) CurrentVersion() string {
_ = cli.initialize()
if cli.client == nil {
return api.DefaultVersion
return client.MaxAPIVersion
}
return cli.client.ClientVersion()
}
@ -157,19 +147,13 @@ func (cli *DockerCli) ServerInfo() ServerInfo {
return cli.serverInfo
}
// ContentTrustEnabled returns whether content trust has been enabled by an
// environment variable.
func (cli *DockerCli) ContentTrustEnabled() bool {
return cli.contentTrust
}
// BuildKitEnabled returns buildkit is enabled or not.
func (cli *DockerCli) BuildKitEnabled() (bool, error) {
// use DOCKER_BUILDKIT env var value if set and not empty
if v := os.Getenv("DOCKER_BUILDKIT"); v != "" {
enabled, err := strconv.ParseBool(v)
if err != nil {
return false, errors.Wrap(err, "DOCKER_BUILDKIT environment variable expects boolean value")
return false, fmt.Errorf("DOCKER_BUILDKIT environment variable expects boolean value: %w", err)
}
return enabled, nil
}
@ -269,7 +253,7 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...CLIOption)
cli.contextStore = &ContextStoreWithDefault{
Store: store.New(config.ContextStoreDir(), *cli.contextStoreConfig),
Resolver: func() (*DefaultContext, error) {
return ResolveDefaultContext(cli.options, *cli.contextStoreConfig)
return resolveDefaultContext(cli.options, *cli.contextStoreConfig)
},
}
@ -306,17 +290,17 @@ func NewAPIClientFromFlags(opts *cliflags.ClientOptions, configFile *configfile.
contextStore := &ContextStoreWithDefault{
Store: store.New(config.ContextStoreDir(), storeConfig),
Resolver: func() (*DefaultContext, error) {
return ResolveDefaultContext(opts, storeConfig)
return resolveDefaultContext(opts, storeConfig)
},
}
endpoint, err := resolveDockerEndpoint(contextStore, resolveContextName(opts, configFile))
if err != nil {
return nil, errors.Wrap(err, "unable to resolve docker endpoint")
return nil, fmt.Errorf("unable to resolve docker endpoint: %w", err)
}
return newAPIClientFromEndpoint(endpoint, configFile)
return newAPIClientFromEndpoint(endpoint, configFile, client.WithUserAgent(UserAgent()))
}
func newAPIClientFromEndpoint(ep docker.Endpoint, configFile *configfile.ConfigFile) (client.APIClient, error) {
func newAPIClientFromEndpoint(ep docker.Endpoint, configFile *configfile.ConfigFile, extraOpts ...client.Opt) (client.APIClient, error) {
opts, err := ep.ClientOpts()
if err != nil {
return nil, err
@ -324,8 +308,15 @@ func newAPIClientFromEndpoint(ep docker.Endpoint, configFile *configfile.ConfigF
if len(configFile.HTTPHeaders) > 0 {
opts = append(opts, client.WithHTTPHeaders(configFile.HTTPHeaders))
}
opts = append(opts, withCustomHeadersFromEnv(), client.WithUserAgent(UserAgent()))
return client.NewClientWithOpts(opts...)
withCustomHeaders, err := withCustomHeadersFromEnv()
if err != nil {
return nil, err
}
if withCustomHeaders != nil {
opts = append(opts, withCustomHeaders)
}
opts = append(opts, extraOpts...)
return client.New(opts...)
}
func resolveDockerEndpoint(s store.Reader, contextName string) (docker.Endpoint, error) {
@ -386,24 +377,21 @@ func (cli *DockerCli) initializeFromClient() {
ctx, cancel := context.WithTimeout(cli.baseCtx, cli.getInitTimeout())
defer cancel()
ping, err := cli.client.Ping(ctx)
ping, err := cli.client.Ping(ctx, client.PingOptions{
NegotiateAPIVersion: true,
ForceNegotiate: true,
})
if err != nil {
// Default to true if we fail to connect to daemon
cli.serverInfo = ServerInfo{HasExperimental: true}
if ping.APIVersion != "" {
cli.client.NegotiateAPIVersionPing(ping)
}
return
}
cli.serverInfo = ServerInfo{
HasExperimental: ping.Experimental,
OSType: ping.OSType,
BuildkitVersion: ping.BuilderVersion,
SwarmStatus: ping.SwarmStatus,
}
cli.client.NegotiateAPIVersionPing(ping)
}
// ContextStore returns the ContextStore
@ -541,11 +529,12 @@ func (cli *DockerCli) initialize() error {
cli.init.Do(func() {
cli.dockerEndpoint, cli.initErr = cli.getDockerEndPoint()
if cli.initErr != nil {
cli.initErr = errors.Wrap(cli.initErr, "unable to resolve docker endpoint")
cli.initErr = fmt.Errorf("unable to resolve docker endpoint: %w", cli.initErr)
return
}
if cli.client == nil {
if cli.client, cli.initErr = newAPIClientFromEndpoint(cli.dockerEndpoint, cli.configFile); cli.initErr != nil {
ops := []client.Opt{client.WithUserAgent(cli.userAgent)}
if cli.client, cli.initErr = newAPIClientFromEndpoint(cli.dockerEndpoint, cli.configFile, ops...); cli.initErr != nil {
return
}
}
@ -557,16 +546,6 @@ func (cli *DockerCli) initialize() error {
return cli.initErr
}
// Apply all the operation on the cli
func (cli *DockerCli) Apply(ops ...CLIOption) error {
for _, op := range ops {
if err := op(cli); err != nil {
return err
}
}
return nil
}
// ServerInfo stores details about the supported features and platform of the
// server
type ServerInfo struct {
@ -581,7 +560,7 @@ type ServerInfo struct {
// in the ping response, or if an error occurred, in which case the client
// should use other ways to get the current swarm status, such as the /swarm
// endpoint.
SwarmStatus *swarm.Status
SwarmStatus *client.SwarmStatus
}
// NewDockerCli returns a DockerCli instance with all operators applied on it.
@ -589,15 +568,17 @@ type ServerInfo struct {
// environment.
func NewDockerCli(ops ...CLIOption) (*DockerCli, error) {
defaultOps := []CLIOption{
WithContentTrustFromEnv(),
WithDefaultContextStoreConfig(),
WithStandardStreams(),
WithUserAgent(UserAgent()),
}
ops = append(defaultOps, ops...)
cli := &DockerCli{baseCtx: context.Background()}
if err := cli.Apply(ops...); err != nil {
return nil, err
for _, op := range ops {
if err := op(cli); err != nil {
return nil, err
}
}
return cli, nil
}
@ -609,11 +590,11 @@ func getServerHost(hosts []string, defaultToTLS bool) (string, error) {
case 1:
return dopts.ParseHost(defaultToTLS, hosts[0])
default:
return "", errors.New("Specify only one -H")
return "", errors.New("specify only one -H")
}
}
// UserAgent returns the user agent string used for making API requests
// UserAgent returns the default user agent string used for making API requests.
func UserAgent() string {
return "Docker-Client/" + version.Version + " (" + runtime.GOOS + ")"
}

View File

@ -3,16 +3,16 @@ package command
import (
"context"
"encoding/csv"
"errors"
"fmt"
"io"
"net/http"
"os"
"strconv"
"strings"
"github.com/docker/cli/cli/streams"
"github.com/docker/docker/client"
"github.com/moby/moby/client"
"github.com/moby/term"
"github.com/pkg/errors"
)
// CLIOption is a functional argument to apply options to a [DockerCli]. These
@ -75,28 +75,6 @@ func WithErrorStream(err io.Writer) CLIOption {
}
}
// WithContentTrustFromEnv enables content trust on a cli from environment variable DOCKER_CONTENT_TRUST value.
func WithContentTrustFromEnv() CLIOption {
return func(cli *DockerCli) error {
cli.contentTrust = false
if e := os.Getenv("DOCKER_CONTENT_TRUST"); e != "" {
if t, err := strconv.ParseBool(e); t || err != nil {
// treat any other value as true
cli.contentTrust = true
}
}
return nil
}
}
// WithContentTrust enables content trust on a cli.
func WithContentTrust(enabled bool) CLIOption {
return func(cli *DockerCli) error {
cli.contentTrust = enabled
return nil
}
}
// WithDefaultContextStoreConfig configures the cli to use the default context store configuration.
func WithDefaultContextStoreConfig() CLIOption {
return func(cli *DockerCli) error {
@ -180,61 +158,70 @@ const envOverrideHTTPHeaders = "DOCKER_CUSTOM_HEADERS"
// override headers with the same name).
//
// TODO(thaJeztah): this is a client Option, and should be moved to the client. It is non-exported for that reason.
func withCustomHeadersFromEnv() client.Opt {
return func(apiClient *client.Client) error {
value := os.Getenv(envOverrideHTTPHeaders)
if value == "" {
return nil
}
csvReader := csv.NewReader(strings.NewReader(value))
fields, err := csvReader.Read()
if err != nil {
return invalidParameter(errors.Errorf(
"failed to parse custom headers from %s environment variable: value must be formatted as comma-separated key=value pairs",
envOverrideHTTPHeaders,
func withCustomHeadersFromEnv() (client.Opt, error) {
value := os.Getenv(envOverrideHTTPHeaders)
if value == "" {
return nil, nil
}
csvReader := csv.NewReader(strings.NewReader(value))
fields, err := csvReader.Read()
if err != nil {
return nil, invalidParameter(fmt.Errorf(
"failed to parse custom headers from %s environment variable: value must be formatted as comma-separated key=value pairs",
envOverrideHTTPHeaders,
))
}
if len(fields) == 0 {
return nil, nil
}
env := map[string]string{}
for _, kv := range fields {
k, v, hasValue := strings.Cut(kv, "=")
// Only strip whitespace in keys; preserve whitespace in values.
k = strings.TrimSpace(k)
if k == "" {
return nil, invalidParameter(fmt.Errorf(
`failed to set custom headers from %s environment variable: value contains a key=value pair with an empty key: '%s'`,
envOverrideHTTPHeaders, kv,
))
}
if len(fields) == 0 {
return nil
// We don't currently allow empty key=value pairs, and produce an error.
// This is something we could allow in future (e.g. to read value
// from an environment variable with the same name). In the meantime,
// produce an error to prevent users from depending on this.
if !hasValue {
return nil, invalidParameter(fmt.Errorf(
`failed to set custom headers from %s environment variable: missing "=" in key=value pair: '%s'`,
envOverrideHTTPHeaders, kv,
))
}
env := map[string]string{}
for _, kv := range fields {
k, v, hasValue := strings.Cut(kv, "=")
env[http.CanonicalHeaderKey(k)] = v
}
// Only strip whitespace in keys; preserve whitespace in values.
k = strings.TrimSpace(k)
if len(env) == 0 {
// We should probably not hit this case, as we don't skip values
// (only return errors), but we don't want to discard existing
// headers with an empty set.
return nil, nil
}
if k == "" {
return invalidParameter(errors.Errorf(
`failed to set custom headers from %s environment variable: value contains a key=value pair with an empty key: '%s'`,
envOverrideHTTPHeaders, kv,
))
}
// TODO(thaJeztah): add a client.WithExtraHTTPHeaders() function to allow these headers to be _added_ to existing ones, instead of _replacing_
// see https://github.com/docker/cli/pull/5098#issuecomment-2147403871 (when updating, also update the WARNING in the function and env-var GoDoc)
return client.WithHTTPHeaders(env), nil
}
// We don't currently allow empty key=value pairs, and produce an error.
// This is something we could allow in future (e.g. to read value
// from an environment variable with the same name). In the meantime,
// produce an error to prevent users from depending on this.
if !hasValue {
return invalidParameter(errors.Errorf(
`failed to set custom headers from %s environment variable: missing "=" in key=value pair: '%s'`,
envOverrideHTTPHeaders, kv,
))
}
env[http.CanonicalHeaderKey(k)] = v
// WithUserAgent configures the User-Agent string for cli HTTP requests.
func WithUserAgent(userAgent string) CLIOption {
return func(cli *DockerCli) error {
if userAgent == "" {
return errors.New("user agent cannot be blank")
}
if len(env) == 0 {
// We should probably not hit this case, as we don't skip values
// (only return errors), but we don't want to discard existing
// headers with an empty set.
return nil
}
// TODO(thaJeztah): add a client.WithExtraHTTPHeaders() function to allow these headers to be _added_ to existing ones, instead of _replacing_
// see https://github.com/docker/cli/pull/5098#issuecomment-2147403871 (when updating, also update the WARNING in the function and env-var GoDoc)
return client.WithHTTPHeaders(env)(apiClient)
cli.userAgent = userAgent
return nil
}
}

View File

@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.23
//go:build go1.24
package command

View File

@ -1,13 +1,15 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.23
//go:build go1.24
package command
import (
"errors"
"fmt"
"github.com/docker/cli/cli/context/docker"
"github.com/docker/cli/cli/context/store"
cliflags "github.com/docker/cli/cli/flags"
"github.com/pkg/errors"
)
const (
@ -51,8 +53,8 @@ type EndpointDefaultResolver interface {
ResolveDefault() (any, *store.EndpointTLSData, error)
}
// ResolveDefaultContext creates a Metadata for the current CLI invocation parameters
func ResolveDefaultContext(opts *cliflags.ClientOptions, config store.Config) (*DefaultContext, error) {
// resolveDefaultContext creates a Metadata for the current CLI invocation parameters
func resolveDefaultContext(opts *cliflags.ClientOptions, config store.Config) (*DefaultContext, error) {
contextTLSData := store.ContextTLSData{
Endpoints: make(map[string]store.EndpointTLSData),
}
@ -185,7 +187,7 @@ func (s *ContextStoreWithDefault) GetTLSData(contextName, endpointName, fileName
return nil, err
}
if defaultContext.TLS.Endpoints[endpointName].Files[fileName] == nil {
return nil, notFound(errors.Errorf("TLS data for %s/%s/%s does not exist", DefaultContextName, endpointName, fileName))
return nil, notFound(fmt.Errorf("TLS data for %s/%s/%s does not exist", DefaultContextName, endpointName, fileName))
}
return defaultContext.TLS.Endpoints[endpointName].Files[fileName], nil
}

View File

@ -6,8 +6,8 @@ import (
"strings"
"time"
"github.com/docker/docker/api/types/build"
"github.com/docker/go-units"
"github.com/moby/moby/api/types/build"
)
const (
@ -51,7 +51,7 @@ shared: {{.Shared}}
return Format(source)
}
func buildCacheSort(buildCache []*build.CacheRecord) {
func buildCacheSort(buildCache []build.CacheRecord) {
sort.Slice(buildCache, func(i, j int) bool {
lui, luj := buildCache[i].LastUsedAt, buildCache[j].LastUsedAt
switch {
@ -70,7 +70,7 @@ func buildCacheSort(buildCache []*build.CacheRecord) {
}
// BuildCacheWrite renders the context for a list of containers
func BuildCacheWrite(ctx Context, buildCaches []*build.CacheRecord) error {
func BuildCacheWrite(ctx Context, buildCaches []build.CacheRecord) error {
render := func(format func(subContext SubContext) error) error {
buildCacheSort(buildCaches)
for _, bc := range buildCaches {
@ -87,7 +87,7 @@ func BuildCacheWrite(ctx Context, buildCaches []*build.CacheRecord) error {
type buildCacheContext struct {
HeaderContext
trunc bool
v *build.CacheRecord
v build.CacheRecord
}
func newBuildCacheContext() *buildCacheContext {
@ -126,8 +126,6 @@ func (c *buildCacheContext) Parent() string {
var parent string
if len(c.v.Parents) > 0 {
parent = strings.Join(c.v.Parents, ", ")
} else {
parent = c.v.Parent //nolint:staticcheck // Ignore SA1019: Field was deprecated in API v1.42, but kept for backward compatibility
}
if c.trunc {
return TruncateID(parent)

View File

@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.23
//go:build go1.24
package formatter
@ -13,8 +13,8 @@ import (
"github.com/containerd/platforms"
"github.com/distribution/reference"
"github.com/docker/docker/api/types/container"
"github.com/docker/go-units"
"github.com/moby/moby/api/types/container"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
@ -170,27 +170,33 @@ func (c *ContainerContext) Image() string {
if c.c.Image == "" {
return "<no image>"
}
if c.trunc {
if trunc := TruncateID(c.c.ImageID); trunc == TruncateID(c.c.Image) {
return trunc
if !c.trunc {
return c.c.Image
}
if trunc := TruncateID(c.c.ImageID); trunc == TruncateID(c.c.Image) {
return trunc
}
ref, err := reference.ParseNormalizedNamed(c.c.Image)
if err != nil {
return c.c.Image
}
if _, ok := ref.(reference.Digested); ok {
// strip the digest, but preserve the tag (if any)
var tag string
if t, ok := ref.(reference.Tagged); ok {
tag = t.Tag()
}
// truncate digest if no-trunc option was not selected
ref, err := reference.ParseNormalizedNamed(c.c.Image)
if err == nil {
if nt, ok := ref.(reference.NamedTagged); ok {
// case for when a tag is provided
if namedTagged, err := reference.WithTag(reference.TrimNamed(nt), nt.Tag()); err == nil {
return reference.FamiliarString(namedTagged)
}
} else {
// case for when a tag is not provided
named := reference.TrimNamed(ref)
return reference.FamiliarString(named)
ref = reference.TrimNamed(ref)
if tag != "" {
if out, err := reference.WithTag(ref, tag); err == nil {
ref = out
}
}
}
return c.c.Image
// Format as "familiar" name with "docker.io[/library]" trimmed.
return reference.FamiliarString(ref)
}
// Command returns's the container's command. If the trunc option is set, the
@ -241,7 +247,7 @@ func (c *ContainerContext) Ports() string {
// State returns the container's current state (e.g. "running" or "paused").
// Refer to [container.ContainerState] for possible states.
func (c *ContainerContext) State() string {
return c.c.State
return string(c.c.State)
}
// Status returns the container's status in a human readable form (for example,
@ -338,7 +344,7 @@ func (c *ContainerContext) Networks() string {
// DisplayablePorts returns formatted string representing open ports of container
// e.g. "0.0.0.0:80->9090/tcp, 9988/tcp"
// it's used by command 'docker ps'
func DisplayablePorts(ports []container.Port) string {
func DisplayablePorts(ports []container.PortSummary) string {
type portGroup struct {
first uint16
last uint16
@ -354,13 +360,13 @@ func DisplayablePorts(ports []container.Port) string {
for _, port := range ports {
current := port.PrivatePort
portKey := port.Type
if port.IP != "" {
if port.IP.IsValid() {
if port.PublicPort != current {
hAddrPort := net.JoinHostPort(port.IP, strconv.Itoa(int(port.PublicPort)))
hAddrPort := net.JoinHostPort(port.IP.String(), strconv.Itoa(int(port.PublicPort)))
hostMappings = append(hostMappings, fmt.Sprintf("%s->%d/%s", hAddrPort, port.PrivatePort, port.Type))
continue
}
portKey = port.IP + "/" + port.Type
portKey = port.IP.String() + "/" + port.Type
}
group := groupMap[portKey]
@ -404,13 +410,13 @@ func formGroup(key string, start, last uint16) string {
return group + "/" + groupType
}
func comparePorts(i, j container.Port) bool {
func comparePorts(i, j container.PortSummary) bool {
if i.PrivatePort != j.PrivatePort {
return i.PrivatePort < j.PrivatePort
}
if i.IP != j.IP {
return i.IP < j.IP
return i.IP.String() < j.IP.String()
}
if i.PublicPort != j.PublicPort {

View File

@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.23
//go:build go1.24
package formatter

View File

@ -7,19 +7,20 @@ import (
"text/template"
"github.com/distribution/reference"
"github.com/docker/docker/api/types/build"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/volume"
"github.com/docker/go-units"
"github.com/moby/moby/api/types/build"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/image"
"github.com/moby/moby/api/types/volume"
"github.com/moby/moby/client"
)
const (
defaultDiskUsageImageTableFormat = "table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.CreatedSince}}\t{{.Size}}\t{{.SharedSize}}\t{{.UniqueSize}}\t{{.Containers}}"
defaultDiskUsageContainerTableFormat = "table {{.ID}}\t{{.Image}}\t{{.Command}}\t{{.LocalVolumes}}\t{{.Size}}\t{{.RunningFor}}\t{{.Status}}\t{{.Names}}"
defaultDiskUsageVolumeTableFormat = "table {{.Name}}\t{{.Links}}\t{{.Size}}"
defaultDiskUsageBuildCacheTableFormat = "table {{.ID}}\t{{.CacheType}}\t{{.Size}}\t{{.CreatedSince}}\t{{.LastUsedSince}}\t{{.UsageCount}}\t{{.Shared}}"
defaultDiskUsageTableFormat = "table {{.Type}}\t{{.TotalCount}}\t{{.Active}}\t{{.Size}}\t{{.Reclaimable}}"
defaultDiskUsageImageTableFormat Format = "table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.CreatedSince}}\t{{.Size}}\t{{.SharedSize}}\t{{.UniqueSize}}\t{{.Containers}}"
defaultDiskUsageContainerTableFormat Format = "table {{.ID}}\t{{.Image}}\t{{.Command}}\t{{.LocalVolumes}}\t{{.Size}}\t{{.RunningFor}}\t{{.Status}}\t{{.Names}}"
defaultDiskUsageVolumeTableFormat Format = "table {{.Name}}\t{{.Links}}\t{{.Size}}"
defaultDiskUsageBuildCacheTableFormat Format = "table {{.ID}}\t{{.CacheType}}\t{{.Size}}\t{{.CreatedSince}}\t{{.LastUsedSince}}\t{{.UsageCount}}\t{{.Shared}}"
defaultDiskUsageTableFormat Format = "table {{.Type}}\t{{.TotalCount}}\t{{.Active}}\t{{.Size}}\t{{.Reclaimable}}"
typeHeader = "TYPE"
totalHeader = "TOTAL"
@ -33,19 +34,18 @@ const (
// DiskUsageContext contains disk usage specific information required by the formatter, encapsulate a Context struct.
type DiskUsageContext struct {
Context
Verbose bool
LayersSize int64
Images []*image.Summary
Containers []*container.Summary
Volumes []*volume.Volume
BuildCache []*build.CacheRecord
BuilderSize int64
Verbose bool
ImageDiskUsage client.ImagesDiskUsage
BuildCacheDiskUsage client.BuildCacheDiskUsage
ContainerDiskUsage client.ContainersDiskUsage
VolumeDiskUsage client.VolumesDiskUsage
}
func (ctx *DiskUsageContext) startSubsection(format string) (*template.Template, error) {
func (ctx *DiskUsageContext) startSubsection(format Format) (*template.Template, error) {
ctx.buffer = &bytes.Buffer{}
ctx.header = ""
ctx.Format = Format(format)
ctx.Format = format
ctx.preFormat()
return ctx.parseFormat()
@ -69,7 +69,7 @@ func NewDiskUsageFormat(source string, verbose bool) Format {
{{end -}}`
return format
case !verbose && source == TableFormatKey:
return Format(defaultDiskUsageTableFormat)
return defaultDiskUsageTableFormat
case !verbose && source == RawFormatKey:
format := `type: {{.Type}}
total: {{.TotalCount}}
@ -96,35 +96,49 @@ func (ctx *DiskUsageContext) Write() (err error) {
}
err = ctx.contextFormat(tmpl, &diskUsageImagesContext{
totalSize: ctx.LayersSize,
images: ctx.Images,
totalCount: ctx.ImageDiskUsage.TotalCount,
activeCount: ctx.ImageDiskUsage.ActiveCount,
totalSize: ctx.ImageDiskUsage.TotalSize,
reclaimable: ctx.ImageDiskUsage.Reclaimable,
images: ctx.ImageDiskUsage.Items,
})
if err != nil {
return err
}
err = ctx.contextFormat(tmpl, &diskUsageContainersContext{
containers: ctx.Containers,
totalCount: ctx.ContainerDiskUsage.TotalCount,
activeCount: ctx.ContainerDiskUsage.ActiveCount,
totalSize: ctx.ContainerDiskUsage.TotalSize,
reclaimable: ctx.ContainerDiskUsage.Reclaimable,
containers: ctx.ContainerDiskUsage.Items,
})
if err != nil {
return err
}
err = ctx.contextFormat(tmpl, &diskUsageVolumesContext{
volumes: ctx.Volumes,
totalCount: ctx.VolumeDiskUsage.TotalCount,
activeCount: ctx.VolumeDiskUsage.ActiveCount,
totalSize: ctx.VolumeDiskUsage.TotalSize,
reclaimable: ctx.VolumeDiskUsage.Reclaimable,
volumes: ctx.VolumeDiskUsage.Items,
})
if err != nil {
return err
}
err = ctx.contextFormat(tmpl, &diskUsageBuilderContext{
builderSize: ctx.BuilderSize,
buildCache: ctx.BuildCache,
totalCount: ctx.BuildCacheDiskUsage.TotalCount,
activeCount: ctx.BuildCacheDiskUsage.ActiveCount,
builderSize: ctx.BuildCacheDiskUsage.TotalSize,
reclaimable: ctx.BuildCacheDiskUsage.Reclaimable,
buildCache: ctx.BuildCacheDiskUsage.Items,
})
if err != nil {
return err
}
diskUsageContainersCtx := diskUsageContainersContext{containers: []*container.Summary{}}
diskUsageContainersCtx := diskUsageContainersContext{containers: []container.Summary{}}
diskUsageContainersCtx.Header = SubHeaderContext{
"Type": typeHeader,
"TotalCount": totalHeader,
@ -146,18 +160,18 @@ type diskUsageContext struct {
func (ctx *DiskUsageContext) verboseWrite() error {
duc := &diskUsageContext{
Images: make([]*imageContext, 0, len(ctx.Images)),
Containers: make([]*ContainerContext, 0, len(ctx.Containers)),
Volumes: make([]*volumeContext, 0, len(ctx.Volumes)),
BuildCache: make([]*buildCacheContext, 0, len(ctx.BuildCache)),
Images: make([]*imageContext, 0, len(ctx.ImageDiskUsage.Items)),
Containers: make([]*ContainerContext, 0, len(ctx.ContainerDiskUsage.Items)),
Volumes: make([]*volumeContext, 0, len(ctx.VolumeDiskUsage.Items)),
BuildCache: make([]*buildCacheContext, 0, len(ctx.BuildCacheDiskUsage.Items)),
}
trunc := ctx.Format.IsTable()
// First images
for _, i := range ctx.Images {
for _, i := range ctx.ImageDiskUsage.Items {
repo := "<none>"
tag := "<none>"
if len(i.RepoTags) > 0 && !isDangling(*i) {
if len(i.RepoTags) > 0 && !isDangling(i) {
// Only show the first tag
ref, err := reference.ParseNormalizedNamed(i.RepoTags[0])
if err != nil {
@ -173,25 +187,25 @@ func (ctx *DiskUsageContext) verboseWrite() error {
repo: repo,
tag: tag,
trunc: trunc,
i: *i,
i: i,
})
}
// Now containers
for _, c := range ctx.Containers {
for _, c := range ctx.ContainerDiskUsage.Items {
// Don't display the virtual size
c.SizeRootFs = 0
duc.Containers = append(duc.Containers, &ContainerContext{trunc: trunc, c: *c})
duc.Containers = append(duc.Containers, &ContainerContext{trunc: trunc, c: c})
}
// And volumes
for _, v := range ctx.Volumes {
duc.Volumes = append(duc.Volumes, &volumeContext{v: *v})
for _, v := range ctx.VolumeDiskUsage.Items {
duc.Volumes = append(duc.Volumes, &volumeContext{v: v})
}
// And build cache
buildCacheSort(ctx.BuildCache)
for _, v := range ctx.BuildCache {
buildCacheSort(ctx.BuildCacheDiskUsage.Items)
for _, v := range ctx.BuildCacheDiskUsage.Items {
duc.BuildCache = append(duc.BuildCache, &buildCacheContext{v: v, trunc: trunc})
}
@ -212,7 +226,7 @@ func (ctx *DiskUsageContext) verboseWriteTable(duc *diskUsageContext) error {
if err != nil {
return err
}
ctx.Output.Write([]byte("Images space usage:\n\n"))
_, _ = ctx.Output.Write([]byte("Images space usage:\n\n"))
for _, img := range duc.Images {
if err := ctx.contextFormat(tmpl, img); err != nil {
return err
@ -224,7 +238,7 @@ func (ctx *DiskUsageContext) verboseWriteTable(duc *diskUsageContext) error {
if err != nil {
return err
}
ctx.Output.Write([]byte("\nContainers space usage:\n\n"))
_, _ = ctx.Output.Write([]byte("\nContainers space usage:\n\n"))
for _, c := range duc.Containers {
if err := ctx.contextFormat(tmpl, c); err != nil {
return err
@ -248,7 +262,7 @@ func (ctx *DiskUsageContext) verboseWriteTable(duc *diskUsageContext) error {
if err != nil {
return err
}
_, _ = fmt.Fprintf(ctx.Output, "\nBuild cache usage: %s\n\n", units.HumanSize(float64(ctx.BuilderSize)))
_, _ = fmt.Fprintf(ctx.Output, "\nBuild cache usage: %s\n\n", units.HumanSize(float64(ctx.BuildCacheDiskUsage.TotalSize)))
for _, v := range duc.BuildCache {
if err := ctx.contextFormat(tmpl, v); err != nil {
return err
@ -261,8 +275,11 @@ func (ctx *DiskUsageContext) verboseWriteTable(duc *diskUsageContext) error {
type diskUsageImagesContext struct {
HeaderContext
totalSize int64
images []*image.Summary
totalSize int64
reclaimable int64
totalCount int64
activeCount int64
images []image.Summary
}
func (c *diskUsageImagesContext) MarshalJSON() ([]byte, error) {
@ -274,18 +291,11 @@ func (*diskUsageImagesContext) Type() string {
}
func (c *diskUsageImagesContext) TotalCount() string {
return strconv.Itoa(len(c.images))
return strconv.FormatInt(c.totalCount, 10)
}
func (c *diskUsageImagesContext) Active() string {
used := 0
for _, i := range c.images {
if i.Containers > 0 {
used++
}
}
return strconv.Itoa(used)
return strconv.FormatInt(c.activeCount, 10)
}
func (c *diskUsageImagesContext) Size() string {
@ -293,27 +303,19 @@ func (c *diskUsageImagesContext) Size() string {
}
func (c *diskUsageImagesContext) Reclaimable() string {
var used int64
for _, i := range c.images {
if i.Containers != 0 {
if i.Size == -1 || i.SharedSize == -1 {
continue
}
used += i.Size - i.SharedSize
}
}
reclaimable := c.totalSize - used
if c.totalSize > 0 {
return fmt.Sprintf("%s (%v%%)", units.HumanSize(float64(reclaimable)), (reclaimable*100)/c.totalSize)
return fmt.Sprintf("%s (%v%%)", units.HumanSize(float64(c.reclaimable)), (c.reclaimable*100)/c.totalSize)
}
return units.HumanSize(float64(reclaimable))
return units.HumanSize(float64(c.reclaimable))
}
type diskUsageContainersContext struct {
HeaderContext
containers []*container.Summary
totalCount int64
activeCount int64
totalSize int64
reclaimable int64
containers []container.Summary
}
func (c *diskUsageContainersContext) MarshalJSON() ([]byte, error) {
@ -325,62 +327,32 @@ func (*diskUsageContainersContext) Type() string {
}
func (c *diskUsageContainersContext) TotalCount() string {
return strconv.Itoa(len(c.containers))
}
func (*diskUsageContainersContext) isActive(ctr container.Summary) bool {
switch ctr.State {
case container.StateRunning, container.StatePaused, container.StateRestarting:
return true
case container.StateCreated, container.StateRemoving, container.StateExited, container.StateDead:
return false
default:
// Unknown state (should never happen).
return false
}
return strconv.FormatInt(c.totalCount, 10)
}
func (c *diskUsageContainersContext) Active() string {
used := 0
for _, ctr := range c.containers {
if c.isActive(*ctr) {
used++
}
}
return strconv.Itoa(used)
return strconv.FormatInt(c.activeCount, 10)
}
func (c *diskUsageContainersContext) Size() string {
var size int64
for _, ctr := range c.containers {
size += ctr.SizeRw
}
return units.HumanSize(float64(size))
return units.HumanSize(float64(c.totalSize))
}
func (c *diskUsageContainersContext) Reclaimable() string {
var reclaimable, totalSize int64
for _, ctr := range c.containers {
if !c.isActive(*ctr) {
reclaimable += ctr.SizeRw
}
totalSize += ctr.SizeRw
if c.totalSize > 0 {
return fmt.Sprintf("%s (%v%%)", units.HumanSize(float64(c.reclaimable)), (c.reclaimable*100)/c.totalSize)
}
if totalSize > 0 {
return fmt.Sprintf("%s (%v%%)", units.HumanSize(float64(reclaimable)), (reclaimable*100)/totalSize)
}
return units.HumanSize(float64(reclaimable))
return units.HumanSize(float64(c.reclaimable))
}
type diskUsageVolumesContext struct {
HeaderContext
volumes []*volume.Volume
totalCount int64
activeCount int64
totalSize int64
reclaimable int64
volumes []volume.Volume
}
func (c *diskUsageVolumesContext) MarshalJSON() ([]byte, error) {
@ -392,56 +364,32 @@ func (*diskUsageVolumesContext) Type() string {
}
func (c *diskUsageVolumesContext) TotalCount() string {
return strconv.Itoa(len(c.volumes))
return strconv.FormatInt(c.totalCount, 10)
}
func (c *diskUsageVolumesContext) Active() string {
used := 0
for _, v := range c.volumes {
if v.UsageData.RefCount > 0 {
used++
}
}
return strconv.Itoa(used)
return strconv.FormatInt(c.activeCount, 10)
}
func (c *diskUsageVolumesContext) Size() string {
var size int64
for _, v := range c.volumes {
if v.UsageData.Size != -1 {
size += v.UsageData.Size
}
}
return units.HumanSize(float64(size))
return units.HumanSize(float64(c.totalSize))
}
func (c *diskUsageVolumesContext) Reclaimable() string {
var reclaimable int64
var totalSize int64
for _, v := range c.volumes {
if v.UsageData.Size != -1 {
if v.UsageData.RefCount == 0 {
reclaimable += v.UsageData.Size
}
totalSize += v.UsageData.Size
}
if c.totalSize > 0 {
return fmt.Sprintf("%s (%v%%)", units.HumanSize(float64(c.reclaimable)), (c.reclaimable*100)/c.totalSize)
}
if totalSize > 0 {
return fmt.Sprintf("%s (%v%%)", units.HumanSize(float64(reclaimable)), (reclaimable*100)/totalSize)
}
return units.HumanSize(float64(reclaimable))
return units.HumanSize(float64(c.reclaimable))
}
type diskUsageBuilderContext struct {
HeaderContext
totalCount int64
activeCount int64
builderSize int64
buildCache []*build.CacheRecord
reclaimable int64
buildCache []build.CacheRecord
}
func (c *diskUsageBuilderContext) MarshalJSON() ([]byte, error) {
@ -453,17 +401,11 @@ func (*diskUsageBuilderContext) Type() string {
}
func (c *diskUsageBuilderContext) TotalCount() string {
return strconv.Itoa(len(c.buildCache))
return strconv.FormatInt(c.totalCount, 10)
}
func (c *diskUsageBuilderContext) Active() string {
numActive := 0
for _, bc := range c.buildCache {
if bc.InUse {
numActive++
}
}
return strconv.Itoa(numActive)
return strconv.FormatInt(c.activeCount, 10)
}
func (c *diskUsageBuilderContext) Size() string {
@ -471,12 +413,5 @@ func (c *diskUsageBuilderContext) Size() string {
}
func (c *diskUsageBuilderContext) Reclaimable() string {
var inUseBytes int64
for _, bc := range c.buildCache {
if bc.InUse && !bc.Shared {
inUseBytes += bc.Size
}
}
return units.HumanSize(float64(c.builderSize - inUseBytes))
return units.HumanSize(float64(c.reclaimable))
}

View File

@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.23
//go:build go1.24
package formatter
@ -8,6 +8,7 @@ import (
"strings"
"unicode/utf8"
"github.com/moby/moby/client/pkg/stringid"
"golang.org/x/text/width"
)
@ -27,23 +28,12 @@ func charWidth(r rune) int {
}
}
const shortLen = 12
// TruncateID returns a shorthand version of a string identifier for presentation,
// after trimming digest algorithm prefix (if any).
//
// This function is a copy of [stringid.TruncateID] for presentation / formatting
// purposes.
//
// [stringid.TruncateID]: https://github.com/moby/moby/blob/v28.3.2/pkg/stringid/stringid.go#L19
// This function is a wrapper for [stringid.TruncateID] for convenience.
func TruncateID(id string) string {
if i := strings.IndexRune(id, ':'); i >= 0 {
id = id[i+1:]
}
if len(id) > shortLen {
id = id[:shortLen]
}
return id
return stringid.TruncateID(id)
}
// Ellipsis truncates a string to fit within maxDisplayWidth, and appends ellipsis (…).

View File

@ -1,17 +1,17 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.23
//go:build go1.24
package formatter
import (
"bytes"
"fmt"
"io"
"strings"
"text/template"
"github.com/docker/cli/cli/command/formatter/tabwriter"
"github.com/docker/cli/templates"
"github.com/pkg/errors"
)
// Format keys used to specify certain kinds of output formats
@ -76,7 +76,7 @@ func (c *Context) preFormat() {
func (c *Context) parseFormat() (*template.Template, error) {
tmpl, err := templates.Parse(c.finalFormat)
if err != nil {
return nil, errors.Wrap(err, "template parsing error")
return nil, fmt.Errorf("template parsing error: %w", err)
}
return tmpl, nil
}
@ -100,7 +100,7 @@ func (c *Context) postFormat(tmpl *template.Template, subContext SubContext) {
func (c *Context) contextFormat(tmpl *template.Template, subContext SubContext) error {
if err := tmpl.Execute(c.buffer, subContext); err != nil {
return errors.Wrap(err, "template parsing error")
return fmt.Errorf("template parsing error: %w", err)
}
if c.Format.IsTable() && c.header != nil {
c.header = subContext.FullHeader()

View File

@ -5,8 +5,8 @@ import (
"time"
"github.com/distribution/reference"
"github.com/docker/docker/api/types/image"
"github.com/docker/go-units"
"github.com/moby/moby/api/types/image"
)
const (
@ -202,7 +202,6 @@ func newImageContext() *imageContext {
"CreatedAt": CreatedAtHeader,
"Size": SizeHeader,
"Containers": containersHeader,
"VirtualSize": SizeHeader, // Deprecated: VirtualSize is deprecated, and equivalent to Size.
"SharedSize": sharedSizeHeader,
"UniqueSize": uniqueSizeHeader,
}
@ -257,15 +256,6 @@ func (c *imageContext) Containers() string {
return strconv.FormatInt(c.i.Containers, 10)
}
// VirtualSize shows the virtual size of the image and all of its parent
// images. Starting with docker 1.10, images are self-contained, and
// the VirtualSize is identical to Size.
//
// Deprecated: VirtualSize is deprecated, and equivalent to [imageContext.Size].
func (c *imageContext) VirtualSize() string {
return units.HumanSize(float64(c.i.Size))
}
func (c *imageContext) SharedSize() string {
if c.i.SharedSize == -1 {
return "N/A"

View File

@ -1,14 +1,14 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.23
//go:build go1.24
package formatter
import (
"encoding/json"
"errors"
"fmt"
"reflect"
"unicode"
"github.com/pkg/errors"
)
// MarshalJSON marshals x into json
@ -25,14 +25,14 @@ func MarshalJSON(x any) ([]byte, error) {
func marshalMap(x any) (map[string]any, error) {
val := reflect.ValueOf(x)
if val.Kind() != reflect.Ptr {
return nil, errors.Errorf("expected a pointer to a struct, got %v", val.Kind())
return nil, fmt.Errorf("expected a pointer to a struct, got %v", val.Kind())
}
if val.IsNil() {
return nil, errors.Errorf("expected a pointer to a struct, got nil pointer")
return nil, errors.New("expected a pointer to a struct, got nil pointer")
}
valElem := val.Elem()
if valElem.Kind() != reflect.Struct {
return nil, errors.Errorf("expected a pointer to a struct, got a pointer to %v", valElem.Kind())
return nil, fmt.Errorf("expected a pointer to a struct, got a pointer to %v", valElem.Kind())
}
typ := val.Type()
m := make(map[string]any)
@ -54,7 +54,7 @@ var unmarshallableNames = map[string]struct{}{"FullHeader": {}}
// It returns ("", nil, nil) for valid but non-marshallable parameter. (e.g. "unexportedFunc()")
func marshalForMethod(typ reflect.Method, val reflect.Value) (string, any, error) {
if val.Kind() != reflect.Func {
return "", nil, errors.Errorf("expected func, got %v", val.Kind())
return "", nil, fmt.Errorf("expected func, got %v", val.Kind())
}
name, numIn, numOut := typ.Name, val.Type().NumIn(), val.Type().NumOut()
_, blackListed := unmarshallableNames[name]

View File

@ -5,8 +5,8 @@ import (
"strconv"
"strings"
"github.com/docker/docker/api/types/volume"
"github.com/docker/go-units"
"github.com/moby/moby/api/types/volume"
)
const (
@ -40,10 +40,10 @@ func NewVolumeFormat(source string, quiet bool) Format {
}
// VolumeWrite writes formatted volumes using the Context
func VolumeWrite(ctx Context, volumes []*volume.Volume) error {
func VolumeWrite(ctx Context, volumes []volume.Volume) error {
render := func(format func(subContext SubContext) error) error {
for _, vol := range volumes {
if err := format(&volumeContext{v: *vol}); err != nil {
if err := format(&volumeContext{v: vol}); err != nil {
return err
}
}

View File

@ -2,6 +2,7 @@ package command
import (
"context"
"errors"
"fmt"
"os"
"runtime"
@ -15,9 +16,9 @@ import (
"github.com/docker/cli/cli/streams"
"github.com/docker/cli/internal/prompt"
"github.com/docker/cli/internal/tui"
registrytypes "github.com/docker/docker/api/types/registry"
"github.com/moby/moby/api/pkg/authconfig"
registrytypes "github.com/moby/moby/api/types/registry"
"github.com/morikuni/aec"
"github.com/pkg/errors"
)
const (
@ -34,42 +35,11 @@ const (
// [registry.IndexServer]: https://pkg.go.dev/github.com/docker/docker@v28.3.3+incompatible/registry#IndexServer
const authConfigKey = "https://index.docker.io/v1/"
// RegistryAuthenticationPrivilegedFunc returns a RequestPrivilegeFunc from the specified registry index info
// for the given command to prompt the user for username and password.
//
// Deprecated: this function is no longer used and will be removed in the next release.
func RegistryAuthenticationPrivilegedFunc(cli Cli, index *registrytypes.IndexInfo, cmdName string) registrytypes.RequestAuthConfig {
configKey := getAuthConfigKey(index.Name)
isDefaultRegistry := configKey == authConfigKey || index.Official
return func(ctx context.Context) (string, error) {
_, _ = fmt.Fprintf(cli.Out(), "\nLogin prior to %s:\n", cmdName)
authConfig, err := GetDefaultAuthConfig(cli.ConfigFile(), true, configKey, isDefaultRegistry)
if err != nil {
_, _ = fmt.Fprintf(cli.Err(), "Unable to retrieve stored credentials for %s, error: %s.\n", configKey, err)
}
select {
case <-ctx.Done():
return "", ctx.Err()
default:
}
authConfig, err = PromptUserForCredentials(ctx, cli, "", "", authConfig.Username, configKey)
if err != nil {
return "", err
}
return registrytypes.EncodeAuthConfig(authConfig)
}
}
// ResolveAuthConfig returns auth-config for the given registry from the
// credential-store. It returns an empty AuthConfig if no credentials were
// found.
//
// It is similar to [registry.ResolveAuthConfig], but uses the credentials-
// store, instead of looking up credentials from a map.
//
// [registry.ResolveAuthConfig]: https://pkg.go.dev/github.com/docker/docker@v28.3.3+incompatible/registry#ResolveAuthConfig
// Deprecated: this function is no longer used, and will be removed in the next release.
func ResolveAuthConfig(cfg *configfile.ConfigFile, index *registrytypes.IndexInfo) registrytypes.AuthConfig {
configKey := index.Name
if index.Official {
@ -77,7 +47,16 @@ func ResolveAuthConfig(cfg *configfile.ConfigFile, index *registrytypes.IndexInf
}
a, _ := cfg.GetAuthConfig(configKey)
return registrytypes.AuthConfig(a)
return registrytypes.AuthConfig{
Username: a.Username,
Password: a.Password,
ServerAddress: a.ServerAddress,
// TODO(thaJeztah): Are these expected to be included?
Auth: a.Auth,
IdentityToken: a.IdentityToken,
RegistryToken: a.RegistryToken,
}
}
// GetDefaultAuthConfig gets the default auth config given a serverAddress
@ -86,19 +65,27 @@ func GetDefaultAuthConfig(cfg *configfile.ConfigFile, checkCredStore bool, serve
if !isDefaultRegistry {
serverAddress = credentials.ConvertToHostname(serverAddress)
}
authconfig := configtypes.AuthConfig{}
authCfg := configtypes.AuthConfig{}
var err error
if checkCredStore {
authconfig, err = cfg.GetAuthConfig(serverAddress)
authCfg, err = cfg.GetAuthConfig(serverAddress)
if err != nil {
return registrytypes.AuthConfig{
ServerAddress: serverAddress,
}, err
}
}
authconfig.ServerAddress = serverAddress
authconfig.IdentityToken = ""
return registrytypes.AuthConfig(authconfig), nil
return registrytypes.AuthConfig{
Username: authCfg.Username,
Password: authCfg.Password,
ServerAddress: serverAddress,
// TODO(thaJeztah): Are these expected to be included?
Auth: authCfg.Auth,
IdentityToken: "",
RegistryToken: authCfg.RegistryToken,
}, nil
}
// PromptUserForCredentials handles the CLI prompt for the user to input
@ -153,7 +140,7 @@ func PromptUserForCredentials(ctx context.Context, cli Cli, argUser, argPassword
argUser = defaultUsername
}
if argUser == "" {
return registrytypes.AuthConfig{}, errors.Errorf("Error: Non-null Username Required")
return registrytypes.AuthConfig{}, errors.New("error: username is required")
}
}
@ -185,7 +172,7 @@ func PromptUserForCredentials(ctx context.Context, cli Cli, argUser, argPassword
}
_, _ = fmt.Fprintln(cli.Out())
if argPassword == "" {
return registrytypes.AuthConfig{}, errors.Errorf("Error: Password Required")
return registrytypes.AuthConfig{}, errors.New("error: password is required")
}
}
@ -213,7 +200,16 @@ func RetrieveAuthTokenFromImage(cfg *configfile.ConfigFile, image string) (strin
return "", err
}
encodedAuth, err := registrytypes.EncodeAuthConfig(registrytypes.AuthConfig(authConfig))
encodedAuth, err := authconfig.Encode(registrytypes.AuthConfig{
Username: authConfig.Username,
Password: authConfig.Password,
ServerAddress: authConfig.ServerAddress,
// TODO(thaJeztah): Are these expected to be included?
Auth: authConfig.Auth,
IdentityToken: authConfig.IdentityToken,
RegistryToken: authConfig.RegistryToken,
})
if err != nil {
return "", err
}

View File

@ -12,11 +12,10 @@ import (
"time"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client"
"github.com/docker/docker/pkg/progress"
"github.com/docker/docker/pkg/streamformatter"
"github.com/moby/moby/api/types/swarm"
"github.com/moby/moby/client"
"github.com/moby/moby/client/pkg/progress"
"github.com/moby/moby/client/pkg/streamformatter"
)
var (
@ -88,24 +87,24 @@ func ServiceProgress(ctx context.Context, apiClient client.APIClient, serviceID
)
for {
service, _, err := apiClient.ServiceInspectWithRaw(ctx, serviceID, swarm.ServiceInspectOptions{})
res, err := apiClient.ServiceInspect(ctx, serviceID, client.ServiceInspectOptions{})
if err != nil {
return err
}
if service.Spec.UpdateConfig != nil && service.Spec.UpdateConfig.Monitor != 0 {
monitor = service.Spec.UpdateConfig.Monitor
if res.Service.Spec.UpdateConfig != nil && res.Service.Spec.UpdateConfig.Monitor != 0 {
monitor = res.Service.Spec.UpdateConfig.Monitor
}
if updater == nil {
updater, err = initializeUpdater(service, progressOut)
updater, err = initializeUpdater(res.Service, progressOut)
if err != nil {
return err
}
}
if service.UpdateStatus != nil {
switch service.UpdateStatus.State {
if res.Service.UpdateStatus != nil {
switch res.Service.UpdateStatus.State {
case swarm.UpdateStateUpdating:
rollback = false
case swarm.UpdateStateCompleted:
@ -113,39 +112,38 @@ func ServiceProgress(ctx context.Context, apiClient client.APIClient, serviceID
return nil
}
case swarm.UpdateStatePaused:
return fmt.Errorf("service update paused: %s", service.UpdateStatus.Message)
return fmt.Errorf("service update paused: %s", res.Service.UpdateStatus.Message)
case swarm.UpdateStateRollbackStarted:
if !rollback && service.UpdateStatus.Message != "" {
if !rollback && res.Service.UpdateStatus.Message != "" {
progressOut.WriteProgress(progress.Progress{
ID: "rollback",
Action: service.UpdateStatus.Message,
Action: res.Service.UpdateStatus.Message,
})
}
rollback = true
case swarm.UpdateStateRollbackPaused:
return fmt.Errorf("service rollback paused: %s", service.UpdateStatus.Message)
return fmt.Errorf("service rollback paused: %s", res.Service.UpdateStatus.Message)
case swarm.UpdateStateRollbackCompleted:
if !converged {
message = &progress.Progress{ID: "rollback", Message: service.UpdateStatus.Message}
message = &progress.Progress{ID: "rollback", Message: res.Service.UpdateStatus.Message}
}
rollback = true
}
}
if converged && time.Since(convergedAt) >= monitor {
progressOut.WriteProgress(progress.Progress{
_ = progressOut.WriteProgress(progress.Progress{
ID: "verify",
Action: fmt.Sprintf("Service %s converged", serviceID),
})
if message != nil {
progressOut.WriteProgress(*message)
_ = progressOut.WriteProgress(*message)
}
return nil
}
tasks, err := apiClient.TaskList(ctx, swarm.TaskListOptions{Filters: filters.NewArgs(
filters.KeyValuePair{Key: "service", Value: service.ID},
filters.KeyValuePair{Key: "_up-to-date", Value: "true"},
)})
tasks, err := apiClient.TaskList(ctx, client.TaskListOptions{
Filters: make(client.Filters).Add("service", res.Service.ID).Add("_up-to-date", "true"),
})
if err != nil {
return err
}
@ -155,7 +153,7 @@ func ServiceProgress(ctx context.Context, apiClient client.APIClient, serviceID
return err
}
converged, err = updater.update(service, tasks, activeNodes, rollback)
converged, err = updater.update(res.Service, tasks.Items, activeNodes, rollback)
if err != nil {
return err
}
@ -167,7 +165,7 @@ func ServiceProgress(ctx context.Context, apiClient client.APIClient, serviceID
// only job services have a non-nil job status, which means we can
// use the presence of this field to check if the service is a job
// here.
if service.JobStatus != nil {
if res.Service.JobStatus != nil {
progress.Message(progressOut, "", "job complete")
return nil
}
@ -177,7 +175,7 @@ func ServiceProgress(ctx context.Context, apiClient client.APIClient, serviceID
}
wait := monitor - time.Since(convergedAt)
if wait >= 0 {
progressOut.WriteProgress(progress.Progress{
_ = progressOut.WriteProgress(progress.Progress{
// Ideally this would have no ID, but
// the progress rendering code behaves
// poorly on an "action" with no ID. It
@ -192,7 +190,7 @@ func ServiceProgress(ctx context.Context, apiClient client.APIClient, serviceID
}
} else {
if !convergedAt.IsZero() {
progressOut.WriteProgress(progress.Progress{
_ = progressOut.WriteProgress(progress.Progress{
ID: "verify",
Action: "Detected task failure",
})
@ -216,13 +214,13 @@ func ServiceProgress(ctx context.Context, apiClient client.APIClient, serviceID
//
// TODO(thaJeztah): this should really be a filter on [apiClient.NodeList] instead of being filtered on the client side.
func getActiveNodes(ctx context.Context, apiClient client.NodeAPIClient) (map[string]struct{}, error) {
nodes, err := apiClient.NodeList(ctx, swarm.NodeListOptions{})
res, err := apiClient.NodeList(ctx, client.NodeListOptions{})
if err != nil {
return nil, err
}
activeNodes := make(map[string]struct{})
for _, n := range nodes {
for _, n := range res.Items {
if n.Status.State != swarm.NodeStateDown {
activeNodes[n.ID] = struct{}{}
}
@ -652,7 +650,7 @@ func (u *replicatedJobProgressUpdater) update(_ swarm.Service, tasks []swarm.Tas
}
func (u *replicatedJobProgressUpdater) writeOverallProgress(active, completed int) {
u.progressOut.WriteProgress(progress.Progress{
_ = u.progressOut.WriteProgress(progress.Progress{
ID: "job progress",
Action: fmt.Sprintf(
// * means "use the next positional arg to compute padding"
@ -669,7 +667,7 @@ func (u *replicatedJobProgressUpdater) writeOverallProgress(active, completed in
actualDesired = u.concurrent
}
u.progressOut.WriteProgress(progress.Progress{
_ = u.progressOut.WriteProgress(progress.Progress{
ID: "active tasks",
Action: fmt.Sprintf(
// [n] notation lets us select a specific argument, 1-indexed
@ -692,14 +690,14 @@ func (u *replicatedJobProgressUpdater) writeTaskProgress(task swarm.Task) {
}
if task.Status.Err != "" {
u.progressOut.WriteProgress(progress.Progress{
_ = u.progressOut.WriteProgress(progress.Progress{
ID: fmt.Sprintf("%d/%d", task.Slot+1, u.total),
Action: truncError(task.Status.Err),
})
return
}
u.progressOut.WriteProgress(progress.Progress{
_ = u.progressOut.WriteProgress(progress.Progress{
ID: fmt.Sprintf("%d/%d", task.Slot+1, u.total),
Action: fmt.Sprintf("%-*s", longestState, task.Status.State),
Current: numberedStates[task.Status.State],
@ -732,7 +730,7 @@ func (u *globalJobProgressUpdater) update(service swarm.Service, tasks []swarm.T
if !u.initialized {
// if there are not yet tasks, then return early.
if len(tasks) == 0 && len(activeNodes) != 0 {
u.progressOut.WriteProgress(progress.Progress{
_ = u.progressOut.WriteProgress(progress.Progress{
ID: "job progress",
Action: "waiting for tasks",
})
@ -810,14 +808,14 @@ func (u *globalJobProgressUpdater) writeTaskProgress(task swarm.Task) {
}
if task.Status.Err != "" {
u.progressOut.WriteProgress(progress.Progress{
_ = u.progressOut.WriteProgress(progress.Progress{
ID: task.NodeID,
Action: truncError(task.Status.Err),
})
return
}
u.progressOut.WriteProgress(progress.Progress{
_ = u.progressOut.WriteProgress(progress.Progress{
ID: task.NodeID,
Action: fmt.Sprintf("%-*s", longestState, task.Status.State),
Current: numberedStates[task.Status.State],
@ -829,7 +827,7 @@ func (u *globalJobProgressUpdater) writeTaskProgress(task swarm.Task) {
func (u *globalJobProgressUpdater) writeOverallProgress(complete int) {
// all tasks for a global job are active at once, so we only write out the
// total progress.
u.progressOut.WriteProgress(progress.Progress{
_ = u.progressOut.WriteProgress(progress.Progress{
// see (*replicatedJobProgressUpdater).writeOverallProgress for an
// explanation of the advanced fmt use in this function.
ID: "job progress",

View File

@ -1,82 +0,0 @@
package formatter
import (
"strconv"
"github.com/docker/cli/cli/command/formatter"
)
const (
// SwarmStackTableFormat is the default Swarm stack format
//
// Deprecated: this type was for internal use and will be removed in the next release.
SwarmStackTableFormat formatter.Format = "table {{.Name}}\t{{.Services}}"
stackServicesHeader = "SERVICES"
// TableFormatKey is an alias for formatter.TableFormatKey
//
// Deprecated: this type was for internal use and will be removed in the next release.
TableFormatKey = formatter.TableFormatKey
)
// Context is an alias for formatter.Context
//
// Deprecated: this type was for internal use and will be removed in the next release.
type Context = formatter.Context
// Format is an alias for formatter.Format
//
// Deprecated: this type was for internal use and will be removed in the next release.
type Format = formatter.Format
// Stack contains deployed stack information.
//
// Deprecated: this type was for internal use and will be removed in the next release.
type Stack struct {
// Name is the name of the stack
Name string
// Services is the number of the services
Services int
}
// StackWrite writes formatted stacks using the Context
//
// Deprecated: this function was for internal use and will be removed in the next release.
func StackWrite(ctx formatter.Context, stacks []*Stack) error {
render := func(format func(subContext formatter.SubContext) error) error {
for _, stack := range stacks {
if err := format(&stackContext{s: stack}); err != nil {
return err
}
}
return nil
}
return ctx.Write(newStackContext(), render)
}
type stackContext struct {
formatter.HeaderContext
s *Stack
}
func newStackContext() *stackContext {
stackCtx := stackContext{}
stackCtx.Header = formatter.SubHeaderContext{
"Name": formatter.NameHeader,
"Services": stackServicesHeader,
}
return &stackCtx
}
func (s *stackContext) MarshalJSON() ([]byte, error) {
return formatter.MarshalJSON(s)
}
func (s *stackContext) Name() string {
return s.s.Name
}
func (s *stackContext) Services() string {
return strconv.Itoa(s.s.Services)
}

View File

@ -11,11 +11,12 @@ import (
"github.com/google/uuid"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/metric"
otelsdk "go.opentelemetry.io/otel/sdk"
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
"go.opentelemetry.io/otel/trace"
)
@ -146,7 +147,7 @@ func defaultResourceOptions() []resource.Option {
semconv.ServiceInstanceID(uuid.NewString()),
),
resource.WithFromEnv(),
resource.WithTelemetrySDK(),
resource.WithDetectors(telemetrySDK{}),
}
}
@ -157,7 +158,10 @@ func (r *telemetryResource) AppendOptions(opts ...resource.Option) {
r.opts = append(r.opts, opts...)
}
type serviceNameDetector struct{}
type (
serviceNameDetector struct{}
telemetrySDK struct{}
)
func (serviceNameDetector) Detect(ctx context.Context) (*resource.Resource, error) {
return resource.StringDetector(
@ -169,6 +173,16 @@ func (serviceNameDetector) Detect(ctx context.Context) (*resource.Resource, erro
).Detect(ctx)
}
// Detect returns a *Resource that describes the OpenTelemetry SDK used.
func (telemetrySDK) Detect(context.Context) (*resource.Resource, error) {
return resource.NewWithAttributes(
semconv.SchemaURL,
semconv.TelemetrySDKName("opentelemetry"),
semconv.TelemetrySDKLanguageGo,
semconv.TelemetrySDKVersion(otelsdk.Version()),
), nil
}
// cliReader is an implementation of Reader that will automatically
// report to a designated Exporter when Shutdown is called.
type cliReader struct {

View File

@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.23
//go:build go1.24
package command
@ -14,7 +14,6 @@ import (
"strings"
"unicode"
"github.com/pkg/errors"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
@ -48,7 +47,7 @@ func dockerExporterOTLPEndpoint(cli Cli) (endpoint string, secure bool) {
if otelCfg != nil {
otelMap, ok := otelCfg.(map[string]any)
if !ok {
otel.Handle(errors.Errorf(
otel.Handle(fmt.Errorf(
"unexpected type for field %q: %T (expected: %T)",
otelContextFieldName,
otelCfg,
@ -76,7 +75,7 @@ func dockerExporterOTLPEndpoint(cli Cli) (endpoint string, secure bool) {
// We pretend we're the same as the environment reader.
u, err := url.Parse(endpoint)
if err != nil {
otel.Handle(errors.Errorf("docker otel endpoint is invalid: %s", err))
otel.Handle(fmt.Errorf("docker otel endpoint is invalid: %s", err))
return "", false
}

View File

@ -2,12 +2,12 @@ package command
import (
"context"
"errors"
"fmt"
"strings"
"time"
"github.com/docker/cli/cli/version"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"

View File

@ -1,74 +1,30 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.23
//go:build go1.24
package command
import (
"context"
"io"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/docker/cli/cli/config"
"github.com/docker/cli/cli/streams"
"github.com/docker/cli/internal/prompt"
"github.com/docker/docker/api/types/filters"
"github.com/pkg/errors"
"github.com/moby/moby/client"
)
// ErrPromptTerminated is returned if the user terminated the prompt.
//
// Deprecated: this error is for internal use and will be removed in the next release.
const ErrPromptTerminated = prompt.ErrTerminated
// DisableInputEcho disables input echo on the provided streams.In.
// This is useful when the user provides sensitive information like passwords.
// The function returns a restore function that should be called to restore the
// terminal state.
//
// Deprecated: this function is for internal use and will be removed in the next release.
func DisableInputEcho(ins *streams.In) (restore func() error, err error) {
return prompt.DisableInputEcho(ins)
}
// PromptForInput requests input from the user.
//
// If the user terminates the CLI with SIGINT or SIGTERM while the prompt is
// active, the prompt will return an empty string ("") with an ErrPromptTerminated error.
// When the prompt returns an error, the caller should propagate the error up
// the stack and close the io.Reader used for the prompt which will prevent the
// background goroutine from blocking indefinitely.
//
// Deprecated: this function is for internal use and will be removed in the next release.
func PromptForInput(ctx context.Context, in io.Reader, out io.Writer, message string) (string, error) {
return prompt.ReadInput(ctx, in, out, message)
}
// PromptForConfirmation requests and checks confirmation from the user.
// This will display the provided message followed by ' [y/N] '. If the user
// input 'y' or 'Y' it returns true otherwise false. If no message is provided,
// "Are you sure you want to proceed? [y/N] " will be used instead.
//
// If the user terminates the CLI with SIGINT or SIGTERM while the prompt is
// active, the prompt will return false with an ErrPromptTerminated error.
// When the prompt returns an error, the caller should propagate the error up
// the stack and close the io.Reader used for the prompt which will prevent the
// background goroutine from blocking indefinitely.
//
// Deprecated: this function is for internal use and will be removed in the next release.
func PromptForConfirmation(ctx context.Context, ins io.Reader, outs io.Writer, message string) (bool, error) {
return prompt.Confirm(ctx, ins, outs, message)
}
// PruneFilters merges prune filters specified in config.json with those specified
// as command-line flags.
// as command-line flags. It returns a deep copy of filters to prevent mutating
// the original.
//
// CLI label filters have precedence over those specified in config.json. If a
// label filter specified as flag conflicts with a label defined in config.json
// (i.e., "label=some-value" conflicts with "label!=some-value", and vice versa),
// then the filter defined in config.json is omitted.
func PruneFilters(dockerCLI config.Provider, pruneFilters filters.Args) filters.Args {
func PruneFilters(dockerCLI config.Provider, filters client.Filters) client.Filters {
pruneFilters := filters.Clone()
cfg := dockerCLI.ConfigFile()
if cfg == nil {
return pruneFilters
@ -84,13 +40,13 @@ func PruneFilters(dockerCLI config.Provider, pruneFilters filters.Args) filters.
switch k {
case "label":
// "label != some-value" conflicts with "label = some-value"
if pruneFilters.ExactMatch("label!", v) {
if pruneFilters["label!"][v] {
continue
}
pruneFilters.Add(k, v)
case "label!":
// "label != some-value" conflicts with "label = some-value"
if pruneFilters.ExactMatch("label", v) {
if pruneFilters["label"][v] {
continue
}
pruneFilters.Add(k, v)
@ -107,7 +63,7 @@ func ValidateOutputPath(path string) error {
dir := filepath.Dir(filepath.Clean(path))
if dir != "" && dir != "." {
if _, err := os.Stat(dir); os.IsNotExist(err) {
return errors.Errorf("invalid output path: directory %q does not exist", dir)
return fmt.Errorf("invalid output path: directory %q does not exist", dir)
}
}
// check whether `path` points to a regular file
@ -122,7 +78,7 @@ func ValidateOutputPath(path string) error {
}
if err := ValidateOutputPathFileMode(fileInfo.Mode()); err != nil {
return errors.Wrapf(err, "invalid output path: %q must be a directory or a regular file", path)
return fmt.Errorf("invalid output path: %q must be a directory or a regular file: %w", path, err)
}
}
return nil

View File

@ -1,14 +1,14 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.23
//go:build go1.24
package interpolation
import (
"fmt"
"os"
"strings"
"github.com/docker/cli/cli/compose/template"
"github.com/pkg/errors"
)
// Options supported by Interpolate
@ -68,7 +68,7 @@ func recursiveInterpolate(value any, path Path, opts Options) (any, error) {
}
casted, err := caster(newValue)
if err != nil {
return casted, newPathError(path, errors.Wrap(err, "failed to cast to expected type"))
return casted, newPathError(path, fmt.Errorf("failed to cast to expected type: %w", err))
}
return casted, nil
@ -104,11 +104,11 @@ func newPathError(path Path, err error) error {
case nil:
return nil
case *template.InvalidTemplateError:
return errors.Errorf(
return fmt.Errorf(
"invalid interpolation format for %s: %#v; you may need to escape any $ with another $",
path, err.Template)
default:
return errors.Wrapf(err, "error while interpolating %s", path)
return fmt.Errorf("error while interpolating %s: %w", path, err)
}
}

View File

@ -1,14 +1,14 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.23
//go:build go1.24
package loader
import (
"fmt"
"strconv"
"strings"
interp "github.com/docker/cli/cli/compose/interpolation"
"github.com/pkg/errors"
)
var interpolateTypeCastMapping = map[interp.Path]interp.Cast{
@ -67,7 +67,7 @@ func toBoolean(value string) (any, error) {
case "n", "no", "false", "off":
return false, nil
default:
return nil, errors.Errorf("invalid boolean: %s", value)
return nil, fmt.Errorf("invalid boolean: %s", value)
}
}

View File

@ -1,9 +1,10 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.23
//go:build go1.24
package loader
import (
"errors"
"fmt"
"path"
"path/filepath"
@ -20,12 +21,12 @@ import (
"github.com/docker/cli/internal/volumespec"
"github.com/docker/cli/opts"
"github.com/docker/cli/opts/swarmopts"
"github.com/docker/docker/api/types/versions"
"github.com/docker/go-connections/nat"
"github.com/docker/go-units"
"github.com/go-viper/mapstructure/v2"
"github.com/google/shlex"
"github.com/pkg/errors"
"github.com/moby/moby/api/types/network"
"github.com/moby/moby/client/pkg/versions"
"github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"
)
@ -64,7 +65,7 @@ func ParseYAML(source []byte) (map[string]any, error) {
}
_, ok := cfg.(map[string]any)
if !ok {
return nil, errors.Errorf("top-level object must be a mapping")
return nil, errors.New("top-level object must be a mapping")
}
converted, err := convertToStringKeysRecursive(cfg, "")
if err != nil {
@ -76,7 +77,7 @@ func ParseYAML(source []byte) (map[string]any, error) {
// Load reads a ConfigDetails and returns a fully loaded configuration
func Load(configDetails types.ConfigDetails, opt ...func(*Options)) (*types.Config, error) {
if len(configDetails.ConfigFiles) < 1 {
return nil, errors.Errorf("No files specified")
return nil, errors.New("no files specified")
}
options := &Options{
@ -101,7 +102,7 @@ func Load(configDetails types.ConfigDetails, opt ...func(*Options)) (*types.Conf
configDetails.Version = version
}
if configDetails.Version != version {
return nil, errors.Errorf("version mismatched between two composefiles : %v and %v", configDetails.Version, version)
return nil, fmt.Errorf("version mismatched between two composefiles : %v and %v", configDetails.Version, version)
}
if err := validateForbidden(configDict); err != nil {
@ -532,7 +533,7 @@ func transformUlimits(data any) (any, error) {
ulimit.Hard = value["hard"].(int)
return ulimit, nil
default:
return data, errors.Errorf("invalid type %T for ulimits", value)
return data, fmt.Errorf("invalid type %T for ulimits", value)
}
}
@ -544,33 +545,31 @@ func LoadNetworks(source map[string]any, version string) (map[string]types.Netwo
if err != nil {
return networks, err
}
for name, network := range networks {
if !network.External.External {
for name, nw := range networks {
if !nw.External.External {
continue
}
switch {
case network.External.Name != "":
if network.Name != "" {
return nil, errors.Errorf("network %s: network.external.name and network.name conflict; only use network.name", name)
case nw.External.Name != "":
if nw.Name != "" {
return nil, fmt.Errorf("network %s: network.external.name and network.name conflict; only use network.name", name)
}
if versions.GreaterThanOrEqualTo(version, "3.5") {
logrus.Warnf("network %s: network.external.name is deprecated in favor of network.name", name)
}
network.Name = network.External.Name
network.External.Name = ""
case network.Name == "":
network.Name = name
nw.Name = nw.External.Name
nw.External.Name = ""
case nw.Name == "":
nw.Name = name
}
network.Extras = loadExtras(name, source)
networks[name] = network
nw.Extras = loadExtras(name, source)
networks[name] = nw
}
return networks, nil
}
func externalVolumeError(volume, key string) error {
return errors.Errorf(
"conflicting parameters \"external\" and %q specified for volume %q",
key, volume)
return fmt.Errorf(`conflicting parameters "external" and %q specified for volume %q`, key, volume)
}
// LoadVolumes produces a VolumeConfig map from a compose file Dict
@ -594,7 +593,7 @@ func LoadVolumes(source map[string]any, version string) (map[string]types.Volume
return nil, externalVolumeError(name, "labels")
case volume.External.Name != "":
if volume.Name != "" {
return nil, errors.Errorf("volume %s: volume.external.name and volume.name conflict; only use volume.name", name)
return nil, fmt.Errorf("volume %s: volume.external.name and volume.name conflict; only use volume.name", name)
}
if versions.GreaterThanOrEqualTo(version, "3.4") {
logrus.Warnf("volume %s: volume.external.name is deprecated in favor of volume.name", name)
@ -655,7 +654,7 @@ func loadFileObjectConfig(name string, objType string, obj types.FileObjectConfi
// handle deprecated external.name
if obj.External.Name != "" {
if obj.Name != "" {
return obj, errors.Errorf("%[1]s %[2]s: %[1]s.external.name and %[1]s.name conflict; only use %[1]s.name", objType, name)
return obj, fmt.Errorf("%[1]s %[2]s: %[1]s.external.name and %[1]s.name conflict; only use %[1]s.name", objType, name)
}
if versions.GreaterThanOrEqualTo(details.Version, "3.5") {
logrus.Warnf("%[1]s %[2]s: %[1]s.external.name is deprecated in favor of %[1]s.name", objType, name)
@ -668,7 +667,7 @@ func loadFileObjectConfig(name string, objType string, obj types.FileObjectConfi
// if not "external: true"
case obj.Driver != "":
if obj.File != "" {
return obj, errors.Errorf("%[1]s %[2]s: %[1]s.driver and %[1]s.file conflict; only use %[1]s.driver", objType, name)
return obj, fmt.Errorf("%[1]s %[2]s: %[1]s.driver and %[1]s.file conflict; only use %[1]s.driver", objType, name)
}
default:
obj.File = absPath(details.WorkingDir, obj.File)
@ -691,7 +690,7 @@ var transformMapStringString TransformerFunc = func(data any) (any, error) {
case map[string]string:
return value, nil
default:
return data, errors.Errorf("invalid type %T for map[string]string", value)
return data, fmt.Errorf("invalid type %T for map[string]string", value)
}
}
@ -702,7 +701,7 @@ var transformExternal TransformerFunc = func(data any) (any, error) {
case map[string]any:
return map[string]any{"external": true, "name": value["name"]}, nil
default:
return data, errors.Errorf("invalid type %T for external", value)
return data, fmt.Errorf("invalid type %T for external", value)
}
}
@ -730,12 +729,12 @@ var transformServicePort TransformerFunc = func(data any) (any, error) {
case map[string]any:
ports = append(ports, value)
default:
return data, errors.Errorf("invalid type %T for port", value)
return data, fmt.Errorf("invalid type %T for port", value)
}
}
return ports, nil
default:
return data, errors.Errorf("invalid type %T for port", entries)
return data, fmt.Errorf("invalid type %T for port", entries)
}
}
@ -746,7 +745,7 @@ var transformStringSourceMap TransformerFunc = func(data any) (any, error) {
case map[string]any:
return data, nil
default:
return data, errors.Errorf("invalid type %T for secret", value)
return data, fmt.Errorf("invalid type %T for secret", value)
}
}
@ -757,7 +756,7 @@ var transformBuildConfig TransformerFunc = func(data any) (any, error) {
case map[string]any:
return data, nil
default:
return data, errors.Errorf("invalid type %T for service build", value)
return data, fmt.Errorf("invalid type %T for service build", value)
}
}
@ -768,7 +767,7 @@ var transformServiceVolumeConfig TransformerFunc = func(data any) (any, error) {
case map[string]any:
return data, nil
default:
return data, errors.Errorf("invalid type %T for service volume", value)
return data, fmt.Errorf("invalid type %T for service volume", value)
}
}
@ -799,7 +798,7 @@ var transformStringList TransformerFunc = func(data any) (any, error) {
case []any:
return value, nil
default:
return data, errors.Errorf("invalid type %T for string list", value)
return data, fmt.Errorf("invalid type %T for string list", value)
}
}
@ -846,7 +845,7 @@ func transformListOrMapping(listOrMapping any, sep string, allowNil bool, allowS
}
return result
}
panic(errors.Errorf("expected a map or a list, got %T: %#v", listOrMapping, listOrMapping))
panic(fmt.Errorf("expected a map or a list, got %T: %#v", listOrMapping, listOrMapping))
}
func transformMappingOrListFunc(sep string, allowNil bool) TransformerFunc {
@ -874,7 +873,7 @@ func transformMappingOrList(mappingOrList any, sep string, allowNil bool) any {
}
return result
}
panic(errors.Errorf("expected a map or a list, got %T: %#v", mappingOrList, mappingOrList))
panic(fmt.Errorf("expected a map or a list, got %T: %#v", mappingOrList, mappingOrList))
}
var transformShellCommand TransformerFunc = func(value any) (any, error) {
@ -891,7 +890,7 @@ var transformHealthCheckTest TransformerFunc = func(data any) (any, error) {
case []any:
return value, nil
default:
return value, errors.Errorf("invalid type %T for healthcheck.test", value)
return value, fmt.Errorf("invalid type %T for healthcheck.test", value)
}
}
@ -902,7 +901,7 @@ var transformSize TransformerFunc = func(value any) (any, error) {
case string:
return units.RAMInBytes(value)
}
panic(errors.Errorf("invalid type for size %T", value))
panic(fmt.Errorf("invalid type for size %T", value))
}
var transformStringToDuration TransformerFunc = func(value any) (any, error) {
@ -914,7 +913,7 @@ var transformStringToDuration TransformerFunc = func(value any) (any, error) {
}
return types.Duration(d), nil
default:
return value, errors.Errorf("invalid type %T for duration", value)
return value, fmt.Errorf("invalid type %T for duration", value)
}
}
@ -926,7 +925,7 @@ func toServicePortConfigs(value string) ([]any, error) {
return nil, err
}
// We need to sort the key of the ports to make sure it is consistent
keys := []string{}
keys := make([]string, 0, len(ports))
for port := range ports {
keys = append(keys, string(port))
}
@ -934,7 +933,11 @@ func toServicePortConfigs(value string) ([]any, error) {
for _, key := range keys {
// Reuse ConvertPortToPortConfig so that it is consistent
portConfig, err := swarmopts.ConvertPortToPortConfig(nat.Port(key), portBindings)
port, err := network.ParsePort(key)
if err != nil {
return nil, err
}
portConfig, err := swarmopts.ConvertPortToPortConfig(port, portBindings)
if err != nil {
return nil, err
}

View File

@ -1,15 +1,15 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.23
//go:build go1.24
package loader
import (
"fmt"
"reflect"
"sort"
"dario.cat/mergo"
"github.com/docker/cli/cli/compose/types"
"github.com/pkg/errors"
)
type specials struct {
@ -29,23 +29,23 @@ func merge(configs []*types.Config) (*types.Config, error) {
var err error
base.Services, err = mergeServices(base.Services, override.Services)
if err != nil {
return base, errors.Wrapf(err, "cannot merge services from %s", override.Filename)
return base, fmt.Errorf("cannot merge services from %s: %w", override.Filename, err)
}
base.Volumes, err = mergeVolumes(base.Volumes, override.Volumes)
if err != nil {
return base, errors.Wrapf(err, "cannot merge volumes from %s", override.Filename)
return base, fmt.Errorf("cannot merge volumes from %s: %w", override.Filename, err)
}
base.Networks, err = mergeNetworks(base.Networks, override.Networks)
if err != nil {
return base, errors.Wrapf(err, "cannot merge networks from %s", override.Filename)
return base, fmt.Errorf("cannot merge networks from %s: %w", override.Filename, err)
}
base.Secrets, err = mergeSecrets(base.Secrets, override.Secrets)
if err != nil {
return base, errors.Wrapf(err, "cannot merge secrets from %s", override.Filename)
return base, fmt.Errorf("cannot merge secrets from %s: %w", override.Filename, err)
}
base.Configs, err = mergeConfigs(base.Configs, override.Configs)
if err != nil {
return base, errors.Wrapf(err, "cannot merge configs from %s", override.Filename)
return base, fmt.Errorf("cannot merge configs from %s: %w", override.Filename, err)
}
}
return base, nil
@ -70,7 +70,7 @@ func mergeServices(base, override []types.ServiceConfig) ([]types.ServiceConfig,
for name, overrideService := range overrideServices {
if baseService, ok := baseServices[name]; ok {
if err := mergo.Merge(&baseService, &overrideService, mergo.WithAppendSlice, mergo.WithOverride, mergo.WithTransformers(specials)); err != nil {
return base, errors.Wrapf(err, "cannot merge service %s", name)
return base, fmt.Errorf("cannot merge service %s: %w", name, err)
}
baseServices[name] = baseService
continue
@ -88,7 +88,7 @@ func mergeServices(base, override []types.ServiceConfig) ([]types.ServiceConfig,
func toServiceSecretConfigsMap(s any) (map[any]any, error) {
secrets, ok := s.([]types.ServiceSecretConfig)
if !ok {
return nil, errors.Errorf("not a serviceSecretConfig: %v", s)
return nil, fmt.Errorf("not a serviceSecretConfig: %v", s)
}
m := map[any]any{}
for _, secret := range secrets {
@ -100,7 +100,7 @@ func toServiceSecretConfigsMap(s any) (map[any]any, error) {
func toServiceConfigObjConfigsMap(s any) (map[any]any, error) {
secrets, ok := s.([]types.ServiceConfigObjConfig)
if !ok {
return nil, errors.Errorf("not a serviceSecretConfig: %v", s)
return nil, fmt.Errorf("not a serviceSecretConfig: %v", s)
}
m := map[any]any{}
for _, secret := range secrets {
@ -112,7 +112,7 @@ func toServiceConfigObjConfigsMap(s any) (map[any]any, error) {
func toServicePortConfigsMap(s any) (map[any]any, error) {
ports, ok := s.([]types.ServicePortConfig)
if !ok {
return nil, errors.Errorf("not a servicePortConfig slice: %v", s)
return nil, fmt.Errorf("not a servicePortConfig slice: %v", s)
}
m := map[any]any{}
for _, p := range ports {
@ -124,7 +124,7 @@ func toServicePortConfigsMap(s any) (map[any]any, error) {
func toServiceVolumeConfigsMap(s any) (map[any]any, error) {
volumes, ok := s.([]types.ServiceVolumeConfig)
if !ok {
return nil, errors.Errorf("not a serviceVolumeConfig slice: %v", s)
return nil, fmt.Errorf("not a serviceVolumeConfig slice: %v", s)
}
m := map[any]any{}
for _, v := range volumes {
@ -211,7 +211,7 @@ func mergeSlice(tomap tomapFn, writeValue writeValueFromMapFn) func(dst, src ref
func sliceToMap(tomap tomapFn, v reflect.Value) (map[any]any, error) {
// check if valid
if !v.IsValid() {
return nil, errors.Errorf("invalid value : %+v", v)
return nil, fmt.Errorf("invalid value : %+v", v)
}
return tomap(v.Interface())
}

View File

@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.23
//go:build go1.24
package schema
@ -11,7 +11,6 @@ import (
"time"
"github.com/docker/go-connections/nat"
"github.com/pkg/errors"
"github.com/xeipuuv/gojsonschema"
)
@ -80,7 +79,7 @@ func Validate(config map[string]any, version string) error {
version = normalizeVersion(version)
schemaData, err := schemas.ReadFile("data/config_schema_v" + version + ".json")
if err != nil {
return errors.Errorf("unsupported Compose file version: %s", version)
return fmt.Errorf("unsupported Compose file version: %s", version)
}
schemaLoader := gojsonschema.NewStringLoader(string(schemaData))

View File

@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.23
//go:build go1.24
package template

View File

@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.23
//go:build go1.24
package types

View File

@ -13,7 +13,6 @@ import (
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/cli/config/credentials"
"github.com/docker/cli/cli/config/types"
"github.com/pkg/errors"
)
const (
@ -101,7 +100,7 @@ func SetDir(dir string) {
func Path(p ...string) (string, error) {
path := filepath.Join(append([]string{Dir()}, p...)...)
if !strings.HasPrefix(path, Dir()+string(filepath.Separator)) {
return "", errors.Errorf("path %q is outside of root config directory %q", path, Dir())
return "", fmt.Errorf("path %q is outside of root config directory %q", path, Dir())
}
return path, nil
}
@ -143,12 +142,12 @@ func load(configDir string) (*configfile.ConfigFile, error) {
return configFile, nil
}
// Any other error happening when failing to read the file must be returned.
return configFile, errors.Wrap(err, "loading config file")
return configFile, fmt.Errorf("loading config file: %w", err)
}
defer file.Close()
defer func() { _ = file.Close() }()
err = configFile.LoadFromReader(file)
if err != nil {
err = errors.Wrapf(err, "parsing config file (%s)", filename)
err = fmt.Errorf("parsing config file (%s): %w", filename, err)
}
return configFile, err
}

View File

@ -3,6 +3,7 @@ package configfile
import (
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"os"
@ -12,7 +13,6 @@ import (
"github.com/docker/cli/cli/config/credentials"
"github.com/docker/cli/cli/config/memorystore"
"github.com/docker/cli/cli/config/types"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
@ -43,9 +43,6 @@ type ConfigFile struct {
Plugins map[string]map[string]string `json:"plugins,omitempty"`
Aliases map[string]string `json:"aliases,omitempty"`
Features map[string]string `json:"features,omitempty"`
// Deprecated: experimental CLI features are always enabled and this field is no longer used. Use [Features] instead for optional features. This field will be removed in a future release.
Experimental string `json:"experimental,omitempty"`
}
type configEnvAuth struct {
@ -167,7 +164,7 @@ func (configFile *ConfigFile) SaveToWriter(writer io.Writer) error {
// Save encodes and writes out all the authorization information
func (configFile *ConfigFile) Save() (retErr error) {
if configFile.Filename == "" {
return errors.Errorf("Can't save config with empty filename")
return errors.New("can't save config with empty filename")
}
dir := filepath.Dir(configFile.Filename)
@ -194,7 +191,7 @@ func (configFile *ConfigFile) Save() (retErr error) {
}
if err := temp.Close(); err != nil {
return errors.Wrap(err, "error closing temp file")
return fmt.Errorf("error closing temp file: %w", err)
}
// Handle situation where the configfile is a symlink, and allow for dangling symlinks
@ -278,11 +275,11 @@ func decodeAuth(authStr string) (string, string, error) {
return "", "", err
}
if n > decLen {
return "", "", errors.Errorf("Something went wrong decoding auth config")
return "", "", errors.New("something went wrong decoding auth config")
}
userName, password, ok := strings.Cut(string(decoded), ":")
if !ok || userName == "" {
return "", "", errors.Errorf("Invalid auth configuration file")
return "", "", errors.New("invalid auth configuration file")
}
return userName, strings.Trim(password, "\x00"), nil
}

View File

@ -1,9 +1,9 @@
//go:build go1.23
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.24
package memorystore
import (
"errors"
"fmt"
"maps"
"os"
@ -13,12 +13,17 @@ import (
"github.com/docker/cli/cli/config/types"
)
var errValueNotFound = errors.New("value not found")
// notFoundErr is the error returned when a plugin could not be found.
type notFoundErr string
func IsErrValueNotFound(err error) bool {
return errors.Is(err, errValueNotFound)
func (notFoundErr) NotFound() {}
func (e notFoundErr) Error() string {
return string(e)
}
var errValueNotFound notFoundErr = "value not found"
type Config struct {
lock sync.RWMutex
memoryCredentials map[string]types.AuthConfig

View File

@ -6,11 +6,6 @@ type AuthConfig struct {
Password string `json:"password,omitempty"`
Auth string `json:"auth,omitempty"`
// Email is an optional value associated with the username.
//
// Deprecated: This field is deprecated since docker 1.11 (API v1.23) and will be removed in the next release.
Email string `json:"email,omitempty"`
ServerAddress string `json:"serveraddress,omitempty"`
// IdentityToken is used to authenticate the user and get

View File

@ -233,11 +233,9 @@ func (c *commandConn) Close() error {
defer c.closing.Store(false)
if err := c.CloseRead(); err != nil {
logrus.Warnf("commandConn.Close: CloseRead: %v", err)
return err
}
if err := c.CloseWrite(); err != nil {
logrus.Warnf("commandConn.Close: CloseWrite: %v", err)
return err
}

View File

@ -175,7 +175,7 @@ func quoteCommand(commandAndArgs ...string) (string, error) {
quotedCmd = a
continue
}
quotedCmd += " " + a
quotedCmd += " " + a //nolint:perfsprint // ignore "concat-loop"; no need to use a string-builder for this.
}
// each part is quoted appropriately, so now we'll have a full
// shell command to pass off to "ssh"

View File

@ -4,6 +4,8 @@ import (
"crypto/tls"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"net"
"net/http"
"strings"
@ -12,9 +14,8 @@ import (
"github.com/docker/cli/cli/connhelper"
"github.com/docker/cli/cli/context"
"github.com/docker/cli/cli/context/store"
"github.com/docker/docker/client"
"github.com/docker/go-connections/tlsconfig"
"github.com/pkg/errors"
"github.com/moby/moby/client"
)
// EndpointMeta is a typed wrapper around a context-store generic endpoint describing
@ -68,7 +69,7 @@ func (ep *Endpoint) tlsConfig() (*tls.Config, error) {
x509cert, err := tls.X509KeyPair(ep.TLSData.Cert, keyBytes)
if err != nil {
return nil, errors.Wrap(err, "failed to retrieve context tls info")
return nil, fmt.Errorf("failed to retrieve context tls info: %w", err)
}
tlsOpts = append(tlsOpts, func(cfg *tls.Config) {
cfg.Certificates = []tls.Certificate{x509cert}
@ -101,7 +102,22 @@ func (ep *Endpoint) ClientOpts() ([]client.Opt, error) {
if err != nil {
return nil, err
}
result = append(result, withHTTPClient(tlsConfig))
// If there's no tlsConfig available, we use the default HTTPClient.
if tlsConfig != nil {
result = append(result,
client.WithHTTPClient(&http.Client{
Transport: &http.Transport{
TLSClientConfig: tlsConfig,
DialContext: (&net.Dialer{
KeepAlive: 30 * time.Second,
Timeout: 30 * time.Second,
}).DialContext,
},
CheckRedirect: client.CheckRedirect,
}),
)
}
}
result = append(result, client.WithHost(ep.Host))
} else {
@ -133,25 +149,6 @@ func isSocket(addr string) bool {
}
}
func withHTTPClient(tlsConfig *tls.Config) func(*client.Client) error {
return func(c *client.Client) error {
if tlsConfig == nil {
// Use the default HTTPClient
return nil
}
return client.WithHTTPClient(&http.Client{
Transport: &http.Transport{
TLSClientConfig: tlsConfig,
DialContext: (&net.Dialer{
KeepAlive: 30 * time.Second,
Timeout: 30 * time.Second,
}).DialContext,
},
CheckRedirect: client.CheckRedirect,
})(c)
}
}
// EndpointFromContext parses a context docker endpoint metadata into a typed EndpointMeta structure
func EndpointFromContext(metadata store.Metadata) (EndpointMeta, error) {
ep, ok := metadata.Endpoints[DockerEndpoint]
@ -160,7 +157,7 @@ func EndpointFromContext(metadata store.Metadata) (EndpointMeta, error) {
}
typed, ok := ep.(EndpointMeta)
if !ok {
return EndpointMeta{}, errors.Errorf("endpoint %q is not of type EndpointMeta", DockerEndpoint)
return EndpointMeta{}, fmt.Errorf("endpoint %q is not of type EndpointMeta", DockerEndpoint)
}
return typed, nil
}

View File

@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.23
//go:build go1.24
package store

View File

@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.23
//go:build go1.24
package store
@ -18,14 +18,9 @@ import (
"path/filepath"
"strings"
"github.com/docker/cli/internal/lazyregexp"
"github.com/opencontainers/go-digest"
)
const restrictedNamePattern = "^[a-zA-Z0-9][a-zA-Z0-9_.+-]+$"
var restrictedNameRegEx = lazyregexp.New(restrictedNamePattern)
// Store provides a context store for easily remembering endpoints configuration
type Store interface {
Reader
@ -225,12 +220,43 @@ func ValidateContextName(name string) error {
if name == "default" {
return errors.New(`"default" is a reserved context name`)
}
if !restrictedNameRegEx.MatchString(name) {
return fmt.Errorf("context name %q is invalid, names are validated against regexp %q", name, restrictedNamePattern)
if !isValidName(name) {
return fmt.Errorf("context name %q is invalid, names are validated against regexp %q", name, validNameFormat)
}
return nil
}
// validNameFormat is used as part of errors for invalid context-names.
// We should consider making this less technical ("must start with "a-z",
// and only consist of alphanumeric characters and separators").
const validNameFormat = `^[a-zA-Z0-9][a-zA-Z0-9_.+-]+$`
// isValidName checks if the context-name is valid ("^[a-zA-Z0-9][a-zA-Z0-9_.+-]+$").
//
// Names must start with an alphanumeric character (a-zA-Z0-9), followed by
// alphanumeric or separators ("_", ".", "+", "-").
func isValidName(s string) bool {
if len(s) < 2 || !isAlphaNum(s[0]) {
return false
}
for i := 1; i < len(s); i++ {
c := s[i]
if isAlphaNum(c) || c == '_' || c == '.' || c == '+' || c == '-' {
continue
}
return false
}
return true
}
func isAlphaNum(c byte) bool {
return (c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9')
}
// Export exports an existing namespace into an opaque data stream
// This stream is actually a tarball containing context metadata and TLS materials, but it does
// not map 1:1 the layout of the context store (don't try to restore it manually without calling store.Import)

View File

@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.23
//go:build go1.24
package store

View File

@ -1,10 +1,10 @@
package context
import (
"fmt"
"os"
"github.com/docker/cli/cli/context/store"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
@ -45,14 +45,14 @@ func (data *TLSData) ToStoreTLSData() *store.EndpointTLSData {
func LoadTLSData(s store.Reader, contextName, endpointName string) (*TLSData, error) {
tlsFiles, err := s.ListTLSFiles(contextName)
if err != nil {
return nil, errors.Wrapf(err, "failed to retrieve TLS files for context %q", contextName)
return nil, fmt.Errorf("failed to retrieve TLS files for context %q: %w", contextName, err)
}
if epTLSFiles, ok := tlsFiles[endpointName]; ok {
var tlsData TLSData
for _, f := range epTLSFiles {
data, err := s.GetTLSData(contextName, endpointName, f)
if err != nil {
return nil, errors.Wrapf(err, "failed to retrieve TLS data (%s) for context %q", f, contextName)
return nil, fmt.Errorf("failed to retrieve TLS data (%s) for context %q: %w", f, contextName, err)
}
switch f {
case caKey:

View File

@ -7,8 +7,8 @@ import (
"path/filepath"
"github.com/docker/cli/cli/config"
"github.com/docker/docker/client"
"github.com/docker/go-connections/tlsconfig"
"github.com/moby/moby/client"
"github.com/sirupsen/logrus"
"github.com/spf13/pflag"
)
@ -110,22 +110,20 @@ func (o *ClientOptions) InstallFlags(flags *pflag.FlagSet) {
if dockerCertPath == "" {
dockerCertPath = configDir
}
o.TLSOptions = &tlsconfig.Options{
CAFile: filepath.Join(dockerCertPath, DefaultCaFile),
CertFile: filepath.Join(dockerCertPath, DefaultCertFile),
KeyFile: filepath.Join(dockerCertPath, DefaultKeyFile),
}
flags.StringVar(&o.ConfigDir, "config", configDir, "Location of client config files")
flags.BoolVarP(&o.Debug, "debug", "D", false, "Enable debug mode")
flags.StringVarP(&o.LogLevel, "log-level", "l", "info", `Set the logging level ("debug", "info", "warn", "error", "fatal")`)
flags.BoolVar(&o.TLS, "tls", dockerTLS, "Use TLS; implied by --tlsverify")
flags.BoolVar(&o.TLSVerify, FlagTLSVerify, dockerTLSVerify, "Use TLS and verify the remote")
o.TLSOptions = &tlsconfig.Options{
CAFile: filepath.Join(dockerCertPath, DefaultCaFile),
CertFile: filepath.Join(dockerCertPath, DefaultCertFile),
KeyFile: filepath.Join(dockerCertPath, DefaultKeyFile),
}
tlsOptions := o.TLSOptions
flags.Var(&quotedString{&tlsOptions.CAFile}, "tlscacert", "Trust certs signed only by this CA")
flags.Var(&quotedString{&tlsOptions.CertFile}, "tlscert", "Path to TLS certificate file")
flags.Var(&quotedString{&tlsOptions.KeyFile}, "tlskey", "Path to TLS key file")
flags.StringVar(&o.TLSOptions.CAFile, "tlscacert", o.TLSOptions.CAFile, "Trust certs signed only by this CA")
flags.StringVar(&o.TLSOptions.CertFile, "tlscert", o.TLSOptions.CertFile, "Path to TLS certificate file")
flags.StringVar(&o.TLSOptions.KeyFile, "tlskey", o.TLSOptions.KeyFile, "Path to TLS key file")
// TODO(thaJeztah): show the default host.
// TODO(thaJeztah): this should be a string, not an "array" as we only allow a single host.
@ -179,33 +177,3 @@ func SetLogLevel(logLevel string) {
logrus.SetLevel(logrus.InfoLevel)
}
}
type quotedString struct {
value *string
}
func (s *quotedString) Set(val string) error {
*s.value = trimQuotes(val)
return nil
}
func (*quotedString) Type() string {
return "string"
}
func (s *quotedString) String() string {
return *s.value
}
func trimQuotes(value string) string {
if len(value) < 2 {
return value
}
lastIndex := len(value) - 1
for _, char := range []byte{'\'', '"'} {
if value[0] == char && value[lastIndex] == char {
return value[1:lastIndex]
}
}
return value
}

View File

@ -1,7 +1,8 @@
package cli
import (
"github.com/pkg/errors"
"fmt"
"github.com/spf13/cobra"
)
@ -12,7 +13,7 @@ func NoArgs(cmd *cobra.Command, args []string) error {
}
if cmd.HasSubCommands() {
return errors.Errorf(
return fmt.Errorf(
"%[1]s: unknown command: %[2]s %[3]s\n\nUsage: %[4]s\n\nRun '%[2]s --help' for more information",
binName(cmd),
cmd.CommandPath(),
@ -21,7 +22,7 @@ func NoArgs(cmd *cobra.Command, args []string) error {
)
}
return errors.Errorf(
return fmt.Errorf(
"%[1]s: '%[2]s' accepts no arguments\n\nUsage: %[3]s\n\nRun '%[2]s --help' for more information",
binName(cmd),
cmd.CommandPath(),
@ -35,7 +36,7 @@ func RequiresMinArgs(minArgs int) cobra.PositionalArgs {
if len(args) >= minArgs {
return nil
}
return errors.Errorf(
return fmt.Errorf(
"%[1]s: '%[2]s' requires at least %[3]d %[4]s\n\nUsage: %[5]s\n\nSee '%[2]s --help' for more information",
binName(cmd),
cmd.CommandPath(),
@ -52,8 +53,8 @@ func RequiresMaxArgs(maxArgs int) cobra.PositionalArgs {
if len(args) <= maxArgs {
return nil
}
return errors.Errorf(
"%[1]s: '%[2]s' requires at most %[3]d %[4]s\n\nUsage: %[5]s\n\nSRun '%[2]s --help' for more information",
return fmt.Errorf(
"%[1]s: '%[2]s' requires at most %[3]d %[4]s\n\nUsage: %[5]s\n\nRun '%[2]s --help' for more information",
binName(cmd),
cmd.CommandPath(),
maxArgs,
@ -69,7 +70,7 @@ func RequiresRangeArgs(minArgs int, maxArgs int) cobra.PositionalArgs {
if len(args) >= minArgs && len(args) <= maxArgs {
return nil
}
return errors.Errorf(
return fmt.Errorf(
"%[1]s: '%[2]s' requires at least %[3]d and at most %[4]d %[5]s\n\nUsage: %[6]s\n\nRun '%[2]s --help' for more information",
binName(cmd),
cmd.CommandPath(),
@ -87,7 +88,7 @@ func ExactArgs(number int) cobra.PositionalArgs {
if len(args) == number {
return nil
}
return errors.Errorf(
return fmt.Errorf(
"%[1]s: '%[2]s' requires %[3]d %[4]s\n\nUsage: %[5]s\n\nRun '%[2]s --help' for more information",
binName(cmd),
cmd.CommandPath(),

View File

@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.23
//go:build go1.24
package tui

View File

@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.23
//go:build go1.24
package tui

View File

@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.23
//go:build go1.24
package tui

View File

@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.23
//go:build go1.24
package tui

View File

@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.23
//go:build go1.24
package tui

View File

@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.23
//go:build go1.24
package tui

View File

@ -1,12 +1,13 @@
package volumespec
import (
"errors"
"fmt"
"strings"
"unicode"
"unicode/utf8"
"github.com/docker/docker/api/types/mount"
"github.com/pkg/errors"
"github.com/moby/moby/api/types/mount"
)
const endOfSpec = rune(0)
@ -24,7 +25,7 @@ func Parse(spec string) (VolumeConfig, error) {
return volume, nil
}
buffer := []rune{}
var buffer []rune
for _, char := range spec + string(endOfSpec) {
switch {
case isWindowsDrive(buffer, char):
@ -32,7 +33,7 @@ func Parse(spec string) (VolumeConfig, error) {
case char == ':' || char == endOfSpec:
if err := populateFieldFromBuffer(char, buffer, &volume); err != nil {
populateType(&volume)
return volume, errors.Wrapf(err, "invalid spec: %s", spec)
return volume, fmt.Errorf("invalid spec: %s: %w", spec, err)
}
buffer = []rune{}
default:

View File

@ -1,14 +0,0 @@
package opts
import (
"os"
"github.com/docker/cli/pkg/kvfile"
)
// ParseEnvFile reads a file with environment variables enumerated by lines
//
// Deprecated: use [kvfile.Parse] and pass [os.LookupEnv] to lookup env-vars from the current environment.
func ParseEnvFile(filename string) ([]string, error) {
return kvfile.Parse(filename, os.LookupEnv)
}

View File

@ -7,7 +7,7 @@ import (
"strconv"
"strings"
"github.com/docker/docker/api/types/container"
"github.com/moby/moby/api/types/container"
)
// GpuOpts is a Value type for parsing mounts

View File

@ -32,23 +32,6 @@ const (
hostGatewayName = "host-gateway"
)
// ValidateHost validates that the specified string is a valid host and returns it.
//
// Deprecated: this function is no longer used, and will be removed in the next release.
func ValidateHost(val string) (string, error) {
host := strings.TrimSpace(val)
// The empty string means default and is not handled by parseDockerDaemonHost
if host != "" {
_, err := parseDockerDaemonHost(host)
if err != nil {
return val, err
}
}
// Note: unlike most flag validators, we don't return the mutated value here
// we need to know what the user entered later (using ParseHost) to adjust for TLS
return val, nil
}
// ParseHost and set defaults for a Daemon host string
func ParseHost(defaultToTLS bool, val string) (string, error) {
host := strings.TrimSpace(val)

View File

@ -9,9 +9,8 @@ import (
"strconv"
"strings"
mounttypes "github.com/docker/docker/api/types/mount"
"github.com/docker/go-units"
"github.com/sirupsen/logrus"
mounttypes "github.com/moby/moby/api/types/mount"
)
// MountOpt is a Value type for parsing mounts
@ -88,8 +87,7 @@ func (m *MountOpt) Set(value string) error {
volumeOptions().NoCopy = true
continue
case "bind-nonrecursive":
bindOptions().NonRecursive = true
continue
return errors.New("bind-nonrecursive is deprecated, use bind-recursive=disabled instead")
default:
return fmt.Errorf("invalid field '%s' must be a key=value pair", field)
}
@ -117,16 +115,12 @@ func (m *MountOpt) Set(value string) error {
case "bind-propagation":
bindOptions().Propagation = mounttypes.Propagation(strings.ToLower(val))
case "bind-nonrecursive":
bindOptions().NonRecursive, err = strconv.ParseBool(val)
if err != nil {
return fmt.Errorf("invalid value for %s: %s", key, val)
}
logrus.Warn("bind-nonrecursive is deprecated, use bind-recursive=disabled instead")
return errors.New("bind-nonrecursive is deprecated, use bind-recursive=disabled instead")
case "bind-recursive":
switch val {
case "enabled": // read-only mounts are recursively read-only if Engine >= v25 && kernel >= v5.12, otherwise writable
// NOP
case "disabled": // alias of bind-nonrecursive=true
case "disabled": // previously "bind-nonrecursive=true"
bindOptions().NonRecursive = true
case "writable": // conforms to the default read-only bind-mount of Docker v24; read-only mounts are recursively mounted but not recursively read-only
bindOptions().ReadOnlyNonRecursive = true

View File

@ -4,6 +4,7 @@ import (
"encoding/csv"
"errors"
"fmt"
"net/netip"
"regexp"
"strconv"
"strings"
@ -26,9 +27,9 @@ type NetworkAttachmentOpts struct {
Aliases []string
DriverOpts map[string]string
Links []string // TODO add support for links in the csv notation of `--network`
IPv4Address string
IPv6Address string
LinkLocalIPs []string
IPv4Address netip.Addr
IPv6Address netip.Addr
LinkLocalIPs []netip.Addr
MacAddress string
GwPriority int
}
@ -70,13 +71,23 @@ func (n *NetworkOpt) Set(value string) error { //nolint:gocyclo
case networkOptAlias:
netOpt.Aliases = append(netOpt.Aliases, val)
case networkOptIPv4Address:
netOpt.IPv4Address = val
netOpt.IPv4Address, err = netip.ParseAddr(val)
if err != nil {
return err
}
case networkOptIPv6Address:
netOpt.IPv6Address = val
netOpt.IPv6Address, err = netip.ParseAddr(val)
if err != nil {
return err
}
case networkOptMacAddress:
netOpt.MacAddress = val
case networkOptLinkLocalIP:
netOpt.LinkLocalIPs = append(netOpt.LinkLocalIPs, val)
a, err := netip.ParseAddr(val)
if err != nil {
return err
}
netOpt.LinkLocalIPs = append(netOpt.LinkLocalIPs, a)
case driverOpt:
key, val, err = parseDriverOpt(val)
if err != nil {

View File

@ -1,6 +1,7 @@
package opts
import (
"encoding/json"
"errors"
"fmt"
"math/big"
@ -9,8 +10,8 @@ import (
"strings"
"github.com/docker/cli/internal/lazyregexp"
"github.com/docker/docker/api/types/filters"
"github.com/docker/go-units"
"github.com/moby/moby/client"
)
var (
@ -60,6 +61,8 @@ func (opts *ListOpts) Set(value string) error {
}
// Delete removes the specified element from the slice.
//
// Deprecated: this method is no longer used and will be removed in the next release.
func (opts *ListOpts) Delete(key string) {
for i, k := range *opts.values {
if k == key {
@ -79,13 +82,6 @@ func (opts *ListOpts) GetMap() map[string]struct{} {
return ret
}
// GetAll returns the values of slice.
//
// Deprecated: use [ListOpts.GetSlice] instead. This method will be removed in a future release.
func (opts *ListOpts) GetAll() []string {
return *opts.values
}
// GetSlice returns the values of slice.
//
// It implements [cobra.SliceValue] to allow shell completion to be provided
@ -132,43 +128,6 @@ func (opts *ListOpts) WithValidator(validator ValidatorFctType) *ListOpts {
return opts
}
// NamedOption is an interface that list and map options
// with names implement.
//
// Deprecated: NamedOption is no longer used and will be removed in the next release.
type NamedOption interface {
Name() string
}
// NamedListOpts is a ListOpts with a configuration name.
// This struct is useful to keep reference to the assigned
// field name in the internal configuration struct.
//
// Deprecated: NamedListOpts is no longer used and will be removed in the next release.
type NamedListOpts struct {
name string
ListOpts
}
var _ NamedOption = &NamedListOpts{}
// NewNamedListOptsRef creates a reference to a new NamedListOpts struct.
//
// Deprecated: NewNamedListOptsRef is no longer used and will be removed in the next release.
func NewNamedListOptsRef(name string, values *[]string, validator ValidatorFctType) *NamedListOpts {
return &NamedListOpts{
name: name,
ListOpts: *NewListOptsRef(values, validator),
}
}
// Name returns the name of the NamedListOpts in the configuration.
//
// Deprecated: NamedListOpts is no longer used and will be removed in the next release.
func (o *NamedListOpts) Name() string {
return o.name
}
// MapOpts holds a map of values and a validation function.
type MapOpts struct {
values map[string]string
@ -215,35 +174,6 @@ func NewMapOpts(values map[string]string, validator ValidatorFctType) *MapOpts {
}
}
// NamedMapOpts is a MapOpts struct with a configuration name.
// This struct is useful to keep reference to the assigned
// field name in the internal configuration struct.
//
// Deprecated: NamedMapOpts is no longer used and will be removed in the next release.
type NamedMapOpts struct {
name string
MapOpts
}
var _ NamedOption = &NamedMapOpts{}
// NewNamedMapOpts creates a reference to a new NamedMapOpts struct.
//
// Deprecated: NamedMapOpts is no longer used and will be removed in the next release.
func NewNamedMapOpts(name string, values map[string]string, validator ValidatorFctType) *NamedMapOpts {
return &NamedMapOpts{
name: name,
MapOpts: *NewMapOpts(values, validator),
}
}
// Name returns the name of the NamedMapOpts in the configuration.
//
// Deprecated: NamedMapOpts is no longer used and will be removed in the next release.
func (o *NamedMapOpts) Name() string {
return o.name
}
// ValidatorFctType defines a validator function that returns a validated string and/or an error.
type ValidatorFctType func(val string) (string, error)
@ -264,6 +194,8 @@ func ValidateIPAddress(val string) (string, error) {
}
// ValidateMACAddress validates a MAC address.
//
// Deprecated: use [net.ParseMAC]. This function will be removed in the next release.
func ValidateMACAddress(val string) (string, error) {
_, err := net.ParseMAC(strings.TrimSpace(val))
if err != nil {
@ -350,20 +282,23 @@ func ValidateSysctl(val string) (string, error) {
// FilterOpt is a flag type for validating filters
type FilterOpt struct {
filter filters.Args
filter client.Filters
}
// NewFilterOpt returns a new FilterOpt
func NewFilterOpt() FilterOpt {
return FilterOpt{filter: filters.NewArgs()}
return FilterOpt{filter: make(client.Filters)}
}
func (o *FilterOpt) String() string {
repr, err := filters.ToJSON(o.filter)
if o == nil || len(o.filter) == 0 {
return ""
}
repr, err := json.Marshal(o.filter)
if err != nil {
return "invalid filters"
}
return repr
return string(repr)
}
// Set sets the value of the opt by parsing the command line value
@ -389,7 +324,7 @@ func (*FilterOpt) Type() string {
}
// Value returns the value of this option
func (o *FilterOpt) Value() filters.Args {
func (o *FilterOpt) Value() client.Filters {
return o.filter
}

View File

@ -7,7 +7,7 @@ import (
"strings"
"github.com/docker/cli/pkg/kvfile"
"github.com/docker/docker/api/types/container"
"github.com/moby/moby/api/types/container"
)
// ReadKVStrings reads a file of line terminated key=value pairs, and overrides any keys

View File

@ -1,44 +0,0 @@
package opts
// QuotedString is a string that may have extra quotes around the value. The
// quotes are stripped from the value.
//
// Deprecated: This option type is no longer used and will be removed in the next release.
type QuotedString struct {
value *string
}
// Set sets a new value
func (s *QuotedString) Set(val string) error {
*s.value = trimQuotes(val)
return nil
}
// Type returns the type of the value
func (*QuotedString) Type() string {
return "string"
}
func (s *QuotedString) String() string {
return *s.value
}
func trimQuotes(value string) string {
if len(value) < 2 {
return value
}
lastIndex := len(value) - 1
for _, char := range []byte{'\'', '"'} {
if value[0] == char && value[lastIndex] == char {
return value[1:lastIndex]
}
}
return value
}
// NewQuotedString returns a new quoted string option
//
// Deprecated: This option type is no longer used and will be removed in the next release.
func NewQuotedString(value *string) *QuotedString {
return &QuotedString{value: value}
}

View File

@ -8,7 +8,7 @@ import (
"strconv"
"strings"
"github.com/docker/docker/api/types/swarm"
"github.com/moby/moby/api/types/swarm"
)
// ConfigOpt is a Value type for parsing configs

View File

@ -9,8 +9,9 @@ import (
"strconv"
"strings"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/go-connections/nat"
"github.com/moby/moby/api/types/network"
"github.com/moby/moby/api/types/swarm"
"github.com/sirupsen/logrus"
)
@ -41,7 +42,10 @@ func (p *PortOpt) Set(value string) error {
return err
}
pConfig := swarm.PortConfig{}
pConfig := swarm.PortConfig{
Protocol: network.TCP,
PublishMode: swarm.PortConfigPublishModeIngress,
}
for _, field := range fields {
// TODO(thaJeztah): these options should not be case-insensitive.
key, val, ok := strings.Cut(strings.ToLower(field), "=")
@ -50,17 +54,19 @@ func (p *PortOpt) Set(value string) error {
}
switch key {
case portOptProtocol:
if val != string(swarm.PortConfigProtocolTCP) && val != string(swarm.PortConfigProtocolUDP) && val != string(swarm.PortConfigProtocolSCTP) {
switch proto := network.IPProtocol(val); proto {
case network.TCP, network.UDP, network.SCTP:
pConfig.Protocol = proto
default:
return fmt.Errorf("invalid protocol value '%s'", val)
}
pConfig.Protocol = swarm.PortConfigProtocol(val)
case portOptMode:
if val != string(swarm.PortConfigPublishModeIngress) && val != string(swarm.PortConfigPublishModeHost) {
switch swarm.PortConfigPublishMode(val) {
case swarm.PortConfigPublishModeIngress, swarm.PortConfigPublishModeHost:
pConfig.PublishMode = swarm.PortConfigPublishMode(val)
default:
return fmt.Errorf("invalid publish mode value (%s): must be either '%s' or '%s'", val, swarm.PortConfigPublishModeIngress, swarm.PortConfigPublishModeHost)
}
pConfig.PublishMode = swarm.PortConfigPublishMode(val)
case portOptTargetPort:
tPort, err := strconv.ParseUint(val, 10, 16)
if err != nil {
@ -92,18 +98,9 @@ func (p *PortOpt) Set(value string) error {
return fmt.Errorf("missing mandatory field '%s'", portOptTargetPort)
}
if pConfig.PublishMode == "" {
pConfig.PublishMode = swarm.PortConfigPublishModeIngress
}
if pConfig.Protocol == "" {
pConfig.Protocol = swarm.PortConfigProtocolTCP
}
p.ports = append(p.ports, pConfig)
} else {
// short syntax
portConfigs := []swarm.PortConfig{}
ports, portBindingMap, err := nat.ParsePortSpecs([]string{value})
if err != nil {
return err
@ -116,8 +113,13 @@ func (p *PortOpt) Set(value string) error {
}
}
var portConfigs []swarm.PortConfig
for port := range ports {
portConfig, err := ConvertPortToPortConfig(port, portBindingMap)
portProto, err := network.ParsePort(string(port))
if err != nil {
return err
}
portConfig, err := ConvertPortToPortConfig(portProto, portBindingMap)
if err != nil {
return err
}
@ -135,7 +137,7 @@ func (*PortOpt) Type() string {
// String returns a string repr of this option
func (p *PortOpt) String() string {
ports := []string{}
ports := make([]string, 0, len(p.ports))
for _, port := range p.ports {
repr := fmt.Sprintf("%v:%v/%s/%s", port.PublishedPort, port.TargetPort, port.Protocol, port.PublishMode)
ports = append(ports, repr)
@ -150,28 +152,27 @@ func (p *PortOpt) Value() []swarm.PortConfig {
// ConvertPortToPortConfig converts ports to the swarm type
func ConvertPortToPortConfig(
port nat.Port,
portProto network.Port,
portBindings map[nat.Port][]nat.PortBinding,
) ([]swarm.PortConfig, error) {
ports := []swarm.PortConfig{}
for _, binding := range portBindings[port] {
ports := make([]swarm.PortConfig, 0, len(portBindings))
for _, binding := range portBindings[nat.Port(portProto.String())] {
if p := net.ParseIP(binding.HostIP); p != nil && !p.IsUnspecified() {
// TODO(thaJeztah): use context-logger, so that this output can be suppressed (in tests).
logrus.Warnf("ignoring IP-address (%s:%s) service will listen on '0.0.0.0'", net.JoinHostPort(binding.HostIP, binding.HostPort), port)
logrus.Warnf("ignoring IP-address (%s:%s) service will listen on '0.0.0.0'", net.JoinHostPort(binding.HostIP, binding.HostPort), portProto.String())
}
startHostPort, endHostPort, err := nat.ParsePortRange(binding.HostPort)
if err != nil && binding.HostPort != "" {
return nil, fmt.Errorf("invalid hostport binding (%s) for port (%s)", binding.HostPort, port.Port())
return nil, fmt.Errorf("invalid hostport binding (%s) for port (%d)", binding.HostPort, portProto.Num())
}
for i := startHostPort; i <= endHostPort; i++ {
ports = append(ports, swarm.PortConfig{
// TODO Name: ?
Protocol: swarm.PortConfigProtocol(strings.ToLower(port.Proto())),
TargetPort: uint32(port.Int()),
Protocol: portProto.Proto(),
TargetPort: uint32(portProto.Num()),
PublishedPort: uint32(i),
PublishMode: swarm.PortConfigPublishModeIngress,
})

View File

@ -8,7 +8,7 @@ import (
"strconv"
"strings"
"github.com/docker/docker/api/types/swarm"
"github.com/moby/moby/api/types/swarm"
)
// SecretOpt is a Value type for parsing secrets

View File

@ -5,8 +5,8 @@ import (
"strconv"
"strings"
"github.com/docker/docker/api/types/blkiodev"
"github.com/docker/go-units"
"github.com/moby/moby/api/types/blkiodev"
)
// ValidatorThrottleFctType defines a validator function that returns a validated struct and/or an error.

View File

@ -4,8 +4,8 @@ import (
"fmt"
"sort"
"github.com/docker/docker/api/types/container"
"github.com/docker/go-units"
"github.com/moby/moby/api/types/container"
)
// UlimitOpt defines a map of Ulimits

View File

@ -5,7 +5,7 @@ import (
"strconv"
"strings"
"github.com/docker/docker/api/types/blkiodev"
"github.com/moby/moby/api/types/blkiodev"
)
// ValidatorWeightFctType defines a validator function that returns a validated struct and/or an error.

View File

@ -1,5 +1,5 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.23
//go:build go1.24
package templates
@ -13,18 +13,7 @@ import (
// basicFunctions are the set of initial
// functions provided to every template.
var basicFunctions = template.FuncMap{
"json": func(v any) string {
buf := &bytes.Buffer{}
enc := json.NewEncoder(buf)
enc.SetEscapeHTML(false)
err := enc.Encode(v)
if err != nil {
panic(err)
}
// Remove the trailing new line added by the encoder
return strings.TrimSpace(buf.String())
},
"json": formatJSON,
"split": strings.Split,
"join": strings.Join,
"title": strings.Title, //nolint:nolintlint,staticcheck // strings.Title is deprecated, but we only use it for ASCII, so replacing with golang.org/x/text is out of scope
@ -71,7 +60,7 @@ var HeaderFunctions = template.FuncMap{
// Parse creates a new anonymous template with the basic functions
// and parses the given format.
func Parse(format string) (*template.Template, error) {
return NewParse("", format)
return template.New("").Funcs(basicFunctions).Parse(format)
}
// New creates a new empty template with the provided tag and built-in
@ -80,12 +69,6 @@ func New(tag string) *template.Template {
return template.New(tag).Funcs(basicFunctions)
}
// NewParse creates a new tagged template with the basic functions
// and parses the given format.
func NewParse(tag, format string) (*template.Template, error) {
return New(tag).Parse(format)
}
// padWithSpace adds whitespace to the input if the input is non-empty
func padWithSpace(source string, prefix, suffix int) string {
if source == "" {
@ -101,3 +84,16 @@ func truncateWithLength(source string, length int) string {
}
return source[:length]
}
func formatJSON(v any) string {
buf := &bytes.Buffer{}
enc := json.NewEncoder(buf)
enc.SetEscapeHTML(false)
err := enc.Encode(v)
if err != nil {
panic(err)
}
// Remove the trailing new line added by the encoder
return strings.TrimSpace(buf.String())
}