Merge pull request #5791 from thaJeztah/multi_errors

cli/command: use errors.Join instead of our own implementation
This commit is contained in:
Sebastiaan van Stijn 2025-02-04 12:23:11 +01:00 committed by GitHub
commit 7c3fa8172b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 204 additions and 274 deletions

View File

@ -2,12 +2,11 @@ package config
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"strings"
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -35,23 +34,17 @@ func newConfigRemoveCommand(dockerCli command.Cli) *cobra.Command {
} }
// RunConfigRemove removes the given Swarm configs. // RunConfigRemove removes the given Swarm configs.
func RunConfigRemove(ctx context.Context, dockerCli command.Cli, opts RemoveOptions) error { func RunConfigRemove(ctx context.Context, dockerCLI command.Cli, opts RemoveOptions) error {
client := dockerCli.Client() apiClient := dockerCLI.Client()
var errs []string
var errs []error
for _, name := range opts.Names { for _, name := range opts.Names {
if err := client.ConfigRemove(ctx, name); err != nil { if err := apiClient.ConfigRemove(ctx, name); err != nil {
errs = append(errs, err.Error()) errs = append(errs, err)
continue continue
} }
_, _ = fmt.Fprintln(dockerCLI.Out(), name)
fmt.Fprintln(dockerCli.Out(), name)
} }
if len(errs) > 0 { return errors.Join(errs...)
return errors.Errorf("%s", strings.Join(errs, "\n"))
}
return nil
} }

View File

@ -2,13 +2,12 @@ package container
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"strings"
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/command/completion"
"github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -44,20 +43,19 @@ func NewKillCommand(dockerCli command.Cli) *cobra.Command {
return cmd return cmd
} }
func runKill(ctx context.Context, dockerCli command.Cli, opts *killOptions) error { func runKill(ctx context.Context, dockerCLI command.Cli, opts *killOptions) error {
var errs []string apiClient := dockerCLI.Client()
errChan := parallelOperation(ctx, opts.containers, func(ctx context.Context, container string) error { errChan := parallelOperation(ctx, opts.containers, func(ctx context.Context, container string) error {
return dockerCli.Client().ContainerKill(ctx, container, opts.signal) return apiClient.ContainerKill(ctx, container, opts.signal)
}) })
var errs []error
for _, name := range opts.containers { for _, name := range opts.containers {
if err := <-errChan; err != nil { if err := <-errChan; err != nil {
errs = append(errs, err.Error()) errs = append(errs, err)
} else { continue
_, _ = fmt.Fprintln(dockerCli.Out(), name)
} }
_, _ = fmt.Fprintln(dockerCLI.Out(), name)
} }
if len(errs) > 0 { return errors.Join(errs...)
return errors.New(strings.Join(errs, "\n"))
}
return nil
} }

View File

@ -2,14 +2,13 @@ package container
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"strings"
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/command/completion"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -38,18 +37,17 @@ func NewPauseCommand(dockerCli command.Cli) *cobra.Command {
} }
} }
func runPause(ctx context.Context, dockerCli command.Cli, opts *pauseOptions) error { func runPause(ctx context.Context, dockerCLI command.Cli, opts *pauseOptions) error {
var errs []string apiClient := dockerCLI.Client()
errChan := parallelOperation(ctx, opts.containers, dockerCli.Client().ContainerPause) errChan := parallelOperation(ctx, opts.containers, apiClient.ContainerPause)
var errs []error
for _, ctr := range opts.containers { for _, ctr := range opts.containers {
if err := <-errChan; err != nil { if err := <-errChan; err != nil {
errs = append(errs, err.Error()) errs = append(errs, err)
continue continue
} }
_, _ = fmt.Fprintln(dockerCli.Out(), ctr) _, _ = fmt.Fprintln(dockerCLI.Out(), ctr)
} }
if len(errs) > 0 { return errors.Join(errs...)
return errors.New(strings.Join(errs, "\n"))
}
return nil
} }

View File

@ -2,14 +2,13 @@ package container
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"strings"
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/command/completion"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -56,27 +55,25 @@ func NewRestartCommand(dockerCli command.Cli) *cobra.Command {
return cmd return cmd
} }
func runRestart(ctx context.Context, dockerCli command.Cli, opts *restartOptions) error { func runRestart(ctx context.Context, dockerCLI command.Cli, opts *restartOptions) error {
var errs []string
var timeout *int var timeout *int
if opts.timeoutChanged { if opts.timeoutChanged {
timeout = &opts.timeout timeout = &opts.timeout
} }
apiClient := dockerCLI.Client()
var errs []error
// TODO(thaJeztah): consider using parallelOperation for restart, similar to "stop" and "remove" // TODO(thaJeztah): consider using parallelOperation for restart, similar to "stop" and "remove"
for _, name := range opts.containers { for _, name := range opts.containers {
err := dockerCli.Client().ContainerRestart(ctx, name, container.StopOptions{ err := apiClient.ContainerRestart(ctx, name, container.StopOptions{
Signal: opts.signal, Signal: opts.signal,
Timeout: timeout, Timeout: timeout,
}) })
if err != nil { if err != nil {
errs = append(errs, err.Error()) errs = append(errs, err)
continue continue
} }
_, _ = fmt.Fprintln(dockerCli.Out(), name) _, _ = fmt.Fprintln(dockerCLI.Out(), name)
} }
if len(errs) > 0 { return errors.Join(errs...)
return errors.New(strings.Join(errs, "\n"))
}
return nil
} }

