Repository: docker/compose
Pull request #12689 (2025-03-31 15:23:33)

bump golangci-lint to version v2.0.2
and apply migration script

**What I did**
bump golangci-lint to version v2.0.2

**Related issue**
N/A

**(not mandatory) A picture of a cute animal, if possible in relation to what you did**



Patch:
diff --git a/.golangci.yml b/.golangci.yml
index 090e12151..ad06ff5bd 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -1,9 +1,8 @@
+version: "2"
 run:
   concurrency: 2
-  timeout: 10m
 linters:
-  enable-all: false
-  disable-all: true
+  default: none
   enable:
     - copyloopvar
     - depguard
@@ -11,73 +10,74 @@ linters:
     - errorlint
     - gocritic
     - gocyclo
-    - gofumpt
-    - goimports
     - gomodguard
-    - revive
-    - gosimple
     - govet
     - ineffassign
     - lll
     - misspell
     - nakedret
     - nolintlint
+    - revive
     - staticcheck
     - testifylint
-    - typecheck
     - unconvert
     - unparam
     - unused
-linters-settings:
-  revive:
-    rules:
-      - name: package-comments
-        disabled: true
-  depguard:
-    rules:
-      all:
-        deny:
-          - pkg: io/ioutil
-            desc: 'io/ioutil package has been deprecated'
-          - pkg: gopkg.in/yaml.v2
-            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"
-        - gotest.tools:
-            version: "< 3.0.0"
-            reason: "deprecated, pre-modules version"
-  gocritic:
-    # Enable multiple checks by tags, run `GL_DEBUG=gocritic golangci-lint run` to see all tags and checks.
-    # Empty list by default. See https://github.com/go-critic/go-critic#usage -> section "Tags".
-    enabled-tags:
-      - diagnostic
-      - opinionated
-      - style
-    disabled-checks:
-      - paramTypeCombine
-      - unnamedResult
-      - whyNoLint
-  gocyclo:
-    min-complexity: 16
-  lll:
-    line-length: 200
+  settings:
+    depguard:
+      rules:
+        all:
+          deny:
+            - pkg: io/ioutil
+              desc: io/ioutil package has been deprecated
+            - pkg: gopkg.in/yaml.v2
+              desc: compose-go uses yaml.v3
+    gocritic:
+      disabled-checks:
+        - paramTypeCombine
+        - unnamedResult
+        - whyNoLint
+      enabled-tags:
+        - diagnostic
+        - opinionated
+        - style
+    gocyclo:
+      min-complexity: 16
+    gomodguard:
+      blocked:
+        modules:
+          - github.com/pkg/errors:
+              recommendations:
+                - errors
+                - fmt
+        versions:
+          - github.com/distribution/distribution:
+              reason: use distribution/reference
+          - gotest.tools:
+              version: < 3.0.0
+              reason: deprecated, pre-modules version
+    lll:
+      line-length: 200
+    revive:
+      rules:
+        - name: package-comments
+          disabled: true
+  exclusions:
+    generated: lax
+    paths:
+      - third_party$
+      - builtin$
+      - examples$
 issues:
-  # golangci hides some golint warnings (the warning about exported things
-  # without documentation for example), this will make it show them anyway.
-  exclude-use-default: false
-  # Maximum issues count per one linter.
-  # Set to 0 to disable.
-  # Default: 50
   max-issues-per-linter: 0
-  # Maximum count of issues with the same text.
-  # Set to 0 to disable.
-  # Default: 3
   max-same-issues: 0
+formatters:
+  enable:
+    - gofumpt
+    - goimports
+  exclusions:
+    generated: lax
+    paths:
+      - third_party$
+      - builtin$
+      - examples$
diff --git a/Dockerfile b/Dockerfile
index 92fb8c5fa..69ff7f1c0 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -17,7 +17,7 @@
 
 ARG GO_VERSION=1.23.6
 ARG XX_VERSION=1.6.1
-ARG GOLANGCI_LINT_VERSION=v1.63.4
+ARG GOLANGCI_LINT_VERSION=v2.0.2
 ARG ADDLICENSE_VERSION=v1.0.0
 
 ARG BUILD_TAGS="e2e"
diff --git a/cmd/compose/compose.go b/cmd/compose/compose.go
index a3cc24517..8bd1bccbe 100644
--- a/cmd/compose/compose.go
+++ b/cmd/compose/compose.go
@@ -550,10 +550,7 @@ func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //noli
 			}
 
 			composeCmd := cmd
