diff --git a/cmd/compose/run.go b/cmd/compose/run.go index c5df79e02..137c2526b 100644 --- a/cmd/compose/run.go +++ b/cmd/compose/run.go @@ -19,8 +19,10 @@ package compose import ( "context" "fmt" + "os" "strings" + "github.com/compose-spec/compose-go/v2/dotenv" "github.com/compose-spec/compose-go/v2/format" xprogress "github.com/moby/buildkit/util/progress/progressui" "github.com/sirupsen/logrus" @@ -44,6 +46,7 @@ type runOptions struct { Service string Command []string environment []string + envFiles []string Detach bool Remove bool noTty bool @@ -116,6 +119,29 @@ func (options runOptions) apply(project *types.Project) (*types.Project, error) return project, nil } +func (options runOptions) getEnvironment() (types.Mapping, error) { + environment := types.NewMappingWithEquals(options.environment).Resolve(os.LookupEnv).ToMapping() + for _, file := range options.envFiles { + f, err := os.Open(file) + if err != nil { + return nil, err + } + vars, err := dotenv.ParseWithLookup(f, func(k string) (string, bool) { + value, ok := environment[k] + return value, ok + }) + if err != nil { + return nil, nil + } + for k, v := range vars { + if _, ok := environment[k]; !ok { + environment[k] = v + } + } + } + return environment, nil +} + func runCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command { options := runOptions{ composeOptions: &composeOptions{ @@ -175,6 +201,7 @@ func runCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) * flags := cmd.Flags() flags.BoolVarP(&options.Detach, "detach", "d", false, "Run container in background and print container ID") flags.StringArrayVarP(&options.environment, "env", "e", []string{}, "Set environment variables") + flags.StringArrayVar(&options.envFiles, "env-from-file", []string{}, "Set environment variables from file") flags.StringArrayVarP(&options.labels, "label", "l", []string{}, "Add or override a label") flags.BoolVar(&options.Remove, "rm", false, "Automatically remove the container when it exits") flags.BoolVarP(&options.noTty, "no-TTY", "T", !dockerCli.Out().IsTerminal(), "Disable pseudo-TTY allocation (default: auto-detected)") @@ -264,6 +291,11 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op buildForRun = &bo } + environment, err := options.getEnvironment() + if err != nil { + return err + } + // start container and attach to container streams runOpts := api.RunOptions{ Build: buildForRun, @@ -278,7 +310,7 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op User: options.user, CapAdd: options.capAdd.GetAll(), CapDrop: options.capDrop.GetAll(), - Environment: options.environment, + Environment: environment.Values(), Entrypoint: options.entrypointCmd, Labels: labels, UseNetworkAliases: options.useAliases, diff --git a/docs/reference/compose_run.md b/docs/reference/compose_run.md index f46d1872d..e4be01d2d 100644 --- a/docs/reference/compose_run.md +++ b/docs/reference/compose_run.md @@ -66,6 +66,7 @@ specified in the service configuration. | `--dry-run` | `bool` | | Execute command in dry run mode | | `--entrypoint` | `string` | | Override the entrypoint of the image | | `-e`, `--env` | `stringArray` | | Set environment variables | +| `--env-from-file` | `stringArray` | | Set environment variables from file | | `-i`, `--interactive` | `bool` | `true` | Keep STDIN open even if not attached | | `-l`, `--label` | `stringArray` | | Add or override a label | | `--name` | `string` | | Assign a name to the container | diff --git a/docs/reference/docker_compose_run.yaml b/docs/reference/docker_compose_run.yaml index dc19a95de..6e6ec71f8 100644 --- a/docs/reference/docker_compose_run.yaml +++ b/docs/reference/docker_compose_run.yaml @@ -117,6 +117,16 @@ options: experimentalcli: false kubernetes: false swarm: false + - option: env-from-file + value_type: stringArray + default_value: '[]' + description: Set environment variables from file + deprecated: false + hidden: false + experimental: false + experimentalcli: false + kubernetes: false + swarm: false - option: interactive shorthand: i value_type: bool diff --git a/pkg/e2e/compose_run_test.go b/pkg/e2e/compose_run_test.go index 438cd6f6f..fa8fda686 100644 --- a/pkg/e2e/compose_run_test.go +++ b/pkg/e2e/compose_run_test.go @@ -178,4 +178,10 @@ func TestLocalComposeRun(t *testing.T) { assert.Assert(t, strings.Contains(res.Combined(), "backend Pulling"), res.Combined()) assert.Assert(t, strings.Contains(res.Combined(), "backend Pulled"), res.Combined()) }) + + t.Run("compose run --env-from-file", func(t *testing.T) { + res := c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/compose.yaml", "run", "--env-from-file", "./fixtures/run-test/run.env", + "front", "env") + res.Assert(t, icmd.Expected{Out: "FOO=BAR"}) + }) } diff --git a/pkg/e2e/fixtures/run-test/compose.yaml b/pkg/e2e/fixtures/run-test/compose.yaml index 0168dc240..8162dba08 100644 --- a/pkg/e2e/fixtures/run-test/compose.yaml +++ b/pkg/e2e/fixtures/run-test/compose.yaml @@ -1,4 +1,3 @@ -version: '3.8' services: back: image: alpine diff --git a/pkg/e2e/fixtures/run-test/run.env b/pkg/e2e/fixtures/run-test/run.env new file mode 100644 index 000000000..6ac867af7 --- /dev/null +++ b/pkg/e2e/fixtures/run-test/run.env @@ -0,0 +1 @@ +FOO=BAR