View File

@ -2,6 +2,7 @@ package container
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"strings" "strings"
@ -10,7 +11,6 @@ import (
"github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/command/completion"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/errdefs" "github.com/docker/docker/errdefs"
"github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -50,33 +50,31 @@ func NewRmCommand(dockerCli command.Cli) *cobra.Command {
return cmd return cmd
} }
func runRm(ctx context.Context, dockerCli command.Cli, opts *rmOptions) error { func runRm(ctx context.Context, dockerCLI command.Cli, opts *rmOptions) error {
var errs []string apiClient := dockerCLI.Client()
errChan := parallelOperation(ctx, opts.containers, func(ctx context.Context, ctrID string) error { errChan := parallelOperation(ctx, opts.containers, func(ctx context.Context, ctrID string) error {
ctrID = strings.Trim(ctrID, "/") ctrID = strings.Trim(ctrID, "/")
if ctrID == "" { if ctrID == "" {
return errors.New("Container name cannot be empty") return errors.New("container name cannot be empty")
} }
return dockerCli.Client().ContainerRemove(ctx, ctrID, container.RemoveOptions{ return apiClient.ContainerRemove(ctx, ctrID, container.RemoveOptions{
RemoveVolumes: opts.rmVolumes, RemoveVolumes: opts.rmVolumes,
RemoveLinks: opts.rmLink, RemoveLinks: opts.rmLink,
Force: opts.force, Force: opts.force,
}) })
}) })
var errs []error
for _, name := range opts.containers { for _, name := range opts.containers {
if err := <-errChan; err != nil { if err := <-errChan; err != nil {
if opts.force && errdefs.IsNotFound(err) { if opts.force && errdefs.IsNotFound(err) {
fmt.Fprintln(dockerCli.Err(), err) _, _ = fmt.Fprintln(dockerCLI.Err(), err)
continue continue
} }
errs = append(errs, err.Error()) errs = append(errs, err)
continue continue
} }
fmt.Fprintln(dockerCli.Out(), name) _, _ = fmt.Fprintln(dockerCLI.Out(), name)
} }
if len(errs) > 0 { return errors.Join(errs...)
return errors.New(strings.Join(errs, "\n"))
}
return nil
} }

View File

@ -3,6 +3,7 @@ package container
import ( import (
"bytes" "bytes"
"context" "context"
"errors"
"fmt" "fmt"
"io" "io"
"strings" "strings"
@ -17,7 +18,6 @@ import (
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/events" "github.com/docker/docker/api/types/events"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
"github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -238,16 +238,16 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
// make sure each container get at least one valid stat data // make sure each container get at least one valid stat data
waitFirst.Wait() waitFirst.Wait()
var errs []string var errs []error
cStats.mu.RLock() cStats.mu.RLock()
for _, c := range cStats.cs { for _, c := range cStats.cs {
if err := c.GetError(); err != nil { if err := c.GetError(); err != nil {
errs = append(errs, err.Error()) errs = append(errs, err)
} }
} }
cStats.mu.RUnlock() cStats.mu.RUnlock()
if len(errs) > 0 { if err := errors.Join(errs...); err != nil {
return errors.New(strings.Join(errs, "\n")) return err
} }
} }

View File

@ -2,14 +2,13 @@ package container
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"strings"
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/command/completion"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -56,28 +55,26 @@ func NewStopCommand(dockerCli command.Cli) *cobra.Command {
return cmd return cmd
} }
func runStop(ctx context.Context, dockerCli command.Cli, opts *stopOptions) error { func runStop(ctx context.Context, dockerCLI command.Cli, opts *stopOptions) error {
var timeout *int var timeout *int
if opts.timeoutChanged { if opts.timeoutChanged {
timeout = &opts.timeout timeout = &opts.timeout
} }
apiClient := dockerCLI.Client()
errChan := parallelOperation(ctx, opts.containers, func(ctx context.Context, id string) error { errChan := parallelOperation(ctx, opts.containers, func(ctx context.Context, id string) error {
return dockerCli.Client().ContainerStop(ctx, id, container.StopOptions{ return apiClient.ContainerStop(ctx, id, container.StopOptions{
Signal: opts.signal, Signal: opts.signal,
Timeout: timeout, Timeout: timeout,
}) })
}) })
var errs []string var errs []error
for _, ctr := range opts.containers { for _, ctr := range opts.containers {
if err := <-errChan; err != nil { if err := <-errChan; err != nil {
errs = append(errs, err.Error()) errs = append(errs, err)
continue continue
} }
_, _ = fmt.Fprintln(dockerCli.Out(), ctr) _, _ = fmt.Fprintln(dockerCLI.Out(), ctr)
} }
if len(errs) > 0 { return errors.Join(errs...)
return errors.New(strings.Join(errs, "\n"))
}
return nil
} }

View File