-			for {
-				if composeCmd.Name() == PluginName {
-					break
-				}
+			for composeCmd.Name() != PluginName {
 				if !composeCmd.HasParent() {
 					return fmt.Errorf("error parsing command line, expected %q", PluginName)
 				}
diff --git a/cmd/compose/exec.go b/cmd/compose/exec.go
index aa6774d5d..a6a8847ea 100644
--- a/cmd/compose/exec.go
+++ b/cmd/compose/exec.go
@@ -86,7 +86,7 @@ func runExec(ctx context.Context, dockerCli command.Cli, backend api.Service, op
 	if err != nil {
 		return err
 	}
-	projectOptions, err := opts.composeOptions.toProjectOptions()
+	projectOptions, err := opts.composeOptions.toProjectOptions() //nolint:staticcheck
 	if err != nil {
 		return err
 	}
diff --git a/cmd/compose/options.go b/cmd/compose/options.go
index 3b6ba1beb..0df72cd85 100644
--- a/cmd/compose/options.go
+++ b/cmd/compose/options.go
@@ -246,7 +246,7 @@ func displayInterpolationVariables(writer io.Writer, varsInfo []varInfo) {
 }
 
 func displayLocationRemoteStack(dockerCli command.Cli, project *types.Project, options buildOptions) {
-	mainComposeFile := options.ProjectOptions.ConfigPaths[0]
+	mainComposeFile := options.ProjectOptions.ConfigPaths[0] //nolint:staticcheck
 	if ui.Mode != ui.ModeQuiet && ui.Mode != ui.ModeJSON {
 		_, _ = fmt.Fprintf(dockerCli.Out(), "Your compose stack %q is stored in %q\n", mainComposeFile, project.WorkingDir)
 	}
@@ -258,8 +258,8 @@ func confirmRemoteIncludes(dockerCli command.Cli, options buildOptions, assumeYe
 	}
 
 	var remoteIncludes []string
-	remoteLoaders := options.ProjectOptions.remoteLoaders(dockerCli)
-	for _, cf := range options.ProjectOptions.ConfigPaths {
+	remoteLoaders := options.ProjectOptions.remoteLoaders(dockerCli) //nolint:staticcheck
+	for _, cf := range options.ProjectOptions.ConfigPaths {          //nolint:staticcheck
 		for _, loader := range remoteLoaders {
 			if loader.Accept(cf) {
 				remoteIncludes = append(remoteIncludes, cf)
diff --git a/cmd/formatter/shortcut.go b/cmd/formatter/shortcut.go
index d6674e8d1..126d4509f 100644
--- a/cmd/formatter/shortcut.go
+++ b/cmd/formatter/shortcut.go
@@ -238,7 +238,7 @@ func (lk *LogKeyboard) openDockerDesktop(ctx context.Context, project *types.Pro
 			link := fmt.Sprintf("docker-desktop://dashboard/apps/%s", project.Name)
 			err := open.Run(link)
 			if err != nil {
-				err = fmt.Errorf("Could not open Docker Desktop")
+				err = fmt.Errorf("could not open Docker Desktop")
 				lk.keyboardError("View", err)
 			}
 			return err
@@ -255,7 +255,7 @@ func (lk *LogKeyboard) openDDComposeUI(ctx context.Context, project *types.Proje
 			link := fmt.Sprintf("docker-desktop://dashboard/docker-compose/%s", project.Name)
 			err := open.Run(link)
 			if err != nil {
-				err = fmt.Errorf("Could not open Docker Desktop Compose UI")
+				err = fmt.Errorf("could not open Docker Desktop Compose UI")
 				lk.keyboardError("View Config", err)
 			}
 			return err
@@ -269,7 +269,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 = fmt.Errorf("could not open Docker Desktop Compose UI")
 				lk.keyboardError("Watch Docs", err)
 			}
 			return err
@@ -299,7 +299,7 @@ func (lk *LogKeyboard) StartWatch(ctx context.Context, doneCh chan bool, project
 		eg.Go(tracing.EventWrapFuncForErrGroup(ctx, "menu/watch", tracing.SpanOptions{},
 			func(ctx context.Context) error {
 				if options.Create.Build == nil {
-					err := fmt.Errorf("Cannot run watch mode with flag --no-build")
+					err := fmt.Errorf("cannot run watch mode with flag --no-build")
 					lk.keyboardError("Watch", err)
 					return err
 				}
diff --git a/pkg/compose/convergence.go b/pkg/compose/convergence.go
index da14323e9..d56ff40a4 100644
--- a/pkg/compose/convergence.go
+++ b/pkg/compose/convergence.go
@@ -47,7 +47,7 @@ import (
 const (
 	doubledContainerNameWarning = "WARNING: The %q service is using the custom container name %q. " +
 		"Docker requires each container to have a unique name. " +
-		"Remove the custom name to scale the service.\n"
+		"Remove the custom name to scale the service"
 )
 
 // convergence manages service's container lifecycle.
diff --git a/pkg/compose/create.go b/pkg/compose/create.go
index ddd4898cb..2a92f92e3 100644
--- a/pkg/compose/create.go
+++ b/pkg/compose/create.go
@@ -486,7 +486,7 @@ func parseSecurityOpts(p *types.Project, securityOpts []string) ([]string, bool,
 			if strings.Contains(opt, ":") {
 				con = strings.SplitN(opt, ":", 2)
 			} else {
-				return securityOpts, false, fmt.Errorf("Invalid security-opt: %q", opt)
+				return securityOpts, false, fmt.Errorf("invalid security-opt: %q", opt)
 			}
 		}
 		if con[0] == "seccomp" && con[1] != "unconfined" && con[1] != "builtin" {
@@ -997,10 +997,10 @@ func buildContainerConfigMounts(p types.Project, s types.ServiceConfig) ([]mount
 		}
 
 		if definedConfig.Driver != "" {
-			return nil, errors.New("Docker Compose does not support configs.*.driver")
+			return nil, errors.New("Docker Compose does not support configs.*.driver") //nolint:staticcheck
 		}
 		if definedConfig.TemplateDriver != "" {
-			return nil, errors.New("Docker Compose does not support configs.*.template_driver")
+			return nil, errors.New("Docker Compose does not support configs.*.template_driver") //nolint:staticcheck
 		}
 
 		if definedConfig.Environment != "" || definedConfig.Content != "" {
@@ -1047,10 +1047,10 @@ func buildContainerSecretMounts(p types.Project, s types.ServiceConfig) ([]mount
 		}
 
 		if definedSecret.Driver != "" {
-			return nil, errors.New("Docker Compose does not support secrets.*.driver")
+			return nil, errors.New("Docker Compose does not support secrets.*.driver") //nolint:staticcheck
 		}
 		if definedSecret.TemplateDriver != "" {
-			return nil, errors.New("Docker Compose does not support secrets.*.template_driver")
+			return nil, errors.New("Docker Compose does not support secrets.*.template_driver") //nolint:staticcheck
 		}
 
 		if definedSecret.Environment != "" {
diff --git a/pkg/compose/ls.go b/pkg/compose/ls.go
index 706bcf0d5..34d3ceb0e 100644
--- a/pkg/compose/ls.go
+++ b/pkg/compose/ls.go
@@ -70,7 +70,7 @@ func combinedConfigFiles(containers []container.Summary) (string, error) {
 	for _, c := range containers {
 		files, ok := c.Labels[api.ConfigFilesLabel]
 		if !ok {
-			return "", fmt.Errorf("No label %q set on container %q of compose project", api.ConfigFilesLabel, c.ID)
+			return "", fmt.Errorf("no label %q set on container %q of compose project", api.ConfigFilesLabel, c.ID)
 		}
 
 		for _, f := range strings.Split(files, ",") {
@@ -120,7 +120,7 @@ func groupContainerByLabel(containers []container.Summary, labelName string) (ma
 	for _, c := range containers {
 		label, ok := c.Labels[labelName]
 		if !ok {
-			return nil, nil, fmt.Errorf("No label %q set on container %q of compose project", labelName, c.ID)
+			return nil, nil, fmt.Errorf("no label %q set on container %q of compose project", labelName, c.ID)
 		}
 		labelContainers, ok := containersByLabel[label]
 		if !ok {
diff --git a/pkg/compose/watch.go b/pkg/compose/watch.go
index fd8a61ebd..85b722af3 100644
--- a/pkg/compose/watch.go
+++ b/pkg/compose/watch.go
@@ -754,7 +754,7 @@ func (s *composeService) imageCreatedTime(ctx context.Context, project *types.Pr
 		return time.Now(), err
 	}
 	if len(containers) == 0 {
-		return time.Now(), fmt.Errorf("Could not get created time for service's image")
+		return time.Now(), fmt.Errorf("could not get created time for service's image")
 	}
 
 	img, err := s.apiClient().ImageInspect(ctx, containers[0].ImageID)
diff --git a/pkg/watch/watcher_naive.go b/pkg/watch/watcher_naive.go
index 5cacd2205..1e03b810c 100644
--- a/pkg/watch/watcher_naive.go
+++ b/pkg/watch/watcher_naive.go
@@ -274,7 +274,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, fmt.Errorf("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'")
 		}
@@ -317,7 +317,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: %w", p, err)
+			return nil, fmt.Errorf("finding ancestor of %s: %w", p, err)
 		}
 		result = append(result, newP)
 	}


Test patch:
diff --git a/cmd/compose/top_test.go b/cmd/compose/top_test.go
index 277219902..a2172a061 100644
--- a/cmd/compose/top_test.go
+++ b/cmd/compose/top_test.go
@@ -220,8 +220,8 @@ func TestRunTopCore(t *testing.T) {
 
 		t.Run(tc.name, func(t *testing.T) {
 			header, entries := collectTop([]api.ContainerProcSummary{summary})
-			assert.EqualValues(t, tc.header, header)
-			assert.EqualValues(t, tc.entries, entries)
+			assert.Equal(t, tc.header, header)
+			assert.Equal(t, tc.entries, entries)
 
 			var buf bytes.Buffer
 			err := topPrint(&buf, header, entries)
@@ -233,7 +233,7 @@ func TestRunTopCore(t *testing.T) {
 
 	t.Run("all", func(t *testing.T) {
 		header, entries := collectTop(all)
-		assert.EqualValues(t, topHeader{
+		assert.Equal(t, topHeader{
 			"SERVICE": 0,
 			"#":       1,
 			"UID":     2,
@@ -246,7 +246,7 @@ func TestRunTopCore(t *testing.T) {
 			"GID":     9,
 			"CMD":     10,
 		}, header)
-		assert.EqualValues(t, []topEntries{
+		assert.Equal(t, []topEntries{
 			{
 				"SERVICE": "simple",
 				"#":       "1",
diff --git a/pkg/compose/ls_test.go b/pkg/compose/ls_test.go
index 214f8c40f..847814c6f 100644
--- a/pkg/compose/ls_test.go
+++ b/pkg/compose/ls_test.go
@@ -104,7 +104,7 @@ func TestCombinedConfigFiles(t *testing.T) {
 	}{
 		"project1": {ConfigFiles: "/home/docker-compose.yaml", Error: nil},
 		"project2": {ConfigFiles: "/home/project2-docker-compose.yaml", Error: nil},
-		"project3": {ConfigFiles: "", Error: fmt.Errorf("No label %q set on container %q of compose project", api.ConfigFilesLabel, "service4")},
+		"project3": {ConfigFiles: "", Error: fmt.Errorf("no label %q set on container %q of compose project", api.ConfigFilesLabel, "service4")},
 	}
 
 	for project, containers := range containersByLabel {
diff --git a/pkg/e2e/up_test.go b/pkg/e2e/up_test.go
index 6a927f0aa..65fecca30 100644
--- a/pkg/e2e/up_test.go
+++ b/pkg/e2e/up_test.go
@@ -98,7 +98,7 @@ func TestUpDependenciesNotStopped(t *testing.T) {
 		if exitErr.ExitCode() == -1 {
 			t.Fatalf("`compose up` was killed: %v", err)
 		}
-		require.EqualValues(t, 130, exitErr.ExitCode())
+		require.Equal(t, 130, exitErr.ExitCode())
 	}
 
 	RequireServiceState(t, c, "app", "exited")
diff --git a/pkg/watch/notify_test.go b/pkg/watch/notify_test.go
index 9146c3c49..9b68c4d05 100644
--- a/pkg/watch/notify_test.go
+++ b/pkg/watch/notify_test.go
@@ -149,7 +149,7 @@ func TestGitBranchSwitch(t *testing.T) {
 	f.assertEvents(path)
 
 	// Make sure there are no errors in the out stream
-	assert.Equal(t, "", f.out.String())
+	assert.Empty(t, f.out.String())
 }
 
 func TestWatchesAreRecursive(t *testing.T) {