Browse code

chore: use errors.Join instead of github.com/hashicorp/go-multierror

Signed-off-by: Matthieu MOREL <matthieu.morel35@gmail.com>

Matthieu MOREL authored on 2025/08/08 22:33:08
Showing 8 changed files
... ...
@@ -69,6 +69,8 @@ linters:
69 69
               desc: Use github.com/moby/sys/userns instead.
70 70
             - pkg: "github.com/tonistiigi/fsutil"
71 71
               desc: The fsutil module does not have a stable API, so we should not have a direct dependency unless necessary.
72
+            - pkg: "github.com/hashicorp/go-multierror"
73
+              desc: "Use errors.Join instead"
72 74
 
73 75
     dupword:
74 76
       ignore:
... ...
@@ -2,16 +2,15 @@ package daemon
2 2
 
3 3
 import (
4 4
 	"context"
5
+	"errors"
5 6
 	"fmt"
6 7
 	"os"
7 8
 
8 9
 	"github.com/containerd/log"
9
-	"github.com/hashicorp/go-multierror"
10 10
 	"github.com/moby/moby/api/types/system"
11 11
 	"github.com/moby/moby/v2/daemon/config"
12 12
 	"github.com/moby/moby/v2/errdefs"
13 13
 	"github.com/opencontainers/runtime-spec/specs-go"
14
-	"github.com/pkg/errors"
15 14
 	"tags.cncf.io/container-device-interface/pkg/cdi"
16 15
 )
17 16
 
... ...
@@ -118,11 +117,11 @@ func (c *cdiHandler) injectCDIDevices(s *specs.Spec, dev *deviceInstance) error
118 118
 
119 119
 // getErrors returns a single error representation of errors that may have occurred while refreshing the CDI registry.
120 120
 func (c *cdiHandler) getErrors() error {
121
-	var err *multierror.Error
122
-	for _, errs := range c.registry.GetErrors() {
123
-		err = multierror.Append(err, errs...)
121
+	var errs []error
122
+	for _, es := range c.registry.GetErrors() {
123
+		errs = append(errs, es...)
124 124
 	}
125
-	return err.ErrorOrNil()
125
+	return errors.Join(errs...)
126 126
 }
127 127
 
128 128
 // listDevices uses the CDI cache to list all discovered CDI devices.
... ...
@@ -2,6 +2,7 @@ package containerd
2 2
 
3 3
 import (
4 4
 	"context"
5
+	"errors"
5 6
 	"sort"
6 7
 	"strings"
7 8
 
... ...
@@ -11,14 +12,12 @@ import (
11 11
 	cerrdefs "github.com/containerd/errdefs"
12 12
 	"github.com/containerd/log"
13 13
 	"github.com/distribution/reference"
14
-	"github.com/hashicorp/go-multierror"
15 14
 	"github.com/moby/moby/api/types/filters"
16 15
 	"github.com/moby/moby/api/types/image"
17 16
 	"github.com/moby/moby/v2/daemon/container"
18 17
 	"github.com/moby/moby/v2/errdefs"
19 18
 	"github.com/opencontainers/go-digest"
20 19
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
21
-	"github.com/pkg/errors"
22 20
 )
23 21
 
24 22
 var imagesAcceptedFilters = map[string]bool{
... ...
@@ -218,7 +217,7 @@ func (i *ImageService) pruneAll(ctx context.Context, imagesToPrune map[string]c8
218 218
 	span.SetAttributes(tracing.Attribute("count", len(imagesToPrune)))
219 219
 	defer span.End()
220 220
 
221
-	var errs error
221
+	var errs []error
222 222
 	for _, img := range imagesToPrune {
223 223
 		log.G(ctx).WithField("image", img).Debug("pruning image")
224 224
 
... ...
@@ -229,17 +228,17 @@ func (i *ImageService) pruneAll(ctx context.Context, imagesToPrune map[string]c8
229 229
 			return nil
230 230
 		})
231 231
 		if err != nil {
232
-			errs = multierror.Append(errs, err)
232
+			errs = append(errs, err)
233 233
 			if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
234
-				return &report, errs
234
+				return &report, errors.Join(errs...)
235 235
 			}
236 236
 			continue
237 237
 		}
238 238
 		err = i.images.Delete(ctx, img.Name, c8dimages.SynchronousDelete())
239 239
 		if err != nil && !cerrdefs.IsNotFound(err) {
240
-			errs = multierror.Append(errs, err)
240
+			errs = append(errs, err)
241 241
 			if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
242
-				return &report, errs
242
+				return &report, errors.Join(errs...)
243 243
 			}
244 244
 			continue
245 245
 		}
... ...
@@ -265,5 +264,5 @@ func (i *ImageService) pruneAll(ctx context.Context, imagesToPrune map[string]c8
265 265
 		}
266 266
 	}
267 267
 
268
-	return &report, errs
268
+	return &report, errors.Join(errs...)
269 269
 }
