diff --git a/cli/command/builder/prune.go b/cli/command/builder/prune.go index ab04ea3597..7a323a3939 100644 --- a/cli/command/builder/prune.go +++ b/cli/command/builder/prune.go @@ -12,8 +12,7 @@ import ( "github.com/docker/cli/internal/prompt" "github.com/docker/cli/opts" "github.com/docker/docker/api/types/build" - "github.com/docker/docker/errdefs" - units "github.com/docker/go-units" + "github.com/docker/go-units" "github.com/spf13/cobra" ) @@ -75,7 +74,7 @@ func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions) return 0, "", err } if !r { - return 0, "", errdefs.Cancelled(errors.New("builder prune has been cancelled")) + return 0, "", cancelledErr{errors.New("builder prune has been cancelled")} } } @@ -101,6 +100,10 @@ func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions) return report.SpaceReclaimed, output, nil } +type cancelledErr struct{ error } + +func (cancelledErr) Cancelled() {} + // CachePrune executes a prune command for build cache func CachePrune(ctx context.Context, dockerCli command.Cli, all bool, filter opts.FilterOpt) (uint64, string, error) { return runPrune(ctx, dockerCli, pruneOptions{force: true, all: all, filter: filter}) diff --git a/cli/command/cli_options.go b/cli/command/cli_options.go index ff6506f042..5d0a8ee3a6 100644 --- a/cli/command/cli_options.go +++ b/cli/command/cli_options.go @@ -11,7 +11,6 @@ import ( "github.com/docker/cli/cli/streams" "github.com/docker/docker/client" - "github.com/docker/docker/errdefs" "github.com/moby/term" "github.com/pkg/errors" ) @@ -178,7 +177,7 @@ func withCustomHeadersFromEnv() client.Opt { csvReader := csv.NewReader(strings.NewReader(value)) fields, err := csvReader.Read() if err != nil { - return errdefs.InvalidParameter(errors.Errorf( + return invalidParameter(errors.Errorf( "failed to parse custom headers from %s environment variable: value must be formatted as comma-separated key=value pairs", envOverrideHTTPHeaders, )) @@ -195,7 +194,7 @@ func withCustomHeadersFromEnv() client.Opt { k = strings.TrimSpace(k) if k == "" { - return errdefs.InvalidParameter(errors.Errorf( + 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, )) @@ -206,7 +205,7 @@ 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( + return invalidParameter(errors.Errorf( `failed to set custom headers from %s environment variable: missing "=" in key=value pair: '%s'`, envOverrideHTTPHeaders, kv, )) diff --git a/cli/command/container/create.go b/cli/command/container/create.go index 0e091b6718..beb11995de 100644 --- a/cli/command/container/create.go +++ b/cli/command/container/create.go @@ -29,7 +29,6 @@ import ( "github.com/docker/docker/api/types/mount" "github.com/docker/docker/api/types/versions" "github.com/docker/docker/client" - "github.com/docker/docker/errdefs" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -317,7 +316,7 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerCfg *c if options.platform != "" && versions.GreaterThanOrEqualTo(dockerCli.Client().ClientVersion(), "1.41") { p, err := platforms.Parse(options.platform) if err != nil { - return "", errors.Wrap(errdefs.InvalidParameter(err), "error parsing specified platform") + return "", errors.Wrap(invalidParameter(err), "error parsing specified platform") } platform = &p } diff --git a/cli/command/container/errors.go b/cli/command/container/errors.go new file mode 100644 index 0000000000..957aa25fa6 --- /dev/null +++ b/cli/command/container/errors.go @@ -0,0 +1,31 @@ +package container + +import cerrdefs "github.com/containerd/errdefs" + +func invalidParameter(err error) error { + if err == nil || cerrdefs.IsInvalidArgument(err) { + return err + } + return invalidParameterErr{err} +} + +type invalidParameterErr struct{ error } + +func (invalidParameterErr) InvalidParameter() {} +func (e invalidParameterErr) Unwrap() error { + return e.error +} + +func notFound(err error) error { + if err == nil || cerrdefs.IsNotFound(err) { + return err + } + return notFoundErr{err} +} + +type notFoundErr struct{ error } + +func (notFoundErr) NotFound() {} +func (e notFoundErr) Unwrap() error { + return e.error +} diff --git a/cli/command/container/opts.go b/cli/command/container/opts.go index 8883271f67..cf9c780623 100644 --- a/cli/command/container/opts.go +++ b/cli/command/container/opts.go @@ -19,7 +19,6 @@ import ( mounttypes "github.com/docker/docker/api/types/mount" networktypes "github.com/docker/docker/api/types/network" "github.com/docker/docker/api/types/strslice" - "github.com/docker/docker/errdefs" "github.com/docker/go-connections/nat" "github.com/pkg/errors" "github.com/spf13/pflag" @@ -781,7 +780,7 @@ func parseNetworkOpts(copts *containerOptions) (map[string]*networktypes.Endpoin return nil, err } if _, ok := endpoints[n.Target]; ok { - return nil, errdefs.InvalidParameter(errors.Errorf("network %q is specified multiple times", n.Target)) + return nil, invalidParameter(errors.Errorf("network %q is specified multiple times", n.Target)) } // For backward compatibility: if no custom options are provided for the network, @@ -795,7 +794,7 @@ func parseNetworkOpts(copts *containerOptions) (map[string]*networktypes.Endpoin endpoints[n.Target] = ep } if hasUserDefined && hasNonUserDefined { - return nil, errdefs.InvalidParameter(errors.New("conflicting options: cannot attach both user-defined and non-user-defined network-modes")) + return nil, invalidParameter(errors.New("conflicting options: cannot attach both user-defined and non-user-defined network-modes")) } return endpoints, nil } @@ -803,22 +802,22 @@ func parseNetworkOpts(copts *containerOptions) (map[string]*networktypes.Endpoin func applyContainerOptions(n *opts.NetworkAttachmentOpts, copts *containerOptions) error { //nolint:gocyclo // TODO should we error if _any_ advanced option is used? (i.e. forbid to combine advanced notation with the "old" flags (`--network-alias`, `--link`, `--ip`, `--ip6`)? if len(n.Aliases) > 0 && copts.aliases.Len() > 0 { - return errdefs.InvalidParameter(errors.New("conflicting options: cannot specify both --network-alias and per-network alias")) + return invalidParameter(errors.New("conflicting options: cannot specify both --network-alias and per-network alias")) } if len(n.Links) > 0 && copts.links.Len() > 0 { - return errdefs.InvalidParameter(errors.New("conflicting options: cannot specify both --link and per-network links")) + return invalidParameter(errors.New("conflicting options: cannot specify both --link and per-network links")) } if n.IPv4Address != "" && copts.ipv4Address != "" { - return errdefs.InvalidParameter(errors.New("conflicting options: cannot specify both --ip and per-network IPv4 address")) + return invalidParameter(errors.New("conflicting options: cannot specify both --ip and per-network IPv4 address")) } if n.IPv6Address != "" && copts.ipv6Address != "" { - return errdefs.InvalidParameter(errors.New("conflicting options: cannot specify both --ip6 and per-network IPv6 address")) + return invalidParameter(errors.New("conflicting options: cannot specify both --ip6 and per-network IPv6 address")) } if n.MacAddress != "" && copts.macAddress != "" { - return errdefs.InvalidParameter(errors.New("conflicting options: cannot specify both --mac-address and per-network MAC address")) + return invalidParameter(errors.New("conflicting options: cannot specify both --mac-address and per-network MAC address")) } if len(n.LinkLocalIPs) > 0 && copts.linkLocalIPs.Len() > 0 { - return errdefs.InvalidParameter(errors.New("conflicting options: cannot specify both --link-local-ip and per-network link-local IP addresses")) + return invalidParameter(errors.New("conflicting options: cannot specify both --link-local-ip and per-network link-local IP addresses")) } if copts.aliases.Len() > 0 { n.Aliases = make([]string, copts.aliases.Len()) diff --git a/cli/command/container/prune.go b/cli/command/container/prune.go index 74f741a7a7..7a1d575d6e 100644 --- a/cli/command/container/prune.go +++ b/cli/command/container/prune.go @@ -9,7 +9,6 @@ import ( "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/internal/prompt" "github.com/docker/cli/opts" - "github.com/docker/docker/errdefs" units "github.com/docker/go-units" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -62,7 +61,7 @@ func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions) return 0, "", err } if !r { - return 0, "", errdefs.Cancelled(errors.New("container prune has been cancelled")) + return 0, "", cancelledErr{errors.New("container prune has been cancelled")} } } @@ -82,6 +81,10 @@ func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions) return spaceReclaimed, output, nil } +type cancelledErr struct{ error } + +func (cancelledErr) Cancelled() {} + // RunPrune calls the Container Prune API // This returns the amount of space reclaimed and a detailed output string func RunPrune(ctx context.Context, dockerCli command.Cli, _ bool, filter opts.FilterOpt) (uint64, string, error) { diff --git a/cli/command/container/restart_test.go b/cli/command/container/restart_test.go index a5cdb1cf1b..f7986a8751 100644 --- a/cli/command/container/restart_test.go +++ b/cli/command/container/restart_test.go @@ -10,7 +10,6 @@ import ( "github.com/docker/cli/internal/test" "github.com/docker/docker/api/types/container" - "github.com/docker/docker/errdefs" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" ) @@ -66,7 +65,7 @@ func TestRestart(t *testing.T) { containerRestartFunc: func(ctx context.Context, containerID string, options container.StopOptions) error { assert.Check(t, is.DeepEqual(options, tc.expectedOpts)) if containerID == "nosuchcontainer" { - return errdefs.NotFound(errors.New("Error: no such container: " + containerID)) + return notFound(errors.New("Error: no such container: " + containerID)) } // TODO(thaJeztah): consider using parallelOperation for restart, similar to "stop" and "remove" diff --git a/cli/command/container/rm_test.go b/cli/command/container/rm_test.go index 505fc7c48b..e9850a9b93 100644 --- a/cli/command/container/rm_test.go +++ b/cli/command/container/rm_test.go @@ -10,7 +10,6 @@ import ( "github.com/docker/cli/internal/test" "github.com/docker/docker/api/types/container" - "github.com/docker/docker/errdefs" "gotest.tools/v3/assert" ) @@ -36,7 +35,7 @@ func TestRemoveForce(t *testing.T) { mutex.Unlock() if container == "nosuchcontainer" { - return errdefs.NotFound(errors.New("Error: no such container: " + container)) + return notFound(errors.New("Error: no such container: " + container)) } return nil }, diff --git a/cli/command/container/stop_test.go b/cli/command/container/stop_test.go index 83a9b706f1..fc2b88c7ac 100644 --- a/cli/command/container/stop_test.go +++ b/cli/command/container/stop_test.go @@ -10,7 +10,6 @@ import ( "github.com/docker/cli/internal/test" "github.com/docker/docker/api/types/container" - "github.com/docker/docker/errdefs" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" ) @@ -66,7 +65,7 @@ func TestStop(t *testing.T) { containerStopFunc: func(ctx context.Context, containerID string, options container.StopOptions) error { assert.Check(t, is.DeepEqual(options, tc.expectedOpts)) if containerID == "nosuchcontainer" { - return errdefs.NotFound(errors.New("Error: no such container: " + containerID)) + return notFound(errors.New("Error: no such container: " + containerID)) } // containerStopFunc is called in parallel for each container diff --git a/cli/command/context/create.go b/cli/command/context/create.go index 9a84546c64..313aca142e 100644 --- a/cli/command/context/create.go +++ b/cli/command/context/create.go @@ -5,6 +5,7 @@ package context import ( "bytes" + "errors" "fmt" cerrdefs "github.com/containerd/errdefs" @@ -14,7 +15,6 @@ import ( "github.com/docker/cli/cli/command/formatter/tabwriter" "github.com/docker/cli/cli/context/docker" "github.com/docker/cli/cli/context/store" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -91,7 +91,7 @@ func createNewContext(contextStore store.ReaderWriter, o *CreateOptions) error { } dockerEP, dockerTLS, err := getDockerEndpointMetadataAndTLS(contextStore, o.Docker) if err != nil { - return errors.Wrap(err, "unable to create docker endpoint config") + return fmt.Errorf("unable to create docker endpoint config: %w", err) } contextMetadata := store.Metadata{ Endpoints: map[string]any{ @@ -124,9 +124,9 @@ func checkContextNameForCreation(s store.Reader, name string) error { } if _, err := s.GetMetadata(name); !cerrdefs.IsNotFound(err) { if err != nil { - return errors.Wrap(err, "error while getting existing contexts") + return fmt.Errorf("error while getting existing contexts: %w", err) } - return errors.Errorf("context %q already exists", name) + return fmt.Errorf("context %q already exists", name) } return nil } diff --git a/cli/command/context/remove.go b/cli/command/context/remove.go index 2630dcd22f..0f73cb1fd7 100644 --- a/cli/command/context/remove.go +++ b/cli/command/context/remove.go @@ -7,7 +7,6 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/docker/errdefs" "github.com/spf13/cobra" ) @@ -75,9 +74,13 @@ func checkContextExists(dockerCli command.Cli, name string) error { contextDir := dockerCli.ContextStore().GetStorageInfo(name).MetadataPath _, err := os.Stat(contextDir) if os.IsNotExist(err) { - return errdefs.NotFound(fmt.Errorf("context %q does not exist", name)) + return notFoundErr{fmt.Errorf("context %q does not exist", name)} } // Ignore other errors; if relevant, they will produce an error when // performing the actual delete. return nil } + +type notFoundErr struct{ error } + +func (notFoundErr) NotFound() {} diff --git a/cli/command/context/update.go b/cli/command/context/update.go index 16ace82f7c..0995c52ef5 100644 --- a/cli/command/context/update.go +++ b/cli/command/context/update.go @@ -9,7 +9,6 @@ import ( "github.com/docker/cli/cli/command/formatter/tabwriter" "github.com/docker/cli/cli/context/docker" "github.com/docker/cli/cli/context/store" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -77,7 +76,7 @@ func RunUpdate(dockerCLI command.Cli, o *UpdateOptions) error { if o.Docker != nil { dockerEP, dockerTLS, err := getDockerEndpointMetadataAndTLS(s, o.Docker) if err != nil { - return errors.Wrap(err, "unable to create docker endpoint config") + return fmt.Errorf("unable to create docker endpoint config: %w", err) } c.Endpoints[docker.DockerEndpoint] = dockerEP tlsDataToReset[docker.DockerEndpoint] = dockerTLS diff --git a/cli/command/defaultcontextstore.go b/cli/command/defaultcontextstore.go index 496233ae18..9b49b3af2a 100644 --- a/cli/command/defaultcontextstore.go +++ b/cli/command/defaultcontextstore.go @@ -7,7 +7,6 @@ import ( "github.com/docker/cli/cli/context/docker" "github.com/docker/cli/cli/context/store" cliflags "github.com/docker/cli/cli/flags" - "github.com/docker/docker/errdefs" "github.com/pkg/errors" ) @@ -117,7 +116,7 @@ func (s *ContextStoreWithDefault) List() ([]store.Metadata, error) { // CreateOrUpdate is not allowed for the default context and fails func (s *ContextStoreWithDefault) CreateOrUpdate(meta store.Metadata) error { if meta.Name == DefaultContextName { - return errdefs.InvalidParameter(errors.New("default context cannot be created nor updated")) + return invalidParameter(errors.New("default context cannot be created nor updated")) } return s.Store.CreateOrUpdate(meta) } @@ -125,7 +124,7 @@ func (s *ContextStoreWithDefault) CreateOrUpdate(meta store.Metadata) error { // Remove is not allowed for the default context and fails func (s *ContextStoreWithDefault) Remove(name string) error { if name == DefaultContextName { - return errdefs.InvalidParameter(errors.New("default context cannot be removed")) + return invalidParameter(errors.New("default context cannot be removed")) } return s.Store.Remove(name) } @@ -145,7 +144,7 @@ func (s *ContextStoreWithDefault) GetMetadata(name string) (store.Metadata, erro // ResetTLSMaterial is not implemented for default context and fails func (s *ContextStoreWithDefault) ResetTLSMaterial(name string, data *store.ContextTLSData) error { if name == DefaultContextName { - return errdefs.InvalidParameter(errors.New("default context cannot be edited")) + return invalidParameter(errors.New("default context cannot be edited")) } return s.Store.ResetTLSMaterial(name, data) } @@ -153,7 +152,7 @@ func (s *ContextStoreWithDefault) ResetTLSMaterial(name string, data *store.Cont // ResetEndpointTLSMaterial is not implemented for default context and fails func (s *ContextStoreWithDefault) ResetEndpointTLSMaterial(contextName string, endpointName string, data *store.EndpointTLSData) error { if contextName == DefaultContextName { - return errdefs.InvalidParameter(errors.New("default context cannot be edited")) + return invalidParameter(errors.New("default context cannot be edited")) } return s.Store.ResetEndpointTLSMaterial(contextName, endpointName, data) } @@ -186,7 +185,7 @@ func (s *ContextStoreWithDefault) GetTLSData(contextName, endpointName, fileName return nil, err } if defaultContext.TLS.Endpoints[endpointName].Files[fileName] == nil { - return nil, errdefs.NotFound(errors.Errorf("TLS data for %s/%s/%s does not exist", DefaultContextName, endpointName, fileName)) + return nil, notFound(errors.Errorf("TLS data for %s/%s/%s does not exist", DefaultContextName, endpointName, fileName)) } return defaultContext.TLS.Endpoints[endpointName].Files[fileName], nil } diff --git a/cli/command/image/prune.go b/cli/command/image/prune.go index 89e84b41f0..eec9b1c0ba 100644 --- a/cli/command/image/prune.go +++ b/cli/command/image/prune.go @@ -11,8 +11,7 @@ import ( "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/internal/prompt" "github.com/docker/cli/opts" - "github.com/docker/docker/errdefs" - units "github.com/docker/go-units" + "github.com/docker/go-units" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -76,7 +75,7 @@ func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions) return 0, "", err } if !r { - return 0, "", errdefs.Cancelled(errors.New("image prune has been cancelled")) + return 0, "", cancelledErr{errors.New("image prune has been cancelled")} } } @@ -106,6 +105,10 @@ func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions) return spaceReclaimed, output, nil } +type cancelledErr struct{ error } + +func (cancelledErr) Cancelled() {} + // RunPrune calls the Image Prune API // This returns the amount of space reclaimed and a detailed output string func RunPrune(ctx context.Context, dockerCli command.Cli, all bool, filter opts.FilterOpt) (uint64, string, error) { diff --git a/cli/command/network/create.go b/cli/command/network/create.go index 7bb8b2a157..c8fca41294 100644 --- a/cli/command/network/create.go +++ b/cli/command/network/create.go @@ -2,6 +2,7 @@ package network import ( "context" + "errors" "fmt" "io" "net" @@ -13,7 +14,6 @@ import ( "github.com/docker/cli/opts" "github.com/docker/docker/api/types/network" "github.com/docker/docker/client" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -143,7 +143,7 @@ func runCreate(ctx context.Context, apiClient client.NetworkAPIClient, output io //nolint:gocyclo func createIPAMConfig(options ipamOptions) (*network.IPAM, error) { if len(options.subnets) < len(options.ipRanges) || len(options.subnets) < len(options.gateways) { - return nil, errors.Errorf("every ip-range or gateway must have a corresponding subnet") + return nil, errors.New("every ip-range or gateway must have a corresponding subnet") } iData := map[string]*network.IPAMConfig{} @@ -159,7 +159,7 @@ func createIPAMConfig(options ipamOptions) (*network.IPAM, error) { return nil, err } if ok1 || ok2 { - return nil, errors.Errorf("multiple overlapping subnet configuration is not supported") + return nil, errors.New("multiple overlapping subnet configuration is not supported") } } iData[s] = &network.IPAMConfig{Subnet: s, AuxAddress: map[string]string{}} @@ -180,14 +180,14 @@ func createIPAMConfig(options ipamOptions) (*network.IPAM, error) { continue } if iData[s].IPRange != "" { - return nil, errors.Errorf("cannot configure multiple ranges (%s, %s) on the same subnet (%s)", r, iData[s].IPRange, s) + return nil, fmt.Errorf("cannot configure multiple ranges (%s, %s) on the same subnet (%s)", r, iData[s].IPRange, s) } d := iData[s] d.IPRange = r match = true } if !match { - return nil, errors.Errorf("no matching subnet for range %s", r) + return nil, fmt.Errorf("no matching subnet for range %s", r) } } @@ -203,14 +203,14 @@ func createIPAMConfig(options ipamOptions) (*network.IPAM, error) { continue } if iData[s].Gateway != "" { - return nil, errors.Errorf("cannot configure multiple gateways (%s, %s) for the same subnet (%s)", g, iData[s].Gateway, s) + return nil, fmt.Errorf("cannot configure multiple gateways (%s, %s) for the same subnet (%s)", g, iData[s].Gateway, s) } d := iData[s] d.Gateway = g match = true } if !match { - return nil, errors.Errorf("no matching subnet for gateway %s", g) + return nil, fmt.Errorf("no matching subnet for gateway %s", g) } } @@ -229,7 +229,7 @@ func createIPAMConfig(options ipamOptions) (*network.IPAM, error) { match = true } if !match { - return nil, errors.Errorf("no matching subnet for aux-address %s", aa) + return nil, fmt.Errorf("no matching subnet for aux-address %s", aa) } } @@ -250,7 +250,7 @@ func subnetMatches(subnet, data string) (bool, error) { _, s, err := net.ParseCIDR(subnet) if err != nil { - return false, errors.Wrap(err, "invalid subnet") + return false, fmt.Errorf("invalid subnet: %w", err) } if strings.Contains(data, "/") { diff --git a/cli/command/network/prune.go b/cli/command/network/prune.go index 8eadee1d81..fce7adf3ed 100644 --- a/cli/command/network/prune.go +++ b/cli/command/network/prune.go @@ -2,14 +2,13 @@ package network import ( "context" + "errors" "fmt" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/internal/prompt" "github.com/docker/cli/opts" - "github.com/docker/docker/errdefs" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -58,7 +57,7 @@ func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions) return "", err } if !r { - return "", errdefs.Cancelled(errors.New("network prune has been cancelled")) + return "", cancelledErr{errors.New("network prune has been cancelled")} } } @@ -77,6 +76,10 @@ func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions) return output, nil } +type cancelledErr struct{ error } + +func (cancelledErr) Cancelled() {} + // RunPrune calls the Network Prune API // This returns the amount of space reclaimed and a detailed output string func RunPrune(ctx context.Context, dockerCli command.Cli, _ bool, filter opts.FilterOpt) (uint64, string, error) { diff --git a/cli/command/network/remove_test.go b/cli/command/network/remove_test.go index a1390d8b8d..fe929d403e 100644 --- a/cli/command/network/remove_test.go +++ b/cli/command/network/remove_test.go @@ -8,11 +8,18 @@ import ( "github.com/docker/cli/internal/test" "github.com/docker/docker/api/types/network" - "github.com/docker/docker/errdefs" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" ) +type forBiddenErr struct{ error } + +func (forBiddenErr) Forbidden() {} + +type notFoundErr struct{ error } + +func (notFoundErr) NotFound() {} + func TestNetworkRemoveForce(t *testing.T) { tests := []struct { doc string @@ -68,9 +75,9 @@ func TestNetworkRemoveForce(t *testing.T) { networkRemoveFunc: func(ctx context.Context, networkID string) error { switch networkID { case "no-such-network": - return errdefs.NotFound(errors.New("no such network: no-such-network")) + return notFoundErr{errors.New("no such network: no-such-network")} case "in-use-network": - return errdefs.Forbidden(errors.New("network is in use")) + return forBiddenErr{errors.New("network is in use")} case "existing-network": return nil default: diff --git a/cli/command/plugin/upgrade.go b/cli/command/plugin/upgrade.go index 494cbdc6a0..da9ea4076d 100644 --- a/cli/command/plugin/upgrade.go +++ b/cli/command/plugin/upgrade.go @@ -10,7 +10,6 @@ import ( "github.com/docker/cli/cli/command" "github.com/docker/cli/internal/jsonstream" "github.com/docker/cli/internal/prompt" - "github.com/docker/docker/errdefs" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -70,7 +69,7 @@ func runUpgrade(ctx context.Context, dockerCLI command.Cli, opts pluginOptions) return err } if !r { - return errdefs.Cancelled(errors.New("plugin upgrade has been cancelled")) + return cancelledErr{errors.New("plugin upgrade has been cancelled")} } } @@ -93,3 +92,7 @@ func runUpgrade(ctx context.Context, dockerCLI command.Cli, opts pluginOptions) _, _ = fmt.Fprintf(dockerCLI.Out(), "Upgraded plugin %s to %s\n", opts.localName, opts.remote) // todo: return proper values from the API for this result return nil } + +type cancelledErr struct{ error } + +func (cancelledErr) Cancelled() {} diff --git a/cli/command/system/prune.go b/cli/command/system/prune.go index c1fa339a5b..a1bcb5f224 100644 --- a/cli/command/system/prune.go +++ b/cli/command/system/prune.go @@ -18,7 +18,6 @@ import ( "github.com/docker/cli/internal/prompt" "github.com/docker/cli/opts" "github.com/docker/docker/api/types/versions" - "github.com/docker/docker/errdefs" "github.com/docker/go-units" "github.com/fvbommel/sortorder" "github.com/pkg/errors" @@ -83,7 +82,7 @@ func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions) return err } if !r { - return errdefs.Cancelled(errors.New("system prune has been cancelled")) + return cancelledErr{errors.New("system prune has been cancelled")} } } pruneFuncs := []func(ctx context.Context, dockerCli command.Cli, all bool, filter opts.FilterOpt) (uint64, string, error){ @@ -115,6 +114,10 @@ func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions) return nil } +type cancelledErr struct{ error } + +func (cancelledErr) Cancelled() {} + // confirmationMessage constructs a confirmation message that depends on the cli options. func confirmationMessage(dockerCli command.Cli, options pruneOptions) string { t := template.Must(template.New("confirmation message").Parse(confirmationTemplate)) diff --git a/cli/command/trust/revoke.go b/cli/command/trust/revoke.go index ec32c797f2..303c7b8c2d 100644 --- a/cli/command/trust/revoke.go +++ b/cli/command/trust/revoke.go @@ -9,7 +9,6 @@ import ( "github.com/docker/cli/cli/command/image" "github.com/docker/cli/cli/trust" "github.com/docker/cli/internal/prompt" - "github.com/docker/docker/errdefs" "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/theupdateframework/notary/client" @@ -50,7 +49,7 @@ func revokeTrust(ctx context.Context, dockerCLI command.Cli, remote string, opti return err } if !deleteRemote { - return errdefs.Cancelled(errors.New("trust revoke has been cancelled")) + return cancelledErr{errors.New("trust revoke has been cancelled")} } } @@ -70,6 +69,10 @@ func revokeTrust(ctx context.Context, dockerCLI command.Cli, remote string, opti return nil } +type cancelledErr struct{ error } + +func (cancelledErr) Cancelled() {} + func revokeSignature(notaryRepo client.Repository, tag string) error { if tag != "" { // Revoke signature for the specified tag diff --git a/cli/command/utils.go b/cli/command/utils.go index 08f96cae36..ab64ef8fc1 100644 --- a/cli/command/utils.go +++ b/cli/command/utils.go @@ -151,3 +151,22 @@ func ValidateOutputPathFileMode(fileMode os.FileMode) error { } return nil } + +func invalidParameter(err error) error { + return invalidParameterErr{err} +} + +type invalidParameterErr struct{ error } + +func (invalidParameterErr) InvalidParameter() {} + +func notFound(err error) error { + return notFoundErr{err} +} + +type notFoundErr struct{ error } + +func (notFoundErr) NotFound() {} +func (e notFoundErr) Unwrap() error { + return e.error +} diff --git a/cli/command/volume/create.go b/cli/command/volume/create.go index 164ff2ebfb..8c13f65a74 100644 --- a/cli/command/volume/create.go +++ b/cli/command/volume/create.go @@ -2,6 +2,7 @@ package volume import ( "context" + "errors" "fmt" "sort" "strings" @@ -11,7 +12,6 @@ import ( "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/opts" "github.com/docker/docker/api/types/volume" - "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/pflag" ) @@ -52,7 +52,7 @@ func newCreateCommand(dockerCli command.Cli) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { if len(args) == 1 { if options.name != "" { - return errors.Errorf("conflicting options: cannot specify a volume-name through both --name and as a positional arg") + return errors.New("conflicting options: cannot specify a volume-name through both --name and as a positional arg") } options.name = args[0] } diff --git a/cli/command/volume/prune.go b/cli/command/volume/prune.go index c366e347f6..0765e02289 100644 --- a/cli/command/volume/prune.go +++ b/cli/command/volume/prune.go @@ -2,6 +2,7 @@ package volume import ( "context" + "errors" "fmt" "github.com/docker/cli/cli" @@ -10,9 +11,7 @@ import ( "github.com/docker/cli/internal/prompt" "github.com/docker/cli/opts" "github.com/docker/docker/api/types/versions" - "github.com/docker/docker/errdefs" - units "github.com/docker/go-units" - "github.com/pkg/errors" + "github.com/docker/go-units" "github.com/spf13/cobra" ) @@ -68,7 +67,7 @@ func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions) if versions.GreaterThanOrEqualTo(dockerCli.CurrentVersion(), "1.42") { if options.all { if pruneFilters.Contains("all") { - return 0, "", errdefs.InvalidParameter(errors.New("conflicting options: cannot specify both --all and --filter all=1")) + return 0, "", invalidParamErr{errors.New("conflicting options: cannot specify both --all and --filter all=1")} } pruneFilters.Add("all", "true") warning = allVolumesWarning @@ -83,7 +82,7 @@ func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions) return 0, "", err } if !r { - return 0, "", errdefs.Cancelled(errors.New("volume prune has been cancelled")) + return 0, "", cancelledErr{errors.New("volume prune has been cancelled")} } } @@ -103,6 +102,14 @@ func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions) return spaceReclaimed, output, nil } +type invalidParamErr struct{ error } + +func (invalidParamErr) InvalidParameter() {} + +type cancelledErr struct{ error } + +func (cancelledErr) Cancelled() {} + // RunPrune calls the Volume Prune API // This returns the amount of space reclaimed and a detailed output string func RunPrune(ctx context.Context, dockerCli command.Cli, _ bool, filter opts.FilterOpt) (uint64, string, error) { diff --git a/cli/command/volume/update.go b/cli/command/volume/update.go index 42ce9ac586..5512c3f367 100644 --- a/cli/command/volume/update.go +++ b/cli/command/volume/update.go @@ -2,12 +2,12 @@ package volume import ( "context" + "errors" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/completion" "github.com/docker/docker/api/types/volume" - "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/pflag" ) @@ -50,7 +50,7 @@ func runUpdate(ctx context.Context, dockerCli command.Cli, volumeID, availabilit } if vol.ClusterVolume == nil { - return errors.New("Can only update cluster volumes") + return errors.New("can only update cluster volumes") } if flags.Changed("availability") { diff --git a/cli/context/store/errors.go b/cli/context/store/errors.go new file mode 100644 index 0000000000..e85ce325a9 --- /dev/null +++ b/cli/context/store/errors.go @@ -0,0 +1,28 @@ +package store + +import cerrdefs "github.com/containerd/errdefs" + +func invalidParameter(err error) error { + if err == nil || cerrdefs.IsInvalidArgument(err) { + return err + } + return invalidParameterErr{err} +} + +type invalidParameterErr struct{ error } + +func (invalidParameterErr) InvalidParameter() {} + +func notFound(err error) error { + if err == nil || cerrdefs.IsNotFound(err) { + return err + } + return notFoundErr{err} +} + +type notFoundErr struct{ error } + +func (notFoundErr) NotFound() {} +func (e notFoundErr) Unwrap() error { + return e.error +} diff --git a/cli/context/store/metadatastore.go b/cli/context/store/metadatastore.go index 8f41a9c525..deec5cc9d7 100644 --- a/cli/context/store/metadatastore.go +++ b/cli/context/store/metadatastore.go @@ -5,16 +5,15 @@ package store import ( "encoding/json" + "errors" "fmt" "os" "path/filepath" "reflect" "sort" - "github.com/docker/docker/errdefs" "github.com/fvbommel/sortorder" "github.com/moby/sys/atomicwriter" - "github.com/pkg/errors" ) const ( @@ -64,7 +63,7 @@ func parseTypedOrMap(payload []byte, getter TypeGetter) (any, error) { func (s *metadataStore) get(name string) (Metadata, error) { m, err := s.getByID(contextdirOf(name)) if err != nil { - return m, errors.Wrapf(err, "context %q", name) + return m, fmt.Errorf("context %q: %w", name, err) } return m, nil } @@ -74,7 +73,7 @@ func (s *metadataStore) getByID(id contextdir) (Metadata, error) { bytes, err := os.ReadFile(fileName) if err != nil { if errors.Is(err, os.ErrNotExist) { - return Metadata{}, errdefs.NotFound(errors.Wrap(err, "context not found")) + return Metadata{}, notFound(fmt.Errorf("context not found: %w", err)) } return Metadata{}, err } @@ -99,7 +98,7 @@ func (s *metadataStore) getByID(id contextdir) (Metadata, error) { func (s *metadataStore) remove(name string) error { if err := os.RemoveAll(s.contextDir(contextdirOf(name))); err != nil { - return errors.Wrapf(err, "failed to remove metadata") + return fmt.Errorf("failed to remove metadata: %w", err) } return nil } @@ -119,7 +118,7 @@ func (s *metadataStore) list() ([]Metadata, error) { if errors.Is(err, os.ErrNotExist) { continue } - return nil, errors.Wrap(err, "failed to read metadata") + return nil, fmt.Errorf("failed to read metadata: %w", err) } res = append(res, c) } diff --git a/cli/context/store/store.go b/cli/context/store/store.go index 7020c7dd19..91d9c19c6b 100644 --- a/cli/context/store/store.go +++ b/cli/context/store/store.go @@ -10,6 +10,8 @@ import ( "bytes" _ "crypto/sha256" // ensure ids can be computed "encoding/json" + "errors" + "fmt" "io" "net/http" "path" @@ -17,9 +19,7 @@ import ( "strings" "github.com/docker/cli/internal/lazyregexp" - "github.com/docker/docker/errdefs" "github.com/opencontainers/go-digest" - "github.com/pkg/errors" ) const restrictedNamePattern = "^[a-zA-Z0-9][a-zA-Z0-9_.+-]+$" @@ -146,10 +146,10 @@ func (s *ContextStore) CreateOrUpdate(meta Metadata) error { // Remove deletes the context with the given name, if found. func (s *ContextStore) Remove(name string) error { if err := s.meta.remove(name); err != nil { - return errors.Wrapf(err, "failed to remove context %s", name) + return fmt.Errorf("failed to remove context %s: %w", name, err) } if err := s.tls.remove(name); err != nil { - return errors.Wrapf(err, "failed to remove context %s", name) + return fmt.Errorf("failed to remove context %s: %w", name, err) } return nil } @@ -226,7 +226,7 @@ func ValidateContextName(name string) error { return errors.New(`"default" is a reserved context name`) } if !restrictedNameRegEx.MatchString(name) { - return errors.Errorf("context name %q is invalid, names are validated against regexp %q", name, restrictedNamePattern) + return fmt.Errorf("context name %q is invalid, names are validated against regexp %q", name, restrictedNamePattern) } return nil } @@ -374,7 +374,7 @@ func importTar(name string, s Writer, reader io.Reader) error { continue } if err := isValidFilePath(hdr.Name); err != nil { - return errors.Wrap(err, hdr.Name) + return fmt.Errorf("%s: %w", hdr.Name, err) } if hdr.Name == metaFile { data, err := io.ReadAll(tr) @@ -400,7 +400,7 @@ func importTar(name string, s Writer, reader io.Reader) error { } } if !importedMetaFile { - return errdefs.InvalidParameter(errors.New("invalid context: no metadata found")) + return invalidParameter(errors.New("invalid context: no metadata found")) } return s.ResetTLSMaterial(name, &tlsData) } @@ -426,7 +426,7 @@ func importZip(name string, s Writer, reader io.Reader) error { continue } if err := isValidFilePath(zf.Name); err != nil { - return errors.Wrap(err, zf.Name) + return fmt.Errorf("%s: %w", zf.Name, err) } if zf.Name == metaFile { f, err := zf.Open() @@ -464,7 +464,7 @@ func importZip(name string, s Writer, reader io.Reader) error { } } if !importedMetaFile { - return errdefs.InvalidParameter(errors.New("invalid context: no metadata found")) + return invalidParameter(errors.New("invalid context: no metadata found")) } return s.ResetTLSMaterial(name, &tlsData) } diff --git a/cli/context/store/tlsstore.go b/cli/context/store/tlsstore.go index 5ce3e23104..c1f5f8caa2 100644 --- a/cli/context/store/tlsstore.go +++ b/cli/context/store/tlsstore.go @@ -1,12 +1,11 @@ package store import ( + "fmt" "os" "path/filepath" - "github.com/docker/docker/errdefs" "github.com/moby/sys/atomicwriter" - "github.com/pkg/errors" ) const tlsDir = "tls" @@ -39,9 +38,9 @@ func (s *tlsStore) getData(name, endpointName, filename string) ([]byte, error) data, err := os.ReadFile(filepath.Join(s.endpointDir(name, endpointName), filename)) if err != nil { if os.IsNotExist(err) { - return nil, errdefs.NotFound(errors.Errorf("TLS data for %s/%s/%s does not exist", name, endpointName, filename)) + return nil, notFound(fmt.Errorf("TLS data for %s/%s/%s does not exist", name, endpointName, filename)) } - return nil, errors.Wrapf(err, "failed to read TLS data for endpoint %s", endpointName) + return nil, fmt.Errorf("failed to read TLS data for endpoint %s: %w", endpointName, err) } return data, nil } @@ -49,14 +48,14 @@ func (s *tlsStore) getData(name, endpointName, filename string) ([]byte, error) // remove deletes all TLS data for the given context. func (s *tlsStore) remove(name string) error { if err := os.RemoveAll(s.contextDir(name)); err != nil { - return errors.Wrapf(err, "failed to remove TLS data") + return fmt.Errorf("failed to remove TLS data: %w", err) } return nil } func (s *tlsStore) removeEndpoint(name, endpointName string) error { if err := os.RemoveAll(s.endpointDir(name, endpointName)); err != nil { - return errors.Wrapf(err, "failed to remove TLS data for endpoint %s", endpointName) + return fmt.Errorf("failed to remove TLS data for endpoint %s: %w", endpointName, err) } return nil } @@ -68,7 +67,7 @@ func (s *tlsStore) listContextData(name string) (map[string]EndpointFiles, error if os.IsNotExist(err) { return map[string]EndpointFiles{}, nil } - return nil, errors.Wrapf(err, "failed to list TLS files for context %s", name) + return nil, fmt.Errorf("failed to list TLS files for context %s: %w", name, err) } r := make(map[string]EndpointFiles) for _, epFS := range epFSs { @@ -78,7 +77,7 @@ func (s *tlsStore) listContextData(name string) (map[string]EndpointFiles, error continue } if err != nil { - return nil, errors.Wrapf(err, "failed to list TLS files for endpoint %s", epFS.Name()) + return nil, fmt.Errorf("failed to list TLS files for endpoint %s: %w", epFS.Name(), err) } var files EndpointFiles for _, fs := range fss {