diff --git a/.golangci.yml b/.golangci.yml index aa201a7f67..314a65ad02 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -69,6 +69,8 @@ linters: desc: Use github.com/moby/sys/userns instead. - pkg: "github.com/tonistiigi/fsutil" desc: The fsutil module does not have a stable API, so we should not have a direct dependency unless necessary. + - pkg: "github.com/hashicorp/go-multierror" + desc: "Use errors.Join instead" dupword: ignore: diff --git a/daemon/cdi.go b/daemon/cdi.go index 7b0b848022..e4074e2130 100644 --- a/daemon/cdi.go +++ b/daemon/cdi.go @@ -2,16 +2,15 @@ package daemon import ( "context" + "errors" "fmt" "os" "github.com/containerd/log" - "github.com/hashicorp/go-multierror" "github.com/moby/moby/api/types/system" "github.com/moby/moby/v2/daemon/config" "github.com/moby/moby/v2/errdefs" "github.com/opencontainers/runtime-spec/specs-go" - "github.com/pkg/errors" "tags.cncf.io/container-device-interface/pkg/cdi" ) @@ -118,11 +117,11 @@ func (c *cdiHandler) injectCDIDevices(s *specs.Spec, dev *deviceInstance) error // getErrors returns a single error representation of errors that may have occurred while refreshing the CDI registry. func (c *cdiHandler) getErrors() error { - var err *multierror.Error - for _, errs := range c.registry.GetErrors() { - err = multierror.Append(err, errs...) + var errs []error + for _, es := range c.registry.GetErrors() { + errs = append(errs, es...) } - return err.ErrorOrNil() + return errors.Join(errs...) } // listDevices uses the CDI cache to list all discovered CDI devices. diff --git a/daemon/containerd/image_prune.go b/daemon/containerd/image_prune.go index 33ef903d85..ba096be18d 100644 --- a/daemon/containerd/image_prune.go +++ b/daemon/containerd/image_prune.go @@ -2,6 +2,7 @@ package containerd import ( "context" + "errors" "sort" "strings" @@ -11,14 +12,12 @@ import ( cerrdefs "github.com/containerd/errdefs" "github.com/containerd/log" "github.com/distribution/reference" - "github.com/hashicorp/go-multierror" "github.com/moby/moby/api/types/filters" "github.com/moby/moby/api/types/image" "github.com/moby/moby/v2/daemon/container" "github.com/moby/moby/v2/errdefs" "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/pkg/errors" ) var imagesAcceptedFilters = map[string]bool{ @@ -218,7 +217,7 @@ func (i *ImageService) pruneAll(ctx context.Context, imagesToPrune map[string]c8 span.SetAttributes(tracing.Attribute("count", len(imagesToPrune))) defer span.End() - var errs error + var errs []error for _, img := range imagesToPrune { log.G(ctx).WithField("image", img).Debug("pruning image") @@ -229,17 +228,17 @@ func (i *ImageService) pruneAll(ctx context.Context, imagesToPrune map[string]c8 return nil }) if err != nil { - errs = multierror.Append(errs, err) + errs = append(errs, err) if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { - return &report, errs + return &report, errors.Join(errs...) } continue } err = i.images.Delete(ctx, img.Name, c8dimages.SynchronousDelete()) if err != nil && !cerrdefs.IsNotFound(err) { - errs = multierror.Append(errs, err) + errs = append(errs, err) if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { - return &report, errs + return &report, errors.Join(errs...) } continue } @@ -265,5 +264,5 @@ func (i *ImageService) pruneAll(ctx context.Context, imagesToPrune map[string]c8 } } - return &report, errs + return &report, errors.Join(errs...) } diff --git a/daemon/containerfs_linux.go b/daemon/containerfs_linux.go index c5fe1a7688..52a362ebbc 100644 --- a/daemon/containerfs_linux.go +++ b/daemon/containerfs_linux.go @@ -10,7 +10,6 @@ import ( "strings" "github.com/containerd/log" - "github.com/hashicorp/go-multierror" "github.com/moby/sys/mount" "github.com/moby/sys/symlink" "golang.org/x/sys/unix" @@ -215,10 +214,13 @@ func (vw *containerFSView) GoInFS(ctx context.Context, fn func()) error { func (vw *containerFSView) Close() error { runtime.SetFinalizer(vw, nil) close(vw.todo) - err := multierror.Append(nil, <-vw.done) - err = multierror.Append(err, vw.ctr.UnmountVolumes(context.TODO(), vw.d.LogVolumeEvent)) - err = multierror.Append(err, vw.d.Unmount(vw.ctr)) - return err.ErrorOrNil() + var errs []error + errs = append(errs, + <-vw.done, + vw.ctr.UnmountVolumes(context.TODO(), vw.d.LogVolumeEvent), + vw.d.Unmount(vw.ctr), + ) + return errors.Join(errs...) } // Stat returns the metadata for path, relative to the current working directory diff --git a/daemon/internal/libcontainerd/remote/client.go b/daemon/internal/libcontainerd/remote/client.go index 7085c225af..f8f5745ac5 100644 --- a/daemon/internal/libcontainerd/remote/client.go +++ b/daemon/internal/libcontainerd/remote/client.go @@ -3,6 +3,7 @@ package remote import ( "context" "encoding/json" + "errors" "io" "os" "path/filepath" @@ -25,7 +26,6 @@ import ( cerrdefs "github.com/containerd/errdefs" "github.com/containerd/log" "github.com/containerd/typeurl/v2" - "github.com/hashicorp/go-multierror" "github.com/moby/moby/v2/daemon/internal/libcontainerd/queue" libcontainerdtypes "github.com/moby/moby/v2/daemon/internal/libcontainerd/types" "github.com/moby/moby/v2/errdefs" @@ -33,7 +33,7 @@ import ( "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/opencontainers/runtime-spec/specs-go" - "github.com/pkg/errors" + pkgerrors "github.com/pkg/errors" "go.opentelemetry.io/otel" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -113,7 +113,7 @@ func (c *container) AttachTask(ctx context.Context, attachStdio libcontainerdtyp } t, err := c.c8dCtr.Task(ctx, attachIO) if err != nil { - return nil, errors.Wrap(wrapError(err), "error getting containerd task for container") + return nil, pkgerrors.Wrap(wrapError(err), "error getting containerd task for container") } return c.newTask(t), nil } @@ -132,7 +132,7 @@ func (c *client) NewContainer(ctx context.Context, id string, ociSpec *specs.Spe ctr, err := c.client.NewContainer(ctx, id, opts...) if err != nil { if cerrdefs.IsAlreadyExists(err) { - return nil, errors.WithStack(errdefs.Conflict(errors.New("id already in use"))) + return nil, pkgerrors.WithStack(errdefs.Conflict(errors.New("id already in use"))) } return nil, wrapError(err) } @@ -177,10 +177,10 @@ func (c *container) NewTask(ctx context.Context, checkpointDir string, withStdin } }() if err := tar.Close(); err != nil { - return nil, errors.Wrap(err, "failed to close checkpoint tar stream") + return nil, pkgerrors.Wrap(err, "failed to close checkpoint tar stream") } if err != nil { - return nil, errors.Wrapf(err, "failed to upload checkpoint to containerd") + return nil, pkgerrors.Wrapf(err, "failed to upload checkpoint to containerd") } } @@ -189,13 +189,13 @@ func (c *container) NewTask(ctx context.Context, checkpointDir string, withStdin // to refresh the metadata separately for spec and labels. md, err := c.c8dCtr.Info(ctx, containerd.WithoutRefreshedMetadata) if err != nil { - return nil, errors.Wrap(err, "failed to retrieve metadata") + return nil, pkgerrors.Wrap(err, "failed to retrieve metadata") } bundle := md.Labels[DockerContainerBundlePath] var spec specs.Spec if err := json.Unmarshal(md.Spec.GetValue(), &spec); err != nil { - return nil, errors.Wrap(err, "failed to retrieve spec") + return nil, pkgerrors.Wrap(err, "failed to retrieve spec") } uid, gid := getSpecUser(&spec) @@ -235,7 +235,7 @@ func (c *container) NewTask(ctx context.Context, checkpointDir string, withStdin rio.Cancel() rio.Close() } - return nil, errors.Wrap(wrapError(err), "failed to create task for container") + return nil, pkgerrors.Wrap(wrapError(err), "failed to create task for container") } // Signal c.createIO that it can call CloseIO @@ -289,7 +289,7 @@ func (t *task) Exec(ctx context.Context, processID string, spec *specs.Process, if err != nil { close(stdinCloseSync) if cerrdefs.IsAlreadyExists(err) { - return nil, errors.WithStack(errdefs.Conflict(errors.New("id already in use"))) + return nil, pkgerrors.WithStack(errdefs.Conflict(errors.New("id already in use"))) } return nil, wrapError(err) } @@ -350,7 +350,7 @@ func (t *task) Summary(ctx context.Context) ([]libcontainerdtypes.Summary, error for _, pi := range pis { i, err := typeurl.UnmarshalAny(pi.Info) if err != nil { - return nil, errors.Wrap(err, "unable to decode process details") + return nil, pkgerrors.Wrap(err, "unable to decode process details") } s, err := summaryFromInterface(i) if err != nil { @@ -440,11 +440,11 @@ func (t *task) CreateCheckpoint(ctx context.Context, checkpointDir string, exit b, err := content.ReadBlob(ctx, t.ctr.client.client.ContentStore(), img.Target()) if err != nil { - return errdefs.System(errors.Wrapf(err, "failed to retrieve checkpoint data")) + return errdefs.System(pkgerrors.Wrapf(err, "failed to retrieve checkpoint data")) } var index ocispec.Index if err := json.Unmarshal(b, &index); err != nil { - return errdefs.System(errors.Wrapf(err, "failed to decode checkpoint data")) + return errdefs.System(pkgerrors.Wrapf(err, "failed to decode checkpoint data")) } var cpDesc *ocispec.Descriptor @@ -455,17 +455,17 @@ func (t *task) CreateCheckpoint(ctx context.Context, checkpointDir string, exit } } if cpDesc == nil { - return errdefs.System(errors.Wrapf(err, "invalid checkpoint")) + return errdefs.System(pkgerrors.Wrapf(err, "invalid checkpoint")) } rat, err := t.ctr.client.client.ContentStore().ReaderAt(ctx, *cpDesc) if err != nil { - return errdefs.System(errors.Wrapf(err, "failed to get checkpoint reader")) + return errdefs.System(pkgerrors.Wrapf(err, "failed to get checkpoint reader")) } defer rat.Close() _, err = archive.Apply(ctx, checkpointDir, content.NewReader(rat)) if err != nil { - return errdefs.System(errors.Wrapf(err, "failed to read checkpoint reader")) + return errdefs.System(pkgerrors.Wrapf(err, "failed to read checkpoint reader")) } return err @@ -476,7 +476,7 @@ func (c *client) LoadContainer(ctx context.Context, id string) (libcontainerdtyp ctr, err := c.client.LoadContainer(ctx, id) if err != nil { if cerrdefs.IsNotFound(err) { - return nil, errors.WithStack(errdefs.NotFound(errors.New("no such container"))) + return nil, pkgerrors.WithStack(errdefs.NotFound(errors.New("no such container"))) } return nil, wrapError(err) } @@ -505,27 +505,20 @@ func (c *container) createIO(fifos *cio.FIFOSet, stdinCloseSync chan containerd. if io.Stdin != nil { var ( - closeErr error + errs []error stdinOnce sync.Once ) pipe := io.Stdin io.Stdin = ioutils.NewWriteCloserWrapper(pipe, func() error { stdinOnce.Do(func() { - closeErr = pipe.Close() + errs = append(errs, pipe.Close()) select { case p, ok := <-stdinCloseSync: if !ok { return } - if err := closeStdin(context.Background(), p); err != nil { - if closeErr != nil { - closeErr = multierror.Append(closeErr, err) - } else { - // Avoid wrapping a single error in a multierror. - closeErr = err - } - } + errs = append(errs, closeStdin(context.Background(), p)) default: // The process wasn't ready. Close its stdin asynchronously. go func() { @@ -541,7 +534,7 @@ func (c *container) createIO(fifos *cio.FIFOSet, stdinCloseSync chan containerd. }() } }) - return closeErr + return errors.Join(errs...) }) } diff --git a/daemon/libnetwork/drivers/overlay/ov_network.go b/daemon/libnetwork/drivers/overlay/ov_network.go index 0a63b2ca02..8bc37e07c5 100644 --- a/daemon/libnetwork/drivers/overlay/ov_network.go +++ b/daemon/libnetwork/drivers/overlay/ov_network.go @@ -15,7 +15,6 @@ import ( "sync" "github.com/containerd/log" - "github.com/hashicorp/go-multierror" "github.com/moby/moby/v2/daemon/libnetwork/driverapi" "github.com/moby/moby/v2/daemon/libnetwork/drivers/overlay/overlayutils" "github.com/moby/moby/v2/daemon/libnetwork/internal/countmap" @@ -543,7 +542,7 @@ func (n *network) initSubnetSandbox(s *subnet) error { } if err := n.driver.programInput(s.vni, n.secure); err != nil { if n.secure { - return multierror.Append(err, n.driver.programMangle(s.vni, false)) + return errors.Join(err, n.driver.programMangle(s.vni, false)) } } diff --git a/daemon/reload.go b/daemon/reload.go index abc0bbe3e6..cc5dd5b170 100644 --- a/daemon/reload.go +++ b/daemon/reload.go @@ -3,11 +3,11 @@ package daemon import ( "context" "encoding/json" + "errors" "fmt" "strconv" "github.com/containerd/log" - "github.com/hashicorp/go-multierror" "github.com/mitchellh/copystructure" "github.com/moby/moby/api/types/events" @@ -35,11 +35,11 @@ func (tx *reloadTxn) run(cbs []func() error) error { tx.onCommit = nil tx.onRollback = nil - var res *multierror.Error + var errs []error for _, cb := range cbs { - res = multierror.Append(res, cb()) + errs = append(errs, cb()) } - return res.ErrorOrNil() + return errors.Join(errs...) } // Commit calls all functions registered with OnCommit. @@ -109,10 +109,7 @@ func (daemon *Daemon) Reload(conf *config.Config) error { daemon.reloadNetworkDiagnosticPort, } { if err := reload(&txn, newCfg, conf, attributes); err != nil { - if rollbackErr := txn.Rollback(); rollbackErr != nil { - return multierror.Append(nil, err, rollbackErr) - } - return err + return errors.Join(err, txn.Rollback()) } } diff --git a/go.mod b/go.mod index b943d1eb63..fa1154d52a 100644 --- a/go.mod +++ b/go.mod @@ -49,7 +49,6 @@ require ( github.com/gorilla/mux v1.8.1 github.com/hashicorp/go-immutable-radix/v2 v2.1.0 github.com/hashicorp/go-memdb v1.3.2 - github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/memberlist v0.4.0 github.com/hashicorp/serf v0.8.5 github.com/ishidawataru/sctp v0.0.0-20250708014235-1989182a9425 @@ -180,6 +179,7 @@ require ( github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-msgpack v0.5.5 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-sockaddr v1.0.2 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect