Browse Source

remove publish limitation on bind mount
list all bind mounts and ask user validation before publishing

Signed-off-by: Guillaume Lours <[email protected]>

Guillaume Lours 5 months ago
parent
commit
ad750d6143
2 changed files with 62 additions and 21 deletions
  1. 29 6
      pkg/compose/publish.go
  2. 33 15
      pkg/e2e/publish_test.go

+ 29 - 6
pkg/compose/publish.go

@@ -236,8 +236,20 @@ func (s *composeService) preChecks(project *types.Project, options api.PublishOp
 	if ok, err := s.checkOnlyBuildSection(project); !ok || err != nil {
 		return false, err
 	}
-	if ok, err := s.checkForBindMount(project); !ok || err != nil {
-		return false, err
+	bindMounts := s.checkForBindMount(project)
+	if len(bindMounts) > 0 {
+		fmt.Println("you are about to publish bind mounts declaration within your OCI artifact.\n" +
+			"only the bind mount declarations will be added to the OCI artifact\n" +
+			"please double check that you are not mounting potential sensitive directories or data")
+		for key, val := range bindMounts {
+			_, _ = fmt.Fprintln(s.dockerCli.Out(), key)
+			for _, v := range val {
+				_, _ = fmt.Fprintf(s.dockerCli.Out(), "%s\n", v.String())
+			}
+		}
+		if ok, err := acceptPublishBindMountDeclarations(s.dockerCli); err != nil || !ok {
+			return false, err
+		}
 	}
 	if options.AssumeYes {
 		return true, nil
@@ -325,6 +337,12 @@ func acceptPublishSensitiveData(cli command.Cli) (bool, error) {
 	return confirm, err
 }
 
+func acceptPublishBindMountDeclarations(cli command.Cli) (bool, error) {
+	msg := "Are you ok to publish these bind mount declarations? [y/N]: "
+	confirm, err := prompt.NewPrompt(cli.In(), cli.Out()).Confirm(msg, false)
+	return confirm, err
+}
+
 func envFileLayers(project *types.Project) []ocipush.Pushable {
 	var layers []ocipush.Pushable
 	for _, service := range project.Services {
@@ -361,15 +379,20 @@ func (s *composeService) checkOnlyBuildSection(project *types.Project) (bool, er
 	return true, nil
 }
 
-func (s *composeService) checkForBindMount(project *types.Project) (bool, error) {
-	for name, config := range project.Services {
+func (s *composeService) checkForBindMount(project *types.Project) map[string][]types.ServiceVolumeConfig {
+	allFindings := map[string][]types.ServiceVolumeConfig{}
+	for serviceName, config := range project.Services {
+		bindMounts := []types.ServiceVolumeConfig{}
 		for _, volume := range config.Volumes {
 			if volume.Type == types.VolumeTypeBind {
-				return false, fmt.Errorf("cannot publish compose file: service %q relies on bind-mount. You should use volumes", name)
+				bindMounts = append(bindMounts, volume)
 			}
 		}
+		if len(bindMounts) > 0 {
+			allFindings[serviceName] = bindMounts
+		}
 	}
-	return true, nil
+	return allFindings
 }
 
 func (s *composeService) checkForSensitiveData(project *types.Project) ([]secrets.DetectedSecret, error) {

+ 33 - 15
pkg/e2e/publish_test.go

@@ -30,14 +30,14 @@ func TestPublishChecks(t *testing.T) {
 
 	t.Run("publish error environment", func(t *testing.T) {
 		res := c.RunDockerComposeCmdNoCheck(t, "-f", "./fixtures/publish/compose-environment.yml",
-			"-p", projectName, "alpha", "publish", "test/test")
+			"-p", projectName, "publish", "test/test")
 		res.Assert(t, icmd.Expected{ExitCode: 1, Err: `service "serviceA" has environment variable(s) declared.
 To avoid leaking sensitive data,`})
 	})
 
 	t.Run("publish error env_file", func(t *testing.T) {
 		res := c.RunDockerComposeCmdNoCheck(t, "-f", "./fixtures/publish/compose-env-file.yml",
-			"-p", projectName, "alpha", "publish", "test/test")
+			"-p", projectName, "publish", "test/test")
 		res.Assert(t, icmd.Expected{ExitCode: 1, Err: `service "serviceA" has env_file declared.
 service "serviceA" has environment variable(s) declared.
 To avoid leaking sensitive data,`})
@@ -45,7 +45,7 @@ To avoid leaking sensitive data,`})
 
 	t.Run("publish multiple errors env_file and environment", func(t *testing.T) {
 		res := c.RunDockerComposeCmdNoCheck(t, "-f", "./fixtures/publish/compose-multi-env-config.yml",
-			"-p", projectName, "alpha", "publish", "test/test")
+			"-p", projectName, "publish", "test/test")
 		// we don't in which order the services will be loaded, so we can't predict the order of the error messages
 		assert.Assert(t, strings.Contains(res.Combined(), `service "serviceB" has env_file declared.`), res.Combined())
 		assert.Assert(t, strings.Contains(res.Combined(), `service "serviceB" has environment variable(s) declared.`), res.Combined())
@@ -57,21 +57,21 @@ or remove sensitive data from your Compose configuration
 
 	t.Run("publish success environment", func(t *testing.T) {
 		res := c.RunDockerComposeCmd(t, "-f", "./fixtures/publish/compose-environment.yml",
-			"-p", projectName, "alpha", "publish", "test/test", "--with-env", "-y", "--dry-run")
+			"-p", projectName, "publish", "test/test", "--with-env", "-y", "--dry-run")
 		assert.Assert(t, strings.Contains(res.Combined(), "test/test publishing"), res.Combined())
 		assert.Assert(t, strings.Contains(res.Combined(), "test/test published"), res.Combined())
 	})
 
 	t.Run("publish success env_file", func(t *testing.T) {
 		res := c.RunDockerComposeCmd(t, "-f", "./fixtures/publish/compose-env-file.yml",
-			"-p", projectName, "alpha", "publish", "test/test", "--with-env", "-y", "--dry-run")
+			"-p", projectName, "publish", "test/test", "--with-env", "-y", "--dry-run")
 		assert.Assert(t, strings.Contains(res.Combined(), "test/test publishing"), res.Combined())
 		assert.Assert(t, strings.Contains(res.Combined(), "test/test published"), res.Combined())
 	})
 
 	t.Run("publish approve validation message", func(t *testing.T) {
 		cmd := c.NewDockerComposeCmd(t, "-f", "./fixtures/publish/compose-env-file.yml",
-			"-p", projectName, "alpha", "publish", "test/test", "--with-env", "--dry-run")
+			"-p", projectName, "publish", "test/test", "--with-env", "--dry-run")
 		cmd.Stdin = strings.NewReader("y\n")
 		res := icmd.RunCmd(cmd)
 		res.Assert(t, icmd.Expected{ExitCode: 0})
@@ -82,7 +82,7 @@ or remove sensitive data from your Compose configuration
 
 	t.Run("publish refuse validation message", func(t *testing.T) {
 		cmd := c.NewDockerComposeCmd(t, "-f", "./fixtures/publish/compose-env-file.yml",
-			"-p", projectName, "alpha", "publish", "test/test", "--with-env", "--dry-run")
+			"-p", projectName, "publish", "test/test", "--with-env", "--dry-run")
 		cmd.Stdin = strings.NewReader("n\n")
 		res := icmd.RunCmd(cmd)
 		res.Assert(t, icmd.Expected{ExitCode: 0})
@@ -93,13 +93,13 @@ or remove sensitive data from your Compose configuration
 
 	t.Run("publish with extends", func(t *testing.T) {
 		res := c.RunDockerComposeCmd(t, "-f", "./fixtures/publish/compose-with-extends.yml",
-			"-p", projectName, "alpha", "publish", "test/test", "--dry-run")
+			"-p", projectName, "publish", "test/test", "--dry-run")
 		assert.Assert(t, strings.Contains(res.Combined(), "test/test published"), res.Combined())
 	})
 
 	t.Run("publish list env variables", func(t *testing.T) {
 		cmd := c.NewDockerComposeCmd(t, "-f", "./fixtures/publish/compose-multi-env-config.yml",
-			"-p", projectName, "alpha", "publish", "test/test", "--with-env", "--dry-run")
+			"-p", projectName, "publish", "test/test", "--with-env", "--dry-run")
 		cmd.Stdin = strings.NewReader("n\n")
 		res := icmd.RunCmd(cmd)
 		res.Assert(t, icmd.Expected{ExitCode: 0})
@@ -115,14 +115,32 @@ FOO=bar`), res.Combined())
 	})
 
 	t.Run("refuse to publish with bind mount", func(t *testing.T) {
-		res := c.RunDockerComposeCmdNoCheck(t, "-f", "./fixtures/publish/compose-bind-mount.yml",
-			"-p", projectName, "alpha", "publish", "test/test", "--dry-run")
-		res.Assert(t, icmd.Expected{ExitCode: 1, Err: `cannot publish compose file: service "serviceA" relies on bind-mount. You should use volumes`})
+		cmd := c.NewDockerComposeCmd(t, "-f", "./fixtures/publish/compose-bind-mount.yml",
+			"-p", projectName, "publish", "test/test", "--dry-run")
+		cmd.Stdin = strings.NewReader("n\n")
+		res := icmd.RunCmd(cmd)
+		res.Assert(t, icmd.Expected{ExitCode: 0})
+		assert.Assert(t, strings.Contains(res.Combined(), "you are about to publish bind mounts declaration within your OCI artifact."), res.Combined())
+		assert.Assert(t, strings.Contains(res.Combined(), "e2e/fixtures/publish:/user-data"), res.Combined())
+		assert.Assert(t, strings.Contains(res.Combined(), "Are you ok to publish these bind mount declarations? [y/N]:"), res.Combined())
+		assert.Assert(t, !strings.Contains(res.Combined(), "serviceA published"), res.Combined())
+	})
+
+	t.Run("publish with bind mount", func(t *testing.T) {
+		cmd := c.NewDockerComposeCmd(t, "-f", "./fixtures/publish/compose-bind-mount.yml",
+			"-p", projectName, "publish", "test/test", "--dry-run")
+		cmd.Stdin = strings.NewReader("y\n")
+		res := icmd.RunCmd(cmd)
+		res.Assert(t, icmd.Expected{ExitCode: 0})
+		assert.Assert(t, strings.Contains(res.Combined(), "you are about to publish bind mounts declaration within your OCI artifact."), res.Combined())
+		assert.Assert(t, strings.Contains(res.Combined(), "Are you ok to publish these bind mount declarations? [y/N]:"), res.Combined())
+		assert.Assert(t, strings.Contains(res.Combined(), "e2e/fixtures/publish:/user-data"), res.Combined())
+		assert.Assert(t, strings.Contains(res.Combined(), "test/test published"), res.Combined())
 	})
 
 	t.Run("refuse to publish with build section only", func(t *testing.T) {
 		res := c.RunDockerComposeCmdNoCheck(t, "-f", "./fixtures/publish/compose-build-only.yml",
-			"-p", projectName, "alpha", "publish", "test/test", "--with-env", "-y", "--dry-run")
+			"-p", projectName, "publish", "test/test", "--with-env", "-y", "--dry-run")
 		res.Assert(t, icmd.Expected{ExitCode: 1})
 		assert.Assert(t, strings.Contains(res.Combined(), "your Compose stack cannot be published as it only contains a build section for service(s):"), res.Combined())
 		assert.Assert(t, strings.Contains(res.Combined(), "serviceA"), res.Combined())
@@ -131,13 +149,13 @@ FOO=bar`), res.Combined())
 
 	t.Run("refuse to publish with local include", func(t *testing.T) {
 		res := c.RunDockerComposeCmdNoCheck(t, "-f", "./fixtures/publish/compose-local-include.yml",
-			"-p", projectName, "alpha", "publish", "test/test", "--dry-run")
+			"-p", projectName, "publish", "test/test", "--dry-run")
 		res.Assert(t, icmd.Expected{ExitCode: 1, Err: "cannot publish compose file with local includes"})
 	})
 
 	t.Run("detect sensitive data", func(t *testing.T) {
 		cmd := c.NewDockerComposeCmd(t, "-f", "./fixtures/publish/compose-sensitive.yml",
-			"-p", projectName, "alpha", "publish", "test/test", "--with-env", "--dry-run")
+			"-p", projectName, "publish", "test/test", "--with-env", "--dry-run")
 		cmd.Stdin = strings.NewReader("n\n")
 		res := icmd.RunCmd(cmd)
 		res.Assert(t, icmd.Expected{ExitCode: 0})