cli/command/context: use errors.Join

Use stdlib multi-errors instead of creating our own; also
touch-up one error and some minor cleanups.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn 2025-02-03 19:19:18 +01:00
parent 150f27b68c
commit 2b9a4d5f4c
No known key found for this signature in database
GPG Key ID: 76698F39D527CE8C
3 changed files with 23 additions and 28 deletions

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`)
} }