Pārlūkot izejas kodu

Fix dot env file to define COMPOSE_* variables

Signed-off-by: Nicolas De Loof <[email protected]>
Nicolas De Loof 1 gadu atpakaļ
vecāks
revīzija
10531f6302

+ 12 - 50
cmd/compose/compose.go

@@ -28,10 +28,8 @@ import (
 	"syscall"
 
 	"github.com/compose-spec/compose-go/v2/cli"
-	"github.com/compose-spec/compose-go/v2/dotenv"
 	"github.com/compose-spec/compose-go/v2/loader"
 	"github.com/compose-spec/compose-go/v2/types"
-	composegoutils "github.com/compose-spec/compose-go/v2/utils"
 	"github.com/docker/buildx/util/logutil"
 	dockercli "github.com/docker/cli/cli"
 	"github.com/docker/cli/cli-plugins/manager"
@@ -329,11 +327,20 @@ func (o *ProjectOptions) toProjectOptions(po ...cli.ProjectOptionsFn) (*cli.Proj
 	return cli.NewProjectOptions(o.ConfigPaths,
 		append(po,
 			cli.WithWorkingDirectory(o.ProjectDir),
+			// First apply os.Environment, always win
 			cli.WithOsEnv,
+			// Load PWD/.env if present and no explicit --env-file has been set
+			cli.WithEnvFiles(o.EnvFiles...),
+			// read dot env file to populate project environment
+			cli.WithDotEnv,
+			// get compose file path set by COMPOSE_FILE
 			cli.WithConfigFileEnv,
+			// if none was selected, get default compose.yaml file from current dir or parent folder
 			cli.WithDefaultConfigPath,
+			// .. and then, a project directory != PWD maybe has been set so let's load .env file
 			cli.WithEnvFiles(o.EnvFiles...),
 			cli.WithDotEnv,
+			// eventually COMPOSE_PROFILES should have been set
 			cli.WithDefaultProfiles(o.Profiles...),
 			cli.WithName(o.ProjectName))...)
 }
@@ -389,16 +396,8 @@ func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //noli
 		},
 		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
 			ctx := cmd.Context()
-
-			// (1) process env vars
-			err := setEnvWithLocalDotEnv(&opts)
-			if err != nil {
-				return err
-			}
 			parent := cmd.Root()
 
-			// (2) call parent pre-run
-			// TODO(milas): this seems incorrect, remove or document
 			if parent != nil {
 				parentPrerun := parent.PersistentPreRunE
 				if parentPrerun != nil {
@@ -409,7 +408,6 @@ func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //noli
 				}
 			}
 
-			// (3) set up display/output
 			if verbose {
 				logrus.SetLevel(logrus.TraceLevel)
 			}
@@ -469,7 +467,7 @@ func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //noli
 			}
 			for i, file := range opts.EnvFiles {
 				if !filepath.IsAbs(file) {
-					file, err = filepath.Abs(file)
+					file, err := filepath.Abs(file)
 					if err != nil {
 						return err
 					}
@@ -500,8 +498,8 @@ func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //noli
 				backend.MaxConcurrency(parallel)
 			}
 
-			// (5) dry run detection
-			ctx, err = backend.DryRunMode(ctx, dryRun)
+			// dry run detection
+			ctx, err := backend.DryRunMode(ctx, dryRun)
 			if err != nil {
 				return err
 			}
@@ -601,42 +599,6 @@ func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //noli
 	return c
 }
 