... ...
@@ -10,7 +10,6 @@ import (
10 10
 	"strings"
11 11
 
12 12
 	"github.com/containerd/log"
13
-	"github.com/hashicorp/go-multierror"
14 13
 	"github.com/moby/sys/mount"
15 14
 	"github.com/moby/sys/symlink"
16 15
 	"golang.org/x/sys/unix"
... ...
@@ -215,10 +214,13 @@ func (vw *containerFSView) GoInFS(ctx context.Context, fn func()) error {
215 215
 func (vw *containerFSView) Close() error {
216 216
 	runtime.SetFinalizer(vw, nil)
217 217
 	close(vw.todo)
218
-	err := multierror.Append(nil, <-vw.done)
219
-	err = multierror.Append(err, vw.ctr.UnmountVolumes(context.TODO(), vw.d.LogVolumeEvent))
220
-	err = multierror.Append(err, vw.d.Unmount(vw.ctr))
221
-	return err.ErrorOrNil()
218
+	var errs []error
219
+	errs = append(errs,
220
+		<-vw.done,
221
+		vw.ctr.UnmountVolumes(context.TODO(), vw.d.LogVolumeEvent),
222
+		vw.d.Unmount(vw.ctr),
223
+	)
224
+	return errors.Join(errs...)
222 225
 }
223 226
 
224 227
 // Stat returns the metadata for path, relative to the current working directory
... ...
@@ -3,6 +3,7 @@ package remote
3 3
 import (
4 4
 	"context"
5 5
 	"encoding/json"
6
+	"errors"
6 7
 	"io"
7 8
 	"os"
8 9
 	"path/filepath"
... ...
@@ -25,7 +26,6 @@ import (
25 25
 	cerrdefs "github.com/containerd/errdefs"
26 26
 	"github.com/containerd/log"
27 27
 	"github.com/containerd/typeurl/v2"
28
-	"github.com/hashicorp/go-multierror"
29 28
 	"github.com/moby/moby/v2/daemon/internal/libcontainerd/queue"
30 29
 	libcontainerdtypes "github.com/moby/moby/v2/daemon/internal/libcontainerd/types"
31 30
 	"github.com/moby/moby/v2/errdefs"
... ...
@@ -33,7 +33,7 @@ import (
33 33
 	"github.com/opencontainers/go-digest"
34 34
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
35 35
 	"github.com/opencontainers/runtime-spec/specs-go"
36
-	"github.com/pkg/errors"
36
+	pkgerrors "github.com/pkg/errors"
37 37
 	"go.opentelemetry.io/otel"
38 38
 	"google.golang.org/grpc/codes"
39 39
 	"google.golang.org/grpc/status"
... ...
@@ -113,7 +113,7 @@ func (c *container) AttachTask(ctx context.Context, attachStdio libcontainerdtyp
113 113
 	}
114 114
 	t, err := c.c8dCtr.Task(ctx, attachIO)
115 115
 	if err != nil {
116
-		return nil, errors.Wrap(wrapError(err), "error getting containerd task for container")
116
+		return nil, pkgerrors.Wrap(wrapError(err), "error getting containerd task for container")
117 117
 	}
118 118
 	return c.newTask(t), nil
119 119
 }
... ...
@@ -132,7 +132,7 @@ func (c *client) NewContainer(ctx context.Context, id string, ociSpec *specs.Spe
132 132
 	ctr, err := c.client.NewContainer(ctx, id, opts...)
133 133
 	if err != nil {
134 134
 		if cerrdefs.IsAlreadyExists(err) {
135
-			return nil, errors.WithStack(errdefs.Conflict(errors.New("id already in use")))
135
+			return nil, pkgerrors.WithStack(errdefs.Conflict(errors.New("id already in use")))
136 136
 		}
137 137
 		return nil, wrapError(err)
138 138
 	}
... ...
@@ -177,10 +177,10 @@ func (c *container) NewTask(ctx context.Context, checkpointDir string, withStdin
177 177
 			}
178 178
 		}()
179 179
 		if err := tar.Close(); err != nil {
180
-			return nil, errors.Wrap(err, "failed to close checkpoint tar stream")
180
+			return nil, pkgerrors.Wrap(err, "failed to close checkpoint tar stream")
181 181
 		}
182 182
 		if err != nil {
183
-			return nil, errors.Wrapf(err, "failed to upload checkpoint to containerd")
183
+			return nil, pkgerrors.Wrapf(err, "failed to upload checkpoint to containerd")
184 184
 		}
185 185
 	}
186 186
 
... ...
@@ -189,13 +189,13 @@ func (c *container) NewTask(ctx context.Context, checkpointDir string, withStdin
189 189
 	// to refresh the metadata separately for spec and labels.
190 190
 	md, err := c.c8dCtr.Info(ctx, containerd.WithoutRefreshedMetadata)
191 191
 	if err != nil {
192
-		return nil, errors.Wrap(err, "failed to retrieve metadata")
192
+		return nil, pkgerrors.Wrap(err, "failed to retrieve metadata")
193 193
 	}
194 194
 	bundle := md.Labels[DockerContainerBundlePath]
195 195
 
196 196
 	var spec specs.Spec
197 197
 	if err := json.Unmarshal(md.Spec.GetValue(), &spec); err != nil {
198
-		return nil, errors.Wrap(err, "failed to retrieve spec")
198
+		return nil, pkgerrors.Wrap(err, "failed to retrieve spec")
199 199
 	}
200 200
 	uid, gid := getSpecUser(&spec)
201 201
 
... ...
@@ -235,7 +235,7 @@ func (c *container) NewTask(ctx context.Context, checkpointDir string, withStdin
235 235
 			rio.Cancel()
236 236
 			rio.Close()
237 237
 		}
238
-		return nil, errors.Wrap(wrapError(err), "failed to create task for container")
238
+		return nil, pkgerrors.Wrap(wrapError(err), "failed to create task for container")
239 239
 	}
240 240
 
241 241
 	// Signal c.createIO that it can call CloseIO
... ...
@@ -289,7 +289,7 @@ func (t *task) Exec(ctx context.Context, processID string, spec *specs.Process,
289 289
 	if err != nil {
290 290
 		close(stdinCloseSync)
291 291
 		if cerrdefs.IsAlreadyExists(err) {
292
-			return nil, errors.WithStack(errdefs.Conflict(errors.New("id already in use")))
292
+			return nil, pkgerrors.WithStack(errdefs.Conflict(errors.New("id already in use")))
293 293
 		}
294 294
 		return nil, wrapError(err)
295 295
 	}
... ...
@@ -350,7 +350,7 @@ func (t *task) Summary(ctx context.Context) ([]libcontainerdtypes.Summary, error
350 350
 	for _, pi := range pis {
351 351
 		i, err := typeurl.UnmarshalAny(pi.Info)
352 352
 		if err != nil {
353
-			return nil, errors.Wrap(err, "unable to decode process details")
353
+			return nil, pkgerrors.Wrap(err, "unable to decode process details")
354 354
 		}
355 355
 		s, err := summaryFromInterface(i)
356 356
 		if err != nil {
... ...
@@ -440,11 +440,11 @@ func (t *task) CreateCheckpoint(ctx context.Context, checkpointDir string, exit
440 440
 
441 441
 	b, err := content.ReadBlob(ctx, t.ctr.client.client.ContentStore(), img.Target())
442 442
 	if err != nil {
443
-		return errdefs.System(errors.Wrapf(err, "failed to retrieve checkpoint data"))
443
+		return errdefs.System(pkgerrors.Wrapf(err, "failed to retrieve checkpoint data"))
444 444
 	}
445 445
 	var index ocispec.Index
446 446
 	if err := json.Unmarshal(b, &index); err != nil {
447
-		return errdefs.System(errors.Wrapf(err, "failed to decode checkpoint data"))
447
+		return errdefs.System(pkgerrors.Wrapf(err, "failed to decode checkpoint data"))
448 448
 	}
449 449
 
450 450
 	var cpDesc *ocispec.Descriptor
... ...
@@ -455,17 +455,17 @@ func (t *task) CreateCheckpoint(ctx context.Context, checkpointDir string, exit
455 455
 		}
456 456
 	}
457 457
 	if cpDesc == nil {
458
-		return errdefs.System(errors.Wrapf(err, "invalid checkpoint"))
458
+		return errdefs.System(pkgerrors.Wrapf(err, "invalid checkpoint"))
459 459
 	}
460 460
 
461 461
 	rat, err := t.ctr.client.client.ContentStore().ReaderAt(ctx, *cpDesc)
462 462
 	if err != nil {
463
-		return errdefs.System(errors.Wrapf(err, "failed to get checkpoint reader"))
463
+		return errdefs.System(pkgerrors.Wrapf(err, "failed to get checkpoint reader"))
464 464
 	}
465 465
 	defer rat.Close()
466 466
 	_, err = archive.Apply(ctx, checkpointDir, content.NewReader(rat))
467 467
 	if err != nil {
468
-		return errdefs.System(errors.Wrapf(err, "failed to read checkpoint reader"))
468
+		return errdefs.System(pkgerrors.Wrapf(err, "failed to read checkpoint reader"))
469 469
 	}
470 470
 
471 471
 	return err
... ...
@@ -476,7 +476,7 @@ func (c *client) LoadContainer(ctx context.Context, id string) (libcontainerdtyp
476 476
 	ctr, err := c.client.LoadContainer(ctx, id)
477 477
 	if err != nil {
478 478
 		if cerrdefs.IsNotFound(err) {
479
-			return nil, errors.WithStack(errdefs.NotFound(errors.New("no such container")))
479
+			return nil, pkgerrors.WithStack(errdefs.NotFound(errors.New("no such container")))
480 480
 		}
481 481
 		return nil, wrapError(err)
482 482
 	}
... ...
@@ -505,27 +505,20 @@ func (c *container) createIO(fifos *cio.FIFOSet, stdinCloseSync chan containerd.
505 505
 
506 506
 	if io.Stdin != nil {
507 507
 		var (
508
-			closeErr  error
508
+			errs      []error
509 509
 			stdinOnce sync.Once
510 510
 		)
511 511
 		pipe := io.Stdin
512 512
 		io.Stdin = ioutils.NewWriteCloserWrapper(pipe, func() error {
513 513
 			stdinOnce.Do(func() {
514
-				closeErr = pipe.Close()
514
+				errs = append(errs, pipe.Close())
515 515
 
516 516
 				select {
517 517
 				case p, ok := <-stdinCloseSync:
518 518
 					if !ok {
519 519
 						return
520 520
 					}
521
-					if err := closeStdin(context.Background(), p); err != nil {
522
-						if closeErr != nil {
523
-							closeErr = multierror.Append(closeErr, err)
524
-						} else {
525
-							// Avoid wrapping a single error in a multierror.
526
-							closeErr = err
527
-						}
528
-					}
521
+					errs = append(errs, closeStdin(context.Background(), p))
529 522
 				default:
530 523
 					// The process wasn't ready. Close its stdin asynchronously.
531 524
 					go func() {
... ...
@@ -541,7 +534,7 @@ func (c *container) createIO(fifos *cio.FIFOSet, stdinCloseSync chan containerd.
541 541
 					}()
542 542
 				}
543 543
 			})
544
-			return closeErr
544
+			return errors.Join(errs...)
545 545
 		})
546 546
 	}
547 547
 
... ...
@@ -15,7 +15,6 @@ import (
15 15
 	"sync"
16 16
 
17 17
 	"github.com/containerd/log"
18
-	"github.com/hashicorp/go-multierror"
19 18
 	"github.com/moby/moby/v2/daemon/libnetwork/driverapi"
20 19
 	"github.com/moby/moby/v2/daemon/libnetwork/drivers/overlay/overlayutils"
21 20
 	"github.com/moby/moby/v2/daemon/libnetwork/internal/countmap"
... ...
@@ -543,7 +542,7 @@ func (n *network) initSubnetSandbox(s *subnet) error {
543 543
 	}
544 544
 	if err := n.driver.programInput(s.vni, n.secure); err != nil {
545 545
 		if n.secure {
546
-			return multierror.Append(err, n.driver.programMangle(s.vni, false))
546
+			return errors.Join(err, n.driver.programMangle(s.vni, false))
547 547
 		}
548 548
 	}
549 549
 
... ...
@@ -3,11 +3,11 @@ package daemon
3 3
 import (
4 4
 	"context"
5 5
 	"encoding/json"
6
+	"errors"
6 7
 	"fmt"
7 8
 	"strconv"
8 9
 
9 10
 	"github.com/containerd/log"
10
-	"github.com/hashicorp/go-multierror"
11 11
 	"github.com/mitchellh/copystructure"
12 12
 	"github.com/moby/moby/api/types/events"
13 13
 
... ...
@@ -35,11 +35,11 @@ func (tx *reloadTxn) run(cbs []func() error) error {
35 35
 	tx.onCommit = nil
36 36
 	tx.onRollback = nil
37 37
 
38
-	var res *multierror.Error
38
+	var errs []error
39 39
 	for _, cb := range cbs {
40
-		res = multierror.Append(res, cb())
40
+		errs = append(errs, cb())
41 41
 	}
42
-	return res.ErrorOrNil()
42
+	return errors.Join(errs...)
43 43
 }
44 44
 
45 45
 // Commit calls all functions registered with OnCommit.
... ...
@@ -109,10 +109,7 @@ func (daemon *Daemon) Reload(conf *config.Config) error {
109 109
 		daemon.reloadNetworkDiagnosticPort,
110 110
 	} {
111 111
 		if err := reload(&txn, newCfg, conf, attributes); err != nil {
112
-			if rollbackErr := txn.Rollback(); rollbackErr != nil {
113
-				return multierror.Append(nil, err, rollbackErr)
114
-			}
115
-			return err
112
+			return errors.Join(err, txn.Rollback())
116 113
 		}
117 114
 	}
118 115
 
... ...
@@ -49,7 +49,6 @@ require (
49 49
 	github.com/gorilla/mux v1.8.1
50 50
 	github.com/hashicorp/go-immutable-radix/v2 v2.1.0
51 51
 	github.com/hashicorp/go-memdb v1.3.2
52
-	github.com/hashicorp/go-multierror v1.1.1
53 52
 	github.com/hashicorp/memberlist v0.4.0
54 53
 	github.com/hashicorp/serf v0.8.5
55 54
 	github.com/ishidawataru/sctp v0.0.0-20250708014235-1989182a9425
... ...
@@ -180,6 +179,7 @@ require (
180 180
 	github.com/hashicorp/errwrap v1.1.0 // indirect
181 181
 	github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
182 182
 	github.com/hashicorp/go-msgpack v0.5.5 // indirect
183
+	github.com/hashicorp/go-multierror v1.1.1 // indirect
183 184
 	github.com/hashicorp/go-sockaddr v1.0.2 // indirect
184 185
 	github.com/hashicorp/golang-lru v0.5.4 // indirect
185 186
 	github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect