diff --git a/cmd/compose/compose.go b/cmd/compose/compose.go index 276b52121..5332033d7 100644 --- a/cmd/compose/compose.go +++ b/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, diff --git a/go.mod b/go.mod index 169d249f1..5d50e283e 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index 874c38593..09a419c8e 100644 --- a/go.sum +++ b/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= diff --git a/pkg/e2e/compose_test.go b/pkg/e2e/compose_test.go index e34156e80..1de52cfca 100644 --- a/pkg/e2e/compose_test.go +++ b/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) diff --git a/pkg/e2e/fixtures/dotenv/.env b/pkg/e2e/fixtures/dotenv/.env index 869938aa7..1230f22dd 100644 --- a/pkg/e2e/fixtures/dotenv/.env +++ b/pkg/e2e/fixtures/dotenv/.env @@ -1 +1,3 @@ -COMPOSE_FILE="${COMPOSE_FILE:-development/compose.yaml}" \ No newline at end of file +COMPOSE_FILE="${COMPOSE_FILE:-development/compose.yaml}" +IMAGE_NAME=test +COMPOSE_PROFILES=test \ No newline at end of file diff --git a/pkg/e2e/fixtures/dotenv/development/compose.yaml b/pkg/e2e/fixtures/dotenv/development/compose.yaml index b44805e30..4731d635b 100644 --- a/pkg/e2e/fixtures/dotenv/development/compose.yaml +++ b/pkg/e2e/fixtures/dotenv/development/compose.yaml @@ -1,3 +1,7 @@ services: backend: image: $IMAGE_NAME:$IMAGE_TAG + test: + profiles: + - test + image: enabled:profile \ No newline at end of file