@ -2,14 +2,13 @@ package container
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"strings"
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/command/completion"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -39,18 +38,16 @@ func NewUnpauseCommand(dockerCli command.Cli) *cobra.Command {
return cmd return cmd
} }
func runUnpause(ctx context.Context, dockerCli command.Cli, opts *unpauseOptions) error { func runUnpause(ctx context.Context, dockerCLI command.Cli, opts *unpauseOptions) error {
var errs []string apiClient := dockerCLI.Client()
errChan := parallelOperation(ctx, opts.containers, dockerCli.Client().ContainerUnpause) errChan := parallelOperation(ctx, opts.containers, apiClient.ContainerUnpause)
var errs []error
for _, ctr := range opts.containers { for _, ctr := range opts.containers {
if err := <-errChan; err != nil { if err := <-errChan; err != nil {
errs = append(errs, err.Error()) errs = append(errs, err)
continue continue
} }
_, _ = fmt.Fprintln(dockerCli.Out(), ctr) _, _ = fmt.Fprintln(dockerCLI.Out(), ctr)
} }
if len(errs) > 0 { return errors.Join(errs...)
return errors.New(strings.Join(errs, "\n"))
}
return nil
} }

View File

@ -2,13 +2,12 @@ package container
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"strings"
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/command/completion"
"github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -37,20 +36,19 @@ func NewWaitCommand(dockerCli command.Cli) *cobra.Command {
return cmd return cmd
} }
func runWait(ctx context.Context, dockerCli command.Cli, opts *waitOptions) error { func runWait(ctx context.Context, dockerCLI command.Cli, opts *waitOptions) error {
var errs []string apiClient := dockerCLI.Client()
var errs []error
for _, ctr := range opts.containers { for _, ctr := range opts.containers {
resultC, errC := dockerCli.Client().ContainerWait(ctx, ctr, "") resultC, errC := apiClient.ContainerWait(ctx, ctr, "")
select { select {
case result := <-resultC: case result := <-resultC:
_, _ = fmt.Fprintf(dockerCli.Out(), "%d\n", result.StatusCode) _, _ = fmt.Fprintf(dockerCLI.Out(), "%d\n", result.StatusCode)
case err := <-errC: case err := <-errC:
errs = append(errs, err.Error()) errs = append(errs, err)
} }
} }
if len(errs) > 0 { return errors.Join(errs...)
return errors.New(strings.Join(errs, "\n"))
}
return nil
} }

View File

