diff --git a/pkg/compose/publish.go b/pkg/compose/publish.go index e15eaec89d..7c8499edb3 100644 --- a/pkg/compose/publish.go +++ b/pkg/compose/publish.go @@ -36,6 +36,7 @@ import ( "github.com/opencontainers/image-spec/specs-go" v1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/sirupsen/logrus" + "go.yaml.in/yaml/v4" "github.com/docker/compose/v5/internal/oci" "github.com/docker/compose/v5/pkg/api" @@ -183,12 +184,12 @@ func (s *composeService) createLayers(ctx context.Context, project *types.Projec } if options.ResolveImageDigests { - yaml, err := s.generateImageDigestsOverride(ctx, project) + overrideYAML, err := s.generateImageDigestsOverride(ctx, project) if err != nil { return nil, err } - layerDescriptor := oci.DescriptorForComposeFile("image-digests.yaml", yaml) + layerDescriptor := oci.DescriptorForComposeFile("image-digests.yaml", overrideYAML) layers = append(layers, layerDescriptor) } return layers, nil @@ -436,6 +437,39 @@ func (s *composeService) checkForSensitiveData(ctx context.Context, project *typ } allFindings = append(allFindings, findings...) } + for _, service := range project.Services { + if len(service.Environment) == 0 { + continue + } + + environment := map[string]string{} + for key, value := range service.Environment { + if value == nil { + continue + } + environment[key] = *value + } + if len(environment) == 0 { + continue + } + + in, err := yaml.Marshal(map[string]any{ + "services": map[string]any{ + service.Name: map[string]any{ + "environment": environment, + }, + }, + }) + if err != nil { + return nil, err + } + + findings, err := scan.ScanReader(bytes.NewReader(in)) + if err != nil { + return nil, fmt.Errorf("failed to scan environment for service %s: %w", service.Name, err) + } + allFindings = append(allFindings, findings...) + } for _, service := range project.Services { // Check env files for _, envFile := range service.EnvFiles { @@ -477,7 +511,7 @@ func composeFileAsByteReader(ctx context.Context, filePath string, project *type if err != nil { return nil, fmt.Errorf("failed to open compose file %s: %w", filePath, err) } - base, err := loader.LoadWithContext(ctx, types.ConfigDetails{ + model, err := loader.LoadModelWithContext(ctx, types.ConfigDetails{ WorkingDir: project.WorkingDir, Environment: project.Environment, ConfigFiles: []types.ConfigFile{ @@ -497,8 +531,7 @@ func composeFileAsByteReader(ctx context.Context, filePath string, project *type if err != nil { return nil, err } - - in, err := base.MarshalYAML() + in, err := yaml.Marshal(model) if err != nil { return nil, err } diff --git a/pkg/compose/publish_test.go b/pkg/compose/publish_test.go index 8f91f663e6..940c27fa0a 100644 --- a/pkg/compose/publish_test.go +++ b/pkg/compose/publish_test.go @@ -17,6 +17,8 @@ package compose import ( + "os" + "path/filepath" "slices" "testing" @@ -100,3 +102,31 @@ services: return !slices.Contains([]string{".Data", ".Digest", ".Size"}, path.String()) }, cmp.Ignore())) } + +func TestComposeFileAsByteReaderWithShortFormPortsAndInterpolation(t *testing.T) { + dir := t.TempDir() + composePath := filepath.Join(dir, "compose.yaml") + err := os.WriteFile(composePath, []byte(`name: publish-test +services: + whoami: + image: docker.io/traefik/whoami:v1.11 + ports: + - ${DASHBOARD_PORT:-3000}:3000 +`), 0o600) + assert.NilError(t, err) + + project, err := loader.LoadWithContext(t.Context(), types.ConfigDetails{ + WorkingDir: dir, + Environment: types.Mapping{ + "DASHBOARD_PORT": "", + }, + ConfigFiles: []types.ConfigFile{ + {Filename: composePath}, + }, + }) + assert.NilError(t, err) + + reader, err := composeFileAsByteReader(t.Context(), composePath, project) + assert.NilError(t, err) + assert.Assert(t, reader != nil) +} diff --git a/pkg/e2e/publish_test.go b/pkg/e2e/publish_test.go index b5488df601..1d668c46a1 100644 --- a/pkg/e2e/publish_test.go +++ b/pkg/e2e/publish_test.go @@ -18,6 +18,8 @@ package e2e import ( "fmt" + "os" + "path/filepath" "strings" "testing" @@ -60,6 +62,23 @@ or remove sensitive data from your Compose configuration assert.Assert(t, strings.Contains(res.Combined(), "test/test published"), res.Combined()) }) + t.Run("publish success short-form port mapping", func(t *testing.T) { + dir := t.TempDir() + composePath := filepath.Join(dir, "compose-short-port.yml") + err := os.WriteFile(composePath, []byte(`services: + whoami: + image: docker.io/traefik/whoami:v1.11 + ports: + - ${DASHBOARD_PORT:-3000}:3000 +`), 0o600) + assert.NilError(t, err) + + res := c.RunDockerComposeCmd(t, "-f", composePath, + "-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 with extends", func(t *testing.T) { res := c.RunDockerComposeCmd(t, "-f", "./fixtures/publish/compose-with-extends.yml", "-p", projectName, "publish", "test/test", "--dry-run")