From 84a404912d9ccfac77654c53e514dd48ad8ed936 Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Mon, 16 Jun 2025 14:27:21 +0200 Subject: [PATCH 1/8] resolve service environment "just in time" Signed-off-by: Nicolas De Loof --- cmd/compose/compose.go | 5 -- go.mod | 2 + go.sum | 4 +- pkg/compose/convergence.go | 21 +++++++++ pkg/compose/plugins.go | 13 ++---- pkg/e2e/fixtures/interpolation/.env | 1 + pkg/e2e/fixtures/interpolation/compose.yaml | 9 ++++ pkg/e2e/fixtures/interpolation/env_file.env | 3 ++ pkg/e2e/interpolation_test.go | 51 +++++++++++++++++++++ 9 files changed, 94 insertions(+), 15 deletions(-) create mode 100644 pkg/e2e/fixtures/interpolation/.env create mode 100644 pkg/e2e/fixtures/interpolation/compose.yaml create mode 100644 pkg/e2e/fixtures/interpolation/env_file.env create mode 100644 pkg/e2e/interpolation_test.go diff --git a/cmd/compose/compose.go b/cmd/compose/compose.go index 10b53e53f85..217d9bc4af6 100644 --- a/cmd/compose/compose.go +++ b/cmd/compose/compose.go @@ -180,11 +180,6 @@ func (o *ProjectOptions) WithServices(dockerCli command.Cli, fn ProjectServicesF ctx = context.WithValue(ctx, tracing.MetricsKey{}, metrics) - project, err = project.WithServicesEnvironmentResolved(true) - if err != nil { - return err - } - return fn(ctx, project, args) }) } diff --git a/go.mod b/go.mod index 536b528c81f..c8d9abf353b 100644 --- a/go.mod +++ b/go.mod @@ -212,3 +212,5 @@ exclude ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 ) + +replace github.com/compose-spec/compose-go/v2 => github.com/ndeloof/compose-go/v2 v2.0.1-0.20250618092235-a694d81af63d diff --git a/go.sum b/go.sum index 9b2461f9dca..c95c9b8693b 100644 --- a/go.sum +++ b/go.sum @@ -80,8 +80,6 @@ github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004 h1:lkAMpLVBDaj17e github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= -github.com/compose-spec/compose-go/v2 v2.6.5-0.20250605125952-a0d3b94b8dc9 h1:VULSSHxkc7u/U349sHp1RbnWAcnf7JD0HY3rGeZrMaM= -github.com/compose-spec/compose-go/v2 v2.6.5-0.20250605125952-a0d3b94b8dc9/go.mod h1:TmjkIB9W73fwVxkYY+u2uhMbMUakjiif79DlYgXsyvU= github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo= github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins= github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro= @@ -361,6 +359,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/ndeloof/compose-go/v2 v2.0.1-0.20250618092235-a694d81af63d h1:fLSNfPyqfQC2uWAL9KxGmAwQm1DZaf6FoshlcJpLIcs= +github.com/ndeloof/compose-go/v2 v2.0.1-0.20250618092235-a694d81af63d/go.mod h1:TmjkIB9W73fwVxkYY+u2uhMbMUakjiif79DlYgXsyvU= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU= diff --git a/pkg/compose/convergence.go b/pkg/compose/convergence.go index 5f10e398621..fa005c8bb8e 100644 --- a/pkg/compose/convergence.go +++ b/pkg/compose/convergence.go @@ -27,6 +27,7 @@ import ( "sync" "time" + "github.com/compose-spec/compose-go/v2/dotenv" "github.com/compose-spec/compose-go/v2/types" "github.com/containerd/platforms" containerType "github.com/docker/docker/api/types/container" @@ -109,7 +110,27 @@ func (c *convergence) apply(ctx context.Context, project *types.Project, options }) } +// mutation of project.Environment requires a mutex as we create services concurrently +var mux sync.Mutex + func (c *convergence) ensureService(ctx context.Context, project *types.Project, service types.ServiceConfig, recreate string, inherit bool, timeout *time.Duration) error { //nolint:gocyclo + mux.Lock() + for key, val := range service.Environment { + if val != nil { + newVal, err := dotenv.ExpandVariables(*val, nil, project.Environment.Resolve) + if err != nil { + return err + } + service.Environment[key] = &newVal + } + } + + err := service.WithEnvironmentResolved(false, project) + mux.Unlock() + if err != nil { + return err + } + if service.Provider != nil { return c.service.runPlugin(ctx, project, service, "up") } diff --git a/pkg/compose/plugins.go b/pkg/compose/plugins.go index 66cfc53fcfd..07ad64dd4ee 100644 --- a/pkg/compose/plugins.go +++ b/pkg/compose/plugins.go @@ -70,15 +70,12 @@ func (s *composeService) runPlugin(ctx context.Context, project *types.Project, return err } - for name, s := range project.Services { - if _, ok := s.DependsOn[service.Name]; ok { - prefix := strings.ToUpper(service.Name) + "_" - for key, val := range variables { - s.Environment[prefix+key] = &val - } - project.Services[name] = s - } + mux.Lock() + prefix := strings.ToUpper(service.Name) + "_" + for key, val := range variables { + project.Environment[prefix+key] = val } + mux.Unlock() return nil } diff --git a/pkg/e2e/fixtures/interpolation/.env b/pkg/e2e/fixtures/interpolation/.env new file mode 100644 index 00000000000..15c36f50ef7 --- /dev/null +++ b/pkg/e2e/fixtures/interpolation/.env @@ -0,0 +1 @@ +FOO=BAR \ No newline at end of file diff --git a/pkg/e2e/fixtures/interpolation/compose.yaml b/pkg/e2e/fixtures/interpolation/compose.yaml new file mode 100644 index 00000000000..b9bcb7ad78c --- /dev/null +++ b/pkg/e2e/fixtures/interpolation/compose.yaml @@ -0,0 +1,9 @@ +services: + test: + image: alpine + command: env + environment: + FOO: ${FOO} + BAR: bar_from_environment + env_file: + - env_file.env \ No newline at end of file diff --git a/pkg/e2e/fixtures/interpolation/env_file.env b/pkg/e2e/fixtures/interpolation/env_file.env new file mode 100644 index 00000000000..e77ff9f2751 --- /dev/null +++ b/pkg/e2e/fixtures/interpolation/env_file.env @@ -0,0 +1,3 @@ +ZOT=${FOO:-ZOT} +QIX=some ${FOO} value +BAR_FROM_ENV_FILE=${BAR} \ No newline at end of file diff --git a/pkg/e2e/interpolation_test.go b/pkg/e2e/interpolation_test.go new file mode 100644 index 00000000000..c5648fe59f4 --- /dev/null +++ b/pkg/e2e/interpolation_test.go @@ -0,0 +1,51 @@ +/* + Copyright 2022 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 ( + "bufio" + "slices" + "strings" + "testing" + + "gotest.tools/v3/assert" +) + +func Test_interpolation(t *testing.T) { + c := NewParallelCLI(t) + const projectName = "interpolation" + t.Cleanup(func() { + c.cleanupWithDown(t, projectName) + }) + + res := c.RunDockerComposeCmdNoCheck(t, "-f", "fixtures/interpolation/compose.yaml", "--project-name", projectName, "up") + var env []string + scanner := bufio.NewScanner(strings.NewReader(res.Stdout())) + for scanner.Scan() { + line := scanner.Text() + if strings.HasPrefix(line, "test-1 | ") { + env = append(env, line[10:]) + } + } + slices.Sort(env) + + assert.Check(t, slices.Contains(env, "FOO=BAR")) + assert.Check(t, slices.Contains(env, "ZOT=BAR")) + assert.Check(t, slices.Contains(env, "QIX=some BAR value")) + assert.Check(t, slices.Contains(env, "BAR_FROM_ENV_FILE=bar_from_environment")) + +} From 96ce1d2be28b8b110ee2769d6e3ba91b63bb80d6 Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Wed, 18 Jun 2025 15:23:27 +0200 Subject: [PATCH 2/8] e2e test to run provider with env variable injection Signed-off-by: Nicolas De Loof --- .github/workflows/ci.yml | 3 ++ Makefile | 6 ++- docs/examples/provider.go | 24 ++++++--- go.mod | 2 +- go.sum | 4 +- pkg/e2e/fixtures/interpolation/.env | 2 +- pkg/e2e/fixtures/interpolation/compose.yaml | 17 ++++-- pkg/e2e/fixtures/interpolation/env_file.env | 3 +- .../interpolation/include/compose.yaml | 3 ++ .../interpolation/include/include.env | 1 + pkg/e2e/interpolation_test.go | 54 +++++++++++++++---- 11 files changed, 94 insertions(+), 25 deletions(-) create mode 100644 pkg/e2e/fixtures/interpolation/include/compose.yaml create mode 100644 pkg/e2e/fixtures/interpolation/include/include.env diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fdf252786a3..10f89dc726b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -190,6 +190,9 @@ jobs: check-latest: true cache: true + - name: Build example provider + run: make example-provider + - name: Build uses: docker/bake-action@v6 with: diff --git a/Makefile b/Makefile index 946947d0e0c..ceae368035c 100644 --- a/Makefile +++ b/Makefile @@ -74,7 +74,8 @@ install: binary install $(or $(DESTDIR),./bin/build)/docker-compose ~/.docker/cli-plugins/docker-compose .PHONY: e2e-compose -e2e-compose: ## Run end to end local tests in plugin mode. Set E2E_TEST=TestName to run a single test +e2e-compose: example-provider + ## Run end to end local tests in plugin mode. Set E2E_TEST=TestName to run a single test go run gotest.tools/gotestsum@latest --format testname --junitfile "/tmp/report/report.xml" -- -v $(TEST_FLAGS) -count=1 ./pkg/e2e .PHONY: e2e-compose-standalone @@ -156,3 +157,6 @@ pre-commit: validate check-dependencies lint build test e2e-compose help: ## Show help @echo Please specify a build target. The choices are: @grep -E '^[0-9a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + +example-provider: + go build -o bin/build/example-provider docs/examples/provider.go \ No newline at end of file diff --git a/docs/examples/provider.go b/docs/examples/provider.go index e500e8d5c51..2959c8bba7b 100644 --- a/docs/examples/provider.go +++ b/docs/examples/provider.go @@ -39,20 +39,30 @@ func main() { } } +type options struct { + db string + size int +} + func composeCommand() *cobra.Command { c := &cobra.Command{ Use: "compose EVENT", TraverseChildren: true, } c.PersistentFlags().String("project-name", "", "compose project name") // unused + + var options options + upCmd := &cobra.Command{ - Use: "up", - Run: up, + Use: "up", + Run: func(_ *cobra.Command, args []string) { + up(options, args) + }, Args: cobra.ExactArgs(1), } - upCmd.Flags().String("type", "", "Database type (mysql, postgres, etc.)") + upCmd.Flags().StringVar(&options.db, "type", "", "Database type (mysql, postgres, etc.)") _ = upCmd.MarkFlagRequired("type") - upCmd.Flags().Int("size", 10, "Database size in GB") + upCmd.Flags().IntVar(&options.size, "size", 10, "Database size in GB") upCmd.Flags().String("name", "", "Name of the database to be created") _ = upCmd.MarkFlagRequired("name") @@ -71,13 +81,13 @@ func composeCommand() *cobra.Command { const lineSeparator = "\n" -func up(_ *cobra.Command, args []string) { +func up(options options, args []string) { servicename := args[0] fmt.Printf(`{ "type": "debug", "message": "Starting %s" }%s`, servicename, lineSeparator) - for i := 0; i < 100; i += 10 { + for i := 0; i < options.size; i++ { time.Sleep(1 * time.Second) - fmt.Printf(`{ "type": "info", "message": "Processing ... %d%%" }%s`, i, lineSeparator) + fmt.Printf(`{ "type": "info", "message": "Processing ... %d%%" }%s`, i*100/options.size, lineSeparator) } fmt.Printf(`{ "type": "setenv", "message": "URL=https://magic.cloud/%s" }%s`, servicename, lineSeparator) } diff --git a/go.mod b/go.mod index c8d9abf353b..d85c3308ca3 100644 --- a/go.mod +++ b/go.mod @@ -213,4 +213,4 @@ exclude ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 ) -replace github.com/compose-spec/compose-go/v2 => github.com/ndeloof/compose-go/v2 v2.0.1-0.20250618092235-a694d81af63d +replace github.com/compose-spec/compose-go/v2 => github.com/ndeloof/compose-go/v2 v2.0.1-0.20250618152329-47e2a42e5ab4 diff --git a/go.sum b/go.sum index c95c9b8693b..3ed3e35775b 100644 --- a/go.sum +++ b/go.sum @@ -359,8 +359,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/ndeloof/compose-go/v2 v2.0.1-0.20250618092235-a694d81af63d h1:fLSNfPyqfQC2uWAL9KxGmAwQm1DZaf6FoshlcJpLIcs= -github.com/ndeloof/compose-go/v2 v2.0.1-0.20250618092235-a694d81af63d/go.mod h1:TmjkIB9W73fwVxkYY+u2uhMbMUakjiif79DlYgXsyvU= +github.com/ndeloof/compose-go/v2 v2.0.1-0.20250618152329-47e2a42e5ab4 h1:n01lYAdi3slIjYGd2XHi9jMJrx/BUqpx/6eiiaxtnmg= +github.com/ndeloof/compose-go/v2 v2.0.1-0.20250618152329-47e2a42e5ab4/go.mod h1:TmjkIB9W73fwVxkYY+u2uhMbMUakjiif79DlYgXsyvU= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU= diff --git a/pkg/e2e/fixtures/interpolation/.env b/pkg/e2e/fixtures/interpolation/.env index 15c36f50ef7..ecb6dbac5b3 100644 --- a/pkg/e2e/fixtures/interpolation/.env +++ b/pkg/e2e/fixtures/interpolation/.env @@ -1 +1 @@ -FOO=BAR \ No newline at end of file +FOO=FOO-from-dot-env \ No newline at end of file diff --git a/pkg/e2e/fixtures/interpolation/compose.yaml b/pkg/e2e/fixtures/interpolation/compose.yaml index b9bcb7ad78c..2023da2916b 100644 --- a/pkg/e2e/fixtures/interpolation/compose.yaml +++ b/pkg/e2e/fixtures/interpolation/compose.yaml @@ -3,7 +3,18 @@ services: image: alpine command: env environment: - FOO: ${FOO} - BAR: bar_from_environment + - FOO + - BAR=bar_from_environment + - BY_PROVIDER_FROM_ENV=${EXAMPLE_URL} env_file: - - env_file.env \ No newline at end of file + - env_file.env + depends_on: + - example + + example: + provider: + type: example-provider + options: + type: test + name: example + size: 0 diff --git a/pkg/e2e/fixtures/interpolation/env_file.env b/pkg/e2e/fixtures/interpolation/env_file.env index e77ff9f2751..0a24f568876 100644 --- a/pkg/e2e/fixtures/interpolation/env_file.env +++ b/pkg/e2e/fixtures/interpolation/env_file.env @@ -1,3 +1,4 @@ ZOT=${FOO:-ZOT} QIX=some ${FOO} value -BAR_FROM_ENV_FILE=${BAR} \ No newline at end of file +BAR_FROM_ENV_FILE=${BAR} +BY_PROVIDER_FROM_ENV_FILE: ${EXAMPLE_URL} \ No newline at end of file diff --git a/pkg/e2e/fixtures/interpolation/include/compose.yaml b/pkg/e2e/fixtures/interpolation/include/compose.yaml new file mode 100644 index 00000000000..b0938b3da75 --- /dev/null +++ b/pkg/e2e/fixtures/interpolation/include/compose.yaml @@ -0,0 +1,3 @@ +include: + - path: ../compose.yaml + env_file: include.env \ No newline at end of file diff --git a/pkg/e2e/fixtures/interpolation/include/include.env b/pkg/e2e/fixtures/interpolation/include/include.env new file mode 100644 index 00000000000..e76ac34cdd4 --- /dev/null +++ b/pkg/e2e/fixtures/interpolation/include/include.env @@ -0,0 +1 @@ +FOO=FOO-from-include \ No newline at end of file diff --git a/pkg/e2e/interpolation_test.go b/pkg/e2e/interpolation_test.go index c5648fe59f4..737385afc7c 100644 --- a/pkg/e2e/interpolation_test.go +++ b/pkg/e2e/interpolation_test.go @@ -18,6 +18,9 @@ package e2e import ( "bufio" + "fmt" + "os" + "path/filepath" "slices" "strings" "testing" @@ -26,15 +29,53 @@ import ( ) func Test_interpolation(t *testing.T) { - c := NewParallelCLI(t) + provider, err := findExecutable("example-provider") + assert.NilError(t, err) + path := fmt.Sprintf("%s%s%s", os.Getenv("PATH"), string(os.PathListSeparator), filepath.Dir(provider)) + c := NewParallelCLI(t, WithEnv("PATH="+path)) + const projectName = "interpolation" t.Cleanup(func() { c.cleanupWithDown(t, projectName) }) - res := c.RunDockerComposeCmdNoCheck(t, "-f", "fixtures/interpolation/compose.yaml", "--project-name", projectName, "up") + res := c.RunDockerComposeCmd(t, "-f", "fixtures/interpolation/compose.yaml", "--project-name", projectName, "up") + env := getEnv(res.Combined()) + + assert.Check(t, slices.Contains(env, "FOO=FOO-from-dot-env")) + assert.Check(t, slices.Contains(env, "ZOT=FOO-from-dot-env")) + assert.Check(t, slices.Contains(env, "QIX=some FOO-from-dot-env value")) + assert.Check(t, slices.Contains(env, "BAR_FROM_ENV_FILE=bar_from_environment")) + + assert.Check(t, slices.Contains(env, "BY_PROVIDER_FROM_ENV=https://magic.cloud/example")) + assert.Check(t, slices.Contains(env, "BY_PROVIDER_FROM_ENV_FILE=https://magic.cloud/example")) +} + +func Test_interpolationWithInclude(t *testing.T) { + provider, err := findExecutable("example-provider") + assert.NilError(t, err) + path := fmt.Sprintf("%s%s%s", os.Getenv("PATH"), string(os.PathListSeparator), filepath.Dir(provider)) + c := NewParallelCLI(t, WithEnv("PATH="+path)) + + const projectName = "interpolation-include" + t.Cleanup(func() { + c.cleanupWithDown(t, projectName) + }) + + res := c.RunDockerComposeCmd(t, "-f", "fixtures/interpolation/include/compose.yaml", "--project-name", projectName, "up") + env := getEnv(res.Combined()) + + assert.Check(t, slices.Contains(env, "FOO=FOO-from-include")) + assert.Check(t, slices.Contains(env, "ZOT=FOO-from-include")) + assert.Check(t, slices.Contains(env, "QIX=some FOO-from-include value")) + + assert.Check(t, slices.Contains(env, "BY_PROVIDER_FROM_ENV=https://magic.cloud/example")) + assert.Check(t, slices.Contains(env, "BY_PROVIDER_FROM_ENV_FILE=https://magic.cloud/example")) +} + +func getEnv(out string) []string { var env []string - scanner := bufio.NewScanner(strings.NewReader(res.Stdout())) + scanner := bufio.NewScanner(strings.NewReader(out)) for scanner.Scan() { line := scanner.Text() if strings.HasPrefix(line, "test-1 | ") { @@ -42,10 +83,5 @@ func Test_interpolation(t *testing.T) { } } slices.Sort(env) - - assert.Check(t, slices.Contains(env, "FOO=BAR")) - assert.Check(t, slices.Contains(env, "ZOT=BAR")) - assert.Check(t, slices.Contains(env, "QIX=some BAR value")) - assert.Check(t, slices.Contains(env, "BAR_FROM_ENV_FILE=bar_from_environment")) - + return env } From 8f4f29f5ed5f22abdb04eebfb24ec9d3b32c86a7 Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Wed, 18 Jun 2025 18:11:39 +0200 Subject: [PATCH 3/8] test interpolation with extends Signed-off-by: Nicolas De Loof --- pkg/e2e/fixtures/interpolation/extends/.env | 1 + .../interpolation/extends/compose.yaml | 12 +++++++++ pkg/e2e/interpolation_test.go | 25 +++++++++++++++++++ 3 files changed, 38 insertions(+) create mode 100644 pkg/e2e/fixtures/interpolation/extends/.env create mode 100644 pkg/e2e/fixtures/interpolation/extends/compose.yaml diff --git a/pkg/e2e/fixtures/interpolation/extends/.env b/pkg/e2e/fixtures/interpolation/extends/.env new file mode 100644 index 00000000000..d87b5290ec7 --- /dev/null +++ b/pkg/e2e/fixtures/interpolation/extends/.env @@ -0,0 +1 @@ +FOO=FOO-from-extends \ No newline at end of file diff --git a/pkg/e2e/fixtures/interpolation/extends/compose.yaml b/pkg/e2e/fixtures/interpolation/extends/compose.yaml new file mode 100644 index 00000000000..9dc022ef114 --- /dev/null +++ b/pkg/e2e/fixtures/interpolation/extends/compose.yaml @@ -0,0 +1,12 @@ +services: + test: + extends: + service: test + file: ../compose.yaml + environment: + - BAR=BAR-from-extends + + example: + extends: + service: example + file: ../compose.yaml \ No newline at end of file diff --git a/pkg/e2e/interpolation_test.go b/pkg/e2e/interpolation_test.go index 737385afc7c..1e905af39bd 100644 --- a/pkg/e2e/interpolation_test.go +++ b/pkg/e2e/interpolation_test.go @@ -43,6 +43,7 @@ func Test_interpolation(t *testing.T) { env := getEnv(res.Combined()) assert.Check(t, slices.Contains(env, "FOO=FOO-from-dot-env")) + assert.Check(t, slices.Contains(env, "BAR=bar_from_environment")) assert.Check(t, slices.Contains(env, "ZOT=FOO-from-dot-env")) assert.Check(t, slices.Contains(env, "QIX=some FOO-from-dot-env value")) assert.Check(t, slices.Contains(env, "BAR_FROM_ENV_FILE=bar_from_environment")) @@ -66,6 +67,7 @@ func Test_interpolationWithInclude(t *testing.T) { env := getEnv(res.Combined()) assert.Check(t, slices.Contains(env, "FOO=FOO-from-include")) + assert.Check(t, slices.Contains(env, "BAR=bar_from_environment")) assert.Check(t, slices.Contains(env, "ZOT=FOO-from-include")) assert.Check(t, slices.Contains(env, "QIX=some FOO-from-include value")) @@ -73,6 +75,29 @@ func Test_interpolationWithInclude(t *testing.T) { assert.Check(t, slices.Contains(env, "BY_PROVIDER_FROM_ENV_FILE=https://magic.cloud/example")) } +func Test_interpolationWithExtends(t *testing.T) { + provider, err := findExecutable("example-provider") + assert.NilError(t, err) + path := fmt.Sprintf("%s%s%s", os.Getenv("PATH"), string(os.PathListSeparator), filepath.Dir(provider)) + c := NewParallelCLI(t, WithEnv("PATH="+path)) + + const projectName = "interpolation-extends" + t.Cleanup(func() { + c.cleanupWithDown(t, projectName) + }) + + res := c.RunDockerComposeCmd(t, "-f", "fixtures/interpolation/extends/compose.yaml", "--project-name", projectName, "up") + env := getEnv(res.Combined()) + + assert.Check(t, slices.Contains(env, "FOO=FOO-from-extends")) + assert.Check(t, slices.Contains(env, "BAR=BAR-from-extends")) + assert.Check(t, slices.Contains(env, "ZOT=FOO-from-extends")) + assert.Check(t, slices.Contains(env, "QIX=some FOO-from-extends value")) + + assert.Check(t, slices.Contains(env, "BY_PROVIDER_FROM_ENV=https://magic.cloud/example")) + assert.Check(t, slices.Contains(env, "BY_PROVIDER_FROM_ENV_FILE=https://magic.cloud/example")) +} + func getEnv(out string) []string { var env []string scanner := bufio.NewScanner(strings.NewReader(out)) From cc226280f58bab2173966df62bd2bc9adb9d237c Mon Sep 17 00:00:00 2001 From: Guillaume Lours <705411+glours@users.noreply.github.com> Date: Fri, 20 Jun 2025 11:07:17 +0200 Subject: [PATCH 4/8] add e2e tests to check interpolation in environment files Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com> --- pkg/e2e/fixtures/interpolation/.env | 3 +- pkg/e2e/fixtures/interpolation/env_file.env | 6 ++- .../interpolation/explicit_env_file.env | 1 + pkg/e2e/interpolation_test.go | 45 ++++++++++++++++--- 4 files changed, 48 insertions(+), 7 deletions(-) create mode 100644 pkg/e2e/fixtures/interpolation/explicit_env_file.env diff --git a/pkg/e2e/fixtures/interpolation/.env b/pkg/e2e/fixtures/interpolation/.env index ecb6dbac5b3..bfb9a803aab 100644 --- a/pkg/e2e/fixtures/interpolation/.env +++ b/pkg/e2e/fixtures/interpolation/.env @@ -1 +1,2 @@ -FOO=FOO-from-dot-env \ No newline at end of file +FOO=FOO-from-dot-env +IMPLICIT_ENV_FILE_TO_ENV_FILE=IMPLICIT_ENV_FILE_VALUE \ No newline at end of file diff --git a/pkg/e2e/fixtures/interpolation/env_file.env b/pkg/e2e/fixtures/interpolation/env_file.env index 0a24f568876..a7f3eb3e94d 100644 --- a/pkg/e2e/fixtures/interpolation/env_file.env +++ b/pkg/e2e/fixtures/interpolation/env_file.env @@ -1,4 +1,8 @@ ZOT=${FOO:-ZOT} QIX=some ${FOO} value BAR_FROM_ENV_FILE=${BAR} -BY_PROVIDER_FROM_ENV_FILE: ${EXAMPLE_URL} \ No newline at end of file +BY_PROVIDER_FROM_ENV_FILE: ${EXAMPLE_URL} +BY_OS_FROM_ENV_FILE: ${OS_TO_ENV_FILE} +BY_CMD_FROM_ENV_FILE: ${CMD_TO_ENV_FILE} +BY_IMPLICIT_ENV_FILE: ${IMPLICIT_ENV_FILE_TO_ENV_FILE} +BY_EXPLICIT_ENV_FILE: ${EXPLICIT_ENV_FILE_TO_ENV_FILE} diff --git a/pkg/e2e/fixtures/interpolation/explicit_env_file.env b/pkg/e2e/fixtures/interpolation/explicit_env_file.env new file mode 100644 index 00000000000..01cf1e10900 --- /dev/null +++ b/pkg/e2e/fixtures/interpolation/explicit_env_file.env @@ -0,0 +1 @@ +EXPLICIT_ENV_FILE_TO_ENV_FILE=EXPLICIT_ENV_FILE_VALUE diff --git a/pkg/e2e/interpolation_test.go b/pkg/e2e/interpolation_test.go index 1e905af39bd..07158aef478 100644 --- a/pkg/e2e/interpolation_test.go +++ b/pkg/e2e/interpolation_test.go @@ -26,6 +26,7 @@ import ( "testing" "gotest.tools/v3/assert" + "gotest.tools/v3/icmd" ) func Test_interpolation(t *testing.T) { @@ -40,7 +41,7 @@ func Test_interpolation(t *testing.T) { }) res := c.RunDockerComposeCmd(t, "-f", "fixtures/interpolation/compose.yaml", "--project-name", projectName, "up") - env := getEnv(res.Combined()) + env := getEnv(res.Combined(), false) assert.Check(t, slices.Contains(env, "FOO=FOO-from-dot-env")) assert.Check(t, slices.Contains(env, "BAR=bar_from_environment")) @@ -64,7 +65,7 @@ func Test_interpolationWithInclude(t *testing.T) { }) res := c.RunDockerComposeCmd(t, "-f", "fixtures/interpolation/include/compose.yaml", "--project-name", projectName, "up") - env := getEnv(res.Combined()) + env := getEnv(res.Combined(), false) assert.Check(t, slices.Contains(env, "FOO=FOO-from-include")) assert.Check(t, slices.Contains(env, "BAR=bar_from_environment")) @@ -87,7 +88,7 @@ func Test_interpolationWithExtends(t *testing.T) { }) res := c.RunDockerComposeCmd(t, "-f", "fixtures/interpolation/extends/compose.yaml", "--project-name", projectName, "up") - env := getEnv(res.Combined()) + env := getEnv(res.Combined(), false) assert.Check(t, slices.Contains(env, "FOO=FOO-from-extends")) assert.Check(t, slices.Contains(env, "BAR=BAR-from-extends")) @@ -98,14 +99,48 @@ func Test_interpolationWithExtends(t *testing.T) { assert.Check(t, slices.Contains(env, "BY_PROVIDER_FROM_ENV_FILE=https://magic.cloud/example")) } -func getEnv(out string) []string { +func TestInterpolationInEnvFile(t *testing.T) { + provider, err := findExecutable("example-provider") + assert.NilError(t, err) + path := fmt.Sprintf("%s%s%s", os.Getenv("PATH"), string(os.PathListSeparator), filepath.Dir(provider)) + c := NewParallelCLI(t, WithEnv("PATH="+path)) + + const projectName = "interpolation-env-file" + t.Log("interpolation in env file from os env and implicit env file") + cmd := c.NewDockerComposeCmd(t, "-f", "fixtures/interpolation/compose.yaml", "--project-name", projectName, "up") + cmd.Env = append(cmd.Env, "OS_TO_ENV_FILE=OS_TO_ENV_FILE_VALUE") + t.Cleanup(func() { + c.cleanupWithDown(t, projectName) + }) + + res := icmd.RunCmd(cmd) + assert.NilError(t, res.Error, res.Combined()) + env := getEnv(res.Combined(), false) + assert.Check(t, slices.Contains(env, "BY_OS_FROM_ENV_FILE=OS_TO_ENV_FILE_VALUE"), env) + assert.Check(t, slices.Contains(env, "BY_IMPLICIT_ENV_FILE=IMPLICIT_ENV_FILE_VALUE"), env) + + t.Log("interpolation in env file from command env and explicit env file") + cmd = c.NewDockerComposeCmd(t, "-f", "fixtures/interpolation/compose.yaml", "--project-name", projectName, + "--env-file", "fixtures/interpolation/explicit_env_file.env", "run", + "--env", "BY_CMD_FROM_ENV_FILE=CMD_TO_ENV_FILE_VALUE", "--rm", "test") + + res = icmd.RunCmd(cmd) + env = getEnv(res.Combined(), true) + assert.Check(t, slices.Contains(env, "BY_EXPLICIT_ENV_FILE=EXPLICIT_ENV_FILE_VALUE"), env) + assert.Check(t, slices.Contains(env, "BY_CMD_FROM_ENV_FILE=CMD_TO_ENV_FILE_VALUE"), env) +} + +func getEnv(out string, run bool) []string { var env []string scanner := bufio.NewScanner(strings.NewReader(out)) for scanner.Scan() { line := scanner.Text() - if strings.HasPrefix(line, "test-1 | ") { + if !run && strings.HasPrefix(line, "test-1 | ") { env = append(env, line[10:]) } + if run && strings.Contains(line, "=") && len(strings.Split(line, "=")) == 2 { + env = append(env, line) + } } slices.Sort(env) return env From 0924db80d3af49509f09903d55fd4e85fed621cf Mon Sep 17 00:00:00 2001 From: Guillaume Lours <705411+glours@users.noreply.github.com> Date: Fri, 20 Jun 2025 11:41:55 +0200 Subject: [PATCH 5/8] fixup! add e2e tests to check interpolation in environment files Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com> --- pkg/e2e/fixtures/interpolation/env_file.env | 2 +- pkg/e2e/interpolation_test.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/e2e/fixtures/interpolation/env_file.env b/pkg/e2e/fixtures/interpolation/env_file.env index a7f3eb3e94d..0fb1aa17f6a 100644 --- a/pkg/e2e/fixtures/interpolation/env_file.env +++ b/pkg/e2e/fixtures/interpolation/env_file.env @@ -3,6 +3,6 @@ QIX=some ${FOO} value BAR_FROM_ENV_FILE=${BAR} BY_PROVIDER_FROM_ENV_FILE: ${EXAMPLE_URL} BY_OS_FROM_ENV_FILE: ${OS_TO_ENV_FILE} -BY_CMD_FROM_ENV_FILE: ${CMD_TO_ENV_FILE} +BY_CMD_FROM_ENV_FILE: ${CMD_TO_ENV_FILE:-CMD_TO_ENV_FILE_DEFAULT_VALUE} BY_IMPLICIT_ENV_FILE: ${IMPLICIT_ENV_FILE_TO_ENV_FILE} BY_EXPLICIT_ENV_FILE: ${EXPLICIT_ENV_FILE_TO_ENV_FILE} diff --git a/pkg/e2e/interpolation_test.go b/pkg/e2e/interpolation_test.go index 07158aef478..06181690dd6 100644 --- a/pkg/e2e/interpolation_test.go +++ b/pkg/e2e/interpolation_test.go @@ -118,6 +118,7 @@ func TestInterpolationInEnvFile(t *testing.T) { env := getEnv(res.Combined(), false) assert.Check(t, slices.Contains(env, "BY_OS_FROM_ENV_FILE=OS_TO_ENV_FILE_VALUE"), env) assert.Check(t, slices.Contains(env, "BY_IMPLICIT_ENV_FILE=IMPLICIT_ENV_FILE_VALUE"), env) + assert.Check(t, slices.Contains(env, "BY_CMD_FROM_ENV_FILE=CMD_TO_ENV_FILE_DEFAULT_VALUE"), env) t.Log("interpolation in env file from command env and explicit env file") cmd = c.NewDockerComposeCmd(t, "-f", "fixtures/interpolation/compose.yaml", "--project-name", projectName, From 7d6191cfa984c2e8f2c5e4de53cdb4e7f1d1ebca Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Fri, 20 Jun 2025 14:25:58 +0200 Subject: [PATCH 6/8] os.env interpolation Signed-off-by: Nicolas De Loof --- pkg/e2e/fixtures/interpolation/.env | 2 ++ pkg/e2e/fixtures/interpolation/compose.yaml | 1 + pkg/e2e/fixtures/interpolation/env_file.env | 2 ++ pkg/e2e/fixtures/interpolation/extends/.env | 4 +++- pkg/e2e/fixtures/interpolation/include/include.env | 4 +++- pkg/e2e/interpolation_test.go | 9 ++++++--- 6 files changed, 17 insertions(+), 5 deletions(-) diff --git a/pkg/e2e/fixtures/interpolation/.env b/pkg/e2e/fixtures/interpolation/.env index bfb9a803aab..ebfdc29c362 100644 --- a/pkg/e2e/fixtures/interpolation/.env +++ b/pkg/e2e/fixtures/interpolation/.env @@ -1,2 +1,4 @@ FOO=FOO-from-dot-env +TEST1=dot-env +TEST2=dot-env IMPLICIT_ENV_FILE_TO_ENV_FILE=IMPLICIT_ENV_FILE_VALUE \ No newline at end of file diff --git a/pkg/e2e/fixtures/interpolation/compose.yaml b/pkg/e2e/fixtures/interpolation/compose.yaml index 2023da2916b..e9e9ddd42fd 100644 --- a/pkg/e2e/fixtures/interpolation/compose.yaml +++ b/pkg/e2e/fixtures/interpolation/compose.yaml @@ -6,6 +6,7 @@ services: - FOO - BAR=bar_from_environment - BY_PROVIDER_FROM_ENV=${EXAMPLE_URL} + - INTERPOLATED=${TEST1}-${TEST2} env_file: - env_file.env depends_on: diff --git a/pkg/e2e/fixtures/interpolation/env_file.env b/pkg/e2e/fixtures/interpolation/env_file.env index 0fb1aa17f6a..f29f9c1a54f 100644 --- a/pkg/e2e/fixtures/interpolation/env_file.env +++ b/pkg/e2e/fixtures/interpolation/env_file.env @@ -1,5 +1,7 @@ ZOT=${FOO:-ZOT} QIX=some ${FOO} value +TEST1=env_file +TEST2=env_file BAR_FROM_ENV_FILE=${BAR} BY_PROVIDER_FROM_ENV_FILE: ${EXAMPLE_URL} BY_OS_FROM_ENV_FILE: ${OS_TO_ENV_FILE} diff --git a/pkg/e2e/fixtures/interpolation/extends/.env b/pkg/e2e/fixtures/interpolation/extends/.env index d87b5290ec7..7b841f77f8a 100644 --- a/pkg/e2e/fixtures/interpolation/extends/.env +++ b/pkg/e2e/fixtures/interpolation/extends/.env @@ -1 +1,3 @@ -FOO=FOO-from-extends \ No newline at end of file +FOO=FOO-from-extends +TEST1=extends +TEST2=extends diff --git a/pkg/e2e/fixtures/interpolation/include/include.env b/pkg/e2e/fixtures/interpolation/include/include.env index e76ac34cdd4..55b4604784d 100644 --- a/pkg/e2e/fixtures/interpolation/include/include.env +++ b/pkg/e2e/fixtures/interpolation/include/include.env @@ -1 +1,3 @@ -FOO=FOO-from-include \ No newline at end of file +FOO=FOO-from-include +TEST1=include +TEST2=include diff --git a/pkg/e2e/interpolation_test.go b/pkg/e2e/interpolation_test.go index 06181690dd6..55e2f502c33 100644 --- a/pkg/e2e/interpolation_test.go +++ b/pkg/e2e/interpolation_test.go @@ -33,7 +33,7 @@ func Test_interpolation(t *testing.T) { provider, err := findExecutable("example-provider") assert.NilError(t, err) path := fmt.Sprintf("%s%s%s", os.Getenv("PATH"), string(os.PathListSeparator), filepath.Dir(provider)) - c := NewParallelCLI(t, WithEnv("PATH="+path)) + c := NewParallelCLI(t, WithEnv("PATH="+path, "TEST1=os.Env")) const projectName = "interpolation" t.Cleanup(func() { @@ -48,6 +48,7 @@ func Test_interpolation(t *testing.T) { assert.Check(t, slices.Contains(env, "ZOT=FOO-from-dot-env")) assert.Check(t, slices.Contains(env, "QIX=some FOO-from-dot-env value")) assert.Check(t, slices.Contains(env, "BAR_FROM_ENV_FILE=bar_from_environment")) + assert.Check(t, slices.Contains(env, "INTERPOLATED=os.Env-dot-env")) assert.Check(t, slices.Contains(env, "BY_PROVIDER_FROM_ENV=https://magic.cloud/example")) assert.Check(t, slices.Contains(env, "BY_PROVIDER_FROM_ENV_FILE=https://magic.cloud/example")) @@ -57,7 +58,7 @@ func Test_interpolationWithInclude(t *testing.T) { provider, err := findExecutable("example-provider") assert.NilError(t, err) path := fmt.Sprintf("%s%s%s", os.Getenv("PATH"), string(os.PathListSeparator), filepath.Dir(provider)) - c := NewParallelCLI(t, WithEnv("PATH="+path)) + c := NewParallelCLI(t, WithEnv("PATH="+path, "TEST1=os.Env")) const projectName = "interpolation-include" t.Cleanup(func() { @@ -71,6 +72,7 @@ func Test_interpolationWithInclude(t *testing.T) { assert.Check(t, slices.Contains(env, "BAR=bar_from_environment")) assert.Check(t, slices.Contains(env, "ZOT=FOO-from-include")) assert.Check(t, slices.Contains(env, "QIX=some FOO-from-include value")) + assert.Check(t, slices.Contains(env, "INTERPOLATED=os.Env-include")) assert.Check(t, slices.Contains(env, "BY_PROVIDER_FROM_ENV=https://magic.cloud/example")) assert.Check(t, slices.Contains(env, "BY_PROVIDER_FROM_ENV_FILE=https://magic.cloud/example")) @@ -80,7 +82,7 @@ func Test_interpolationWithExtends(t *testing.T) { provider, err := findExecutable("example-provider") assert.NilError(t, err) path := fmt.Sprintf("%s%s%s", os.Getenv("PATH"), string(os.PathListSeparator), filepath.Dir(provider)) - c := NewParallelCLI(t, WithEnv("PATH="+path)) + c := NewParallelCLI(t, WithEnv("PATH="+path, "TEST1=os.Env")) const projectName = "interpolation-extends" t.Cleanup(func() { @@ -94,6 +96,7 @@ func Test_interpolationWithExtends(t *testing.T) { assert.Check(t, slices.Contains(env, "BAR=BAR-from-extends")) assert.Check(t, slices.Contains(env, "ZOT=FOO-from-extends")) assert.Check(t, slices.Contains(env, "QIX=some FOO-from-extends value")) + assert.Check(t, slices.Contains(env, "INTERPOLATED=os.Env-extends")) assert.Check(t, slices.Contains(env, "BY_PROVIDER_FROM_ENV=https://magic.cloud/example")) assert.Check(t, slices.Contains(env, "BY_PROVIDER_FROM_ENV_FILE=https://magic.cloud/example")) From 581e1fca471a6fa9805cee66ba56e24b01ff567a Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Mon, 23 Jun 2025 13:47:01 +0200 Subject: [PATCH 7/8] capture loader's environment for late interpolation Signed-off-by: Nicolas De Loof --- go.mod | 2 +- go.sum | 4 ++-- pkg/compose/convergence.go | 5 +++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index d85c3308ca3..1bb7ebff3e0 100644 --- a/go.mod +++ b/go.mod @@ -213,4 +213,4 @@ exclude ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 ) -replace github.com/compose-spec/compose-go/v2 => github.com/ndeloof/compose-go/v2 v2.0.1-0.20250618152329-47e2a42e5ab4 +replace github.com/compose-spec/compose-go/v2 => github.com/ndeloof/compose-go/v2 v2.0.1-0.20250623114502-42c1cc814431 diff --git a/go.sum b/go.sum index 3ed3e35775b..b2486291ecd 100644 --- a/go.sum +++ b/go.sum @@ -359,8 +359,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/ndeloof/compose-go/v2 v2.0.1-0.20250618152329-47e2a42e5ab4 h1:n01lYAdi3slIjYGd2XHi9jMJrx/BUqpx/6eiiaxtnmg= -github.com/ndeloof/compose-go/v2 v2.0.1-0.20250618152329-47e2a42e5ab4/go.mod h1:TmjkIB9W73fwVxkYY+u2uhMbMUakjiif79DlYgXsyvU= +github.com/ndeloof/compose-go/v2 v2.0.1-0.20250623114502-42c1cc814431 h1:hQ2liObnBBybZ4yrrfKczUXk2qDDYkzMfC8mtBdlpJU= +github.com/ndeloof/compose-go/v2 v2.0.1-0.20250623114502-42c1cc814431/go.mod h1:TmjkIB9W73fwVxkYY+u2uhMbMUakjiif79DlYgXsyvU= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU= diff --git a/pkg/compose/convergence.go b/pkg/compose/convergence.go index fa005c8bb8e..1655382dee1 100644 --- a/pkg/compose/convergence.go +++ b/pkg/compose/convergence.go @@ -115,9 +115,10 @@ var mux sync.Mutex func (c *convergence) ensureService(ctx context.Context, project *types.Project, service types.ServiceConfig, recreate string, inherit bool, timeout *time.Duration) error { //nolint:gocyclo mux.Lock() + service.LoaderEnv.OverrideBy(project.Environment) for key, val := range service.Environment { if val != nil { - newVal, err := dotenv.ExpandVariables(*val, nil, project.Environment.Resolve) + newVal, err := dotenv.ExpandVariables(*val, nil, service.LoaderEnv.Resolve) if err != nil { return err } @@ -125,7 +126,7 @@ func (c *convergence) ensureService(ctx context.Context, project *types.Project, } } - err := service.WithEnvironmentResolved(false, project) + err := service.WithEnvironmentResolved(false) mux.Unlock() if err != nil { return err From aa62a35af9b9e025744150caf8cf44f97e50cb54 Mon Sep 17 00:00:00 2001 From: Guillaume Lours <705411+glours@users.noreply.github.com> Date: Mon, 23 Jun 2025 17:01:53 +0200 Subject: [PATCH 8/8] add override interpolation e2e tests Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com> --- pkg/e2e/fixtures/interpolation/.env | 3 +- .../interpolation/compose.override.yaml | 5 +++ pkg/e2e/fixtures/interpolation/compose.yaml | 3 ++ .../interpolation/override/override.yaml | 5 +++ pkg/e2e/interpolation_test.go | 34 +++++++++++++++++++ 5 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 pkg/e2e/fixtures/interpolation/compose.override.yaml create mode 100644 pkg/e2e/fixtures/interpolation/override/override.yaml diff --git a/pkg/e2e/fixtures/interpolation/.env b/pkg/e2e/fixtures/interpolation/.env index ebfdc29c362..5de888f66e0 100644 --- a/pkg/e2e/fixtures/interpolation/.env +++ b/pkg/e2e/fixtures/interpolation/.env @@ -1,4 +1,5 @@ FOO=FOO-from-dot-env TEST1=dot-env TEST2=dot-env -IMPLICIT_ENV_FILE_TO_ENV_FILE=IMPLICIT_ENV_FILE_VALUE \ No newline at end of file +IMPLICIT_ENV_FILE_TO_ENV_FILE=IMPLICIT_ENV_FILE_VALUE +IMPLICIT_ENV_FILE_TO_OVERRIDE=IMPLICIT_ENV_FILE_TO_OVERRIDE_VALUE \ No newline at end of file diff --git a/pkg/e2e/fixtures/interpolation/compose.override.yaml b/pkg/e2e/fixtures/interpolation/compose.override.yaml new file mode 100644 index 00000000000..3eaf2f89246 --- /dev/null +++ b/pkg/e2e/fixtures/interpolation/compose.override.yaml @@ -0,0 +1,5 @@ +services: + test: + environment: + - IMPLICIT_OVERRIDE_FILE=implicit_override_value + - IMPLICIT_OVERRIDE_FROM_IMPLICIT_ENV_FILE=${IMPLICIT_ENV_FILE_TO_OVERRIDE} diff --git a/pkg/e2e/fixtures/interpolation/compose.yaml b/pkg/e2e/fixtures/interpolation/compose.yaml index e9e9ddd42fd..5e49d524afe 100644 --- a/pkg/e2e/fixtures/interpolation/compose.yaml +++ b/pkg/e2e/fixtures/interpolation/compose.yaml @@ -7,6 +7,9 @@ services: - BAR=bar_from_environment - BY_PROVIDER_FROM_ENV=${EXAMPLE_URL} - INTERPOLATED=${TEST1}-${TEST2} + - BY_IMPLICIT_OVERRIDE_FILE=${IMPLICIT_OVERRIDE_FILE} + - BY_IMPLICIT_OVERRIDE_FROM_IMPLICIT_ENV_FILE=${IMPLICIT_OVERRIDE_FROM_IMPLICIT_ENV_FILE} + - BY_EXPLICIT_OVERRIDE_FROM_IMPLICIT_ENV_FILE=${EXPLICIT_OVERRIDE_FROM_IMPLICIT_ENV_FILE} env_file: - env_file.env depends_on: diff --git a/pkg/e2e/fixtures/interpolation/override/override.yaml b/pkg/e2e/fixtures/interpolation/override/override.yaml new file mode 100644 index 00000000000..e037cb39ea0 --- /dev/null +++ b/pkg/e2e/fixtures/interpolation/override/override.yaml @@ -0,0 +1,5 @@ +services: + test: + environment: + - EXPLICIT_OVERRIDE_FILE=explicit_override_value + - EXPLICIT_OVERRIDE_FROM_IMPLICIT_ENV_FILE=${IMPLICIT_ENV_FILE_TO_OVERRIDE} diff --git a/pkg/e2e/interpolation_test.go b/pkg/e2e/interpolation_test.go index 55e2f502c33..19a2d9e3528 100644 --- a/pkg/e2e/interpolation_test.go +++ b/pkg/e2e/interpolation_test.go @@ -134,6 +134,40 @@ func TestInterpolationInEnvFile(t *testing.T) { assert.Check(t, slices.Contains(env, "BY_CMD_FROM_ENV_FILE=CMD_TO_ENV_FILE_VALUE"), env) } +func TestOverrideFiles(t *testing.T) { + provider, err := findExecutable("example-provider") + assert.NilError(t, err) + path := fmt.Sprintf("%s%s%s", os.Getenv("PATH"), string(os.PathListSeparator), filepath.Dir(provider)) + c := NewParallelCLI(t, WithEnv("PATH="+path)) + + const projectName = "interpolation-override" + + t.Run("interpolation from implicit override file", func(t *testing.T) { + t.Cleanup(func() { + c.cleanupWithDown(t, projectName) + }) + + res := c.RunDockerComposeCmd(t, "--project-directory", "fixtures/interpolation", "--project-name", projectName, "up") + env := getEnv(res.Combined(), false) + + assert.Check(t, slices.Contains(env, "BY_IMPLICIT_OVERRIDE_FILE=implicit_override_value"), env) + assert.Check(t, slices.Contains(env, "BY_IMPLICIT_OVERRIDE_FROM_IMPLICIT_ENV_FILE=IMPLICIT_ENV_FILE_TO_OVERRIDE_VALUE"), env) + }) + + t.Run("interpolation from explicit override file", func(t *testing.T) { + t.Cleanup(func() { + c.cleanupWithDown(t, projectName) + }) + + res := c.RunDockerComposeCmd(t, "-f", "fixtures/interpolation/compose.yaml", + "-f", "fixtures/interpolation/override/override.yaml", "--project-name", projectName, "up") + env := getEnv(res.Combined(), false) + + assert.Check(t, slices.Contains(env, "BY_EXPLICIT_OVERRIDE_FILE=explicit_override_value"), env) + assert.Check(t, slices.Contains(env, "BY_EXPLICIT_OVERRIDE_FROM_IMPLICIT_ENV_FILE=IMPLICIT_ENV_FILE_TO_OVERRIDE_VALUE"), env) + }) +} + func getEnv(out string, run bool) []string { var env []string scanner := bufio.NewScanner(strings.NewReader(out))