@ -1,14 +1,14 @@
package context package context
import ( import (
"errors"
"fmt"
"strconv" "strconv"
"strings"
"github.com/docker/cli/cli/context" "github.com/docker/cli/cli/context"
"github.com/docker/cli/cli/context/docker" "github.com/docker/cli/cli/context/docker"
"github.com/docker/cli/cli/context/store" "github.com/docker/cli/cli/context/store"
"github.com/docker/docker/client" "github.com/docker/docker/client"
"github.com/pkg/errors"
) )
const ( const (
@ -68,20 +68,17 @@ func parseBool(config map[string]string, name string) (bool, error) {
return false, nil return false, nil
} }
res, err := strconv.ParseBool(strVal) res, err := strconv.ParseBool(strVal)
return res, errors.Wrap(err, name) return res, fmt.Errorf("name: %w", err)
} }
func validateConfig(config map[string]string, allowedKeys map[string]struct{}) error { func validateConfig(config map[string]string, allowedKeys map[string]struct{}) error {
var errs []string var errs []error
for k := range config { for k := range config {
if _, ok := allowedKeys[k]; !ok { if _, ok := allowedKeys[k]; !ok {
errs = append(errs, "unrecognized config key: "+k) errs = append(errs, errors.New("unrecognized config key: "+k))
} }
} }
if len(errs) == 0 { return errors.Join(errs...)
return nil
}
return errors.New(strings.Join(errs, "\n"))
} }
func getDockerEndpoint(contextStore store.Reader, config map[string]string) (docker.Endpoint, error) { func getDockerEndpoint(contextStore store.Reader, config map[string]string) (docker.Endpoint, error) {
@ -96,7 +93,7 @@ func getDockerEndpoint(contextStore store.Reader, config map[string]string) (doc
if ep, ok := metadata.Endpoints[docker.DockerEndpoint].(docker.EndpointMeta); ok { if ep, ok := metadata.Endpoints[docker.DockerEndpoint].(docker.EndpointMeta); ok {
return docker.Endpoint{EndpointMeta: ep}, nil return docker.Endpoint{EndpointMeta: ep}, nil
} }
return docker.Endpoint{}, errors.Errorf("unable to get endpoint from context %q", contextName) return docker.Endpoint{}, fmt.Errorf("unable to get endpoint from context %q", contextName)
} }
tlsData, err := context.TLSDataFromFiles(config[keyCA], config[keyCert], config[keyKey]) tlsData, err := context.TLSDataFromFiles(config[keyCA], config[keyCert], config[keyKey])
if err != nil { if err != nil {
@ -116,10 +113,11 @@ func getDockerEndpoint(contextStore store.Reader, config map[string]string) (doc
// try to resolve a docker client, validating the configuration // try to resolve a docker client, validating the configuration
opts, err := ep.ClientOpts() opts, err := ep.ClientOpts()
if err != nil { if err != nil {
return docker.Endpoint{}, errors.Wrap(err, "invalid docker endpoint options") return docker.Endpoint{}, fmt.Errorf("invalid docker endpoint options: %w", err)
} }
// FIXME(thaJeztah): this creates a new client (but discards it) only to validate the options; are the validation steps above not enough?
if _, err := client.NewClientWithOpts(opts...); err != nil { if _, err := client.NewClientWithOpts(opts...); err != nil {
return docker.Endpoint{}, errors.Wrap(err, "unable to apply docker endpoint options") return docker.Endpoint{}, fmt.Errorf("unable to apply docker endpoint options: %w", err)
} }
return ep, nil return ep, nil
} }

View File

@ -1,14 +1,13 @@
package context package context
import ( import (
"errors"
"fmt" "fmt"
"os" "os"
"strings"
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/docker/errdefs" "github.com/docker/docker/errdefs"
"github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -33,28 +32,25 @@ func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
} }
// RunRemove removes one or more contexts // RunRemove removes one or more contexts
func RunRemove(dockerCli command.Cli, opts RemoveOptions, names []string) error { func RunRemove(dockerCLI command.Cli, opts RemoveOptions, names []string) error {
var errs []string var errs []error
currentCtx := dockerCli.CurrentContext() currentCtx := dockerCLI.CurrentContext()
for _, name := range names { for _, name := range names {
if name == "default" { if name == "default" {
errs = append(errs, `default: context "default" cannot be removed`) errs = append(errs, errors.New(`context "default" cannot be removed`))
} else if err := doRemove(dockerCli, name, name == currentCtx, opts.Force); err != nil { } else if err := doRemove(dockerCLI, name, name == currentCtx, opts.Force); err != nil {
errs = append(errs, err.Error()) errs = append(errs, err)
} else { } else {
fmt.Fprintln(dockerCli.Out(), name) _, _ = fmt.Fprintln(dockerCLI.Out(), name)
} }
} }
if len(errs) > 0 { return errors.Join(errs...)
return errors.New(strings.Join(errs, "\n"))
}
return nil
} }
func doRemove(dockerCli command.Cli, name string, isCurrent, force bool) error { func doRemove(dockerCli command.Cli, name string, isCurrent, force bool) error {
if isCurrent { if isCurrent {
if !force { if !force {
return errors.Errorf("context %q is in use, set -f flag to force remove", name) return fmt.Errorf("context %q is in use, set -f flag to force remove", name)
} }
// fallback to DOCKER_HOST // fallback to DOCKER_HOST
cfg := dockerCli.ConfigFile() cfg := dockerCli.ConfigFile()
@ -65,6 +61,7 @@ func doRemove(dockerCli command.Cli, name string, isCurrent, force bool) error {
} }
if !force { if !force {
// TODO(thaJeztah): instead of checking before removing, can we make ContextStore().Remove() return a proper errdef and ignore "not found" errors?
if err := checkContextExists(dockerCli, name); err != nil { if err := checkContextExists(dockerCli, name); err != nil {
return err return err
} }
@ -77,7 +74,7 @@ func checkContextExists(dockerCli command.Cli, name string) error {
contextDir := dockerCli.ContextStore().GetStorageInfo(name).MetadataPath contextDir := dockerCli.ContextStore().GetStorageInfo(name).MetadataPath
_, err := os.Stat(contextDir) _, err := os.Stat(contextDir)
if os.IsNotExist(err) { if os.IsNotExist(err) {
return errdefs.NotFound(errors.Errorf("context %q does not exist", name)) return errdefs.NotFound(fmt.Errorf("context %q does not exist", name))
} }
// Ignore other errors; if relevant, they will produce an error when // Ignore other errors; if relevant, they will produce an error when
// performing the actual delete. // performing the actual delete.

View File

@ -60,5 +60,5 @@ func TestRemoveDefault(t *testing.T) {
createTestContext(t, cli, "other", nil) createTestContext(t, cli, "other", nil)
cli.SetCurrentContext("current") cli.SetCurrentContext("current")
err := RunRemove(cli, RemoveOptions{}, []string{"default"}) err := RunRemove(cli, RemoveOptions{}, []string{"default"})
assert.ErrorContains(t, err, `default: context "default" cannot be removed`) assert.ErrorContains(t, err, `context "default" cannot be removed`)
} }

View File

@ -2,15 +2,14 @@ package image
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"strings"
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/command/completion"
"github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/image"
"github.com/docker/docker/errdefs" "github.com/docker/docker/errdefs"
"github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -59,15 +58,16 @@ func runRemove(ctx context.Context, dockerCLI command.Cli, opts removeOptions, i
PruneChildren: !opts.noPrune, PruneChildren: !opts.noPrune,
} }
var errs []string // TODO(thaJeztah): this logic can likely be simplified: do we want to print "not found" errors at all when using "force"?
fatalErr := false fatalErr := false
var errs []error
for _, img := range images { for _, img := range images {
dels, err := apiClient.ImageRemove(ctx, img, options) dels, err := apiClient.ImageRemove(ctx, img, options)
if err != nil { if err != nil {
if !errdefs.IsNotFound(err) { if !errdefs.IsNotFound(err) {
fatalErr = true fatalErr = true
} }
errs = append(errs, err.Error()) errs = append(errs, err)
} else { } else {
for _, del := range dels { for _, del := range dels {
if del.Deleted != "" { if del.Deleted != "" {
@ -79,12 +79,11 @@ func runRemove(ctx context.Context, dockerCLI command.Cli, opts removeOptions, i
} }
} }
if len(errs) > 0 { if err := errors.Join(errs...); err != nil {
msg := strings.Join(errs, "\n")
if !opts.force || fatalErr { if !opts.force || fatalErr {
return errors.New(msg) return err
} }
_, _ = fmt.Fprintln(dockerCLI.Err(), msg) _, _ = fmt.Fprintln(dockerCLI.Err(), err)
} }
return nil return nil
} }

View File

@ -6,13 +6,13 @@ package inspect
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"errors"
"fmt"
"io" "io"
"strings"
"text/template" "text/template"
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
"github.com/docker/cli/templates" "github.com/docker/cli/templates"
"github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@ -53,7 +53,7 @@ func NewTemplateInspectorFromString(out io.Writer, tmplStr string) (Inspector, e
tmpl, err := templates.Parse(tmplStr) tmpl, err := templates.Parse(tmplStr)
if err != nil { if err != nil {
return nil, errors.Errorf("template parsing error: %s", err) return nil, fmt.Errorf("template parsing error: %w", err)
} }
return NewTemplateInspector(out, tmpl), nil return NewTemplateInspector(out, tmpl), nil
} }
@ -70,16 +70,16 @@ func Inspect(out io.Writer, references []string, tmplStr string, getRef GetRefFu
return cli.StatusError{StatusCode: 64, Status: err.Error()} return cli.StatusError{StatusCode: 64, Status: err.Error()}
} }
var inspectErrs []string var errs []error
for _, ref := range references { for _, ref := range references {
element, raw, err := getRef(ref) element, raw, err := getRef(ref)
if err != nil { if err != nil {
inspectErrs = append(inspectErrs, err.Error()) errs = append(errs, err)
continue continue
} }
if err := inspector.Inspect(element, raw); err != nil { if err := inspector.Inspect(element, raw); err != nil {
inspectErrs = append(inspectErrs, err.Error()) errs = append(errs, err)
} }
} }
@ -87,10 +87,10 @@ func Inspect(out io.Writer, references []string, tmplStr string, getRef GetRefFu
logrus.Error(err) logrus.Error(err)
} }
if len(inspectErrs) != 0 { if err := errors.Join(errs...); err != nil {
return cli.StatusError{ return cli.StatusError{
StatusCode: 1, StatusCode: 1,
Status: strings.Join(inspectErrs, "\n"), Status: err.Error(),
} }
} }
return nil return nil
@ -103,7 +103,7 @@ func (i *TemplateInspector) Inspect(typedElement any, rawElement []byte) error {
buffer := new(bytes.Buffer) buffer := new(bytes.Buffer)
if err := i.tmpl.Execute(buffer, typedElement); err != nil { if err := i.tmpl.Execute(buffer, typedElement); err != nil {
if rawElement == nil { if rawElement == nil {
return errors.Errorf("template parsing error: %v", err) return fmt.Errorf("template parsing error: %w", err)
} }
return i.tryRawInspectFallback(rawElement) return i.tryRawInspectFallback(rawElement)
} }
@ -121,13 +121,13 @@ func (i *TemplateInspector) tryRawInspectFallback(rawElement []byte) error {
dec := json.NewDecoder(rdr) dec := json.NewDecoder(rdr)
dec.UseNumber() dec.UseNumber()
if rawErr := dec.Decode(&raw); rawErr != nil { if err := dec.Decode(&raw); err != nil {
return errors.Errorf("unable to read inspect data: %v", rawErr) return fmt.Errorf("unable to read inspect data: %w", err)
} }
tmplMissingKey := i.tmpl.Option("missingkey=error") tmplMissingKey := i.tmpl.Option("missingkey=error")
if rawErr := tmplMissingKey.Execute(buffer, raw); rawErr != nil { if err := tmplMissingKey.Execute(buffer, raw); err != nil {
return errors.Errorf("template parsing error: %v", rawErr) return fmt.Errorf("template parsing error: %w", err)
} }
i.buffer.Write(buffer.Bytes()) i.buffer.Write(buffer.Bytes())

View File

@ -2,47 +2,47 @@ package manifest
import ( import (
"context" "context"
"strings" "errors"
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/pkg/errors" manifeststore "github.com/docker/cli/cli/manifest/store"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func newRmManifestListCommand(dockerCli command.Cli) *cobra.Command { func newRmManifestListCommand(dockerCLI command.Cli) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "rm MANIFEST_LIST [MANIFEST_LIST...]", Use: "rm MANIFEST_LIST [MANIFEST_LIST...]",
Short: "Delete one or more manifest lists from local storage", Short: "Delete one or more manifest lists from local storage",
Args: cli.RequiresMinArgs(1), Args: cli.RequiresMinArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
return runRm(cmd.Context(), dockerCli, args) return runRemove(cmd.Context(), dockerCLI.ManifestStore(), args)
}, },
} }
return cmd return cmd
} }
func runRm(_ context.Context, dockerCli command.Cli, targets []string) error { func runRemove(ctx context.Context, store manifeststore.Store, targets []string) error {
var errs []string var errs []error
for _, target := range targets { for _, target := range targets {
targetRef, refErr := normalizeReference(target) if ctx.Err() != nil {
if refErr != nil { return ctx.Err()
errs = append(errs, refErr.Error()) }
targetRef, err := normalizeReference(target)
if err != nil {
errs = append(errs, err)
continue continue
} }
_, searchErr := dockerCli.ManifestStore().GetList(targetRef) _, err = store.GetList(targetRef)
if searchErr != nil { if err != nil {
errs = append(errs, searchErr.Error()) errs = append(errs, err)
continue continue
} }
rmErr := dockerCli.ManifestStore().Remove(targetRef) err = store.Remove(targetRef)
if rmErr != nil { if err != nil {
errs = append(errs, rmErr.Error()) errs = append(errs, err)
} }
} }
if len(errs) > 0 { return errors.Join(errs...)
return errors.New(strings.Join(errs, "\n"))
}
return nil
} }

View File

@ -2,13 +2,12 @@ package node
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"strings"
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -36,20 +35,13 @@ func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
func runRemove(ctx context.Context, dockerCLI command.Cli, nodeIDs []string, opts removeOptions) error { func runRemove(ctx context.Context, dockerCLI command.Cli, nodeIDs []string, opts removeOptions) error {
apiClient := dockerCLI.Client() apiClient := dockerCLI.Client()
var errs []string var errs []error
for _, id := range nodeIDs { for _, id := range nodeIDs {
err := apiClient.NodeRemove(ctx, id, types.NodeRemoveOptions{Force: opts.force}) if err := apiClient.NodeRemove(ctx, id, types.NodeRemoveOptions{Force: opts.force}); err != nil {
if err != nil { errs = append(errs, err)
errs = append(errs, err.Error())
continue continue
} }
_, _ = fmt.Fprintln(dockerCLI.Out(), id) _, _ = fmt.Fprintln(dockerCLI.Out(), id)
} }
return errors.Join(errs...)
if len(errs) > 0 {
return errors.Errorf("%s", strings.Join(errs, "\n"))
}
return nil
} }

View File

@ -2,12 +2,11 @@ package secret
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"strings"
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -15,7 +14,7 @@ type removeOptions struct {
names []string names []string
} }
func newSecretRemoveCommand(dockerCli command.Cli) *cobra.Command { func newSecretRemoveCommand(dockerCLI command.Cli) *cobra.Command {
return &cobra.Command{ return &cobra.Command{
Use: "rm SECRET [SECRET...]", Use: "rm SECRET [SECRET...]",
Aliases: []string{"remove"}, Aliases: []string{"remove"},
@ -25,31 +24,24 @@ func newSecretRemoveCommand(dockerCli command.Cli) *cobra.Command {
opts := removeOptions{ opts := removeOptions{
names: args, names: args,
} }
return runSecretRemove(cmd.Context(), dockerCli, opts) return runRemove(cmd.Context(), dockerCLI, opts)
}, },
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return completeNames(dockerCli)(cmd, args, toComplete) return completeNames(dockerCLI)(cmd, args, toComplete)
}, },
} }
} }
func runSecretRemove(ctx context.Context, dockerCli command.Cli, opts removeOptions) error { func runRemove(ctx context.Context, dockerCLI command.Cli, opts removeOptions) error {
client := dockerCli.Client() apiClient := dockerCLI.Client()
var errs []string
var errs []error
for _, name := range opts.names { for _, name := range opts.names {
if err := client.SecretRemove(ctx, name); err != nil { if err := apiClient.SecretRemove(ctx, name); err != nil {
errs = append(errs, err.Error()) errs = append(errs, err)
continue continue
} }
_, _ = fmt.Fprintln(dockerCLI.Out(), name)
fmt.Fprintln(dockerCli.Out(), name)
} }
return errors.Join(errs...)
if len(errs) > 0 {
return errors.Errorf("%s", strings.Join(errs, "\n"))
}
return nil
} }

