diff --git a/cmd/compose/compose.go b/cmd/compose/compose.go index 329572966c6..2d2d5fdd05e 100644 --- a/cmd/compose/compose.go +++ b/cmd/compose/compose.go @@ -21,6 +21,7 @@ import ( "encoding/json" "errors" "fmt" + "io" "os" "os/signal" "path/filepath" @@ -37,6 +38,7 @@ import ( dockercli "github.com/docker/cli/cli" "github.com/docker/cli/cli-plugins/manager" "github.com/docker/cli/cli/command" + "github.com/docker/cli/pkg/kvfile" "github.com/docker/compose/v2/cmd/formatter" "github.com/docker/compose/v2/internal/desktop" "github.com/docker/compose/v2/internal/experimental" @@ -70,6 +72,26 @@ const ( ComposeMenu = "COMPOSE_MENU" ) +// rawEnv load a dot env file using docker/cli key=value parser, without attempt to interpolate or evaluate values +func rawEnv(r io.Reader, filename string, lookup func(key string) (string, bool)) (map[string]string, error) { + lines, err := kvfile.ParseFromReader(r, lookup) + if err != nil { + return nil, fmt.Errorf("failed to parse env_file %s: %w", filename, err) + } + vars := types.Mapping{} + for _, line := range lines { + key, value, _ := strings.Cut(line, "=") + vars[key] = value + } + return vars, nil +} + +func init() { + // compose evaluates env file values for interpolation + // `raw` format allows to load env_file with the same parser used by docker run --env-file + dotenv.RegisterFormat("raw", rawEnv) +} + type Backend interface { api.Service diff --git a/cmd/formatter/colors.go b/cmd/formatter/colors.go index 8b2ed5246dc..a2f57f438e7 100644 --- a/cmd/formatter/colors.go +++ b/cmd/formatter/colors.go @@ -21,7 +21,7 @@ import ( "strconv" "sync" - "github.com/docker/compose/v2/pkg/api" + "github.com/docker/cli/cli/command" ) var names = []string{ @@ -59,7 +59,7 @@ const ( ) // SetANSIMode configure formatter for colored output on ANSI-compliant console -func SetANSIMode(streams api.Streams, ansi string) { +func SetANSIMode(streams command.Streams, ansi string) { if !useAnsi(streams, ansi) { nextColor = func() colorFunc { return monochrome @@ -68,7 +68,7 @@ func SetANSIMode(streams api.Streams, ansi string) { } } -func useAnsi(streams api.Streams, ansi string) bool { +func useAnsi(streams command.Streams, ansi string) bool { switch ansi { case Always: return true diff --git a/go.mod b/go.mod index 71b76016fbb..4a944d89302 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/davecgh/go-spew v1.1.1 github.com/distribution/reference v0.6.0 github.com/docker/buildx v0.17.1 - github.com/docker/cli v27.3.1+incompatible + github.com/docker/cli v27.3.2-0.20241008150905-cb3048fbebb1+incompatible github.com/docker/cli-docs-tool v0.8.0 github.com/docker/docker v27.3.1+incompatible github.com/docker/go-connections v0.5.0 diff --git a/go.sum b/go.sum index 7e98faf3917..ca163141a4e 100644 --- a/go.sum +++ b/go.sum @@ -128,8 +128,8 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/buildx v0.17.1 h1:9ob2jGp4+W9PxWw68GsoNFp+eYFc7eUoRL9VljLCSM4= github.com/docker/buildx v0.17.1/go.mod h1:kJOhOhS47LRvrLFRulFiO5SE6VJf54yYMn7DzjgO5W0= -github.com/docker/cli v27.3.1+incompatible h1:qEGdFBF3Xu6SCvCYhc7CzaQTlBmqDuzxPDpigSyeKQQ= -github.com/docker/cli v27.3.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v27.3.2-0.20241008150905-cb3048fbebb1+incompatible h1:fJ3SzYiebfWoas3qOJpmRsNrDL2w1XIpPFywSLFhhfk= +github.com/docker/cli v27.3.2-0.20241008150905-cb3048fbebb1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli-docs-tool v0.8.0 h1:YcDWl7rQJC3lJ7WVZRwSs3bc9nka97QLWfyJQli8yJU= github.com/docker/cli-docs-tool v0.8.0/go.mod h1:8TQQ3E7mOXoYUs811LiPdUnAhXrcVsBIrW21a5pUbdk= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= diff --git a/pkg/e2e/env_file_test.go b/pkg/e2e/env_file_test.go new file mode 100644 index 00000000000..f4982655da0 --- /dev/null +++ b/pkg/e2e/env_file_test.go @@ -0,0 +1,31 @@ +/* + 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" +) + +func TestRawEnvFile(t *testing.T) { + c := NewParallelCLI(t) + + res := c.RunDockerComposeCmd(t, "-f", "./fixtures/dotenv/raw.yaml", "run", "test") + assert.Equal(t, strings.TrimSpace(res.Stdout()), "'{\"key\": \"value\"}'") +} diff --git a/pkg/e2e/fixtures/dotenv/.env.raw b/pkg/e2e/fixtures/dotenv/.env.raw new file mode 100644 index 00000000000..306900800fc --- /dev/null +++ b/pkg/e2e/fixtures/dotenv/.env.raw @@ -0,0 +1 @@ +TEST_VAR='{"key": "value"}' \ No newline at end of file diff --git a/pkg/e2e/fixtures/dotenv/raw.yaml b/pkg/e2e/fixtures/dotenv/raw.yaml new file mode 100644 index 00000000000..a65664273d3 --- /dev/null +++ b/pkg/e2e/fixtures/dotenv/raw.yaml @@ -0,0 +1,7 @@ +services: + test: + image: alpine + command: sh -c "echo $$TEST_VAR" + env_file: + - path: .env.raw + format: raw # parse without interpolation \ No newline at end of file