Repository: docker/compose-cli
Pull request #1532 (2021-04-13 11:55:52)

Fix scan message with quiet flag
**What I did**
* Do not print the scan message when the quiet flag is specified
* Print the scan message to stderr instead of stdout

**Related issue**
Fixes: #1517 

/test-windows


Patch:
diff --git a/api/compose/api.go b/api/compose/api.go
index 48f83c94..3014401f 100644
--- a/api/compose/api.go
+++ b/api/compose/api.go
@@ -86,6 +86,8 @@ type BuildOptions struct {
 	Args types.Mapping
 	// NoCache disables cache use
 	NoCache bool
+	// Quiet make the build process not output to the console
+	Quiet bool
 }
 
 // CreateOptions group options of the Create API
diff --git a/cli/cmd/compose/build.go b/cli/cmd/compose/build.go
index 7c250b5f..4f5e83e7 100644
--- a/cli/cmd/compose/build.go
+++ b/cli/cmd/compose/build.go
@@ -97,6 +97,7 @@ func runBuild(ctx context.Context, opts buildOptions, services []string) error {
 			Progress: opts.progress,
 			Args:     types.NewMapping(opts.args),
 			NoCache:  opts.noCache,
+			Quiet:    opts.quiet,
 		})
 	})
 	return err
diff --git a/cli/metrics/metrics.go b/cli/metrics/metrics.go
index e73bc6f8..1811e0c3 100644
--- a/cli/metrics/metrics.go
+++ b/cli/metrics/metrics.go
@@ -57,6 +57,17 @@ func isCommandFlag(word string) bool {
 	return utils.StringContains(commandFlags, word)
 }
 
