From 063ef45da1f11308055acc4899906424b731e746 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Wed, 9 Sep 2026 17:01:54 +0200 Subject: [PATCH] golangci-lint: enable perfsprint linter Signed-off-by: Sebastiaan van Stijn --- .golangci.yml | 1 + cmd/compose/compose.go | 4 +-- cmd/compose/create.go | 5 ++-- cmd/compose/exec.go | 4 +-- cmd/compose/generate.go | 3 ++- cmd/compose/options.go | 5 ++-- cmd/compose/run.go | 8 +++--- cmd/compose/up.go | 22 ++++++++--------- cmd/formatter/container.go | 2 +- cmd/formatter/shortcut.go | 12 ++++----- internal/locker/pidfile.go | 3 +-- internal/memnet/conn_unix.go | 6 ++--- internal/tracing/attributes.go | 4 +-- pkg/compose/build_bake.go | 6 ++--- pkg/compose/build_classic.go | 12 ++++----- pkg/compose/commit.go | 3 +-- pkg/compose/compose.go | 2 +- pkg/compose/create.go | 4 +-- pkg/compose/dependencies_test.go | 14 +++++------ pkg/compose/down.go | 4 +-- pkg/compose/down_test.go | 3 ++- pkg/compose/executor_test.go | 12 ++++----- pkg/compose/export.go | 3 ++- pkg/compose/hook_test.go | 10 ++++---- pkg/compose/model.go | 3 ++- pkg/compose/plugins.go | 2 +- pkg/compose/pre_start_test.go | 32 ++++++++++++------------ pkg/compose/publish.go | 3 ++- pkg/compose/reconcile.go | 18 +++++++------- pkg/compose/reconcile_test.go | 3 ++- pkg/compose/remove.go | 2 +- pkg/compose/service_containers.go | 6 ++--- pkg/compose/service_containers_test.go | 3 ++- pkg/compose/watch.go | 6 ++--- pkg/dryrun/dryrunclient.go | 3 ++- pkg/e2e/bridge_convert_output_test.go | 5 ++-- pkg/e2e/bridge_test.go | 7 +++--- pkg/e2e/checks.go | 34 ++++++++++++++------------ pkg/e2e/framework.go | 2 +- pkg/e2e/watch_test.go | 3 ++- pkg/remote/oci.go | 5 ++-- pkg/watch/dockerignore.go | 2 +- pkg/watch/notify.go | 3 +-- pkg/watch/paths.go | 3 ++- pkg/watch/watcher_naive.go | 3 ++- pkg/watch/watcher_naive_test.go | 12 ++++----- 46 files changed, 161 insertions(+), 151 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index d7f6b48b4..cce5d7e52 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -27,6 +27,7 @@ linters: - misspell - nakedret - nolintlint + - perfsprint - revive - staticcheck - testifylint diff --git a/cmd/compose/compose.go b/cmd/compose/compose.go index 7fb104555..14757f8dd 100644 --- a/cmd/compose/compose.go +++ b/cmd/compose/compose.go @@ -718,13 +718,13 @@ func selectEventProcessor(dockerCli command.Cli, progress, ansi string, detached } case display.ModeTTY: if ansi == "never" { - return nil, fmt.Errorf("can't use --progress tty while ANSI support is disabled") + return nil, errors.New("can't use --progress tty while ANSI support is disabled") } display.Mode = display.ModeTTY return display.Full(dockerCli.Err(), stdinfo(dockerCli), detached), nil case display.ModePlain: if ansi == "always" { - return nil, fmt.Errorf("can't use --progress plain while ANSI support is forced") + return nil, errors.New("can't use --progress plain while ANSI support is forced") } display.Mode = display.ModePlain return display.Plain(dockerCli.Err()), nil diff --git a/cmd/compose/create.go b/cmd/compose/create.go index 41cac7de9..d8dc01b81 100644 --- a/cmd/compose/create.go +++ b/cmd/compose/create.go @@ -18,6 +18,7 @@ package compose import ( "context" + "errors" "fmt" "slices" "strconv" @@ -63,10 +64,10 @@ func createCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Bac PreRunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error { opts.pullChanged = cmd.Flags().Changed("pull") if opts.Build && opts.noBuild { - return fmt.Errorf("--build and --no-build are incompatible") + return errors.New("--build and --no-build are incompatible") } if opts.forceRecreate && opts.noRecreate { - return fmt.Errorf("--force-recreate and --no-recreate are incompatible") + return errors.New("--force-recreate and --no-recreate are incompatible") } return nil }), diff --git a/cmd/compose/exec.go b/cmd/compose/exec.go index f548730dc..a8450b4a2 100644 --- a/cmd/compose/exec.go +++ b/cmd/compose/exec.go @@ -19,8 +19,8 @@ package compose import ( "context" "errors" - "fmt" "os" + "strconv" "github.com/compose-spec/compose-go/v2/types" "github.com/docker/cli/cli" @@ -133,7 +133,7 @@ func runExec(ctx context.Context, dockerCli command.Cli, backendOptions *Backend } exitCode, err := backend.Exec(ctx, projectName, execOpts) if exitCode != 0 { - errMsg := fmt.Sprintf("exit status %d", exitCode) + errMsg := "exit status " + strconv.Itoa(exitCode) if err != nil && err.Error() != "" { errMsg = err.Error() } diff --git a/cmd/compose/generate.go b/cmd/compose/generate.go index 3fe5b2389..e84b3b111 100644 --- a/cmd/compose/generate.go +++ b/cmd/compose/generate.go @@ -18,6 +18,7 @@ package compose import ( "context" + "errors" "fmt" "os" @@ -58,7 +59,7 @@ func generateCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *B func runGenerate(ctx context.Context, dockerCli command.Cli, backendOptions *BackendOptions, opts generateOptions, containers []string) error { _, _ = fmt.Fprintln(os.Stderr, "generate command is EXPERIMENTAL") if len(containers) == 0 { - return fmt.Errorf("at least one container must be specified") + return errors.New("at least one container must be specified") } backend, err := compose.NewComposeService(dockerCli, backendOptions.Options...) diff --git a/cmd/compose/options.go b/cmd/compose/options.go index b639afabd..fee495709 100644 --- a/cmd/compose/options.go +++ b/cmd/compose/options.go @@ -18,6 +18,7 @@ package compose import ( "context" + "errors" "fmt" "io" "os" @@ -169,7 +170,7 @@ func promptForInterpolatedVariables(ctx context.Context, dockerCli command.Cli, } if !confirmed { - return fmt.Errorf("operation cancelled by user") + return errors.New("operation cancelled by user") } return nil @@ -292,7 +293,7 @@ func confirmRemoteIncludes(dockerCli command.Cli, options buildOptions, assumeYe return err } if !confirmed { - return fmt.Errorf("operation cancelled by user") + return errors.New("operation cancelled by user") } return nil diff --git a/cmd/compose/run.go b/cmd/compose/run.go index 199e86f11..b88f66cf5 100644 --- a/cmd/compose/run.go +++ b/cmd/compose/run.go @@ -18,7 +18,7 @@ package compose import ( "context" - "fmt" + "errors" "os" "strings" @@ -168,7 +168,7 @@ func runCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Backen options.Command = args[1:] } if len(options.publish) > 0 && options.servicePorts { - return fmt.Errorf("--service-ports and --publish are incompatible") + return errors.New("--service-ports and --publish are incompatible") } if cmd.Flags().Changed("entrypoint") { command, err := shellwords.Parse(options.entrypoint) @@ -179,7 +179,7 @@ func runCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Backen } if cmd.Flags().Changed("tty") { if cmd.Flags().Changed("no-tty") { - return fmt.Errorf("--tty and --no-tty can't be used together") + return errors.New("--tty and --no-tty can't be used together") } else { options.noTty = !ttyFlag } @@ -303,7 +303,7 @@ func runRun(ctx context.Context, backend api.Compose, project *types.Project, op for _, s := range options.labels { key, val, ok := strings.Cut(s, "=") if !ok { - return fmt.Errorf("label must be set as KEY=VALUE") + return errors.New("label must be set as KEY=VALUE") } labels[key] = val } diff --git a/cmd/compose/up.go b/cmd/compose/up.go index e69e455e2..89bb63991 100644 --- a/cmd/compose/up.go +++ b/cmd/compose/up.go @@ -138,7 +138,7 @@ func upCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Backend up.resolveNavigationMenu(dockerCli) if !p.All && len(project.Services) == 0 { - return fmt.Errorf("no service selected") + return errors.New("no service selected") } return runUp(ctx, dockerCli, backendOptions, create, up, build, project, services) @@ -188,41 +188,41 @@ func upCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Backend func validateFlags(up *upOptions, create *createOptions) error { if up.waitTimeout < 0 { - return fmt.Errorf("--wait-timeout must be a non-negative integer") + return errors.New("--wait-timeout must be a non-negative integer") } if up.exitCodeFrom != "" && !up.cascadeFail { up.cascadeStop = true } if up.cascadeStop && up.cascadeFail { - return fmt.Errorf("--abort-on-container-failure cannot be combined with --abort-on-container-exit") + return errors.New("--abort-on-container-failure cannot be combined with --abort-on-container-exit") } if up.wait { if up.attachDependencies || up.cascadeStop || len(up.attach) > 0 { - return fmt.Errorf("--wait cannot be combined with --abort-on-container-exit, --attach or --attach-dependencies") + return errors.New("--wait cannot be combined with --abort-on-container-exit, --attach or --attach-dependencies") } up.Detach = true } if create.Build && create.noBuild { - return fmt.Errorf("--build and --no-build are incompatible") + return errors.New("--build and --no-build are incompatible") } if up.Detach && (up.attachDependencies || up.cascadeStop || up.cascadeFail || len(up.attach) > 0 || up.watch) { if up.wait { - return fmt.Errorf("--wait cannot be combined with --abort-on-container-exit, --abort-on-container-failure, --attach, --attach-dependencies or --watch") + return errors.New("--wait cannot be combined with --abort-on-container-exit, --abort-on-container-failure, --attach, --attach-dependencies or --watch") } else { - return fmt.Errorf("--detach cannot be combined with --abort-on-container-exit, --abort-on-container-failure, --attach, --attach-dependencies or --watch") + return errors.New("--detach cannot be combined with --abort-on-container-exit, --abort-on-container-failure, --attach, --attach-dependencies or --watch") } } if create.noInherit && create.noRecreate { - return fmt.Errorf("--no-recreate and --renew-anon-volumes are incompatible") + return errors.New("--no-recreate and --renew-anon-volumes are incompatible") } if create.forceRecreate && create.noRecreate { - return fmt.Errorf("--force-recreate and --no-recreate are incompatible") + return errors.New("--force-recreate and --no-recreate are incompatible") } if create.recreateDeps && create.noRecreate { - return fmt.Errorf("--always-recreate-deps and --no-recreate are incompatible") + return errors.New("--always-recreate-deps and --no-recreate are incompatible") } if create.noBuild && up.watch { - return fmt.Errorf("--no-build and --watch are incompatible") + return errors.New("--no-build and --watch are incompatible") } return nil } diff --git a/cmd/formatter/container.go b/cmd/formatter/container.go index 860e7a607..4849f7f09 100644 --- a/cmd/formatter/container.go +++ b/cmd/formatter/container.go @@ -283,7 +283,7 @@ func (c *ContainerContext) Mounts() string { // LocalVolumes returns the number of volumes using the "local" volume driver. func (c *ContainerContext) LocalVolumes() string { - return fmt.Sprintf("%d", c.c.LocalVolumes) + return strconv.Itoa(c.c.LocalVolumes) } // Networks returns a comma-separated string of networks that the container is diff --git a/cmd/formatter/shortcut.go b/cmd/formatter/shortcut.go index ffa083e2f..054b076aa 100644 --- a/cmd/formatter/shortcut.go +++ b/cmd/formatter/shortcut.go @@ -61,7 +61,7 @@ func (ke *KeyboardError) printError(height int, info string) { func (ke *KeyboardError) addError(prefix string, err error) { ke.timeStart = time.Now() - prefix = ansiColor(CYAN, fmt.Sprintf("%s →", prefix), BOLD) + prefix = ansiColor(CYAN, prefix+" →", BOLD) errorString := fmt.Sprintf("%s %s", prefix, err.Error()) ke.err = errors.New(errorString) @@ -210,10 +210,10 @@ func (lk *LogKeyboard) openDockerDesktop(ctx context.Context, project *types.Pro go func() { _ = tracing.EventWrapFuncForErrGroup(ctx, "menu/gui", tracing.SpanOptions{}, func(ctx context.Context) error { - link := fmt.Sprintf("docker-desktop://dashboard/apps/%s", project.Name) + link := "docker-desktop://dashboard/apps/" + project.Name err := open.Run(link) if err != nil { - err = fmt.Errorf("could not open Docker Desktop") + err = errors.New("could not open Docker Desktop") lk.keyboardError("View", err) } return err @@ -228,10 +228,10 @@ func (lk *LogKeyboard) openDDComposeUI(ctx context.Context, project *types.Proje go func() { _ = tracing.EventWrapFuncForErrGroup(ctx, "menu/gui/composeview", tracing.SpanOptions{}, func(ctx context.Context) error { - link := fmt.Sprintf("docker-desktop://dashboard/docker-compose/%s", project.Name) + link := "docker-desktop://dashboard/docker-compose/" + project.Name err := open.Run(link) if err != nil { - err = fmt.Errorf("could not open Docker Desktop Compose UI") + err = errors.New("could not open Docker Desktop Compose UI") lk.keyboardError("View Config", err) } return err @@ -264,7 +264,7 @@ func (lk *LogKeyboard) openDDWatchDocs(ctx context.Context, project *types.Proje link := fmt.Sprintf("docker-desktop://dashboard/docker-compose/%s/watch", project.Name) err := open.Run(link) if err != nil { - err = fmt.Errorf("could not open Docker Desktop Compose UI") + err = errors.New("could not open Docker Desktop Compose UI") lk.keyboardError("Watch Docs", err) } return err diff --git a/internal/locker/pidfile.go b/internal/locker/pidfile.go index 08dcea1f3..893c57727 100644 --- a/internal/locker/pidfile.go +++ b/internal/locker/pidfile.go @@ -17,7 +17,6 @@ package locker import ( - "fmt" "path/filepath" ) @@ -30,6 +29,6 @@ func NewPidfile(projectName string) (*Pidfile, error) { if err != nil { return nil, err } - path := filepath.Join(run, fmt.Sprintf("%s.pid", projectName)) + path := filepath.Join(run, projectName+".pid") return &Pidfile{path: path}, nil } diff --git a/internal/memnet/conn_unix.go b/internal/memnet/conn_unix.go index e15198484..7532f7190 100644 --- a/internal/memnet/conn_unix.go +++ b/internal/memnet/conn_unix.go @@ -20,7 +20,7 @@ package memnet import ( "context" - "fmt" + "errors" "net" "syscall" ) @@ -28,12 +28,12 @@ import ( const maxUnixSocketPathSize = len(syscall.RawSockaddrUnix{}.Path) func dialNamedPipe(_ context.Context, _ string) (net.Conn, error) { - return nil, fmt.Errorf("named pipes are only available on Windows") + return nil, errors.New("named pipes are only available on Windows") } func validateSocketPath(addr string) error { if len(addr) > maxUnixSocketPathSize { - return fmt.Errorf("socket address is too long: %s", addr) + return errors.New("socket address is too long: " + addr) } return nil } diff --git a/internal/tracing/attributes.go b/internal/tracing/attributes.go index 895700915..07fbf64a7 100644 --- a/internal/tracing/attributes.go +++ b/internal/tracing/attributes.go @@ -19,8 +19,8 @@ package tracing import ( "context" "crypto/sha256" + hex "encoding/hex" "encoding/json" - "fmt" "strings" "time" @@ -192,5 +192,5 @@ func projectHash(p *types.Project) (string, bool) { return "", false } d := sha256.Sum256(projData) - return fmt.Sprintf("%x", d), true + return hex.EncodeToString(d[:]), true } diff --git a/pkg/compose/build_bake.go b/pkg/compose/build_bake.go index 255a64d8e..0530d72ff 100644 --- a/pkg/compose/build_bake.go +++ b/pkg/compose/build_bake.go @@ -485,7 +485,7 @@ func (s *composeService) getBuildxPlugin() (*manager.Plugin, error) { } if buildx.Version == "" { - return nil, fmt.Errorf("failed to get version of buildx") + return nil, errors.New("failed to get version of buildx") } if versions.LessThan(buildx.Version[1:], buildxMinVersion) { @@ -647,12 +647,12 @@ func (s *composeService) dryRunBake(cfg bakeConfig) map[string]string { s.events.On(api.Resource{ ID: name + " ==>", Status: api.Done, - Text: fmt.Sprintf("==> writing image %s", dryRunUUID), + Text: "==> writing image " + dryRunUUID, }) s.events.On(api.Resource{ ID: name + " ==> ==>", Status: api.Done, - Text: fmt.Sprintf(`naming to %s`, target.Tags[0]), + Text: "naming to " + target.Tags[0], }) bakeResponse[name] = dryRunUUID } diff --git a/pkg/compose/build_classic.go b/pkg/compose/build_classic.go index 857b53290..a6e213202 100644 --- a/pkg/compose/build_classic.go +++ b/pkg/compose/build_classic.go @@ -232,19 +232,19 @@ func (s *composeService) doBuildImage(ctx context.Context, project *types.Projec // doesn't implement func checkClassicBuilderSupported(service types.ServiceConfig) error { if len(service.Build.Platforms) > 1 { - return fmt.Errorf("the classic builder doesn't support multi-arch build; building with BuildKit requires the buildx Docker CLI plugin, and DOCKER_BUILDKIT must not be set to 0") + return errors.New("the classic builder doesn't support multi-arch build; building with BuildKit requires the buildx Docker CLI plugin, and DOCKER_BUILDKIT must not be set to 0") } if service.Build.Privileged { - return fmt.Errorf("the classic builder doesn't support privileged mode; building with BuildKit requires the buildx Docker CLI plugin, and DOCKER_BUILDKIT must not be set to 0") + return errors.New("the classic builder doesn't support privileged mode; building with BuildKit requires the buildx Docker CLI plugin, and DOCKER_BUILDKIT must not be set to 0") } if len(service.Build.AdditionalContexts) > 0 { - return fmt.Errorf("the classic builder doesn't support additional contexts; building with BuildKit requires the buildx Docker CLI plugin, and DOCKER_BUILDKIT must not be set to 0") + return errors.New("the classic builder doesn't support additional contexts; building with BuildKit requires the buildx Docker CLI plugin, and DOCKER_BUILDKIT must not be set to 0") } if len(service.Build.SSH) > 0 { - return fmt.Errorf("the classic builder doesn't support SSH keys; building with BuildKit requires the buildx Docker CLI plugin, and DOCKER_BUILDKIT must not be set to 0") + return errors.New("the classic builder doesn't support SSH keys; building with BuildKit requires the buildx Docker CLI plugin, and DOCKER_BUILDKIT must not be set to 0") } if len(service.Build.Secrets) > 0 { - return fmt.Errorf("the classic builder doesn't support secrets; building with BuildKit requires the buildx Docker CLI plugin, and DOCKER_BUILDKIT must not be set to 0") + return errors.New("the classic builder doesn't support secrets; building with BuildKit requires the buildx Docker CLI plugin, and DOCKER_BUILDKIT must not be set to 0") } return nil } @@ -260,7 +260,7 @@ func prepareClassicBuildContext(specifiedContext, dockerfileName string, progBuf bctx := &classicBuildContext{cleanup: func() {}} switch contextType { case build.ContextTypeStdin: - return nil, fmt.Errorf("building from STDIN is not supported") + return nil, errors.New("building from STDIN is not supported") case build.ContextTypeLocal: bctx.contextDir, bctx.relDockerfile, err = build.GetContextFromLocalDir(specifiedContext, dockerfileName) if err != nil { diff --git a/pkg/compose/commit.go b/pkg/compose/commit.go index 84e145cba..bfc2b413d 100644 --- a/pkg/compose/commit.go +++ b/pkg/compose/commit.go @@ -18,7 +18,6 @@ package compose import ( "context" - "fmt" "strings" "github.com/moby/moby/client" @@ -71,7 +70,7 @@ func (s *composeService) commit(ctx context.Context, projectName string, options s.events.On(api.Resource{ ID: name, - Text: fmt.Sprintf("Committed as %s", response.ID), + Text: "Committed as " + response.ID, Status: api.Done, }) diff --git a/pkg/compose/compose.go b/pkg/compose/compose.go index dd7b9dd12..ee797274f 100644 --- a/pkg/compose/compose.go +++ b/pkg/compose/compose.go @@ -526,7 +526,7 @@ func (s *composeService) RuntimeAPIVersion(ctx context.Context) (string, error) version := cli.ClientVersion() if version == "" { - return "", fmt.Errorf("docker client returned empty version after successful API negotiation") + return "", errors.New("docker client returned empty version after successful API negotiation") } s.runtimeAPIVersion.val = version diff --git a/pkg/compose/create.go b/pkg/compose/create.go index 2f24e8504..b5496a68e 100644 --- a/pkg/compose/create.go +++ b/pkg/compose/create.go @@ -1452,7 +1452,7 @@ func (s *composeService) createNetwork(ctx context.Context, n *types.NetworkConf EnableIPv4: n.EnableIPv4, } - networkEventName := fmt.Sprintf("Network %s", n.Name) + networkEventName := "Network " + n.Name s.events.On(creatingEvent(networkEventName)) if _, err := s.apiClient().NetworkCreate(ctx, n.Name, networkCreateOptions); err != nil { @@ -1523,7 +1523,7 @@ func (s *composeService) resolveExternalNetwork(ctx context.Context, n *types.Ne } func (s *composeService) createVolume(ctx context.Context, volume types.VolumeConfig) error { - eventName := fmt.Sprintf("Volume %s", volume.Name) + eventName := "Volume " + volume.Name s.events.On(creatingEvent(eventName)) hash, err := VolumeHash(volume) if err != nil { diff --git a/pkg/compose/dependencies_test.go b/pkg/compose/dependencies_test.go index 74b22f583..65dccbeaf 100644 --- a/pkg/compose/dependencies_test.go +++ b/pkg/compose/dependencies_test.go @@ -18,8 +18,8 @@ package compose import ( "context" - "fmt" "sort" + "strconv" "sync" "testing" @@ -62,12 +62,10 @@ func TestTraversalWithMultipleParents(t *testing.T) { Services: types.Services{"dependent": dependent}, } - for i := 1; i <= 100; i++ { - name := fmt.Sprintf("svc_%d", i) + for i := range 100 { + name := "svc_" + strconv.Itoa(i+1) dependent.DependsOn[name] = types.ServiceDependency{} - - svc := types.ServiceConfig{Name: name} - project.Services[name] = svc + project.Services[name] = types.ServiceConfig{Name: name} } svc := make(chan string, 10) @@ -262,7 +260,7 @@ func TestBuildGraph(t *testing.T) { } graph, err := NewGraph(&project, ServiceStopped) - assert.NilError(t, err, fmt.Sprintf("failed to build graph for: %s", tC.desc)) + assert.NilError(t, err, "failed to build graph for: "+tC.desc) for k, vertex := range graph.Vertices { expected, ok := tC.expectedVertices[k] @@ -312,7 +310,7 @@ func TestBuildGraphDependsOn(t *testing.T) { } graph, err := NewGraph(&project, ServiceStopped) - assert.NilError(t, err, fmt.Sprintf("failed to build graph for: %s", tC.desc)) + assert.NilError(t, err, "failed to build graph for: "+tC.desc) for k, vertex := range graph.Vertices { expected, ok := tC.expectedVertices[k] diff --git a/pkg/compose/down.go b/pkg/compose/down.go index a05191d3c..2e6108f66 100644 --- a/pkg/compose/down.go +++ b/pkg/compose/down.go @@ -232,7 +232,7 @@ func (s *composeService) removeNetwork(ctx context.Context, composeNetworkName s return nil } - eventName := fmt.Sprintf("Network %s", name) + eventName := "Network " + name s.events.On(removingEvent(eventName)) var found int @@ -277,7 +277,7 @@ func (s *composeService) removeNetwork(ctx context.Context, composeNetworkName s } func (s *composeService) removeVolume(ctx context.Context, id string) error { - resource := fmt.Sprintf("Volume %s", id) + resource := "Volume " + id _, err := s.apiClient().VolumeInspect(ctx, id, client.VolumeInspectOptions{}) if errdefs.IsNotFound(err) { diff --git a/pkg/compose/down_test.go b/pkg/compose/down_test.go index 5b1f55116..cd0aba534 100644 --- a/pkg/compose/down_test.go +++ b/pkg/compose/down_test.go @@ -17,6 +17,7 @@ package compose import ( + "errors" "fmt" "os" "strings" @@ -669,7 +670,7 @@ func TestDownHookContainerRemovalFailureIsNonFatal(t *testing.T) { // Removal fails — Down must still return nil. api.EXPECT().ContainerRemove(gomock.Any(), "hook-2", client.ContainerRemoveOptions{Force: true, RemoveVolumes: true}). - Return(client.ContainerRemoveResult{}, fmt.Errorf("daemon busy")) + Return(client.ContainerRemoveResult{}, errors.New("daemon busy")) err = tested.Down(t.Context(), strings.ToLower(testProject), compose.DownOptions{}) assert.NilError(t, err) diff --git a/pkg/compose/executor_test.go b/pkg/compose/executor_test.go index 376e148ff..9e711c22f 100644 --- a/pkg/compose/executor_test.go +++ b/pkg/compose/executor_test.go @@ -19,7 +19,7 @@ package compose import ( "context" "errors" - "fmt" + "strconv" "testing" "github.com/compose-spec/compose-go/v2/types" @@ -233,11 +233,11 @@ func TestExecutePlanConcurrentRemovesCacheCoherence(t *testing.T) { ctrs := make([]container.Summary, replicas) for i := range ctrs { ctrs[i] = container.Summary{ - ID: fmt.Sprintf("c%d", i), - Names: []string{fmt.Sprintf("/test-web-%d", i+1)}, + ID: "c" + strconv.Itoa(i), + Names: []string{"/test-web-" + strconv.Itoa(i+1)}, Labels: map[string]string{ api.ServiceLabel: "web", - api.ContainerNumberLabel: fmt.Sprintf("%d", i+1), + api.ContainerNumberLabel: strconv.Itoa(i + 1), }, } } @@ -268,13 +268,13 @@ func TestExecutePlanConcurrentRemovesCacheCoherence(t *testing.T) { for i := range ctrs { stop := plan.addNode(Operation{ Type: OpStopContainer, - ResourceID: fmt.Sprintf("service:web:%d", i+1), + ResourceID: "service:web:" + strconv.Itoa(i+1), Cause: "scale down", Container: &ctrs[i], }, "") plan.addNode(Operation{ Type: OpRemoveContainer, - ResourceID: fmt.Sprintf("service:web:%d", i+1), + ResourceID: "service:web:" + strconv.Itoa(i+1), Cause: "scale down", Container: &ctrs[i], }, "", stop) diff --git a/pkg/compose/export.go b/pkg/compose/export.go index f85e99f88..9b27232ba 100644 --- a/pkg/compose/export.go +++ b/pkg/compose/export.go @@ -18,6 +18,7 @@ package compose import ( "context" + "errors" "fmt" "io" "strings" @@ -45,7 +46,7 @@ func (s *composeService) export(ctx context.Context, projectName string, options if options.Output == "" { if s.stdout().IsTerminal() { - return fmt.Errorf("output option is required when exporting to terminal") + return errors.New("output option is required when exporting to terminal") } } else if err := command.ValidateOutputPath(options.Output); err != nil { return fmt.Errorf("failed to export container: %w", err) diff --git a/pkg/compose/hook_test.go b/pkg/compose/hook_test.go index 5a4ad1d8e..3f5ef1490 100644 --- a/pkg/compose/hook_test.go +++ b/pkg/compose/hook_test.go @@ -22,7 +22,7 @@ import ( "bufio" "context" "encoding/binary" - "fmt" + "errors" "io" "net" "os" @@ -460,7 +460,7 @@ func TestRunHook_CopyError(t *testing.T) { // Build a pipe whose read side immediately returns a hard error, simulating // an I/O failure mid-stream rather than a clean EOF. pr, pw := io.Pipe() - _ = pw.CloseWithError(fmt.Errorf("simulated I/O failure")) + _ = pw.CloseWithError(errors.New("simulated I/O failure")) // Use net.Pipe() only for the Conn field (Close); reads come from pr. serverConn, clientConn := net.Pipe() @@ -524,7 +524,7 @@ func TestRunHook_ExecCreateError(t *testing.T) { mockAPI.EXPECT(). ExecCreate(gomock.Any(), "ctr-1", gomock.Any()). - Return(client.ExecCreateResult{}, fmt.Errorf("exec create failed")) + Return(client.ExecCreateResult{}, errors.New("exec create failed")) s, err := NewComposeService(mockCli) assert.NilError(t, err) @@ -552,7 +552,7 @@ func TestRunHook_ExecAttachError(t *testing.T) { Return(client.ExecCreateResult{ID: "exec-1"}, nil) mockAPI.EXPECT(). ExecAttach(gomock.Any(), "exec-1", gomock.Any()). - Return(client.ExecAttachResult{}, fmt.Errorf("exec attach failed")) + Return(client.ExecAttachResult{}, errors.New("exec attach failed")) s, err := NewComposeService(mockCli) assert.NilError(t, err) @@ -590,7 +590,7 @@ func TestRunHook_ExecInspectError(t *testing.T) { mockAPI.EXPECT(). ExecInspect(gomock.Any(), "exec-1", gomock.Any()). - Return(client.ExecInspectResult{}, fmt.Errorf("inspect failed")) + Return(client.ExecInspectResult{}, errors.New("inspect failed")) s, err := NewComposeService(mockCli) assert.NilError(t, err) diff --git a/pkg/compose/model.go b/pkg/compose/model.go index 345ad1272..928ff7180 100644 --- a/pkg/compose/model.go +++ b/pkg/compose/model.go @@ -20,6 +20,7 @@ import ( "bufio" "context" "encoding/json" + "errors" "fmt" "os/exec" "slices" @@ -82,7 +83,7 @@ func (s *composeService) newModelAPI(project *types.Project) (*modelAPI, error) dockerModel, err := manager.GetPlugin("model", s.dockerCli, &cobra.Command{}) if err != nil { if errdefs.IsNotFound(err) { - return nil, fmt.Errorf("'models' support requires Docker Model plugin") + return nil, errors.New("'models' support requires Docker Model plugin") } return nil, err } diff --git a/pkg/compose/plugins.go b/pkg/compose/plugins.go index 84a40775e..9142a7a38 100644 --- a/pkg/compose/plugins.go +++ b/pkg/compose/plugins.go @@ -287,7 +287,7 @@ func (s *composeService) setupPluginCommand(ctx context.Context, project *types. return nil, err } - args := []string{"compose", fmt.Sprintf("--project-name=%s", project.Name), command} + args := []string{"compose", "--project-name=" + project.Name, command} for k, v := range provider.Options { for _, value := range v { if _, ok := currentCommandMetadata.GetParameter(k); commandMetadataIsEmpty || ok { diff --git a/pkg/compose/pre_start_test.go b/pkg/compose/pre_start_test.go index 97e6cbc62..635eef673 100644 --- a/pkg/compose/pre_start_test.go +++ b/pkg/compose/pre_start_test.go @@ -19,7 +19,7 @@ package compose import ( "bytes" "context" - "fmt" + "errors" "io" "strings" "testing" @@ -301,7 +301,7 @@ func TestPreStart_ContainerCreateFailurePropagates(t *testing.T) { scan := expectEmptyOrphanScan(apiClient) apiClient.EXPECT().ContainerCreate(gomock.Any(), gomock.Any()). - Return(client.ContainerCreateResult{}, fmt.Errorf("no such image: missing:latest")).After(scan) + Return(client.ContainerCreateResult{}, errors.New("no such image: missing:latest")).After(scan) err := tested.runPreStart(t.Context(), project, service, ctr, func(api.ContainerEvent) {}) assert.ErrorContains(t, err, "no such image") @@ -328,7 +328,7 @@ func TestPreStart_ContainerStartFailurePropagates(t *testing.T) { logs1 := apiClient.EXPECT().ContainerLogs(gomock.Any(), "hook-1", gomock.Any()). Return(emptyLogs(), nil).After(wait1) start1 := apiClient.EXPECT().ContainerStart(gomock.Any(), "hook-1", gomock.Any()). - Return(client.ContainerStartResult{}, fmt.Errorf("daemon: container start failed")).After(logs1) + Return(client.ContainerStartResult{}, errors.New("daemon: container start failed")).After(logs1) // AutoRemove never fires when start fails, so the hook must drop the ghost // container explicitly. This is distinct from the success-path removal // (RemoveVolumes:true) — the never-started container has no logs to preserve. @@ -404,7 +404,7 @@ func TestWaitPreStart_RaceRealErrorAndResult(t *testing.T) { resultC := make(chan container.WaitResponse, 1) errC := make(chan error, 1) resultC <- container.WaitResponse{StatusCode: 0} - errC <- fmt.Errorf("daemon: connection lost") + errC <- errors.New("daemon: connection lost") waitRes := client.ContainerWaitResult{Result: resultC, Error: errC} err := waitPreStart(t.Context(), "web", 0, waitRes) assert.ErrorContains(t, err, "connection lost") @@ -691,7 +691,7 @@ func TestPreStart_SuccessRemoveFailureIsNonFatal(t *testing.T) { // Simulate a removal failure. apiClient.EXPECT(). ContainerRemove(gomock.Any(), "hook-1", client.ContainerRemoveOptions{RemoveVolumes: true}). - Return(client.ContainerRemoveResult{}, fmt.Errorf("already removed")) + Return(client.ContainerRemoveResult{}, errors.New("already removed")) // The hook succeeded; the service must start even if removal failed. err := tested.runPreStart(t.Context(), project, service, ctr, nil) @@ -778,7 +778,7 @@ func TestPreStart_StreamLogsError_NilListener(t *testing.T) { Return(waitResultExit(0)) // ContainerLogs fails; nil listener → no warning event, done closed immediately. apiClient.EXPECT().ContainerLogs(gomock.Any(), "hook-1", gomock.Any()). - Return(nil, fmt.Errorf("logs: connection refused")) + Return(nil, errors.New("logs: connection refused")) apiClient.EXPECT().ContainerStart(gomock.Any(), "hook-1", gomock.Any()). Return(client.ContainerStartResult{}, nil) expectSuccessRemove(apiClient, "hook-1") @@ -809,7 +809,7 @@ func TestPreStart_StreamLogsError_WithListener(t *testing.T) { apiClient.EXPECT().ContainerWait(gomock.Any(), "hook-1", gomock.Any()). Return(waitResultExit(0)) apiClient.EXPECT().ContainerLogs(gomock.Any(), "hook-1", gomock.Any()). - Return(nil, fmt.Errorf("logs: daemon unavailable")) + Return(nil, errors.New("logs: daemon unavailable")) apiClient.EXPECT().ContainerStart(gomock.Any(), "hook-1", gomock.Any()). Return(client.ContainerStartResult{}, nil) expectSuccessRemove(apiClient, "hook-1") @@ -917,7 +917,7 @@ func TestPreStart_ConnectExtraNetworksFails(t *testing.T) { apiClient.EXPECT(). NetworkConnect(gomock.Any(), "proj_extra", gomock.Any()). - Return(client.NetworkConnectResult{}, fmt.Errorf("network not found")) + Return(client.NetworkConnectResult{}, errors.New("network not found")) err := tested.connectPreStartExtraNetworks(t.Context(), project, service, "ctr-id", "proj_default") assert.ErrorContains(t, err, "network not found") @@ -947,10 +947,10 @@ func TestPreStart_ContainerStartFailureAndRemoveFails(t *testing.T) { logs1 := apiClient.EXPECT().ContainerLogs(gomock.Any(), "hook-1", gomock.Any()). Return(emptyLogs(), nil) start1 := apiClient.EXPECT().ContainerStart(gomock.Any(), "hook-1", gomock.Any()). - Return(client.ContainerStartResult{}, fmt.Errorf("start failed")).After(logs1) + Return(client.ContainerStartResult{}, errors.New("start failed")).After(logs1) // Both the start AND the cleanup removal fail. apiClient.EXPECT().ContainerRemove(gomock.Any(), "hook-1", client.ContainerRemoveOptions{Force: true, RemoveVolumes: true}). - Return(client.ContainerRemoveResult{}, fmt.Errorf("removal failed")).After(start1) + Return(client.ContainerRemoveResult{}, errors.New("removal failed")).After(start1) err := tested.runPreStart(t.Context(), project, service, ctr, nil) // The original start error must be returned, not the removal error. @@ -975,7 +975,7 @@ func TestPreStart_OrphanScanFails(t *testing.T) { // ContainerList fails → Warnf in runPreStart; hook still proceeds. apiClient.EXPECT().ContainerList(gomock.Any(), gomock.Any()). - Return(client.ContainerListResult{}, fmt.Errorf("daemon unavailable")) + Return(client.ContainerListResult{}, errors.New("daemon unavailable")) apiClient.EXPECT().ContainerCreate(gomock.Any(), gomock.Any()). Return(client.ContainerCreateResult{ID: "hook-1"}, nil) apiClient.EXPECT().ContainerWait(gomock.Any(), "hook-1", gomock.Any()). @@ -1012,7 +1012,7 @@ func TestPreStart_OrphanRemovalFails(t *testing.T) { Return(client.ContainerListResult{Items: []container.Summary{orphan}}, nil) apiClient.EXPECT(). ContainerRemove(gomock.Any(), "orphan-123", client.ContainerRemoveOptions{Force: true, RemoveVolumes: true}). - Return(client.ContainerRemoveResult{}, fmt.Errorf("permission denied")) + Return(client.ContainerRemoveResult{}, errors.New("permission denied")) // Hook still runs and succeeds. apiClient.EXPECT().ContainerCreate(gomock.Any(), gomock.Any()). Return(client.ContainerCreateResult{ID: "hook-1"}, nil) @@ -1059,7 +1059,7 @@ func TestPreStart_OldAPINetworkConnectFails(t *testing.T) { Return(client.ContainerCreateResult{ID: "hook-1"}, nil).After(scan) // NetworkConnect for the secondary network fails. apiClient.EXPECT().NetworkConnect(gomock.Any(), "proj_extra", gomock.Any()). - Return(client.NetworkConnectResult{}, fmt.Errorf("network not found")) + Return(client.NetworkConnectResult{}, errors.New("network not found")) // The never-started container is cleaned up (Force:true). apiClient.EXPECT(). ContainerRemove(gomock.Any(), "hook-1", client.ContainerRemoveOptions{Force: true, RemoveVolumes: true}). @@ -1098,11 +1098,11 @@ func TestPreStart_OldAPINetworkConnectAndRemoveFails(t *testing.T) { apiClient.EXPECT().ContainerCreate(gomock.Any(), gomock.Any()). Return(client.ContainerCreateResult{ID: "hook-1"}, nil).After(scan) apiClient.EXPECT().NetworkConnect(gomock.Any(), "proj_extra", gomock.Any()). - Return(client.NetworkConnectResult{}, fmt.Errorf("network not found")) + Return(client.NetworkConnectResult{}, errors.New("network not found")) // Cleanup removal also fails → Warnf; original error is still returned. apiClient.EXPECT(). ContainerRemove(gomock.Any(), "hook-1", client.ContainerRemoveOptions{Force: true, RemoveVolumes: true}). - Return(client.ContainerRemoveResult{}, fmt.Errorf("removal also failed")) + Return(client.ContainerRemoveResult{}, errors.New("removal also failed")) err := tested.runPreStart(t.Context(), project, service, ctr, nil) assert.ErrorContains(t, err, "network not found") @@ -1139,7 +1139,7 @@ func TestPreStart_RuntimeAPIVersionError(t *testing.T) { // createPreStartContainer calls RuntimeAPIVersion → Ping fails. apiClient.EXPECT(). Ping(gomock.Any(), client.PingOptions{NegotiateAPIVersion: true}). - Return(client.PingResult{}, fmt.Errorf("daemon unreachable")) + Return(client.PingResult{}, errors.New("daemon unreachable")) err = s.runPreStart(t.Context(), project, service, ctr, nil) assert.ErrorContains(t, err, "daemon unreachable") diff --git a/pkg/compose/publish.go b/pkg/compose/publish.go index f0fb5c231..4f362bef1 100644 --- a/pkg/compose/publish.go +++ b/pkg/compose/publish.go @@ -26,6 +26,7 @@ import ( "io" "os" "slices" + "strconv" "strings" "github.com/DefangLabs/secret-detector/pkg/detectors/keyword" @@ -610,7 +611,7 @@ func buildEnvPromptMessage(services map[string]*serviceEnvFindings) string { if keys := f.sortedSuspiciousKeys(); len(keys) > 0 { quoted := make([]string, len(keys)) for i, k := range keys { - quoted[i] = fmt.Sprintf("%q", k) + quoted[i] = strconv.Quote(k) } fmt.Fprintf(&b, " service %q: literal value for %s\n", name, strings.Join(quoted, ", ")) } diff --git a/pkg/compose/reconcile.go b/pkg/compose/reconcile.go index 1d6b7f98e..d22f03561 100644 --- a/pkg/compose/reconcile.go +++ b/pkg/compose/reconcile.go @@ -229,7 +229,7 @@ func (r *reconciler) reconcileNetworks() error { func (r *reconciler) planCreateNetwork(key string, networkConfig *types.NetworkConfig, cause string) { r.networkNodes[key] = r.plan.addNode(Operation{ Type: OpCreateNetwork, - ResourceID: fmt.Sprintf("network:%s", key), + ResourceID: "network:" + key, Cause: cause, Name: networkConfig.Name, Network: networkConfig, @@ -299,7 +299,7 @@ func (r *reconciler) planRecreateNetworks(keys []string) { removeNode := r.plan.addNode(Operation{ Type: OpRemoveNetwork, - ResourceID: fmt.Sprintf("network:%s", key), + ResourceID: "network:" + key, Cause: removeCause, Name: observed.Name, BestEffort: rename, @@ -311,7 +311,7 @@ func (r *reconciler) planRecreateNetworks(keys []string) { } createNode := r.plan.addNode(Operation{ Type: OpCreateNetwork, - ResourceID: fmt.Sprintf("network:%s", key), + ResourceID: "network:" + key, Cause: createCause, Name: desired.Name, Network: &desired, @@ -403,7 +403,7 @@ func (r *reconciler) reconcileVolumes() error { func (r *reconciler) planCreateVolume(key string, vol *types.VolumeConfig, cause string) { r.volumeNodes[key] = r.plan.addNode(Operation{ Type: OpCreateVolume, - ResourceID: fmt.Sprintf("volume:%s", key), + ResourceID: "volume:" + key, Cause: cause, Name: vol.Name, Volume: vol, @@ -477,13 +477,13 @@ func (r *reconciler) planRecreateVolumes(keys []string) { desired := r.project.Volumes[key] removeVolNode := r.plan.addNode(Operation{ Type: OpRemoveVolume, - ResourceID: fmt.Sprintf("volume:%s", key), + ResourceID: "volume:" + key, Cause: "config hash diverged", Name: r.resolvedVolumes[key].Name, }, "", removeNodes...) createVolNode := r.plan.addNode(Operation{ Type: OpCreateVolume, - ResourceID: fmt.Sprintf("volume:%s", key), + ResourceID: "volume:" + key, Cause: "recreate after config change", Name: desired.Name, Volume: &desired, @@ -649,7 +649,7 @@ func (r *reconciler) reconcileService(service types.ServiceConfig) error { deps := r.infrastructureDeps(service) node := r.plan.addNode(Operation{ Type: OpRunProvider, - ResourceID: fmt.Sprintf("provider:%s", service.Name), + ResourceID: "provider:" + service.Name, Cause: "provider service", Service: &serviceCopy, }, "", deps...) @@ -1051,14 +1051,14 @@ func (r *reconciler) reconcileOrphans() { for i, oc := range r.observed.Orphans { stopNode := r.plan.addNode(Operation{ Type: OpStopContainer, - ResourceID: fmt.Sprintf("orphan:%s", oc.Name), + ResourceID: "orphan:" + oc.Name, Cause: "orphaned container", Container: &r.observed.Orphans[i].Summary, Timeout: r.options.Timeout, }, "") r.plan.addNode(Operation{ Type: OpRemoveContainer, - ResourceID: fmt.Sprintf("orphan:%s", oc.Name), + ResourceID: "orphan:" + oc.Name, Cause: "orphaned container", Container: &r.observed.Orphans[i].Summary, }, "", stopNode) diff --git a/pkg/compose/reconcile_test.go b/pkg/compose/reconcile_test.go index 94b08aba8..c0336ce16 100644 --- a/pkg/compose/reconcile_test.go +++ b/pkg/compose/reconcile_test.go @@ -17,6 +17,7 @@ package compose import ( + "errors" "fmt" "strconv" "strings" @@ -511,7 +512,7 @@ func TestReconcileVolumes_DivergedPromptMessage(t *testing.T) { func TestReconcileVolumes_DivergedPromptError(t *testing.T) { project, observed := divergedVolumeProject(t, 1, 1) - boom := func(_ string, _ bool) (bool, error) { return false, fmt.Errorf("boom") } + boom := func(_ string, _ bool) (bool, error) { return false, errors.New("boom") } _, err := reconcile(t.Context(), project, observed, defaultReconcileOptions(), boom) assert.ErrorContains(t, err, "boom") } diff --git a/pkg/compose/remove.go b/pkg/compose/remove.go index cae608505..b24f0cfb3 100644 --- a/pkg/compose/remove.go +++ b/pkg/compose/remove.go @@ -80,7 +80,7 @@ func (s *composeService) Remove(ctx context.Context, projectName string, options return api.ErrNoResources } - msg := fmt.Sprintf("Going to remove %s", strings.Join(names, ", ")) + msg := "Going to remove " + strings.Join(names, ", ") if options.Force { _, _ = fmt.Fprintln(s.stdout(), msg) } else { diff --git a/pkg/compose/service_containers.go b/pkg/compose/service_containers.go index 5f37a0da5..716acd0a1 100644 --- a/pkg/compose/service_containers.go +++ b/pkg/compose/service_containers.go @@ -189,7 +189,7 @@ func (s *composeService) waitDependencies(ctx context.Context, project *types.Pr } err := eg.Wait() if errors.Is(err, context.DeadlineExceeded) { - return fmt.Errorf("timeout waiting for dependencies") + return errors.New("timeout waiting for dependencies") } return err } @@ -299,12 +299,12 @@ func (s *composeService) checkDependencyCompleted(ctx context.Context, dep strin if !config.Required { // optional -> mark as skipped & don't propagate error s.events.On(containerReasonEvents(waitingFor, skippedEvent, - fmt.Sprintf("optional dependency %s", messageSuffix))...) + "optional dependency "+messageSuffix)...) logrus.Warnf("optional dependency %s", messageSuffix) return true, nil } - msg := fmt.Sprintf("service %s", messageSuffix) + msg := "service " + messageSuffix s.events.On(containerEvents(waitingFor, func(s string) api.Resource { return errorEventf(s, "service %s", messageSuffix) })...) diff --git a/pkg/compose/service_containers_test.go b/pkg/compose/service_containers_test.go index c36dda510..d82c82641 100644 --- a/pkg/compose/service_containers_test.go +++ b/pkg/compose/service_containers_test.go @@ -18,6 +18,7 @@ package compose import ( "context" + "errors" "fmt" "net/netip" "strings" @@ -695,7 +696,7 @@ func TestCreateMobyContainerLegacyAPI_NetworkConnectFailure(t *testing.T) { Return(client.ContainerCreateResult{ID: "an-id"}, nil) // NetworkConnect fails - connectErr := fmt.Errorf("network connect failed") + connectErr := errors.New("network connect failed") apiClient.EXPECT().NetworkConnect(gomock.Any(), gomock.Eq("a-moby-name"), gomock.Any()). Return(client.NetworkConnectResult{}, connectErr) diff --git a/pkg/compose/watch.go b/pkg/compose/watch.go index baba7707d..8b6b5bd8f 100644 --- a/pkg/compose/watch.go +++ b/pkg/compose/watch.go @@ -77,7 +77,7 @@ func NewWatcher(project *types.Project, options api.UpOptions, w WatchFunc, cons } } // none of the services is eligible to watch - return nil, fmt.Errorf("none of the selected services is configured for watch, see https://docs.docker.com/compose/how-tos/file-watch/") + return nil, errors.New("none of the selected services is configured for watch, see https://docs.docker.com/compose/how-tos/file-watch/") } // ensure state changes are atomic @@ -236,7 +236,7 @@ func (s *composeService) watch(ctx context.Context, project *types.Project, opti } if len(paths) == 0 { - return nil, fmt.Errorf("none of the selected services is configured for watch, consider setting a 'develop' section") + return nil, errors.New("none of the selected services is configured for watch, consider setting a 'develop' section") } watcher, err := watch.NewWatcher(paths) @@ -543,7 +543,7 @@ func (t tarDockerClient) Exec(ctx context.Context, containerID string, cmd []str return errors.New("process still running") } if execResult.ExitCode != 0 { - return fmt.Errorf("exit code %d", execResult.ExitCode) + return errors.New("exit code " + strconv.Itoa(execResult.ExitCode)) } return nil } diff --git a/pkg/dryrun/dryrunclient.go b/pkg/dryrun/dryrunclient.go index cf8f7ba43..fbcc18cb7 100644 --- a/pkg/dryrun/dryrunclient.go +++ b/pkg/dryrun/dryrunclient.go @@ -20,6 +20,7 @@ import ( "bytes" "context" "crypto/rand" + "encoding/hex" "encoding/json" "errors" "fmt" @@ -300,7 +301,7 @@ func (d *DryRunClient) VolumeRemove(ctx context.Context, volumeID string, option func (d *DryRunClient) ExecCreate(ctx context.Context, container string, config client.ExecCreateOptions) (client.ExecCreateResult, error) { b := make([]byte, 32) _, _ = rand.Read(b) - id := fmt.Sprintf("%x", b) + id := hex.EncodeToString(b) d.execs.Store(id, execDetails{ container: container, command: config.Cmd, diff --git a/pkg/e2e/bridge_convert_output_test.go b/pkg/e2e/bridge_convert_output_test.go index 3b1e4b5f8..e8d572dd5 100644 --- a/pkg/e2e/bridge_convert_output_test.go +++ b/pkg/e2e/bridge_convert_output_test.go @@ -19,7 +19,6 @@ package e2e import ( - "fmt" "os" "path/filepath" "testing" @@ -49,7 +48,7 @@ func TestBridgeConvertOutputNotEmptyDeclined(t *testing.T) { s.Step("convert without confirmation leaves the non-empty output directory untouched", ComposeCmd("bridge", "convert", "--output", outDir, - "--transformation", fmt.Sprintf("docker/compose-bridge-kubernetes:%s", bridgeImageVersion)).MayFail(), + "--transformation", "docker/compose-bridge-kubernetes:"+bridgeImageVersion).MayFail(), ExitCode(1), FileContains(guardFile, "do not delete me")) } @@ -60,7 +59,7 @@ func TestBridgeConvertOutputNotEmptyConfirmed(t *testing.T) { s.Step("convert --yes wipes the stale content and regenerates the output", ComposeCmd("bridge", "convert", "--output", outDir, "--yes", - "--transformation", fmt.Sprintf("docker/compose-bridge-kubernetes:%s", bridgeImageVersion)), + "--transformation", "docker/compose-bridge-kubernetes:"+bridgeImageVersion), FileAbsent(guardFile), FileExists(filepath.Join(outDir, "base", "0-"+s.Project()+"-namespace.yaml"))) } diff --git a/pkg/e2e/bridge_test.go b/pkg/e2e/bridge_test.go index abb8f657f..5165d3c68 100644 --- a/pkg/e2e/bridge_test.go +++ b/pkg/e2e/bridge_test.go @@ -19,7 +19,6 @@ package e2e import ( - "fmt" "os" "path/filepath" "strings" @@ -39,7 +38,7 @@ func TestConvertAndTransformList(t *testing.T) { t.Run("kubernetes manifests", func(t *testing.T) { kubedir := filepath.Join(tmpDir, "kubernetes") res := c.RunDockerComposeCmd(t, "-f", "./fixtures/bridge/compose.yaml", "--project-name", projectName, "bridge", "convert", - "--output", kubedir, "--transformation", fmt.Sprintf("docker/compose-bridge-kubernetes:%s", bridgeImageVersion)) + "--output", kubedir, "--transformation", "docker/compose-bridge-kubernetes:"+bridgeImageVersion) assert.NilError(t, res.Error) assert.Equal(t, res.ExitCode, 0) res = c.RunCmd(t, "diff", "-r", kubedir, "./fixtures/bridge/expected-kubernetes") @@ -49,7 +48,7 @@ func TestConvertAndTransformList(t *testing.T) { t.Run("helm charts", func(t *testing.T) { helmDir := filepath.Join(tmpDir, "helm") res := c.RunDockerComposeCmd(t, "-f", "./fixtures/bridge/compose.yaml", "--project-name", projectName, "bridge", "convert", - "--output", helmDir, "--transformation", fmt.Sprintf("docker/compose-bridge-helm:%s", bridgeImageVersion)) + "--output", helmDir, "--transformation", "docker/compose-bridge-helm:"+bridgeImageVersion) assert.NilError(t, res.Error) assert.Equal(t, res.ExitCode, 0) res = c.RunCmd(t, "diff", "-r", helmDir, "./fixtures/bridge/expected-helm") @@ -69,7 +68,7 @@ func TestConvertBuildOnlyService(t *testing.T) { outDir := t.TempDir() res := c.RunDockerComposeCmd(t, "-f", "./fixtures/bridge-build-only/compose.yaml", "--project-name", "bridge-build-only", "bridge", "convert", - "--output", outDir, "--transformation", fmt.Sprintf("docker/compose-bridge-kubernetes:%s", bridgeImageVersion)) + "--output", outDir, "--transformation", "docker/compose-bridge-kubernetes:"+bridgeImageVersion) assert.NilError(t, res.Error) assert.Equal(t, res.ExitCode, 0) diff --git a/pkg/e2e/checks.go b/pkg/e2e/checks.go index 7d1dca12b..43366c3b2 100644 --- a/pkg/e2e/checks.go +++ b/pkg/e2e/checks.go @@ -23,11 +23,13 @@ package e2e // test-specific logic), and are named after the observable they assert. import ( + "errors" "fmt" "os" "regexp" "runtime" "slices" + "strconv" "strings" "time" @@ -67,7 +69,7 @@ func OutputContains(sub string) Check { name: fmt.Sprintf("output contains %q", sub), fn: func(ctx *CheckContext) error { if !strings.Contains(ctx.result.Combined(), sub) { - return fmt.Errorf("not found in output") + return errors.New("not found in output") } return nil }, @@ -82,7 +84,7 @@ func StdoutContains(sub string) Check { name: fmt.Sprintf("stdout contains %q", sub), fn: func(ctx *CheckContext) error { if !strings.Contains(ctx.result.Stdout(), sub) { - return fmt.Errorf("not found in stdout") + return errors.New("not found in stdout") } return nil }, @@ -97,7 +99,7 @@ func OutputMatches(pattern string) Check { name: fmt.Sprintf("output matches %q", pattern), fn: func(ctx *CheckContext) error { if !re.MatchString(ctx.result.Stdout()) { - return fmt.Errorf("no match in stdout") + return errors.New("no match in stdout") } return nil }, @@ -127,7 +129,7 @@ func StderrContains(sub string) Check { name: fmt.Sprintf("stderr contains %q", sub), fn: func(ctx *CheckContext) error { if !strings.Contains(ctx.result.Stderr(), sub) { - return fmt.Errorf("not found in stderr") + return errors.New("not found in stderr") } return nil }, @@ -140,7 +142,7 @@ func OutputNotContains(sub string) Check { name: fmt.Sprintf("output does not contain %q", sub), fn: func(ctx *CheckContext) error { if strings.Contains(ctx.result.Combined(), sub) { - return fmt.Errorf("found in output") + return errors.New("found in output") } return nil }, @@ -220,7 +222,7 @@ func ServiceState(service, state string) Check { fn: func(ctx *CheckContext) error { containers := ctx.curr.service(service) if len(containers) == 0 { - return fmt.Errorf("service has no container") + return errors.New("service has no container") } for _, c := range containers { if c.State != state { @@ -264,7 +266,7 @@ func ReplicaNumbers(service string, numbers ...int) Check { slices.Sort(actual) var expected []string for _, n := range numbers { - expected = append(expected, fmt.Sprint(n)) + expected = append(expected, strconv.Itoa(n)) } slices.Sort(expected) if !slices.Equal(actual, expected) { @@ -353,7 +355,7 @@ func ServiceHealthy(service string) Check { fn: func(ctx *CheckContext) error { containers := ctx.curr.service(service) if len(containers) == 0 { - return fmt.Errorf("service has no container") + return errors.New("service has no container") } for _, c := range containers { res := icmd.RunCmd(ctx.scenario.cli.NewDockerCmd(ctx.scenario.t, @@ -378,7 +380,7 @@ func OneOffState(service, state string) Check { fn: func(ctx *CheckContext) error { containers := ctx.curr.oneOffs(service) if len(containers) == 0 { - return fmt.Errorf("service has no one-off container") + return errors.New("service has no one-off container") } for _, c := range containers { if c.State != state { @@ -399,7 +401,7 @@ func OneOffsUntouched(service string) Check { fn: func(ctx *CheckContext) error { before, after := ctx.prev.oneOffs(service), ctx.curr.oneOffs(service) if len(before) == 0 { - return fmt.Errorf("service had no one-off container before the step") + return errors.New("service had no one-off container before the step") } if !slices.Equal(containerIDs(before), containerIDs(after)) { return fmt.Errorf("one-off containers changed: %v -> %v", containerIDs(before), containerIDs(after)) @@ -424,7 +426,7 @@ func OneOffsRemoved(service string) Check { name: fmt.Sprintf("one-offs of service %q removed", service), fn: func(ctx *CheckContext) error { if len(ctx.prev.oneOffs(service)) == 0 { - return fmt.Errorf("service had no one-off container before the step") + return errors.New("service had no one-off container before the step") } var names []string for _, c := range ctx.curr.oneOffs(service) { @@ -464,7 +466,7 @@ func FileExists(path string) Check { return err } if info.Size() == 0 { - return fmt.Errorf("file is empty") + return errors.New("file is empty") } return nil }, @@ -496,7 +498,7 @@ func FileAbsent(path string) Check { name: fmt.Sprintf("file %q is absent", path), fn: func(ctx *CheckContext) error { if _, err := os.Stat(path); err == nil { - return fmt.Errorf("file still exists") + return errors.New("file still exists") } else if !os.IsNotExist(err) { return err } @@ -512,7 +514,7 @@ func LabelSet(service, key string) Check { fn: func(ctx *CheckContext) error { containers := ctx.curr.service(service) if len(containers) == 0 { - return fmt.Errorf("service has no container") + return errors.New("service has no container") } for _, c := range containers { if c.Labels[key] == "" { @@ -555,7 +557,7 @@ func LabelUnchanged(service, key string) Check { fn: func(ctx *CheckContext) error { before, after := ctx.prev.service(service), ctx.curr.service(service) if len(before) == 0 || len(after) == 0 { - return fmt.Errorf("service has no container to compare") + return errors.New("service has no container to compare") } if before[0].Labels[key] != after[0].Labels[key] { return fmt.Errorf("label changed: %q -> %q", before[0].Labels[key], after[0].Labels[key]) @@ -573,7 +575,7 @@ func RunsOnPlatform(service, platform string) Check { fn: func(ctx *CheckContext) error { containers := ctx.curr.service(service) if len(containers) == 0 { - return fmt.Errorf("service has no container") + return errors.New("service has no container") } res := icmd.RunCmd(ctx.scenario.cli.NewDockerCmd(ctx.scenario.t, "inspect", "--format", "{{.ImageManifestDescriptor.Platform.OS}}/{{.ImageManifestDescriptor.Platform.Architecture}}", diff --git a/pkg/e2e/framework.go b/pkg/e2e/framework.go index eda55b561..ba444767f 100644 --- a/pkg/e2e/framework.go +++ b/pkg/e2e/framework.go @@ -282,7 +282,7 @@ func (c *CLI) BaseEnvironment() []string { _, filename, _, _ := runtime.Caller(0) root := filepath.Join(filepath.Dir(filename), "..", "..") coverdir = filepath.Join(root, coverdir) - env = append(env, fmt.Sprintf("GOCOVERDIR=%s", coverdir)) + env = append(env, "GOCOVERDIR="+coverdir) } return env } diff --git a/pkg/e2e/watch_test.go b/pkg/e2e/watch_test.go index 726791850..3e186b0d6 100644 --- a/pkg/e2e/watch_test.go +++ b/pkg/e2e/watch_test.go @@ -21,6 +21,7 @@ package e2e import ( "bytes" "crypto/rand" + "encoding/hex" "fmt" "os" "path/filepath" @@ -203,7 +204,7 @@ func doTest(t *testing.T, svcName string) { waitForFlush := func() { b := make([]byte, 32) _, _ = rand.Read(b) - sentinelVal := fmt.Sprintf("%x", b) + sentinelVal := hex.EncodeToString(b) writeDataFile("wait.txt", sentinelVal) poll.WaitOn(t, checkFileContents("/app/data/wait.txt", sentinelVal)) } diff --git a/pkg/remote/oci.go b/pkg/remote/oci.go index 1c14d3150..46df64e40 100644 --- a/pkg/remote/oci.go +++ b/pkg/remote/oci.go @@ -19,6 +19,7 @@ package remote import ( "context" "encoding/json" + "errors" "fmt" "net/http" "os" @@ -49,7 +50,7 @@ const ( func validatePathInBase(base, unsafePath string) error { // Reject paths with path separators regardless of OS if strings.ContainsAny(unsafePath, "\\/") { - return fmt.Errorf("invalid OCI artifact") + return errors.New("invalid OCI artifact") } // Join the base with the untrusted path @@ -64,7 +65,7 @@ func validatePathInBase(base, unsafePath string) error { // Check if the target directory is the same as base directory if cleanTargetDir != cleanBase { - return fmt.Errorf("invalid OCI artifact") + return errors.New("invalid OCI artifact") } return nil diff --git a/pkg/watch/dockerignore.go b/pkg/watch/dockerignore.go index ce7d57fbf..5033b2405 100644 --- a/pkg/watch/dockerignore.go +++ b/pkg/watch/dockerignore.go @@ -120,7 +120,7 @@ func absPatterns(absRoot string, patterns []string) []string { } absPattern := pPath if isExclusion { - absPattern = fmt.Sprintf("!%s", pPath) + absPattern = "!" + pPath } absPatterns = append(absPatterns, absPattern) } diff --git a/pkg/watch/notify.go b/pkg/watch/notify.go index d63f5caf2..922c84029 100644 --- a/pkg/watch/notify.go +++ b/pkg/watch/notify.go @@ -18,7 +18,6 @@ package watch import ( "expvar" - "fmt" "os" "path/filepath" "strconv" @@ -30,7 +29,7 @@ type FileEvent string func NewFileEvent(p string) FileEvent { if !filepath.IsAbs(p) { - panic(fmt.Sprintf("NewFileEvent only accepts absolute paths. Actual: %s", p)) + panic("NewFileEvent only accepts absolute paths. Actual: " + p) } return FileEvent(p) } diff --git a/pkg/watch/paths.go b/pkg/watch/paths.go index c0c893cd9..fa3f85ad4 100644 --- a/pkg/watch/paths.go +++ b/pkg/watch/paths.go @@ -17,6 +17,7 @@ package watch import ( + "errors" "fmt" "os" "path/filepath" @@ -25,7 +26,7 @@ import ( func greatestExistingAncestor(path string) (string, error) { if path == string(filepath.Separator) || path == fmt.Sprintf("%s%s", filepath.VolumeName(path), string(filepath.Separator)) { - return "", fmt.Errorf("cannot watch root directory") + return "", errors.New("cannot watch root directory") } _, err := os.Stat(path) diff --git a/pkg/watch/watcher_naive.go b/pkg/watch/watcher_naive.go index b148f8a39..00b81e6cf 100644 --- a/pkg/watch/watcher_naive.go +++ b/pkg/watch/watcher_naive.go @@ -19,6 +19,7 @@ package watch import ( + "errors" "fmt" "io/fs" "os" @@ -301,7 +302,7 @@ func newWatcher(paths []string) (Notify, error) { fsw, err := fsnotify.NewWatcher() if err != nil { if strings.Contains(err.Error(), "too many open files") && runtime.GOOS == "linux" { - return nil, fmt.Errorf("hit OS limits creating a watcher.\n" + + return nil, errors.New("hit OS limits creating a watcher.\n" + "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'") } diff --git a/pkg/watch/watcher_naive_test.go b/pkg/watch/watcher_naive_test.go index 79efb78eb..a2172860c 100644 --- a/pkg/watch/watcher_naive_test.go +++ b/pkg/watch/watcher_naive_test.go @@ -57,7 +57,7 @@ func TestDontWatchEachFile(t *testing.T) { } for i := range 100 { - f.WriteFile(f.JoinPath(initialDir, fmt.Sprintf("%d", i)), "initial data") + f.WriteFile(f.JoinPath(initialDir, strconv.Itoa(i)), "initial data") } f.watch(watched) @@ -80,7 +80,7 @@ func TestDontWatchEachFile(t *testing.T) { } for i := range 100 { - f.WriteFile(f.JoinPath(inplaceDir, fmt.Sprintf("%d", i)), "inplace data") + f.WriteFile(f.JoinPath(inplaceDir, strconv.Itoa(i)), "inplace data") } f.fsync() @@ -99,7 +99,7 @@ func TestDontWatchEachFile(t *testing.T) { } for i := range 100 { - f.WriteFile(f.JoinPath(stagedDir, fmt.Sprintf("%d", i)), "staged data") + f.WriteFile(f.JoinPath(stagedDir, strconv.Itoa(i)), "staged data") } if err := os.Rename(staged, f.JoinPath(watched, "staged")); err != nil { @@ -115,7 +115,7 @@ func TestDontWatchEachFile(t *testing.T) { n, err := inotifyNodes() assert.NilError(t, err) if n > 10 { - t.Fatalf("watching more than 10 files: %d", n) + t.Fatal("watching more than 10 files:", n) } } @@ -147,14 +147,14 @@ func TestDontRecurseWhenWatchingParentsOfNonExistentFiles(t *testing.T) { excludedDir := f.JoinPath(watched, "excluded") for i := range 10 { - f.WriteFile(f.JoinPath(excludedDir, fmt.Sprintf("%d", i), "data.txt"), "initial data") + f.WriteFile(f.JoinPath(excludedDir, strconv.Itoa(i), "data.txt"), "initial data") } f.fsync() n, err := inotifyNodes() assert.NilError(t, err) if n > 5 { - t.Fatalf("watching more than 5 files: %d", n) + t.Fatal("watching more than 5 files:", n) } }