View File

@ -2,12 +2,11 @@ package service
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"strings"
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -32,17 +31,13 @@ func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
func runRemove(ctx context.Context, dockerCLI command.Cli, serviceIDs []string) error { func runRemove(ctx context.Context, dockerCLI command.Cli, serviceIDs []string) error {
apiClient := dockerCLI.Client() apiClient := dockerCLI.Client()
var errs []string var errs []error
for _, id := range serviceIDs { for _, id := range serviceIDs {
err := apiClient.ServiceRemove(ctx, id) if err := apiClient.ServiceRemove(ctx, id); err != nil {
if err != nil { errs = append(errs, err)
errs = append(errs, err.Error())
continue continue
} }
_, _ = fmt.Fprintln(dockerCLI.Out(), id) _, _ = fmt.Fprintln(dockerCLI.Out(), id)
} }
if len(errs) > 0 { return errors.Join(errs...)
return errors.New(strings.Join(errs, "\n"))
}
return nil
} }

View File

@ -42,12 +42,9 @@ func runRollback(ctx context.Context, dockerCLI command.Cli, options *serviceOpt
return err return err
} }
spec := &service.Spec response, err := apiClient.ServiceUpdate(ctx, service.ID, service.Version, service.Spec, types.ServiceUpdateOptions{
updateOpts := types.ServiceUpdateOptions{ Rollback: "previous", // TODO(thaJeztah): this should have a const defined
Rollback: "previous", })
}
response, err := apiClient.ServiceUpdate(ctx, service.ID, service.Version, *spec, updateOpts)
if err != nil { if err != nil {
return err return err
} }