+// HasQuietFlag returns true if one of the arguments is `--quiet` or `-q`
+func HasQuietFlag(args []string) bool {
+	for _, a := range args {
+		switch a {
+		case "--quiet", "-q":
+			return true
+		}
+	}
+	return false
+}
+
 // GetCommand get the invoked command
 func GetCommand(args []string) string {
 	result := ""
diff --git a/cli/mobycli/exec.go b/cli/mobycli/exec.go
index d710fdb2..9fddaaca 100644
--- a/cli/mobycli/exec.go
+++ b/cli/mobycli/exec.go
@@ -80,7 +80,7 @@ func Exec(root *cobra.Command) {
 		os.Exit(1)
 	}
 	command := metrics.GetCommand(os.Args[1:])
-	if command == "build" {
+	if command == "build" && !metrics.HasQuietFlag(os.Args[1:]) {
 		utils.DisplayScanSuggestMsg()
 	}
 	metrics.Track(store.DefaultContextType, os.Args[1:], metrics.SuccessStatus)
diff --git a/local/compose/build.go b/local/compose/build.go
index 8b93aa9c..35a113f4 100644
--- a/local/compose/build.go
+++ b/local/compose/build.go
@@ -75,7 +75,7 @@ func (s *composeService) Build(ctx context.Context, project *types.Project, opti
 
 	err := s.build(ctx, project, opts, Containers{}, options.Progress)
 	if err == nil {
-		if len(imagesToBuild) > 0 {
+		if len(imagesToBuild) > 0 && !options.Quiet {
 			utils.DisplayScanSuggestMsg()
 		}
 	}
diff --git a/local/compose/create.go b/local/compose/create.go
index 7cf4b501..69c69369 100644
--- a/local/compose/create.go
+++ b/local/compose/create.go
@@ -774,50 +774,18 @@ func buildMount(project types.Project, volume types.ServiceVolumeConfig) (mount.
 		}
 	}
 
-	bind, vol, tmpfs := buildMountOptions(volume)
-
 	return mount.Mount{
 		Type:          mount.Type(volume.Type),
 		Source:        source,
 		Target:        volume.Target,
 		ReadOnly:      volume.ReadOnly,
 		Consistency:   mount.Consistency(volume.Consistency),
-		BindOptions:   bind,
-		VolumeOptions: vol,
-		TmpfsOptions:  tmpfs,
+		BindOptions:   buildBindOption(volume.Bind),
+		VolumeOptions: buildVolumeOptions(volume.Volume),
+		TmpfsOptions:  buildTmpfsOptions(volume.Tmpfs),
 	}, nil
 }
 
-func buildMountOptions(volume types.ServiceVolumeConfig) (*mount.BindOptions, *mount.VolumeOptions, *mount.TmpfsOptions) {
-	switch volume.Type {
-	case "bind":
-		if volume.Volume != nil {
-			logrus.Warnf("mount of type `bind` should not define `volume` option")
-		}
-		if volume.Tmpfs != nil {
-			logrus.Warnf("mount of type `tmpfs` should not define `tmpfs` option")
-		}
-		return buildBindOption(volume.Bind), nil, nil
-	case "volume":
-		if volume.Bind != nil {
-			logrus.Warnf("mount of type `volume` should not define `bind` option")
-		}
-		if volume.Tmpfs != nil {
-			logrus.Warnf("mount of type `volume` should not define `tmpfs` option")
-		}
-		return nil, buildVolumeOptions(volume.Volume), nil
-	case "tmpfs":
-		if volume.Bind != nil {
-			logrus.Warnf("mount of type `tmpfs` should not define `bind` option")
-		}
-		if volume.Tmpfs != nil {
-			logrus.Warnf("mount of type `tmpfs` should not define `volumeZ` option")
-		}
-		return nil, nil, buildTmpfsOptions(volume.Tmpfs)
-	}
-	return nil, nil, nil
-}
-
 func buildBindOption(bind *types.ServiceVolumeBind) *mount.BindOptions {
 	if bind == nil {
 		return nil
diff --git a/utils/scan_suggest.go b/utils/scan_suggest.go
index c8eb7bca..943a9b7b 100644
--- a/utils/scan_suggest.go
+++ b/utils/scan_suggest.go
@@ -39,8 +39,7 @@ func DisplayScanSuggestMsg() {
 	if scanAlreadyInvoked() {
 		return
 	}
-	fmt.Println()
-	fmt.Println("Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them")
+	fmt.Fprintf(os.Stderr, "\nUse 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them\n")
 }
 
 func scanAlreadyInvoked() bool {


Test patch:
diff --git a/cli/metrics/metrics_test.go b/cli/metrics/metrics_test.go
index 6223d8fe..8711a07b 100644
--- a/cli/metrics/metrics_test.go
+++ b/cli/metrics/metrics_test.go
@@ -22,6 +22,36 @@ import (
 	"gotest.tools/v3/assert"
 )
 
+func TestHasQuietFlag(t *testing.T) {
+	cases := []struct {
+		name     string
+		args     []string
+		expected bool
+	}{
+		{
+			name:     "long flag",
+			args:     []string{"build", "-t", "tag", "--quiet", "."},
+			expected: true,
+		},
+		{
+			name:     "short flag",
+			args:     []string{"build", "-t", "tag", "-q", "."},
+			expected: true,
+		},
+		{
+			name:     "no flag",
+			args:     []string{"build", "-t", "tag", "."},
+			expected: false,
+		},
+	}
+	for _, c := range cases {
+		t.Run(c.name, func(t *testing.T) {
+			result := HasQuietFlag(c.args)
+			assert.Equal(t, c.expected, result)
+		})
+	}
+}
+
 func TestGetCommand(t *testing.T) {
 	testCases := []struct {
 		name     string
diff --git a/local/e2e/compose/scan_message_test.go b/local/e2e/compose/scan_message_test.go
index f1b252ba..be87c68b 100644
--- a/local/e2e/compose/scan_message_test.go
+++ b/local/e2e/compose/scan_message_test.go
@@ -42,19 +42,43 @@ func TestDisplayScanMessageAfterBuild(t *testing.T) {
 
 	t.Run("display when docker build", func(t *testing.T) {
 		res := c.RunDockerCmd("build", "-t", "test-image-scan-msg", "./fixtures/simple-build-test/nginx-build")
-		res.Assert(t, icmd.Expected{Out: "Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them"})
+		defer c.RunDockerCmd("rmi", "-f", "test-image-scan-msg")
+		res.Assert(t, icmd.Expected{Err: "Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them"})
 	})
 
-	t.Run("do not display if envvar  DOCKER_SCAN_SUGGEST=false", func(t *testing.T) {
+	t.Run("do not display with docker build and quiet flag", func(t *testing.T) {
+		res := c.RunDockerCmd("build", "-t", "test-image-scan-msg-quiet", "--quiet", "./fixtures/simple-build-test/nginx-build")
+		defer c.RunDockerCmd("rmi", "-f", "test-image-scan-msg-quiet")
+		assert.Assert(t, !strings.Contains(res.Combined(), "docker scan"))
+
+		res = c.RunDockerCmd("build", "-t", "test-image-scan-msg-q", "-q", "./fixtures/simple-build-test/nginx-build")
+		defer c.RunDockerCmd("rmi", "-f", "test-image-scan-msg-q")
+		assert.Assert(t, !strings.Contains(res.Combined(), "docker scan"))
+	})
+
+	t.Run("do not display if envvar DOCKER_SCAN_SUGGEST=false", func(t *testing.T) {
 		cmd := c.NewDockerCmd("build", "-t", "test-image-scan-msg", "./fixtures/build-test/nginx-build")
+		defer c.RunDockerCmd("rmi", "-f", "test-image-scan-msg")
 		cmd.Env = append(cmd.Env, "DOCKER_SCAN_SUGGEST=false")
 		res := icmd.StartCmd(cmd)
 		assert.Assert(t, !strings.Contains(res.Combined(), "docker scan"), res.Combined())
 	})
 
 	t.Run("display on compose build", func(t *testing.T) {
-		res := c.RunDockerCmd("compose", "-f", "./fixtures/simple-build-test/compose.yml", "-p", "scan-msg-test", "build")
-		res.Assert(t, icmd.Expected{Out: "Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them"})
+		res := c.RunDockerCmd("compose", "-f", "./fixtures/simple-build-test/compose.yml", "-p", "scan-msg-test-compose-build", "build")
+		defer c.RunDockerCmd("rmi", "-f", "scan-msg-test-compose-build_nginx")
+		res.Assert(t, icmd.Expected{Err: "Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them"})
+	})
+
+	t.Run("do not display on compose build with quiet flag", func(t *testing.T) {
+		res := c.RunDockerCmd("compose", "-f", "./fixtures/simple-build-test/compose.yml", "-p", "scan-msg-test-quiet", "build", "--quiet")
+		assert.Assert(t, !strings.Contains(res.Combined(), "docker scan"), res.Combined())
+		res = c.RunDockerCmd("rmi", "-f", "scan-msg-test-quiet_nginx")
+		assert.Assert(t, !strings.Contains(res.Combined(), "No such image"))
+
+		res = c.RunDockerCmd("compose", "-f", "./fixtures/simple-build-test/compose.yml", "-p", "scan-msg-test-q", "build", "-q")
+		defer c.RunDockerCmd("rmi", "-f", "scan-msg-test-q_nginx")
+		assert.Assert(t, !strings.Contains(res.Combined(), "docker scan"), res.Combined())
 	})
 
 	_ = c.RunDockerOrExitError("rmi", "scan-msg-test_nginx")
@@ -62,12 +86,12 @@ func TestDisplayScanMessageAfterBuild(t *testing.T) {
 	t.Run("display on compose up if image is built", func(t *testing.T) {
 		res := c.RunDockerCmd("compose", "-f", "./fixtures/simple-build-test/compose.yml", "-p", "scan-msg-test", "up", "-d")
 		defer c.RunDockerCmd("compose", "-f", "./fixtures/simple-build-test/compose.yml", "-p", "scan-msg-test", "down")
-		res.Assert(t, icmd.Expected{Out: "Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them"})
+		res.Assert(t, icmd.Expected{Err: "Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them"})
 	})
 
 	t.Run("do not display on compose up if no image built", func(t *testing.T) { // re-run the same Compose aproject
 		res := c.RunDockerCmd("compose", "-f", "./fixtures/simple-build-test/compose.yml", "-p", "scan-msg-test", "up", "-d")
-		defer c.RunDockerCmd("compose", "-f", "./fixtures/simple-build-test/compose.yml", "-p", "scan-msg-test", "down")
+		defer c.RunDockerCmd("compose", "-f", "./fixtures/simple-build-test/compose.yml", "-p", "scan-msg-test", "down", "--rmi", "all")
 		assert.Assert(t, !strings.Contains(res.Combined(), "docker scan"), res.Combined())
 	})