-// If user has a local .env file, load it as os.environment so it can be used to set COMPOSE_ variables
-// This also allows to override values set by the default .env in a compose project when ran from a distinct folder
-func setEnvWithLocalDotEnv(prjOpts *ProjectOptions) error {
-	if len(prjOpts.EnvFiles) > 0 {
-		return nil
-	}
-
-	wd, err := os.Getwd()
-	if err != nil {
-		return compose.WrapComposeError(err)
-	}
-
-	defaultDotEnv := filepath.Join(wd, ".env")
-
-	s, err := os.Stat(defaultDotEnv)
-	if os.IsNotExist(err) || s.IsDir() {
-		return nil
-	}
-	if err != nil {
-		return err
-	}
-
-	envFromFile, err := dotenv.GetEnvFromFile(composegoutils.GetAsEqualsMap(os.Environ()), []string{defaultDotEnv})
-	if err != nil {
-		return err
-	}
-	for k, v := range envFromFile {
-		if _, ok := os.LookupEnv(k); !ok { // Precedence to OS Env
-			if err := os.Setenv(k, v); err != nil {
-				return err
-			}
-		}
-	}
-	return nil
-}
-
 var printerModes = []string{
 	ui.ModeAuto,
 	ui.ModeTTY,

+ 3 - 2
go.mod

@@ -97,6 +97,7 @@ require (
 	github.com/go-openapi/jsonpointer v0.19.6 // indirect
 	github.com/go-openapi/jsonreference v0.20.2 // indirect
 	github.com/go-openapi/swag v0.22.3 // indirect
+	github.com/go-viper/mapstructure/v2 v2.0.0 // indirect
 	github.com/gofrs/flock v0.8.1 // indirect
 	github.com/gogo/googleapis v1.4.1 // indirect
 	github.com/gogo/protobuf v1.3.2 // indirect
@@ -125,8 +126,6 @@ require (
 	github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
 	github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
 	github.com/miekg/pkcs11 v1.1.1 // indirect
-	github.com/mitchellh/copystructure v1.2.0 // indirect
-	github.com/mitchellh/reflectwalk v1.0.2 // indirect
 	github.com/moby/docker-image-spec v1.3.1 // indirect
 	github.com/moby/locker v1.0.1 // indirect
 	github.com/moby/spdystream v0.2.0 // indirect
@@ -191,3 +190,5 @@ require (
 	sigs.k8s.io/yaml v1.3.0 // indirect
 	tags.cncf.io/container-device-interface v0.7.2 // indirect
 )
+
+replace github.com/compose-spec/compose-go/v2 => github.com/ndeloof/compose-go/v2 v2.0.1-0.20240606144025-9ba1fb10d14c

+ 4 - 6
go.sum

@@ -90,8 +90,6 @@ github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+g
 github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 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.1.2-0.20240530052535-7dfa54c9658b h1:tjysHJZrxQVzGbklQsdYOjgZC9rVFa8Ersn82QP8H1M=
-github.com/compose-spec/compose-go/v2 v2.1.2-0.20240530052535-7dfa54c9658b/go.mod h1:bEPizBkIojlQ20pi2vNluBa58tevvj0Y18oUSHPyfdc=
 github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=
 github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw=
 github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro=
@@ -190,6 +188,8 @@ github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
 github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
+github.com/go-viper/mapstructure/v2 v2.0.0 h1:dhn8MZ1gZ0mzeodTG3jt5Vj/o87xZKuNAprG2mQfMfc=
+github.com/go-viper/mapstructure/v2 v2.0.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
 github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
 github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
 github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0=
@@ -320,15 +320,11 @@ github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyex
 github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
 github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU=
 github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
-github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
-github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
 github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
 github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
 github.com/mitchellh/mapstructure v0.0.0-20150613213606-2caf8efc9366/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
 github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
-github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
-github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
 github.com/moby/buildkit v0.13.2 h1:nXNszM4qD9E7QtG7bFWPnDI1teUQFQglBzon/IU3SzI=
 github.com/moby/buildkit v0.13.2/go.mod h1:2cyVOv9NoHM7arphK9ZfHIWKn9YVZRFd1wXB8kKmEzY=
 github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
@@ -365,6 +361,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.20240606144025-9ba1fb10d14c h1:VP4OJnRYZEwJFcxUCEKvn8cC855C7GiK8MmwdfP+m5I=
+github.com/ndeloof/compose-go/v2 v2.0.1-0.20240606144025-9ba1fb10d14c/go.mod h1:lFN0DrMxIncJGYAXTfWuajfwj5haBJqrBkarHcnjJKc=
 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=

+ 39 - 1
pkg/e2e/compose_test.go

@@ -318,18 +318,56 @@ func TestRemoveOrphaned(t *testing.T) {
 	res.Assert(t, icmd.Expected{Out: fmt.Sprintf("%s-words-1", projectName)})
 }
 
-func TestResolveDotEnv(t *testing.T) {
+func TestComposeFileSetByDotEnv(t *testing.T) {
 	c := NewCLI(t)
 
 	cmd := c.NewDockerComposeCmd(t, "config")
 	cmd.Dir = filepath.Join(".", "fixtures", "dotenv")
 	res := icmd.RunCmd(cmd)
+	res.Assert(t, icmd.Expected{
+		ExitCode: 0,
+		Out:      "image: test:latest",
+	})
+	res.Assert(t, icmd.Expected{
+		Out: "image: enabled:profile",
+	})
+}
+
+func TestComposeFileSetByProjectDirectory(t *testing.T) {
+	c := NewCLI(t)
+
+	dir := filepath.Join(".", "fixtures", "dotenv", "development")
+	cmd := c.NewDockerComposeCmd(t, "--project-directory", dir, "config")
+	res := icmd.RunCmd(cmd)
 	res.Assert(t, icmd.Expected{
 		ExitCode: 0,
 		Out:      "image: backend:latest",
 	})
 }
 
+func TestComposeFileSetByEnvFile(t *testing.T) {
+	c := NewCLI(t)
+
+	dotEnv, err := os.CreateTemp(t.TempDir(), ".env")
+	assert.NilError(t, err)
+	err = os.WriteFile(dotEnv.Name(), []byte(`
+COMPOSE_FILE=fixtures/dotenv/development/compose.yaml
+IMAGE_NAME=test
+IMAGE_TAG=latest
+COMPOSE_PROFILES=test
+`), 0o700)
+	assert.NilError(t, err)
+
+	cmd := c.NewDockerComposeCmd(t, "--env-file", dotEnv.Name(), "config")
+	res := icmd.RunCmd(cmd)
+	res.Assert(t, icmd.Expected{
+		Out: "image: test:latest",
+	})
+	res.Assert(t, icmd.Expected{
+		Out: "image: enabled:profile",
+	})
+}
+
 func TestNestedDotEnv(t *testing.T) {
 	c := NewCLI(t)
 

+ 3 - 1
pkg/e2e/fixtures/dotenv/.env

@@ -1 +1,3 @@
-COMPOSE_FILE="${COMPOSE_FILE:-development/compose.yaml}"
+COMPOSE_FILE="${COMPOSE_FILE:-development/compose.yaml}"
+IMAGE_NAME=test
+COMPOSE_PROFILES=test

+ 4 - 0
pkg/e2e/fixtures/dotenv/development/compose.yaml

@@ -1,3 +1,7 @@
 services:
   backend:
     image: $IMAGE_NAME:$IMAGE_TAG
+  test:
+    profiles:
+      - test
+    image: enabled:profile