View File

@ -2,6 +2,7 @@ package service
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"strconv" "strconv"
"strings" "strings"
@ -10,7 +11,7 @@ import (
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/versions" "github.com/docker/docker/api/types/versions"
"github.com/pkg/errors" "github.com/docker/docker/client"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -44,8 +45,8 @@ func scaleArgs(cmd *cobra.Command, args []string) error {
} }
for _, arg := range args { for _, arg := range args {
if k, v, ok := strings.Cut(arg, "="); !ok || k == "" || v == "" { if k, v, ok := strings.Cut(arg, "="); !ok || k == "" || v == "" {
return errors.Errorf( return fmt.Errorf(
"Invalid scale specifier '%s'.\nSee '%s --help'.\n\nUsage: %s\n\n%s", "invalid scale specifier '%s'.\nSee '%s --help'.\n\nUsage: %s\n\n%s",
arg, arg,
cmd.CommandPath(), cmd.CommandPath(),
cmd.UseLine(), cmd.UseLine(),
@ -56,49 +57,48 @@ func scaleArgs(cmd *cobra.Command, args []string) error {
return nil return nil
} }
func runScale(ctx context.Context, dockerCli command.Cli, options *scaleOptions, args []string) error { func runScale(ctx context.Context, dockerCLI command.Cli, options *scaleOptions, args []string) error {
var errs []string apiClient := dockerCLI.Client()
var serviceIDs []string var (
errs []error
serviceIDs = make([]string, 0, len(args))
)
for _, arg := range args { for _, arg := range args {
serviceID, scaleStr, _ := strings.Cut(arg, "=") serviceID, scaleStr, _ := strings.Cut(arg, "=")
// validate input arg scale number // validate input arg scale number
scale, err := strconv.ParseUint(scaleStr, 10, 64) scale, err := strconv.ParseUint(scaleStr, 10, 64)
if err != nil { if err != nil {
errs = append(errs, fmt.Sprintf("%s: invalid replicas value %s: %v", serviceID, scaleStr, err)) errs = append(errs, fmt.Errorf("%s: invalid replicas value %s: %v", serviceID, scaleStr, err))
continue continue
} }
if err := runServiceScale(ctx, dockerCli, serviceID, scale); err != nil { warnings, err := runServiceScale(ctx, apiClient, serviceID, scale)
errs = append(errs, fmt.Sprintf("%s: %v", serviceID, err)) if err != nil {
} else { errs = append(errs, fmt.Errorf("%s: %v", serviceID, err))
serviceIDs = append(serviceIDs, serviceID) continue
} }
for _, warning := range warnings {
_, _ = fmt.Fprintln(dockerCLI.Err(), warning)
}
_, _ = fmt.Fprintf(dockerCLI.Out(), "%s scaled to %d\n", serviceID, scale)
serviceIDs = append(serviceIDs, serviceID)
} }
if len(serviceIDs) > 0 { if len(serviceIDs) > 0 && !options.detach && versions.GreaterThanOrEqualTo(dockerCLI.Client().ClientVersion(), "1.29") {
if !options.detach && versions.GreaterThanOrEqualTo(dockerCli.Client().ClientVersion(), "1.29") { for _, serviceID := range serviceIDs {
for _, serviceID := range serviceIDs { if err := WaitOnService(ctx, dockerCLI, serviceID, false); err != nil {
if err := WaitOnService(ctx, dockerCli, serviceID, false); err != nil { errs = append(errs, fmt.Errorf("%s: %v", serviceID, err))
errs = append(errs, fmt.Sprintf("%s: %v", serviceID, err))
}
} }
} }
} }
return errors.Join(errs...)
if len(errs) == 0 {
return nil
}
return errors.New(strings.Join(errs, "\n"))
} }
func runServiceScale(ctx context.Context, dockerCli command.Cli, serviceID string, scale uint64) error { func runServiceScale(ctx context.Context, apiClient client.ServiceAPIClient, serviceID string, scale uint64) (warnings []string, _ error) {
client := dockerCli.Client() service, _, err := apiClient.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{})
service, _, err := client.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{})
if err != nil { if err != nil {
return err return nil, err
} }
serviceMode := &service.Spec.Mode serviceMode := &service.Spec.Mode
@ -108,18 +108,12 @@ func runServiceScale(ctx context.Context, dockerCli command.Cli, serviceID strin
case serviceMode.ReplicatedJob != nil: case serviceMode.ReplicatedJob != nil:
serviceMode.ReplicatedJob.TotalCompletions = &scale serviceMode.ReplicatedJob.TotalCompletions = &scale
default: default:
return errors.Errorf("scale can only be used with replicated or replicated-job mode") return nil, errors.New("scale can only be used with replicated or replicated-job mode")
} }
response, err := client.ServiceUpdate(ctx, service.ID, service.Version, service.Spec, types.ServiceUpdateOptions{}) response, err := apiClient.ServiceUpdate(ctx, service.ID, service.Version, service.Spec, types.ServiceUpdateOptions{})
if err != nil { if err != nil {
return err return nil, err
} }
return response.Warnings, nil
for _, warning := range response.Warnings {
_, _ = fmt.Fprintln(dockerCli.Err(), warning)
}
_, _ = fmt.Fprintf(dockerCli.Out(), "%s scaled to %d\n", serviceID, scale)
return nil
} }

View File

@ -161,7 +161,7 @@ func TestRemoveContinueAfterError(t *testing.T) {
cmd.SetErr(io.Discard) cmd.SetErr(io.Discard)
cmd.SetArgs([]string{"foo", "bar"}) cmd.SetArgs([]string{"foo", "bar"})
assert.Error(t, cmd.Execute(), "Failed to remove some resources from stack: foo") assert.Error(t, cmd.Execute(), "failed to remove some resources from stack: foo")
assert.Check(t, is.DeepEqual(allServiceIDs, removedServices)) assert.Check(t, is.DeepEqual(allServiceIDs, removedServices))
assert.Check(t, is.DeepEqual(allNetworkIDs, cli.removedNetworks)) assert.Check(t, is.DeepEqual(allNetworkIDs, cli.removedNetworks))
assert.Check(t, is.DeepEqual(allSecretIDs, cli.removedSecrets)) assert.Check(t, is.DeepEqual(allSecretIDs, cli.removedSecrets))

View File

@ -2,9 +2,9 @@ package swarm
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"sort" "sort"
"strings"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/stack/options" "github.com/docker/cli/cli/command/stack/options"
@ -12,14 +12,13 @@ import (
"github.com/docker/docker/api/types/swarm" "github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/versions" "github.com/docker/docker/api/types/versions"
"github.com/docker/docker/client" "github.com/docker/docker/client"
"github.com/pkg/errors"
) )
// RunRemove is the swarm implementation of docker stack remove // RunRemove is the swarm implementation of docker stack remove
func RunRemove(ctx context.Context, dockerCli command.Cli, opts options.Remove) error { func RunRemove(ctx context.Context, dockerCli command.Cli, opts options.Remove) error {
apiClient := dockerCli.Client() apiClient := dockerCli.Client()
var errs []string var errs []error
for _, namespace := range opts.Namespaces { for _, namespace := range opts.Namespaces {
services, err := getStackServices(ctx, apiClient, namespace) services, err := getStackServices(ctx, apiClient, namespace)
if err != nil { if err != nil {
@ -52,28 +51,25 @@ func RunRemove(ctx context.Context, dockerCli command.Cli, opts options.Remove)
continue continue
} }
// TODO(thaJeztah): change this "hasError" boolean to return a (multi-)error for each of these functions instead.
hasError := removeServices(ctx, dockerCli, services) hasError := removeServices(ctx, dockerCli, services)
hasError = removeSecrets(ctx, dockerCli, secrets) || hasError hasError = removeSecrets(ctx, dockerCli, secrets) || hasError
hasError = removeConfigs(ctx, dockerCli, configs) || hasError hasError = removeConfigs(ctx, dockerCli, configs) || hasError
hasError = removeNetworks(ctx, dockerCli, networks) || hasError hasError = removeNetworks(ctx, dockerCli, networks) || hasError
if hasError { if hasError {
errs = append(errs, "Failed to remove some resources from stack: "+namespace) errs = append(errs, errors.New("failed to remove some resources from stack: "+namespace))
continue continue
} }
if !opts.Detach { if !opts.Detach {
err = waitOnTasks(ctx, apiClient, namespace) err = waitOnTasks(ctx, apiClient, namespace)
if err != nil { if err != nil {
errs = append(errs, fmt.Sprintf("Failed to wait on tasks of stack: %s: %s", namespace, err)) errs = append(errs, fmt.Errorf("failed to wait on tasks of stack: %s: %w", namespace, err))
} }
} }
} }
return errors.Join(errs...)
if len(errs) > 0 {
return errors.New(strings.Join(errs, "\n"))
}
return nil
} }
func sortServiceByName(services []swarm.Service) func(i, j int) bool { func sortServiceByName(services []swarm.Service) func(i, j int) bool {

View File

@ -2,13 +2,12 @@ package volume
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"strings"
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/command/completion"
"github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -43,18 +42,13 @@ func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
func runRemove(ctx context.Context, dockerCLI command.Cli, opts *removeOptions) error { func runRemove(ctx context.Context, dockerCLI command.Cli, opts *removeOptions) error {
apiClient := dockerCLI.Client() apiClient := dockerCLI.Client()
var errs []string var errs []error
for _, name := range opts.volumes { for _, name := range opts.volumes {
if err := apiClient.VolumeRemove(ctx, name, opts.force); err != nil { if err := apiClient.VolumeRemove(ctx, name, opts.force); err != nil {
errs = append(errs, err.Error()) errs = append(errs, err)
continue continue
} }
_, _ = fmt.Fprintln(dockerCLI.Out(), name) _, _ = fmt.Fprintln(dockerCLI.Out(), name)
} }
return errors.Join(errs...)
if len(errs) > 0 {
return errors.Errorf("%s", strings.Join(errs, "\n"))
}
return nil
} }