Browse Source

deps: remove deprecated github.com/pkg/errors

Signed-off-by: Matthieu MOREL <[email protected]>
Matthieu MOREL 2 years ago
parent
commit
4f694919ff

+ 6 - 0
.golangci.yml

@@ -7,6 +7,7 @@ linters:
   enable:
     - depguard
     - errcheck
+    - errorlint
     - gocritic
     - gocyclo
     - gofmt
@@ -40,6 +41,11 @@ linters-settings:
             desc: 'compose-go uses yaml.v3'
   gomodguard:
     blocked:
+      modules:
+        - github.com/pkg/errors:
+            recommendations:
+              - errors
+              - fmt
       versions:
         - github.com/distribution/distribution:
             reason: "use distribution/reference"

+ 1 - 1
cmd/compose/compose.go

@@ -18,6 +18,7 @@ package compose
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"os"
 	"os/signal"
@@ -37,7 +38,6 @@ import (
 	"github.com/docker/cli/cli/command"
 	"github.com/docker/compose/v2/pkg/remote"
 	"github.com/morikuni/aec"
-	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
 	"github.com/spf13/pflag"

+ 1 - 1
cmd/compose/ps.go

@@ -18,6 +18,7 @@ package compose
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"sort"
 	"strings"
@@ -29,7 +30,6 @@ import (
 	"github.com/docker/cli/cli/command"
 	cliformatter "github.com/docker/cli/cli/command/formatter"
 	cliflags "github.com/docker/cli/cli/flags"
-	"github.com/pkg/errors"
 	"github.com/spf13/cobra"
 )
 

+ 3 - 3
cmd/compose/scale.go

@@ -18,13 +18,13 @@ package compose
 
 import (
 	"context"
+	"fmt"
 	"strconv"
 	"strings"
 
 	"github.com/docker/cli/cli/command"
 
 	"github.com/compose-spec/compose-go/types"
-	"github.com/pkg/errors"
 	"golang.org/x/exp/maps"
 
 	"github.com/docker/compose/v2/pkg/api"
@@ -95,12 +95,12 @@ func parseServicesReplicasArgs(args []string) (map[string]int, error) {
 	for _, arg := range args {
 		key, val, ok := strings.Cut(arg, "=")
 		if !ok || key == "" || val == "" {
-			return nil, errors.Errorf("invalid scale specifier: %s", arg)
+			return nil, fmt.Errorf("invalid scale specifier: %s", arg)
 		}
 		intValue, err := strconv.Atoi(val)
 
 		if err != nil {
-			return nil, errors.Errorf("invalid scale specifier: can't parse replica value as int: %v", arg)
+			return nil, fmt.Errorf("invalid scale specifier: can't parse replica value as int: %v", arg)
 		}
 		serviceReplicaTuples[key] = intValue
 	}

+ 2 - 2
cmd/compose/watch.go

@@ -78,10 +78,10 @@ func runWatch(ctx context.Context, dockerCli command.Cli, backend api.Service, w
 	// validation done -- ensure we have the lockfile for this project before doing work
 	l, err := locker.NewPidfile(project.Name)
 	if err != nil {
-		return fmt.Errorf("cannot take exclusive lock for project %q: %v", project.Name, err)
+		return fmt.Errorf("cannot take exclusive lock for project %q: %w", project.Name, err)
 	}
 	if err := l.Lock(); err != nil {
-		return fmt.Errorf("cannot take exclusive lock for project %q: %v", project.Name, err)
+		return fmt.Errorf("cannot take exclusive lock for project %q: %w", project.Name, err)
 	}
 
 	if !watchOpts.noUp {

+ 1 - 3
cmd/formatter/formatter.go

@@ -23,8 +23,6 @@ import (
 	"strings"
 
 	"github.com/docker/compose/v2/pkg/api"
-
-	"github.com/pkg/errors"
 )
 
 // Print prints formatted lists in different formats
@@ -67,7 +65,7 @@ func Print(toJSON interface{}, format string, outWriter io.Writer, writerFn func
 			_, _ = fmt.Fprintln(outWriter, outJSON)
 		}
 	default:
-		return errors.Wrapf(api.ErrParsingFailed, "format value %q could not be parsed", format)
+		return fmt.Errorf("format value %q could not be parsed: %w", format, api.ErrParsingFailed)
 	}
 	return nil
 }

+ 2 - 3
go.mod

@@ -33,7 +33,6 @@ require (
 	github.com/morikuni/aec v1.0.0
 	github.com/opencontainers/go-digest v1.0.0
 	github.com/opencontainers/image-spec v1.1.0-rc5
-	github.com/pkg/errors v0.9.1
 	github.com/sirupsen/logrus v1.9.3
 	github.com/spf13/cobra v1.7.0
 	github.com/spf13/pflag v1.0.5
@@ -46,13 +45,12 @@ require (
 	go.opentelemetry.io/otel/sdk v1.14.0
 	go.opentelemetry.io/otel/trace v1.14.0
 	go.uber.org/goleak v1.2.1
+	golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1
 	golang.org/x/sync v0.3.0
 	google.golang.org/grpc v1.58.2
 	gotest.tools/v3 v3.5.1
 )
 
-require golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1
-
 require (
 	github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect
 	github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
@@ -132,6 +130,7 @@ require (
 	github.com/opencontainers/runc v1.1.7 // indirect
 	github.com/pelletier/go-toml v1.9.5 // indirect
 	github.com/pelletier/go-toml/v2 v2.0.5 // indirect
+	github.com/pkg/errors v0.9.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/prometheus/client_golang v1.14.0 // indirect
 	github.com/prometheus/client_model v0.3.0 // indirect

+ 3 - 3
internal/sync/tar.go

@@ -21,6 +21,7 @@ import (
 	"archive/tar"
 	"bytes"
 	"context"
+	"errors"
 	"fmt"
 	"io"
 	"io/fs"
@@ -30,7 +31,6 @@ import (
 	"strings"
 
 	"github.com/hashicorp/go-multierror"
-	"github.com/pkg/errors"
 
 	"github.com/compose-spec/compose-go/types"
 	moby "github.com/docker/docker/api/types"
@@ -212,7 +212,7 @@ func (a *ArchiveBuilder) writeEntry(entry archiveEntry) error {
 	if useBuf {
 		a.copyBuf.Reset()
 		_, err = io.Copy(a.copyBuf, file)
-		if err != nil && err != io.EOF {
+		if err != nil && !errors.Is(err, io.EOF) {
 			return fmt.Errorf("copying %q: %w", pathInTar, err)
 		}
 		header.Size = int64(len(a.copyBuf.Bytes()))
@@ -232,7 +232,7 @@ func (a *ArchiveBuilder) writeEntry(entry archiveEntry) error {
 		_, err = io.Copy(a.tw, file)
 	}
 
-	if err != nil && err != io.EOF {
+	if err != nil && !errors.Is(err, io.EOF) {
 		return fmt.Errorf("copying %q: %w", pathInTar, err)
 	}
 

+ 2 - 2
internal/sync/writer_test.go

@@ -18,12 +18,12 @@ package sync
 
 import (
 	"context"
+	"errors"
 	"io"
 	"sync"
 	"testing"
 	"time"
 
-	"github.com/pkg/errors"
 	"github.com/stretchr/testify/require"
 )
 
@@ -115,7 +115,7 @@ func (b *bufReader) consume() {
 			b.data = append(b.data, buf[:n]...)
 			b.mu.Unlock()
 		}
-		if err == io.EOF {
+		if errors.Is(err, io.EOF) {
 			return
 		}
 		if err != nil {

+ 4 - 4
internal/tracing/docker_context.go

@@ -39,7 +39,7 @@ func traceClientFromDockerContext(dockerCli command.Cli, otelEnv envMap) (otlptr
 	// automatic integration with Docker Desktop;
 	cfg, err := ConfigFromDockerContext(dockerCli.ContextStore(), dockerCli.CurrentContext())
 	if err != nil {
-		return nil, fmt.Errorf("loading otel config from docker context metadata: %v", err)
+		return nil, fmt.Errorf("loading otel config from docker context metadata: %w", err)
 	}
 
 	if cfg.Endpoint == "" {
@@ -52,13 +52,13 @@ func traceClientFromDockerContext(dockerCli command.Cli, otelEnv envMap) (otlptr
 	defer func() {
 		for k, v := range otelEnv {
 			if err := os.Setenv(k, v); err != nil {
-				panic(fmt.Errorf("restoring env for %q: %v", k, err))
+				panic(fmt.Errorf("restoring env for %q: %w", k, err))
 			}
 		}
 	}()
 	for k := range otelEnv {
 		if err := os.Unsetenv(k); err != nil {
-			return nil, fmt.Errorf("stashing env for %q: %v", k, err)
+			return nil, fmt.Errorf("stashing env for %q: %w", k, err)
 		}
 	}
 
@@ -71,7 +71,7 @@ func traceClientFromDockerContext(dockerCli command.Cli, otelEnv envMap) (otlptr
 		grpc.WithTransportCredentials(insecure.NewCredentials()),
 	)
 	if err != nil {
-		return nil, fmt.Errorf("initializing otel connection from docker context metadata: %v", err)
+		return nil, fmt.Errorf("initializing otel connection from docker context metadata: %w", err)
 	}
 
 	client := otlptracegrpc.NewClient(otlptracegrpc.WithGRPCConn(conn))

+ 1 - 1
internal/tracing/tracing.go

@@ -111,7 +111,7 @@ func InitProvider(dockerCli command.Cli) (ShutdownFunc, error) {
 		),
 	)
 	if err != nil {
-		return nil, fmt.Errorf("failed to create resource: %v", err)
+		return nil, fmt.Errorf("failed to create resource: %w", err)
 	}
 
 	muxExporter := MuxExporter{exporters: exporters}

+ 1 - 1
pkg/api/dryrunclient.go

@@ -21,6 +21,7 @@ import (
 	"context"
 	"crypto/rand"
 	"encoding/json"
+	"errors"
 	"fmt"
 	"io"
 	"net"
@@ -44,7 +45,6 @@ import (
 	"github.com/docker/docker/client"
 	"github.com/docker/docker/pkg/jsonmessage"
 	specs "github.com/opencontainers/image-spec/specs-go/v1"
-	"github.com/pkg/errors"
 )
 
 const (

+ 1 - 1
pkg/api/errors.go

@@ -17,7 +17,7 @@
 package api
 
 import (
-	"github.com/pkg/errors"
+	"errors"
 )
 
 const (

+ 6 - 5
pkg/api/errors_test.go

@@ -17,35 +17,36 @@
 package api
 
 import (
+	"errors"
+	"fmt"
 	"testing"
 
-	"github.com/pkg/errors"
 	"gotest.tools/v3/assert"
 )
 
 func TestIsNotFound(t *testing.T) {
-	err := errors.Wrap(ErrNotFound, `object "name"`)
+	err := fmt.Errorf(`object "name": %w`, ErrNotFound)
 	assert.Assert(t, IsNotFoundError(err))
 
 	assert.Assert(t, !IsNotFoundError(errors.New("another error")))
 }
 
 func TestIsAlreadyExists(t *testing.T) {
-	err := errors.Wrap(ErrAlreadyExists, `object "name"`)
+	err := fmt.Errorf(`object "name": %w`, ErrAlreadyExists)
 	assert.Assert(t, IsAlreadyExistsError(err))
 
 	assert.Assert(t, !IsAlreadyExistsError(errors.New("another error")))
 }
 
 func TestIsForbidden(t *testing.T) {
-	err := errors.Wrap(ErrForbidden, `object "name"`)
+	err := fmt.Errorf(`object "name": %w`, ErrForbidden)
 	assert.Assert(t, IsForbiddenError(err))
 
 	assert.Assert(t, !IsForbiddenError(errors.New("another error")))
 }
 
 func TestIsUnknown(t *testing.T) {
-	err := errors.Wrap(ErrUnknown, `object "name"`)
+	err := fmt.Errorf(`object "name": %w`, ErrUnknown)
 	assert.Assert(t, IsUnknownError(err))
 
 	assert.Assert(t, !IsUnknownError(errors.New("another error")))

+ 3 - 1
pkg/compose/attach.go

@@ -18,6 +18,7 @@ package compose
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"io"
 	"strings"
@@ -132,7 +133,8 @@ func (s *composeService) attachContainerStreams(ctx context.Context, container s
 	if streamIn != nil && stdin != nil {
 		go func() {
 			_, err := io.Copy(streamIn, stdin)
-			if _, ok := err.(term.EscapeError); ok {
+			var escapeErr term.EscapeError
+			if errors.As(err, &escapeErr) {
 				close(detached)
 			}
 		}()

+ 12 - 11
pkg/compose/build_classic.go

@@ -19,6 +19,7 @@ package compose
 import (
 	"context"
 	"encoding/json"
+	"errors"
 	"fmt"
 	"io"
 	"os"
@@ -41,7 +42,6 @@ import (
 	"github.com/docker/docker/pkg/jsonmessage"
 	"github.com/docker/docker/pkg/progress"
 	"github.com/docker/docker/pkg/streamformatter"
-	"github.com/pkg/errors"
 
 	"github.com/docker/compose/v2/pkg/api"
 )
@@ -64,19 +64,19 @@ func (s *composeService) doBuildClassic(ctx context.Context, project *types.Proj
 	buildBuff := s.stdout()
 
 	if len(service.Build.Platforms) > 1 {
-		return "", errors.Errorf("the classic builder doesn't support multi-arch build, set DOCKER_BUILDKIT=1 to use BuildKit")
+		return "", fmt.Errorf("the classic builder doesn't support multi-arch build, set DOCKER_BUILDKIT=1 to use BuildKit")
 	}
 	if service.Build.Privileged {
-		return "", errors.Errorf("the classic builder doesn't support privileged mode, set DOCKER_BUILDKIT=1 to use BuildKit")
+		return "", fmt.Errorf("the classic builder doesn't support privileged mode, set DOCKER_BUILDKIT=1 to use BuildKit")
 	}
 	if len(service.Build.AdditionalContexts) > 0 {
-		return "", errors.Errorf("the classic builder doesn't support additional contexts, set DOCKER_BUILDKIT=1 to use BuildKit")
+		return "", fmt.Errorf("the classic builder doesn't support additional contexts, set DOCKER_BUILDKIT=1 to use BuildKit")
 	}
 	if len(service.Build.SSH) > 0 {
-		return "", errors.Errorf("the classic builder doesn't support SSH keys, set DOCKER_BUILDKIT=1 to use BuildKit")
+		return "", fmt.Errorf("the classic builder doesn't support SSH keys, set DOCKER_BUILDKIT=1 to use BuildKit")
 	}
 	if len(service.Build.Secrets) > 0 {
-		return "", errors.Errorf("the classic builder doesn't support secrets, set DOCKER_BUILDKIT=1 to use BuildKit")
+		return "", fmt.Errorf("the classic builder doesn't support secrets, set DOCKER_BUILDKIT=1 to use BuildKit")
 	}
 
 	if service.Build.Labels == nil {
@@ -91,7 +91,7 @@ func (s *composeService) doBuildClassic(ctx context.Context, project *types.Proj
 			// Dockerfile is outside of build-context; read the Dockerfile and pass it as dockerfileCtx
 			dockerfileCtx, err = os.Open(dockerfileName)
 			if err != nil {
-				return "", errors.Errorf("unable to open Dockerfile: %v", err)
+				return "", fmt.Errorf("unable to open Dockerfile: %w", err)
 			}
 			defer dockerfileCtx.Close() //nolint:errcheck
 		}
@@ -100,11 +100,11 @@ func (s *composeService) doBuildClassic(ctx context.Context, project *types.Proj
 	case urlutil.IsURL(specifiedContext):
 		buildCtx, relDockerfile, err = build.GetContextFromURL(progBuff, specifiedContext, dockerfileName)
 	default:
-		return "", errors.Errorf("unable to prepare context: path %q not found", specifiedContext)
+		return "", fmt.Errorf("unable to prepare context: path %q not found", specifiedContext)
 	}
 
 	if err != nil {
-		return "", errors.Errorf("unable to prepare context: %s", err)
+		return "", fmt.Errorf("unable to prepare context: %w", err)
 	}
 
 	if tempDir != "" {
@@ -120,7 +120,7 @@ func (s *composeService) doBuildClassic(ctx context.Context, project *types.Proj
 		}
 
 		if err := build.ValidateContextDirectory(contextDir, excludes); err != nil {
-			return "", errors.Wrap(err, "checking context")
+			return "", fmt.Errorf("checking context: %w", err)
 		}
 
 		// And canonicalize dockerfile name to a platform-independent one
@@ -188,7 +188,8 @@ func (s *composeService) doBuildClassic(ctx context.Context, project *types.Proj
 
 	err = jsonmessage.DisplayJSONMessagesStream(response.Body, buildBuff, progBuff.FD(), true, aux)
 	if err != nil {
-		if jerr, ok := err.(*jsonmessage.JSONError); ok {
+		var jerr *jsonmessage.JSONError
+		if errors.As(err, &jerr) {
 			// If no error code is set, default to 1
 			if jerr.Code == 0 {
 				jerr.Code = 1

+ 2 - 3
pkg/compose/compose.go

@@ -41,7 +41,6 @@ import (
 	"github.com/docker/docker/api/types/swarm"
 	"github.com/docker/docker/client"
 	"github.com/opencontainers/go-digest"
-	"github.com/pkg/errors"
 )
 
 var stdioToStdout bool
@@ -180,7 +179,7 @@ func (s *composeService) projectFromName(containers Containers, projectName stri
 		Name: projectName,
 	}
 	if len(containers) == 0 {
-		return project, errors.Wrap(api.ErrNotFound, fmt.Sprintf("no container found for project %q", projectName))
+		return project, fmt.Errorf("no container found for project %q: %w", projectName, api.ErrNotFound)
 	}
 	set := map[string]*types.ServiceConfig{}
 	for _, c := range containers {
@@ -226,7 +225,7 @@ SERVICES:
 				continue SERVICES
 			}
 		}
-		return project, errors.Wrapf(api.ErrNotFound, "no such service: %q", qs)
+		return project, fmt.Errorf("no such service: %q: %w", qs, api.ErrNotFound)
 	}
 	err := project.ForServices(services)
 	if err != nil {

+ 2 - 2
pkg/compose/convergence.go

@@ -18,6 +18,7 @@ package compose
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"sort"
 	"strconv"
@@ -34,7 +35,6 @@ import (
 	moby "github.com/docker/docker/api/types"
 	containerType "github.com/docker/docker/api/types/container"
 	specs "github.com/opencontainers/image-spec/specs-go/v1"
-	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 	"golang.org/x/sync/errgroup"
 
@@ -365,7 +365,7 @@ func (s *composeService) waitDependencies(ctx context.Context, project *types.Pr
 							return nil
 						}
 						w.Events(containerEvents(waitingFor, progress.ErrorEvent))
-						return errors.Wrap(err, "dependency failed to start")
+						return fmt.Errorf("dependency failed to start: %w", err)
 					}
 					if healthy {
 						w.Events(containerEvents(waitingFor, progress.Healthy))

+ 3 - 3
pkg/compose/cp.go

@@ -18,6 +18,7 @@ package compose
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"io"
 	"os"
@@ -32,7 +33,6 @@ import (
 	moby "github.com/docker/docker/api/types"
 	"github.com/docker/docker/pkg/archive"
 	"github.com/docker/docker/pkg/system"
-	"github.com/pkg/errors"
 )
 
 type copyDirection int
@@ -175,7 +175,7 @@ func (s *composeService) copyToContainer(ctx context.Context, containerID string
 
 	// Validate the destination path
 	if err := command.ValidateOutputPathFileMode(dstStat.Mode); err != nil {
-		return errors.Wrapf(err, `destination "%s:%s" must be a directory or a regular file`, containerID, dstPath)
+		return fmt.Errorf(`destination "%s:%s" must be a directory or a regular file: %w`, containerID, dstPath, err)
 	}
 
 	// Ignore any error and assume that the parent directory of the destination
@@ -197,7 +197,7 @@ func (s *composeService) copyToContainer(ctx context.Context, containerID string
 		content = s.stdin()
 		resolvedDstPath = dstInfo.Path
 		if !dstInfo.IsDir {
-			return errors.Errorf("destination \"%s:%s\" must be a directory", containerID, dstPath)
+			return fmt.Errorf("destination \"%s:%s\" must be a directory", containerID, dstPath)
 		}
 	} else {
 		// Prepare source copy info.

+ 4 - 5
pkg/compose/create.go

@@ -38,7 +38,6 @@ import (
 	"github.com/docker/docker/errdefs"
 	"github.com/docker/go-connections/nat"
 	"github.com/docker/go-units"
-	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 
 	"github.com/compose-spec/compose-go/types"
@@ -345,17 +344,17 @@ func parseSecurityOpts(p *types.Project, securityOpts []string) ([]string, bool,
 			if strings.Contains(opt, ":") {
 				con = strings.SplitN(opt, ":", 2)
 			} else {
-				return securityOpts, false, errors.Errorf("Invalid security-opt: %q", opt)
+				return securityOpts, false, fmt.Errorf("Invalid security-opt: %q", opt)
 			}
 		}
 		if con[0] == "seccomp" && con[1] != "unconfined" {
 			f, err := os.ReadFile(p.RelativePath(con[1]))
 			if err != nil {
-				return securityOpts, false, errors.Errorf("opening seccomp profile (%s) failed: %v", con[1], err)
+				return securityOpts, false, fmt.Errorf("opening seccomp profile (%s) failed: %w", con[1], err)
 			}
 			b := bytes.NewBuffer(nil)
 			if err := json.Compact(b, f); err != nil {
-				return securityOpts, false, errors.Errorf("compacting json for seccomp profile (%s) failed: %v", con[1], err)
+				return securityOpts, false, fmt.Errorf("compacting json for seccomp profile (%s) failed: %w", con[1], err)
 			}
 			parsed = append(parsed, fmt.Sprintf("seccomp=%s", b.Bytes()))
 		} else {
@@ -1111,7 +1110,7 @@ func (s *composeService) resolveOrCreateNetwork(ctx context.Context, n *types.Ne
 	_, err = s.apiClient().NetworkCreate(ctx, n.Name, createOpts)
 	if err != nil {
 		w.Event(progress.ErrorEvent(networkEventName))
-		return errors.Wrapf(err, "failed to create network %s", n.Name)
+		return fmt.Errorf("failed to create network %s: %w", n.Name, err)
 	}
 	w.Event(progress.CreatedEvent(networkEventName))
 	return nil

+ 2 - 3
pkg/compose/dependencies.go

@@ -24,7 +24,6 @@ import (
 
 	"github.com/compose-spec/compose-go/types"
 	"github.com/docker/compose/v2/pkg/api"
-	"github.com/pkg/errors"
 	"golang.org/x/sync/errgroup"
 
 	"github.com/docker/compose/v2/pkg/utils"
@@ -324,10 +323,10 @@ func (g *Graph) AddEdge(source string, destination string) error {
 	destinationVertex := g.Vertices[destination]
 
 	if sourceVertex == nil {
-		return errors.Wrapf(api.ErrNotFound, "could not find %s", source)
+		return fmt.Errorf("could not find %s: %w", source, api.ErrNotFound)
 	}
 	if destinationVertex == nil {
-		return errors.Wrapf(api.ErrNotFound, "could not find %s", destination)
+		return fmt.Errorf("could not find %s: %w", destination, api.ErrNotFound)
 	}
 
 	// If they are already connected

+ 2 - 3
pkg/compose/down.go

@@ -29,7 +29,6 @@ import (
 	containerType "github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/filters"
 	"github.com/docker/docker/errdefs"
-	"github.com/pkg/errors"
 	"golang.org/x/sync/errgroup"
 
 	"github.com/docker/compose/v2/pkg/api"
@@ -192,7 +191,7 @@ func (s *composeService) removeNetwork(ctx context.Context, composeNetworkName s
 			networkFilter(composeNetworkName)),
 	})
 	if err != nil {
-		return errors.Wrapf(err, "failed to list networks")
+		return fmt.Errorf("failed to list networks: %w", err)
 	}
 
 	if len(networks) == 0 {
@@ -226,7 +225,7 @@ func (s *composeService) removeNetwork(ctx context.Context, composeNetworkName s
 				continue
 			}
 			w.Event(progress.ErrorEvent(eventName))
-			return errors.Wrapf(err, fmt.Sprintf("failed to remove network %s", name))
+			return fmt.Errorf("failed to remove network %s: %w", name, err)
 		}
 		w.Event(progress.RemovedEvent(eventName))
 		found++

+ 1 - 2
pkg/compose/errors.go

@@ -17,10 +17,9 @@
 package compose
 
 import (
+	"errors"
 	"io/fs"
 
-	"github.com/pkg/errors"
-
 	"github.com/compose-spec/compose-go/errdefs"
 )
 

+ 3 - 1
pkg/compose/exec.go

@@ -18,6 +18,7 @@ package compose
 
 import (
 	"context"
+	"errors"
 	"strings"
 
 	"github.com/docker/cli/cli"
@@ -50,7 +51,8 @@ func (s *composeService) Exec(ctx context.Context, projectName string, options a
 	}
 
 	err = container.RunExec(s.dockerCli, exec)
-	if sterr, ok := err.(cli.StatusError); ok {
+	var sterr cli.StatusError
+	if errors.As(err, &sterr) {
 		return sterr.StatusCode, nil
 	}
 	return 0, err

+ 5 - 2
pkg/compose/logs.go

@@ -18,6 +18,7 @@ package compose
 
 import (
 	"context"
+	"errors"
 	"io"
 	"strings"
 	"time"
@@ -56,7 +57,8 @@ func (s *composeService) Logs(
 		c := c
 		eg.Go(func() error {
 			err := s.logContainers(ctx, consumer, c, options)
-			if _, ok := err.(errdefs.ErrNotImplemented); ok {
+			var notImplErr errdefs.ErrNotImplemented
+			if errors.As(err, &notImplErr) {
 				logrus.Warnf("Can't retrieve logs for %q: %s", getCanonicalContainerName(c), err.Error())
 				return nil
 			}
@@ -97,7 +99,8 @@ func (s *composeService) Logs(
 						Tail:       options.Tail,
 						Timestamps: options.Timestamps,
 					})
-					if _, ok := err.(errdefs.ErrNotImplemented); ok {
+					var notImplErr errdefs.ErrNotImplemented
+					if errors.As(err, &notImplErr) {
 						// ignore
 						return nil
 					}

+ 1 - 1
pkg/compose/pull.go

@@ -215,7 +215,7 @@ func (s *composeService) pullServiceImage(ctx context.Context, service types.Ser
 	for {
 		var jm jsonmessage.JSONMessage
 		if err := dec.Decode(&jm); err != nil {
-			if err == io.EOF {
+			if errors.Is(err, io.EOF) {
 				break
 			}
 			return "", WrapCategorisedComposeError(err, PullFailure)

+ 2 - 2
pkg/compose/push.go

@@ -20,6 +20,7 @@ import (
 	"context"
 	"encoding/base64"
 	"encoding/json"
+	"errors"
 	"fmt"
 	"io"
 
@@ -29,7 +30,6 @@ import (
 	moby "github.com/docker/docker/api/types"
 	"github.com/docker/docker/pkg/jsonmessage"
 	"github.com/docker/docker/registry"
-	"github.com/pkg/errors"
 	"golang.org/x/sync/errgroup"
 
 	"github.com/docker/compose/v2/pkg/api"
@@ -117,7 +117,7 @@ func (s *composeService) pushServiceImage(ctx context.Context, service types.Ser
 	for {
 		var jm jsonmessage.JSONMessage
 		if err := dec.Decode(&jm); err != nil {
-			if err == io.EOF {
+			if errors.Is(err, io.EOF) {
 				break
 			}
 			return err

+ 2 - 2
pkg/compose/start.go

@@ -18,6 +18,7 @@ package compose
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"strings"
 	"time"
@@ -31,7 +32,6 @@ import (
 	"github.com/compose-spec/compose-go/types"
 	moby "github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/filters"
-	"github.com/pkg/errors"
 	"golang.org/x/sync/errgroup"
 )
 
@@ -150,7 +150,7 @@ func (s *composeService) start(ctx context.Context, projectName string, options
 
 		err = s.waitDependencies(ctx, project, depends, containers)
 		if err != nil {
-			if ctx.Err() == context.DeadlineExceeded {
+			if errors.Is(ctx.Err(), context.DeadlineExceeded) {
 				return fmt.Errorf("application not healthy after %s", options.WaitTimeout)
 			}
 			return err

+ 1 - 1
pkg/compose/watch.go

@@ -16,6 +16,7 @@ package compose
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"io"
 	"os"
@@ -33,7 +34,6 @@ import (
 	moby "github.com/docker/docker/api/types"
 	"github.com/jonboulle/clockwork"
 	"github.com/mitchellh/mapstructure"
-	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 	"golang.org/x/sync/errgroup"
 )

+ 2 - 2
pkg/e2e/framework.go

@@ -18,6 +18,7 @@ package e2e
 
 import (
 	"encoding/json"
+	"errors"
 	"fmt"
 	"io"
 	"io/fs"
@@ -29,7 +30,6 @@ import (
 	"testing"
 	"time"
 
-	"github.com/pkg/errors"
 	"github.com/stretchr/testify/require"
 	"gotest.tools/v3/assert"
 	"gotest.tools/v3/icmd"
@@ -192,7 +192,7 @@ func findPluginExecutable(pluginExecutableName string) (string, error) {
 	if _, err := os.Stat(bin); err == nil {
 		return bin, nil
 	}
-	return "", errors.Wrap(os.ErrNotExist, fmt.Sprintf("plugin not found %s", pluginExecutableName))
+	return "", fmt.Errorf("plugin not found %s: %w", pluginExecutableName, os.ErrNotExist)
 }
 
 // CopyFile copies a file from a sourceFile to a destinationFile setting permissions to 0755

+ 4 - 1
pkg/e2e/pause_test.go

@@ -18,6 +18,7 @@ package e2e
 
 import (
 	"encoding/json"
+	"errors"
 	"fmt"
 	"net"
 	"net/http"
@@ -61,7 +62,9 @@ func TestPause(t *testing.T) {
 		_ = resp.Body.Close()
 	}
 	require.Error(t, err, "a should no longer respond")
-	require.True(t, err.(net.Error).Timeout(), "Error should have indicated a timeout")
+	var netErr net.Error
+	errors.As(err, &netErr)
+	require.True(t, netErr.Timeout(), "Error should have indicated a timeout")
 	HTTPGetWithRetry(t, urls["b"], http.StatusOK, 50*time.Millisecond, 5*time.Second)
 
 	// unpause a and verify that both containers work again

+ 3 - 1
pkg/e2e/up_test.go

@@ -21,6 +21,7 @@ package e2e
 
 import (
 	"context"
+	"errors"
 	"os/exec"
 	"strings"
 	"syscall"
@@ -90,7 +91,8 @@ func TestUpDependenciesNotStopped(t *testing.T) {
 	t.Log("Waiting for `compose up` to exit")
 	err = cmd.Wait()
 	if err != nil {
-		exitErr := err.(*exec.ExitError)
+		var exitErr *exec.ExitError
+		errors.As(err, &exitErr)
 		if exitErr.ExitCode() == -1 {
 			t.Fatalf("`compose up` was killed: %v", err)
 		}

+ 2 - 3
pkg/remote/git.go

@@ -32,14 +32,13 @@ import (
 	"github.com/compose-spec/compose-go/types"
 	"github.com/docker/compose/v2/pkg/api"
 	"github.com/moby/buildkit/util/gitutil"
-	"github.com/pkg/errors"
 )
 
 func GitRemoteLoaderEnabled() (bool, error) {
 	if v := os.Getenv("COMPOSE_EXPERIMENTAL_GIT_REMOTE"); v != "" {
 		enabled, err := strconv.ParseBool(v)
 		if err != nil {
-			return false, errors.Wrap(err, "COMPOSE_EXPERIMENTAL_GIT_REMOTE environment variable expects boolean value")
+			return false, fmt.Errorf("COMPOSE_EXPERIMENTAL_GIT_REMOTE environment variable expects boolean value: %w", err)
 		}
 		return enabled, err
 	}
@@ -90,7 +89,7 @@ func (g gitRemoteLoader) Load(ctx context.Context, path string) (string, error)
 		out, err := cmd.Output()
 		if err != nil {
 			if cmd.ProcessState.ExitCode() == 2 {
-				return "", errors.Wrapf(err, "repository does not contain ref %s, output: %q", path, string(out))
+				return "", fmt.Errorf("repository does not contain ref %s, output: %q: %w", path, string(out), err)
 			}
 			return "", err
 		}

+ 1 - 2
pkg/remote/oci.go

@@ -33,14 +33,13 @@ import (
 	v1 "github.com/opencontainers/image-spec/specs-go/v1"
 
 	"github.com/compose-spec/compose-go/loader"
-	"github.com/pkg/errors"
 )
 
 func OCIRemoteLoaderEnabled() (bool, error) {
 	if v := os.Getenv("COMPOSE_EXPERIMENTAL_OCI_REMOTE"); v != "" {
 		enabled, err := strconv.ParseBool(v)
 		if err != nil {
-			return false, errors.Wrap(err, "COMPOSE_EXPERIMENTAL_OCI_REMOTE environment variable expects boolean value")
+			return false, fmt.Errorf("COMPOSE_EXPERIMENTAL_OCI_REMOTE environment variable expects boolean value: %w", err)
 		}
 		return enabled, err
 	}

+ 1 - 1
pkg/watch/notify.go

@@ -17,6 +17,7 @@
 package watch
 
 import (
+	"errors"
 	"expvar"
 	"fmt"
 	"os"
@@ -24,7 +25,6 @@ import (
 	"runtime"
 	"strconv"
 
-	"github.com/pkg/errors"
 	"github.com/tilt-dev/fsnotify"
 )
 

+ 1 - 3
pkg/watch/paths.go

@@ -21,8 +21,6 @@ import (
 	"os"
 	"path/filepath"
 	"strings"
-
-	"github.com/pkg/errors"
 )
 
 func greatestExistingAncestor(path string) (string, error) {
@@ -33,7 +31,7 @@ func greatestExistingAncestor(path string) (string, error) {
 
 	_, err := os.Stat(path)
 	if err != nil && !os.IsNotExist(err) {
-		return "", errors.Wrapf(err, "os.Stat(%q)", path)
+		return "", fmt.Errorf("os.Stat(%q): %w", path, err)
 	}
 
 	if os.IsNotExist(err) {

+ 2 - 2
pkg/watch/watcher_darwin.go

@@ -20,12 +20,12 @@
 package watch
 
 import (
+	"fmt"
 	"os"
 	"path/filepath"
 	"time"
 
 	"github.com/fsnotify/fsevents"
-	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 )
 
@@ -136,7 +136,7 @@ func newWatcher(paths []string, ignore PathMatcher) (Notify, error) {
 	for _, path := range paths {
 		path, err := filepath.Abs(path)
 		if err != nil {
-			return nil, errors.Wrap(err, "newWatcher")
+			return nil, fmt.Errorf("newWatcher: %w", err)
 		}
 		dw.initAdd(path)
 	}

+ 9 - 10
pkg/watch/watcher_naive.go

@@ -27,7 +27,6 @@ import (
 	"runtime"
 	"strings"
 
-	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 
 	"github.com/tilt-dev/fsnotify"
@@ -78,7 +77,7 @@ func (d *naiveNotify) Start() error {
 	for _, name := range pathsToWatch {
 		fi, err := os.Stat(name)
 		if err != nil && !os.IsNotExist(err) {
-			return errors.Wrapf(err, "notify.Add(%q)", name)
+			return fmt.Errorf("notify.Add(%q): %w", name, err)
 		}
 
 		// if it's a file that doesn't exist,
@@ -90,12 +89,12 @@ func (d *naiveNotify) Start() error {
 		if fi.IsDir() {
 			err = d.watchRecursively(name)
 			if err != nil {
-				return errors.Wrapf(err, "notify.Add(%q)", name)
+				return fmt.Errorf("notify.Add(%q): %w", name, err)
 			}
 		} else {
 			err = d.add(filepath.Dir(name))
 			if err != nil {
-				return errors.Wrapf(err, "notify.Add(%q)", filepath.Dir(name))
+				return fmt.Errorf("notify.Add(%q): %w", filepath.Dir(name), err)
 			}
 		}
 	}
@@ -111,7 +110,7 @@ func (d *naiveNotify) watchRecursively(dir string) error {
 		if err == nil || os.IsNotExist(err) {
 			return nil
 		}
-		return errors.Wrapf(err, "watcher.Add(%q)", dir)
+		return fmt.Errorf("watcher.Add(%q): %w", dir, err)
 	}
 
 	return filepath.WalkDir(dir, func(path string, info fs.DirEntry, err error) error {
@@ -138,7 +137,7 @@ func (d *naiveNotify) watchRecursively(dir string) error {
 			if os.IsNotExist(err) {
 				return nil
 			}
-			return errors.Wrapf(err, "watcher.Add(%q)", path)
+			return fmt.Errorf("watcher.Add(%q): %w", path, err)
 		}
 		return nil
 	})
@@ -262,7 +261,7 @@ func (d *naiveNotify) shouldSkipDir(path string) (bool, error) {
 
 	skip, err := d.ignore.MatchesEntireDir(path)
 	if err != nil {
-		return false, errors.Wrap(err, "shouldSkipDir")
+		return false, fmt.Errorf("shouldSkipDir: %w", err)
 	}
 
 	if skip {
@@ -311,7 +310,7 @@ func newWatcher(paths []string, ignore PathMatcher) (Notify, error) {
 				"Run 'sysctl fs.inotify.max_user_instances' to check your inotify limits.\n" +
 				"To raise them, run 'sudo sysctl fs.inotify.max_user_instances=1024'")
 		}
-		return nil, errors.Wrap(err, "creating file watcher")
+		return nil, fmt.Errorf("creating file watcher: %w", err)
 	}
 	MaybeIncreaseBufferSize(fsw)
 
@@ -326,7 +325,7 @@ func newWatcher(paths []string, ignore PathMatcher) (Notify, error) {
 	for _, path := range paths {
 		path, err := filepath.Abs(path)
 		if err != nil {
-			return nil, errors.Wrap(err, "newWatcher")
+			return nil, fmt.Errorf("newWatcher: %w", err)
 		}
 		notifyList[path] = true
 	}
@@ -351,7 +350,7 @@ func greatestExistingAncestors(paths []string) ([]string, error) {
 	for _, p := range paths {
 		newP, err := greatestExistingAncestor(p)
 		if err != nil {
-			return nil, fmt.Errorf("Finding ancestor of %s: %v", p, err)
+			return nil, fmt.Errorf("Finding ancestor of %s: %w", p, err)
 		}
 		result = append(result, newP)
 	}

+ 2 - 2
pkg/watch/watcher_naive_test.go

@@ -125,12 +125,12 @@ func inotifyNodes() (int, error) {
 	output, err := exec.Command("/bin/sh", "-c", fmt.Sprintf(
 		"find /proc/%d/fd -lname anon_inode:inotify -printf '%%hinfo/%%f\n' | xargs cat | grep -c '^inotify'", pid)).Output()
 	if err != nil {
-		return 0, fmt.Errorf("error running command to determine number of watched files: %v\n %s", err, output)
+		return 0, fmt.Errorf("error running command to determine number of watched files: %w\n %s", err, output)
 	}
 
 	n, err := strconv.Atoi(strings.TrimSpace(string(output)))
 	if err != nil {
-		return 0, fmt.Errorf("couldn't parse number of watched files: %v", err)
+		return 0, fmt.Errorf("couldn't parse number of watched files: %w", err)
 	}
 	return n, nil
 }