diff --git a/cmd/compose/publish.go b/cmd/compose/publish.go index 6740986a20..a3bd334e7b 100644 --- a/cmd/compose/publish.go +++ b/cmd/compose/publish.go @@ -29,6 +29,7 @@ type publishOptions struct { *ProjectOptions resolveImageDigests bool ociVersion string + withEnvironment bool } func publishCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command { @@ -46,6 +47,8 @@ func publishCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Servic flags := cmd.Flags() flags.BoolVar(&opts.resolveImageDigests, "resolve-image-digests", false, "Pin image tags to digests") flags.StringVar(&opts.ociVersion, "oci-version", "", "OCI Image/Artifact specification version (automatically determined by default)") + flags.BoolVar(&opts.withEnvironment, "with-env", false, "Include environment variables in the published Artifact") + return cmd } @@ -58,5 +61,6 @@ func runPublish(ctx context.Context, dockerCli command.Cli, backend api.Service, return backend.Publish(ctx, project, repository, api.PublishOptions{ ResolveImageDigests: opts.resolveImageDigests, OCIVersion: api.OCIVersion(opts.ociVersion), + WithEnvironment: opts.withEnvironment, }) } diff --git a/docs/reference/compose_alpha_publish.md b/docs/reference/compose_alpha_publish.md index 7fe79480ba..a02b17809b 100644 --- a/docs/reference/compose_alpha_publish.md +++ b/docs/reference/compose_alpha_publish.md @@ -10,6 +10,7 @@ Publish compose application | `--dry-run` | `bool` | | Execute command in dry run mode | | `--oci-version` | `string` | | OCI Image/Artifact specification version (automatically determined by default) | | `--resolve-image-digests` | `bool` | | Pin image tags to digests | +| `--with-env` | `bool` | | Include environment variables in the published Artifact | diff --git a/docs/reference/docker_compose_alpha_publish.yaml b/docs/reference/docker_compose_alpha_publish.yaml index 7a2da5ca92..63a31307b9 100644 --- a/docs/reference/docker_compose_alpha_publish.yaml +++ b/docs/reference/docker_compose_alpha_publish.yaml @@ -25,6 +25,16 @@ options: experimentalcli: false kubernetes: false swarm: false + - option: with-env + value_type: bool + default_value: "false" + description: Include environment variables in the published Artifact + deprecated: false + hidden: false + experimental: false + experimentalcli: false + kubernetes: false + swarm: false inherited_options: - option: dry-run value_type: bool diff --git a/pkg/api/api.go b/pkg/api/api.go index 8d2d128032..06aea8064f 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -420,6 +420,7 @@ const ( // PublishOptions group options of the Publish API type PublishOptions struct { ResolveImageDigests bool + WithEnvironment bool OCIVersion OCIVersion } diff --git a/pkg/compose/publish.go b/pkg/compose/publish.go index d1c4cd1038..fddafe3c4f 100644 --- a/pkg/compose/publish.go +++ b/pkg/compose/publish.go @@ -18,6 +18,7 @@ package compose import ( "context" + "fmt" "os" "github.com/compose-spec/compose-go/v2/types" @@ -35,7 +36,11 @@ func (s *composeService) Publish(ctx context.Context, project *types.Project, re } func (s *composeService) publish(ctx context.Context, project *types.Project, repository string, options api.PublishOptions) error { - err := s.Push(ctx, project, api.PushOptions{IgnoreFailures: true, ImageMandatory: true}) + err := preChecks(project, options) + if err != nil { + return err + } + err = s.Push(ctx, project, api.PushOptions{IgnoreFailures: true, ImageMandatory: true}) if err != nil { return err } @@ -120,3 +125,22 @@ func (s *composeService) generateImageDigestsOverride(ctx context.Context, proje } return override.MarshalYAML() } + +func preChecks(project *types.Project, options api.PublishOptions) error { + if !options.WithEnvironment { + for _, service := range project.Services { + if len(service.EnvFiles) > 0 { + return fmt.Errorf("service %q has env_file declared. To avoid leaking sensitive data, "+ + "you must either explicitly allow the sending of environment variables by using the --with-env flag,"+ + " or remove sensitive data from your Compose configuration", service.Name) + } + if len(service.Environment) > 0 { + return fmt.Errorf("service %q has environment variable(s) declared. To avoid leaking sensitive data, "+ + "you must either explicitly allow the sending of environment variables by using the --with-env flag,"+ + " or remove sensitive data from your Compose configuration", service.Name) + } + } + } + + return nil +} diff --git a/pkg/e2e/fixtures/publish/compose-env-file.yml b/pkg/e2e/fixtures/publish/compose-env-file.yml new file mode 100644 index 0000000000..b438c71dab --- /dev/null +++ b/pkg/e2e/fixtures/publish/compose-env-file.yml @@ -0,0 +1,7 @@ +services: + serviceA: + image: "alpine:3.12" + env_file: + - publish.env + serviceB: + image: "alpine:3.12" diff --git a/pkg/e2e/fixtures/publish/compose-environment.yml b/pkg/e2e/fixtures/publish/compose-environment.yml new file mode 100644 index 0000000000..27e3a4b31b --- /dev/null +++ b/pkg/e2e/fixtures/publish/compose-environment.yml @@ -0,0 +1,7 @@ +services: + serviceA: + image: "alpine:3.12" + environment: + - "FOO=bar" + serviceB: + image: "alpine:3.12" diff --git a/pkg/e2e/fixtures/publish/publish.env b/pkg/e2e/fixtures/publish/publish.env new file mode 100644 index 0000000000..c075a74be9 --- /dev/null +++ b/pkg/e2e/fixtures/publish/publish.env @@ -0,0 +1 @@ +FOO=bar diff --git a/pkg/e2e/publish_test.go b/pkg/e2e/publish_test.go new file mode 100644 index 0000000000..2f7ad239c8 --- /dev/null +++ b/pkg/e2e/publish_test.go @@ -0,0 +1,56 @@ +/* + Copyright 2020 Docker Compose CLI authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package e2e + +import ( + "strings" + "testing" + + "gotest.tools/v3/assert" + "gotest.tools/v3/icmd" +) + +func TestPublishChecks(t *testing.T) { + c := NewParallelCLI(t) + const projectName = "compose-e2e-explicit-profiles" + + 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") + 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") + res.Assert(t, icmd.Expected{ExitCode: 1, Err: `service "serviceA" has env_file declared. To avoid leaking sensitive data,`}) + }) + + 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", "--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", "--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()) + }) +}