build: go 1.24

We were running behind and there were quite some deprecations to update.
This was mostly in the upstream copy/pasta package but seems quite
minimal.
This commit is contained in:
2025-03-16 12:04:32 +01:00
parent a2b678caf6
commit 1723025fbf
822 changed files with 25433 additions and 197407 deletions

View File

@ -92,12 +92,8 @@ func FlagErrorFunc(cmd *cobra.Command, err error) error {
return nil
}
usage := ""
if cmd.HasSubCommands() {
usage = "\n\n" + cmd.UsageString()
}
return StatusError{
Status: fmt.Sprintf("%s\nSee '%s --help'.%s", err, cmd.CommandPath(), usage),
Status: fmt.Sprintf("%s\n\nUsage: %s\n\nRun '%s --help' for more information", err, cmd.UseLine(), cmd.CommandPath()),
StatusCode: 125,
}
}
@ -341,8 +337,10 @@ func operationSubCommands(cmd *cobra.Command) []*cobra.Command {
return cmds
}
const defaultTermWidth = 80
func wrappedFlagUsages(cmd *cobra.Command) string {
width := 80
width := defaultTermWidth
if ws, err := term.GetWinsize(0); err == nil {
width = int(ws.Width)
}
@ -522,4 +520,4 @@ Run '{{.CommandPath}} COMMAND --help' for more information on a command.
`
const helpTemplate = `
{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}`
{{- if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}`

View File

@ -97,7 +97,7 @@ type DockerCli struct {
}
// DefaultVersion returns api.defaultVersion.
func (cli *DockerCli) DefaultVersion() string {
func (*DockerCli) DefaultVersion() string {
return api.DefaultVersion
}
@ -114,7 +114,7 @@ func (cli *DockerCli) CurrentVersion() string {
// Client returns the APIClient
func (cli *DockerCli) Client() client.APIClient {
if err := cli.initialize(); err != nil {
_, _ = fmt.Fprintf(cli.Err(), "Failed to initialize: %s\n", err)
_, _ = fmt.Fprintln(cli.Err(), "Failed to initialize:", err)
os.Exit(1)
}
return cli.client
@ -231,7 +231,7 @@ func (cli *DockerCli) HooksEnabled() bool {
}
// ManifestStore returns a store for local manifests
func (cli *DockerCli) ManifestStore() manifeststore.Store {
func (*DockerCli) ManifestStore() manifeststore.Store {
// TODO: support override default location from config file
return manifeststore.NewStore(filepath.Join(config.Dir(), "manifests"))
}
@ -272,7 +272,7 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...CLIOption)
debug.Enable()
}
if opts.Context != "" && len(opts.Hosts) > 0 {
return errors.New("conflicting options: either specify --host or --context, not both")
return errors.New("conflicting options: cannot specify both --host and --context")
}
cli.options = opts
@ -299,7 +299,7 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...CLIOption)
// NewAPIClientFromFlags creates a new APIClient from command line flags
func NewAPIClientFromFlags(opts *cliflags.ClientOptions, configFile *configfile.ConfigFile) (client.APIClient, error) {
if opts.Context != "" && len(opts.Hosts) > 0 {
return nil, errors.New("conflicting options: either specify --host or --context, not both")
return nil, errors.New("conflicting options: cannot specify both --host and --context")
}
storeConfig := DefaultContextStoreConfig()
@ -475,7 +475,7 @@ func (cli *DockerCli) DockerEndpoint() docker.Endpoint {
if err := cli.initialize(); err != nil {
// Note that we're not terminating here, as this function may be used
// in cases where we're able to continue.
_, _ = fmt.Fprintf(cli.Err(), "%v\n", cli.initErr)
_, _ = fmt.Fprintln(cli.Err(), cli.initErr)
}
return cli.dockerEndpoint
}

View File

@ -177,7 +177,10 @@ func withCustomHeadersFromEnv() client.Opt {
csvReader := csv.NewReader(strings.NewReader(value))
fields, err := csvReader.Read()
if err != nil {
return errdefs.InvalidParameter(errors.Errorf("failed to parse custom headers from %s environment variable: value must be formatted as comma-separated key=value pairs", envOverrideHTTPHeaders))
return errdefs.InvalidParameter(errors.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
@ -191,7 +194,10 @@ func withCustomHeadersFromEnv() client.Opt {
k = strings.TrimSpace(k)
if k == "" {
return errdefs.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))
return errdefs.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,
))
}
// We don't currently allow empty key=value pairs, and produce an error.
@ -199,7 +205,10 @@ func withCustomHeadersFromEnv() client.Opt {
// from an environment variable with the same name). In the meantime,
// produce an error to prevent users from depending on this.
if !hasValue {
return errdefs.InvalidParameter(errors.Errorf(`failed to set custom headers from %s environment variable: missing "=" in key=value pair: '%s'`, envOverrideHTTPHeaders, kv))
return errdefs.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

View File

@ -12,7 +12,7 @@ import (
"time"
"github.com/distribution/reference"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/go-units"
)
@ -67,10 +67,10 @@ ports: {{- pad .Ports 1 0}}
}
// ContainerWrite renders the context for a list of containers
func ContainerWrite(ctx Context, containers []types.Container) error {
func ContainerWrite(ctx Context, containers []container.Summary) error {
render := func(format func(subContext SubContext) error) error {
for _, container := range containers {
err := format(&ContainerContext{trunc: ctx.Trunc, c: container})
for _, ctr := range containers {
err := format(&ContainerContext{trunc: ctx.Trunc, c: ctr})
if err != nil {
return err
}
@ -84,7 +84,7 @@ func ContainerWrite(ctx Context, containers []types.Container) error {
type ContainerContext struct {
HeaderContext
trunc bool
c types.Container
c container.Summary
// FieldsUsed is used in the pre-processing step to detect which fields are
// used in the template. It's currently only used to detect use of the .Size
@ -193,7 +193,9 @@ func (c *ContainerContext) Command() string {
return strconv.Quote(command)
}
// CreatedAt returns the "Created" date/time of the container as a unix timestamp.
// CreatedAt returns the formatted string representing the container's creation date/time.
// The format may include nanoseconds if present.
// e.g. "2006-01-02 15:04:05.999999999 -0700 MST" or "2006-01-02 15:04:05 -0700 MST"
func (c *ContainerContext) CreatedAt() string {
return time.Unix(c.c.Created, 0).String()
}
@ -314,7 +316,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 []types.Port) string {
func DisplayablePorts(ports []container.Port) string {
type portGroup struct {
first uint16
last uint16
@ -375,12 +377,12 @@ func formGroup(key string, start, last uint16) string {
group = fmt.Sprintf("%s-%d", group, last)
}
if ip != "" {
group = fmt.Sprintf("%s:%s->%s", ip, group, group)
group = fmt.Sprintf("%s->%s", net.JoinHostPort(ip, group), group)
}
return group + "/" + groupType
}
func comparePorts(i, j types.Port) bool {
func comparePorts(i, j container.Port) bool {
if i.PrivatePort != j.PrivatePort {
return i.PrivatePort < j.PrivatePort
}

View File

@ -32,7 +32,7 @@ type SubContext interface {
type SubHeaderContext map[string]string
// Label returns the header label for the specified string
func (c SubHeaderContext) Label(name string) string {
func (SubHeaderContext) Label(name string) string {
n := strings.Split(name, ".")
r := strings.NewReplacer("-", " ", "_", " ")
h := r.Replace(n[len(n)-1])

View File

@ -9,6 +9,7 @@ import (
"github.com/distribution/reference"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/volume"
units "github.com/docker/go-units"
@ -36,7 +37,7 @@ type DiskUsageContext struct {
Verbose bool
LayersSize int64
Images []*image.Summary
Containers []*types.Container
Containers []*container.Summary
Volumes []*volume.Volume
BuildCache []*types.BuildCache
BuilderSize int64
@ -124,7 +125,7 @@ func (ctx *DiskUsageContext) Write() (err error) {
return err
}
diskUsageContainersCtx := diskUsageContainersContext{containers: []*types.Container{}}
diskUsageContainersCtx := diskUsageContainersContext{containers: []*container.Summary{}}
diskUsageContainersCtx.Header = SubHeaderContext{
"Type": typeHeader,
"TotalCount": totalHeader,
@ -236,7 +237,7 @@ func (ctx *DiskUsageContext) verboseWriteTable(duc *diskUsageContext) error {
if err != nil {
return err
}
ctx.Output.Write([]byte("\nLocal Volumes space usage:\n\n"))
_, _ = ctx.Output.Write([]byte("\nLocal Volumes space usage:\n\n"))
for _, v := range duc.Volumes {
if err := ctx.contextFormat(tmpl, v); err != nil {
return err
@ -248,7 +249,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.BuilderSize)))
for _, v := range duc.BuildCache {
if err := ctx.contextFormat(tmpl, v); err != nil {
return err
@ -269,7 +270,7 @@ func (c *diskUsageImagesContext) MarshalJSON() ([]byte, error) {
return MarshalJSON(c)
}
func (c *diskUsageImagesContext) Type() string {
func (*diskUsageImagesContext) Type() string {
return "Images"
}
@ -313,14 +314,14 @@ func (c *diskUsageImagesContext) Reclaimable() string {
type diskUsageContainersContext struct {
HeaderContext
containers []*types.Container
containers []*container.Summary
}
func (c *diskUsageContainersContext) MarshalJSON() ([]byte, error) {
return MarshalJSON(c)
}
func (c *diskUsageContainersContext) Type() string {
func (*diskUsageContainersContext) Type() string {
return "Containers"
}
@ -328,16 +329,16 @@ func (c *diskUsageContainersContext) TotalCount() string {
return strconv.Itoa(len(c.containers))
}
func (c *diskUsageContainersContext) isActive(container types.Container) bool {
return strings.Contains(container.State, "running") ||
strings.Contains(container.State, "paused") ||
strings.Contains(container.State, "restarting")
func (*diskUsageContainersContext) isActive(ctr container.Summary) bool {
return strings.Contains(ctr.State, "running") ||
strings.Contains(ctr.State, "paused") ||
strings.Contains(ctr.State, "restarting")
}
func (c *diskUsageContainersContext) Active() string {
used := 0
for _, container := range c.containers {
if c.isActive(*container) {
for _, ctr := range c.containers {
if c.isActive(*ctr) {
used++
}
}
@ -348,22 +349,21 @@ func (c *diskUsageContainersContext) Active() string {
func (c *diskUsageContainersContext) Size() string {
var size int64
for _, container := range c.containers {
size += container.SizeRw
for _, ctr := range c.containers {
size += ctr.SizeRw
}
return units.HumanSize(float64(size))
}
func (c *diskUsageContainersContext) Reclaimable() string {
var reclaimable int64
var totalSize int64
var reclaimable, totalSize int64
for _, container := range c.containers {
if !c.isActive(*container) {
reclaimable += container.SizeRw
for _, ctr := range c.containers {
if !c.isActive(*ctr) {
reclaimable += ctr.SizeRw
}
totalSize += container.SizeRw
totalSize += ctr.SizeRw
}
if totalSize > 0 {
@ -382,7 +382,7 @@ func (c *diskUsageVolumesContext) MarshalJSON() ([]byte, error) {
return MarshalJSON(c)
}
func (c *diskUsageVolumesContext) Type() string {
func (*diskUsageVolumesContext) Type() string {
return "Local Volumes"
}
@ -443,7 +443,7 @@ func (c *diskUsageBuilderContext) MarshalJSON() ([]byte, error) {
return MarshalJSON(c)
}
func (c *diskUsageBuilderContext) Type() string {
func (*diskUsageBuilderContext) Type() string {
return "Build Cache"
}

View File

@ -13,9 +13,10 @@ import (
configtypes "github.com/docker/cli/cli/config/types"
"github.com/docker/cli/cli/hints"
"github.com/docker/cli/cli/streams"
"github.com/docker/docker/api/types"
"github.com/docker/cli/internal/tui"
registrytypes "github.com/docker/docker/api/types/registry"
"github.com/docker/docker/registry"
"github.com/morikuni/aec"
"github.com/pkg/errors"
)
@ -29,7 +30,7 @@ const (
// RegistryAuthenticationPrivilegedFunc returns a RequestPrivilegeFunc from the specified registry index info
// for the given command.
func RegistryAuthenticationPrivilegedFunc(cli Cli, index *registrytypes.IndexInfo, cmdName string) types.RequestPrivilegeFunc {
func RegistryAuthenticationPrivilegedFunc(cli Cli, index *registrytypes.IndexInfo, cmdName string) registrytypes.RequestAuthConfig {
return func(ctx context.Context) (string, error) {
_, _ = fmt.Fprintf(cli.Out(), "\nLogin prior to %s:\n", cmdName)
indexServer := registry.GetAuthConfigKey(index)
@ -179,6 +180,9 @@ func PromptUserForCredentials(ctx context.Context, cli Cli, argUser, argPassword
}
}()
out := tui.NewOutput(cli.Err())
out.PrintNote("A Personal Access Token (PAT) can be used instead.\n" +
"To create a PAT, visit " + aec.Underline.Apply("https://app.docker.com/settings") + "\n\n")
argPassword, err = PromptForInput(ctx, cli.In(), cli.Out(), "Password: ")
if err != nil {
return registrytypes.AuthConfig{}, err

View File

@ -79,14 +79,6 @@ func ServiceProgress(ctx context.Context, apiClient client.APIClient, serviceID
signal.Notify(sigint, os.Interrupt)
defer signal.Stop(sigint)
taskFilter := filters.NewArgs()
taskFilter.Add("service", serviceID)
taskFilter.Add("_up-to-date", "true")
getUpToDateTasks := func() ([]swarm.Task, error) {
return apiClient.TaskList(ctx, types.TaskListOptions{Filters: taskFilter})
}
var (
updater progressUpdater
converged bool
@ -151,7 +143,10 @@ func ServiceProgress(ctx context.Context, apiClient client.APIClient, serviceID
return nil
}
tasks, err := getUpToDateTasks()
tasks, err := apiClient.TaskList(ctx, types.TaskListOptions{Filters: filters.NewArgs(
filters.KeyValuePair{Key: "service", Value: service.ID},
filters.KeyValuePair{Key: "_up-to-date", Value: "true"},
)})
if err != nil {
return err
}
@ -218,7 +213,10 @@ func ServiceProgress(ctx context.Context, apiClient client.APIClient, serviceID
}
}
func getActiveNodes(ctx context.Context, apiClient client.APIClient) (map[string]struct{}, error) {
// getActiveNodes returns all nodes that are currently not in status [swarm.NodeStateDown].
//
// 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, types.NodeListOptions{})
if err != nil {
return nil, err
@ -275,8 +273,9 @@ func truncError(errMsg string) string {
// Limit the length to 75 characters, so that even on narrow terminals
// this will not overflow to the next line.
if len(errMsg) > 75 {
errMsg = errMsg[:74] + "…"
const maxWidth = 75
if len(errMsg) > maxWidth {
errMsg = errMsg[:maxWidth-1] + "…"
}
return errMsg
}
@ -351,7 +350,7 @@ func (u *replicatedProgressUpdater) update(service swarm.Service, tasks []swarm.
return running == replicas, nil
}
func (u *replicatedProgressUpdater) tasksBySlot(tasks []swarm.Task, activeNodes map[string]struct{}) map[int]swarm.Task {
func (*replicatedProgressUpdater) tasksBySlot(tasks []swarm.Task, activeNodes map[string]struct{}) map[int]swarm.Task {
// If there are multiple tasks with the same slot number, favor the one
// with the *lowest* desired state. This can happen in restart
// scenarios.
@ -472,7 +471,7 @@ func (u *globalProgressUpdater) update(_ swarm.Service, tasks []swarm.Task, acti
return running == nodeCount, nil
}
func (u *globalProgressUpdater) tasksByNode(tasks []swarm.Task) map[string]swarm.Task {
func (*globalProgressUpdater) tasksByNode(tasks []swarm.Task) map[string]swarm.Task {
// If there are multiple tasks with the same node ID, favor the one
// with the *lowest* desired state. This can happen in restart
// scenarios.
@ -573,16 +572,17 @@ type replicatedJobProgressUpdater struct {
}
func newReplicatedJobProgressUpdater(service swarm.Service, progressOut progress.Output) *replicatedJobProgressUpdater {
u := &replicatedJobProgressUpdater{
progressOut: progressOut,
concurrent: int(*service.Spec.Mode.ReplicatedJob.MaxConcurrent),
total: int(*service.Spec.Mode.ReplicatedJob.TotalCompletions),
jobIteration: service.JobStatus.JobIteration.Index,
}
u.progressDigits = len(strconv.Itoa(u.total))
u.activeDigits = len(strconv.Itoa(u.concurrent))
concurrent := int(*service.Spec.Mode.ReplicatedJob.MaxConcurrent)
total := int(*service.Spec.Mode.ReplicatedJob.TotalCompletions)
return u
return &replicatedJobProgressUpdater{
progressOut: progressOut,
concurrent: concurrent,
total: total,
jobIteration: service.JobStatus.JobIteration.Index,
progressDigits: len(strconv.Itoa(total)),
activeDigits: len(strconv.Itoa(concurrent)),
}
}
// update writes out the progress of the replicated job.

View File

@ -14,7 +14,7 @@ import (
"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.21.0"
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
"go.opentelemetry.io/otel/trace"
)
@ -54,11 +54,11 @@ func (cli *DockerCli) Resource() *resource.Resource {
return cli.res.Get()
}
func (cli *DockerCli) TracerProvider() trace.TracerProvider {
func (*DockerCli) TracerProvider() trace.TracerProvider {
return otel.GetTracerProvider()
}
func (cli *DockerCli) MeterProvider() metric.MeterProvider {
func (*DockerCli) MeterProvider() metric.MeterProvider {
return otel.GetMeterProvider()
}

View File

@ -199,7 +199,7 @@ func PruneFilters(dockerCli Cli, pruneFilters filters.Args) filters.Args {
// AddPlatformFlag adds `platform` to a set of flags for API version 1.32 and later.
func AddPlatformFlag(flags *pflag.FlagSet, target *string) {
flags.StringVar(target, "platform", os.Getenv("DOCKER_DEFAULT_PLATFORM"), "Set platform if server is multi-platform capable")
flags.SetAnnotation("platform", "version", []string{"1.32"})
_ = flags.SetAnnotation("platform", "version", []string{"1.32"})
}
// ValidateOutputPath validates the output paths of the `export` and `save` commands.

View File

@ -25,7 +25,7 @@ import (
"github.com/google/shlex"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
yaml "gopkg.in/yaml.v2"
"gopkg.in/yaml.v3"
)
// Options supported by Load
@ -53,11 +53,11 @@ func ParseYAML(source []byte) (map[string]any, error) {
if err := yaml.Unmarshal(source, &cfg); err != nil {
return nil, err
}
cfgMap, ok := cfg.(map[any]any)
_, ok := cfg.(map[string]any)
if !ok {
return nil, errors.Errorf("top-level object must be a mapping")
}
converted, err := convertToStringKeysRecursive(cfgMap, "")
converted, err := convertToStringKeysRecursive(cfg, "")
if err != nil {
return nil, err
}
@ -269,7 +269,7 @@ type ForbiddenPropertiesError struct {
Properties map[string]string
}
func (e *ForbiddenPropertiesError) Error() string {
func (*ForbiddenPropertiesError) Error() string {
return "Configuration contains forbidden properties"
}
@ -349,24 +349,20 @@ func createTransformHook(additionalTransformers ...Transformer) mapstructure.Dec
// keys needs to be converted to strings for jsonschema
func convertToStringKeysRecursive(value any, keyPrefix string) (any, error) {
if mapping, ok := value.(map[any]any); ok {
if mapping, ok := value.(map[string]any); ok {
dict := make(map[string]any)
for key, entry := range mapping {
str, ok := key.(string)
if !ok {
return nil, formatInvalidKeyError(keyPrefix, key)
}
var newKeyPrefix string
if keyPrefix == "" {
newKeyPrefix = str
newKeyPrefix = key
} else {
newKeyPrefix = fmt.Sprintf("%s.%s", keyPrefix, str)
newKeyPrefix = fmt.Sprintf("%s.%s", keyPrefix, key)
}
convertedEntry, err := convertToStringKeysRecursive(entry, newKeyPrefix)
if err != nil {
return nil, err
}
dict[str] = convertedEntry
dict[key] = convertedEntry
}
return dict, nil
}
@ -385,16 +381,6 @@ func convertToStringKeysRecursive(value any, keyPrefix string) (any, error) {
return value, nil
}
func formatInvalidKeyError(keyPrefix string, key any) error {
var location string
if keyPrefix == "" {
location = "at top level"
} else {
location = "in " + keyPrefix
}
return errors.Errorf("non-string key %s: %#v", location, key)
}
// LoadServices produces a ServiceConfig map from a compose file Dict
// the servicesDict is not validated if directly used. Use Load() to enable validation
func LoadServices(servicesDict map[string]any, workingDir string, lookupEnv template.Mapping) ([]types.ServiceConfig, error) {

View File

@ -68,7 +68,6 @@ func mergeServices(base, override []types.ServiceConfig) ([]types.ServiceConfig,
},
}
for name, overrideService := range overrideServices {
overrideService := overrideService
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)

View File

@ -6,9 +6,11 @@ package schema
import (
"embed"
"fmt"
"math/big"
"strings"
"time"
"github.com/docker/go-connections/nat"
"github.com/pkg/errors"
"github.com/xeipuuv/gojsonschema"
)
@ -20,14 +22,23 @@ const (
type portsFormatChecker struct{}
func (checker portsFormatChecker) IsFormat(_ any) bool {
// TODO: implement this
return true
func (portsFormatChecker) IsFormat(input any) bool {
var portSpec string
switch p := input.(type) {
case string:
portSpec = p
case *big.Rat:
portSpec = strings.Split(p.String(), "/")[0]
}
_, err := nat.ParsePortSpec(portSpec)
return err == nil
}
type durationFormatChecker struct{}
func (checker durationFormatChecker) IsFormat(input any) bool {
func (durationFormatChecker) IsFormat(input any) bool {
value, ok := input.(string)
if !ok {
return false
@ -37,7 +48,6 @@ func (checker durationFormatChecker) IsFormat(input any) bool {
}
func init() {
gojsonschema.FormatCheckers.Add("expose", portsFormatChecker{})
gojsonschema.FormatCheckers.Add("ports", portsFormatChecker{})
gojsonschema.FormatCheckers.Add("duration", durationFormatChecker{})
}

View File

@ -398,6 +398,7 @@ type ServiceVolumeConfig struct {
Consistency string `yaml:",omitempty" json:"consistency,omitempty"`
Bind *ServiceVolumeBind `yaml:",omitempty" json:"bind,omitempty"`
Volume *ServiceVolumeVolume `yaml:",omitempty" json:"volume,omitempty"`
Image *ServiceVolumeImage `yaml:",omitempty" json:"image,omitempty"`
Tmpfs *ServiceVolumeTmpfs `yaml:",omitempty" json:"tmpfs,omitempty"`
Cluster *ServiceVolumeCluster `yaml:",omitempty" json:"cluster,omitempty"`
}
@ -409,7 +410,13 @@ type ServiceVolumeBind struct {
// ServiceVolumeVolume are options for a service volume of type volume
type ServiceVolumeVolume struct {
NoCopy bool `mapstructure:"nocopy" yaml:"nocopy,omitempty" json:"nocopy,omitempty"`
NoCopy bool `mapstructure:"nocopy" yaml:"nocopy,omitempty" json:"nocopy,omitempty"`
Subpath string `mapstructure:"subpath" yaml:"subpath,omitempty" json:"subpath,omitempty"`
}
// ServiceVolumeImage are options for a service volume of type image
type ServiceVolumeImage struct {
Subpath string `mapstructure:"subpath" yaml:"subpath,omitempty" json:"subpath,omitempty"`
}
// ServiceVolumeTmpfs are options for a service volume of type tmpfs

View File

@ -143,7 +143,7 @@ func load(configDir string) (*configfile.ConfigFile, error) {
defer file.Close()
err = configFile.LoadFromReader(file)
if err != nil {
err = errors.Wrapf(err, "loading config file: %s: ", filename)
err = errors.Wrapf(err, "parsing config file (%s)", filename)
}
return configFile, err
}

View File

@ -1,9 +1,12 @@
package credentials
import (
"fmt"
"net"
"net/url"
"os"
"strings"
"sync/atomic"
"github.com/docker/cli/cli/config/types"
)
@ -57,6 +60,21 @@ func (c *fileStore) GetAll() (map[string]types.AuthConfig, error) {
return c.file.GetAuthConfigs(), nil
}
// unencryptedWarning warns the user when using an insecure credential storage.
// After a deprecation period, user will get prompted if stdin and stderr are a terminal.
// Otherwise, we'll assume they want it (sadly), because people may have been scripting
// insecure logins and we don't want to break them. Maybe they'll see the warning in their
// logs and fix things.
const unencryptedWarning = `
WARNING! Your credentials are stored unencrypted in '%s'.
Configure a credential helper to remove this warning. See
https://docs.docker.com/go/credential-store/
`
// alreadyPrinted ensures that we only print the unencryptedWarning once per
// CLI invocation (no need to warn the user multiple times per command).
var alreadyPrinted atomic.Bool
// Store saves the given credentials in the file store. This function is
// idempotent and does not update the file if credentials did not change.
func (c *fileStore) Store(authConfig types.AuthConfig) error {
@ -66,15 +84,19 @@ func (c *fileStore) Store(authConfig types.AuthConfig) error {
return nil
}
authConfigs[authConfig.ServerAddress] = authConfig
return c.file.Save()
}
if err := c.file.Save(); err != nil {
return err
}
func (c *fileStore) GetFilename() string {
return c.file.GetFilename()
}
if !alreadyPrinted.Load() && authConfig.Password != "" {
// Display a warning if we're storing the users password (not a token).
//
// FIXME(thaJeztah): make output configurable instead of hardcoding to os.Stderr
_, _ = fmt.Fprintln(os.Stderr, fmt.Sprintf(unencryptedWarning, c.file.GetFilename()))
alreadyPrinted.Store(true)
}
func (c *fileStore) IsFileStore() bool {
return true
return nil
}
// ConvertToHostname converts a registry url which has http|https prepended

View File

@ -33,18 +33,28 @@ import (
)
// New returns net.Conn
func New(_ context.Context, cmd string, args ...string) (net.Conn, error) {
var (
c commandConn
err error
)
c.cmd = exec.Command(cmd, args...)
func New(ctx context.Context, cmd string, args ...string) (net.Conn, error) {
// Don't kill the ssh process if the context is cancelled. Killing the
// ssh process causes an error when go's http.Client tries to reuse the
// net.Conn (commandConn).
//
// Not passing down the Context might seem counter-intuitive, but in this
// case, the lifetime of the process should be managed by the http.Client,
// not the caller's Context.
//
// Further details;;
//
// - https://github.com/docker/cli/pull/3900
// - https://github.com/docker/compose/issues/9448#issuecomment-1264263721
ctx = context.WithoutCancel(ctx)
c := commandConn{cmd: exec.CommandContext(ctx, cmd, args...)}
// we assume that args never contains sensitive information
logrus.Debugf("commandconn: starting %s with %v", cmd, args)
c.cmd.Env = os.Environ()
c.cmd.SysProcAttr = &syscall.SysProcAttr{}
setPdeathsig(c.cmd)
createSession(c.cmd)
var err error
c.stdin, err = c.cmd.StdinPipe()
if err != nil {
return nil, err
@ -243,17 +253,17 @@ func (c *commandConn) RemoteAddr() net.Addr {
return c.remoteAddr
}
func (c *commandConn) SetDeadline(t time.Time) error {
func (*commandConn) SetDeadline(t time.Time) error {
logrus.Debugf("unimplemented call: SetDeadline(%v)", t)
return nil
}
func (c *commandConn) SetReadDeadline(t time.Time) error {
func (*commandConn) SetReadDeadline(t time.Time) error {
logrus.Debugf("unimplemented call: SetReadDeadline(%v)", t)
return nil
}
func (c *commandConn) SetWriteDeadline(t time.Time) error {
func (*commandConn) SetWriteDeadline(t time.Time) error {
logrus.Debugf("unimplemented call: SetWriteDeadline(%v)", t)
return nil
}

View File

@ -12,7 +12,7 @@ import (
"sort"
"github.com/docker/docker/errdefs"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/atomicwriter"
"github.com/fvbommel/sortorder"
"github.com/pkg/errors"
)
@ -40,7 +40,7 @@ func (s *metadataStore) createOrUpdate(meta Metadata) error {
if err != nil {
return err
}
return ioutils.AtomicWriteFile(filepath.Join(contextDir, metaFile), bytes, 0o644)
return atomicwriter.WriteFile(filepath.Join(contextDir, metaFile), bytes, 0o644)
}
func parseTypedOrMap(payload []byte, getter TypeGetter) (any, error) {

View File

@ -5,7 +5,7 @@ import (
"path/filepath"
"github.com/docker/docker/errdefs"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/atomicwriter"
"github.com/pkg/errors"
)
@ -32,7 +32,7 @@ func (s *tlsStore) createOrUpdate(name, endpointName, filename string, data []by
if err := os.MkdirAll(endpointDir, 0o700); err != nil {
return err
}
return ioutils.AtomicWriteFile(filepath.Join(endpointDir, filename), data, 0o600)
return atomicwriter.WriteFile(filepath.Join(endpointDir, filename), data, 0o600)
}
func (s *tlsStore) getData(name, endpointName, filename string) ([]byte, error) {

View File

@ -10,14 +10,14 @@ import (
// Enable sets the DEBUG env var to true
// and makes the logger to log at debug level.
func Enable() {
os.Setenv("DEBUG", "1")
_ = os.Setenv("DEBUG", "1")
logrus.SetLevel(logrus.DebugLevel)
}
// Disable sets the DEBUG env var to false
// and makes the logger to log at info level.
func Disable() {
os.Setenv("DEBUG", "")
_ = os.Setenv("DEBUG", "")
logrus.SetLevel(logrus.InfoLevel)
}

View File

@ -1,35 +1,27 @@
package cli
import (
"fmt"
"strings"
)
// Errors is a list of errors.
// Useful in a loop if you don't want to return the error right away and you want to display after the loop,
// all the errors that happened during the loop.
//
// Deprecated: use [errors.Join] instead; will be removed in the next release.
type Errors []error
func (errList Errors) Error() string {
if len(errList) < 1 {
return ""
}
out := make([]string, len(errList))
for i := range errList {
out[i] = errList[i].Error()
}
return strings.Join(out, ", ")
}
// StatusError reports an unsuccessful exit by a command.
type StatusError struct {
Cause error
Status string
StatusCode int
}
// Error formats the error for printing. If a custom Status is provided,
// it is returned as-is, otherwise it generates a generic error-message
// based on the StatusCode.
func (e StatusError) Error() string {
return fmt.Sprintf("Status: %s, Code: %d", e.Status, e.StatusCode)
if e.Status != "" {
return e.Status
}
if e.Cause != nil {
return e.Cause.Error()
}
// we don't want to set a default message here,
// some commands might want to be explicit about the error message
return ""
}
func (e StatusError) Unwrap() error {
return e.Cause
}

View File

@ -138,7 +138,7 @@ func SetLogLevel(logLevel string) {
if logLevel != "" {
lvl, err := logrus.ParseLevel(logLevel)
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to parse logging level: %s\n", logLevel)
_, _ = fmt.Fprintln(os.Stderr, "Unable to parse logging level:", logLevel)
os.Exit(1)
}
logrus.SetLevel(lvl)

View File

@ -5,7 +5,9 @@ import (
"strconv"
)
// Enabled returns whether cli hints are enabled or not
// Enabled returns whether cli hints are enabled or not. Hints are enabled by
// default, but can be disabled through the "DOCKER_CLI_HINTS" environment
// variable.
func Enabled() bool {
if v := os.Getenv("DOCKER_CLI_HINTS"); v != "" {
enabled, err := strconv.ParseBool(v)

View File

@ -44,7 +44,7 @@ func (s *fsStore) Get(listRef reference.Reference, manifest reference.Reference)
return s.getFromFilename(manifest, filename)
}
func (s *fsStore) getFromFilename(ref reference.Reference, filename string) (types.ImageManifest, error) {
func (*fsStore) getFromFilename(ref reference.Reference, filename string) (types.ImageManifest, error) {
bytes, err := os.ReadFile(filename)
switch {
case os.IsNotExist(err):
@ -165,7 +165,7 @@ func (n *notFoundError) Error() string {
}
// NotFound interface
func (n *notFoundError) NotFound() {}
func (*notFoundError) NotFound() {}
// IsNotFound returns true if the error is a not found error
func IsNotFound(err error) bool {

View File

@ -22,12 +22,7 @@ type repositoryEndpoint struct {
// Name returns the repository name
func (r repositoryEndpoint) Name() string {
repoName := r.info.Name.Name()
// If endpoint does not support CanonicalName, use the RemoteName instead
if r.endpoint.TrimHostname {
repoName = reference.Path(r.info.Name)
}
return repoName
return reference.Path(r.info.Name)
}
// BaseURL returns the endpoint url
@ -128,6 +123,6 @@ func (th *existingTokenHandler) AuthorizeRequest(req *http.Request, _ map[string
return nil
}
func (th *existingTokenHandler) Scheme() string {
func (*existingTokenHandler) Scheme() string {
return "bearer"
}

View File

@ -304,4 +304,4 @@ func (n *notFoundError) Error() string {
}
// NotFound satisfies interface github.com/docker/docker/errdefs.ErrNotFound
func (n *notFoundError) NotFound() {}
func (notFoundError) NotFound() {}

View File

@ -1,8 +1,6 @@
package cli
import (
"strings"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
@ -14,15 +12,20 @@ func NoArgs(cmd *cobra.Command, args []string) error {
}
if cmd.HasSubCommands() {
return errors.New("\n" + strings.TrimRight(cmd.UsageString(), "\n"))
return errors.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(),
args[0],
cmd.UseLine(),
)
}
return errors.Errorf(
"%q accepts no arguments.\nSee '%s --help'.\n\nUsage: %s\n\n%s",
cmd.CommandPath(),
"%[1]s: '%[2]s' accepts no arguments\n\nUsage: %[3]s\n\nRun '%[2]s --help' for more information",
binName(cmd),
cmd.CommandPath(),
cmd.UseLine(),
cmd.Short,
)
}
@ -33,13 +36,12 @@ func RequiresMinArgs(minArgs int) cobra.PositionalArgs {
return nil
}
return errors.Errorf(
"%q requires at least %d %s.\nSee '%s --help'.\n\nUsage: %s\n\n%s",
"%[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(),
minArgs,
pluralize("argument", minArgs),
cmd.CommandPath(),
cmd.UseLine(),
cmd.Short,
)
}
}
@ -51,13 +53,12 @@ func RequiresMaxArgs(maxArgs int) cobra.PositionalArgs {
return nil
}
return errors.Errorf(
"%q requires at most %d %s.\nSee '%s --help'.\n\nUsage: %s\n\n%s",
"%[1]s: '%[2]s' requires at most %[3]d %[4]s\n\nUsage: %[5]s\n\nSRun '%[2]s --help' for more information",
binName(cmd),
cmd.CommandPath(),
maxArgs,
pluralize("argument", maxArgs),
cmd.CommandPath(),
cmd.UseLine(),
cmd.Short,
)
}
}
@ -69,14 +70,13 @@ func RequiresRangeArgs(minArgs int, maxArgs int) cobra.PositionalArgs {
return nil
}
return errors.Errorf(
"%q requires at least %d and at most %d %s.\nSee '%s --help'.\n\nUsage: %s\n\n%s",
"%[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(),
minArgs,
maxArgs,
pluralize("argument", maxArgs),
cmd.CommandPath(),
cmd.UseLine(),
cmd.Short,
)
}
}
@ -88,17 +88,21 @@ func ExactArgs(number int) cobra.PositionalArgs {
return nil
}
return errors.Errorf(
"%q requires exactly %d %s.\nSee '%s --help'.\n\nUsage: %s\n\n%s",
"%[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(),
number,
pluralize("argument", number),
cmd.CommandPath(),
cmd.UseLine(),
cmd.Short,
)
}
}
// binName returns the name of the binary / root command (usually 'docker').
func binName(cmd *cobra.Command) string {
return cmd.Root().Name()
}
//nolint:unparam
func pluralize(word string, number int) string {
if number == 1 {

View File

@ -89,7 +89,7 @@ func (scs simpleCredentialStore) RefreshToken(*url.URL, string) string {
return scs.auth.IdentityToken
}
func (scs simpleCredentialStore) SetRefreshToken(*url.URL, string, string) {}
func (simpleCredentialStore) SetRefreshToken(*url.URL, string, string) {}
// GetNotaryRepository returns a NotaryRepository which stores all the
// information needed to operate on a notary repository.