Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ TODO.md
__debug_bin*
# Added by goreleaser init:
dist/
.env
2 changes: 1 addition & 1 deletion cmd/lets/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ func main() {
if errors.As(err, &depErr) {
executor.PrintDependencyTree(depErr, os.Stderr)
}
log.Error(err.Error())
log.Errorf("lets: %s", err.Error())
os.Exit(getExitCode(err, 1))
}
}
Expand Down
1 change: 1 addition & 0 deletions docs/docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ title: Changelog
* `[Added]` Expose `LETS_OS` and `LETS_ARCH` environment variables at command runtime.
* `[Removed]` Drop deprecated `eval_env` directive. Use `env` with `sh` execution mode instead.
* `[Added]` When a command or its `depends` chain fails, print an indented tree to stderr showing the full chain with the failing command highlighted
* `[Added]` Support `env_file` in global config and commands. File names are expanded after `env` is resolved, and values loaded from env files override values from `env`.

## [0.0.59](https://github.com/lets-cli/lets/releases/tag/v0.0.59)

Expand Down
89 changes: 89 additions & 0 deletions docs/docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,58 @@ env:
sh: echo "${ENGINE}-compose"
```

### Global env_file

`key: env_file`

`type: string | map | list`

Load one or more dotenv-style files and expose their values to all commands.

Supported forms:

```yaml
env_file: .env
env_file: -.env.local
env_file:
name: .env.${TARGET}
required: false
env_file:
- .env
- -.env.local
- name: .env.${LETS_OS}
required: true
```

Rules:

- `-filename` is a short form of `required: false`
- files are resolved relative to the config directory
- file names are expanded after global `env` is resolved, so `env_file` can depend on global `env`
- values loaded from `env_file` have higher precedence than values from `env`
- missing files fail by default
- invalid env file syntax reports an error with the file name

Example:

```yaml
shell: bash

env:
TARGET: dev
ENGINE: docker

env_file:
- .env.${TARGET}
- -.env.local

commands:
echo-env:
cmd: |
echo ENGINE=${ENGINE}
echo API_URL=${API_URL}
```

### Global before

`key: before`
Expand Down Expand Up @@ -696,6 +748,43 @@ commands:
```


### `env_file`

`key: env_file`

`type: string | map | list`

Load dotenv-style env files for a single command.

Rules:

- command `env` is resolved first
- command `env_file` file names are expanded using builtin lets vars, merged global env, and resolved command `env`
- values loaded from command `env_file` override values from command `env`
- paths are resolved relative to the config directory, not `work_dir`

Example:

```yaml
shell: bash

env:
TARGET: dev

commands:
up:
env:
SUFFIX: local
ENGINE: docker
env_file:
- .env.${TARGET}.${SUFFIX}
- -.env.override
cmd: |
echo ENGINE=${ENGINE}
echo API_URL=${API_URL}
```


### `checksum`

`key: checksum`
Expand Down
22 changes: 22 additions & 0 deletions docs/docs/env.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,28 @@ title: Environment

### Override command env with -E flag

### `env_file` precedence

`env_file` loads dotenv-style files from config and command definitions.

Precedence order is:

* process env
* builtin lets vars
* global `env`
* global `env_file`
* command `env`
* command `env_file`
* command options, `-E` / `--env`, checksum vars

When the same key is present in both directives, `env_file` wins over `env` at the same scope.

`env_file` file names are expanded after `env` is resolved. This means:

* global `env_file` can depend on global `env`
* command `env_file` can depend on merged global env and command `env`
* `env.sh` still does not read values from `env_file`

You can override environment for command with `-E` flag:

```yaml
Expand Down
45 changes: 45 additions & 0 deletions docs/static/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@
"env": {
"$ref": "#/definitions/env"
},
"env_file": {
"$ref": "#/definitions/env_file"
},
"before": {
"type": "string",
"description": "Commands to run before the main script."
Expand Down Expand Up @@ -137,6 +140,9 @@
"env": {
"$ref": "#/definitions/env"
},
"env_file": {
"$ref": "#/definitions/env_file"
},
"after": {
"type": "string",
"description": "A shell sctipt to run after the command."
Expand Down Expand Up @@ -192,6 +198,45 @@
}
},
"additionalProperties": false
},
"env_file_entry": {
"oneOf": [
{
"type": "string",
"description": "Path to an env file. Prefix with '-' to make it optional."
},
{
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Path to an env file."
},
"required": {
"type": "boolean",
"description": "Whether the env file must exist. Defaults to true."
}
},
"required": [
"name"
],
"additionalProperties": false
}
]
},
"env_file": {
"description": "Env file or list of env files to load.",
"oneOf": [
{
"$ref": "#/definitions/env_file_entry"
},
{
"type": "array",
"items": {
"$ref": "#/definitions/env_file_entry"
}
}
]
}
},
"additionalProperties": false
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ require (
require (
github.com/h2non/filetype v1.1.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/joho/godotenv v1.5.1
github.com/juju/errors v0.0.0-20200330140219-3fe23663418f // indirect
github.com/juju/testing v0.0.0-20201216035041-2be42bba85f3 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSAS
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/juju/ansiterm v0.0.0-20160907234532-b99631de12cf/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU=
github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c/go.mod h1:nD0vlnrUjcjJhqN5WuCWZyzfd5AHZAC9/ajvbSx69xA=
github.com/juju/cmd v0.0.0-20171107070456-e74f39857ca0/go.mod h1:yWJQHl73rdSX4DHVKGqkAip+huBslxRwS8m9CrOLq18=
Expand Down
39 changes: 36 additions & 3 deletions internal/config/config/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ type Command struct {
Description string
// env from command
Env *Envs
// env files from command
EnvFiles *EnvFiles
// store docopts from options directive
Docopts string
SkipDocopts bool // default false
Expand Down Expand Up @@ -62,6 +64,7 @@ func (c *Command) UnmarshalYAML(unmarshal func(interface{}) error) error {
Description string
Shell string
Env *Envs
EnvFiles *EnvFiles `yaml:"env_file"`
Options string
Depends *Deps
WorkDir string `yaml:"work_dir"`
Expand All @@ -87,6 +90,10 @@ func (c *Command) UnmarshalYAML(unmarshal func(interface{}) error) error {
if c.Env == nil {
c.Env = &Envs{}
}
c.EnvFiles = cmd.EnvFiles
if c.EnvFiles == nil {
c.EnvFiles = &EnvFiles{}
}

c.Shell = cmd.Shell
c.Docopts = cmd.Options
Expand Down Expand Up @@ -126,12 +133,37 @@ func (c *Command) UnmarshalYAML(unmarshal func(interface{}) error) error {
return nil
}

func (c *Command) GetEnv(cfg Config) (map[string]string, error) {
if err := c.Env.Execute(cfg, cfg.GetEnv()); err != nil {
func (c *Command) GetEnv(cfg Config, builtinEnv map[string]string) (map[string]string, error) {
baseEnv := cloneMap(builtinEnv)
if baseEnv == nil {
baseEnv = make(map[string]string)
}
for key, value := range cfg.GetEnv() {
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
baseEnv[key] = value
}

envs := c.Env.Clone()
if err := envs.Execute(cfg, baseEnv); err != nil {
return nil, err
}

return c.Env.Dump(), nil
filenameEnv := cloneMap(baseEnv)
for key, value := range envs.Dump() {
filenameEnv[key] = value
}

envFiles := c.EnvFiles.Clone()
envFileEnv, err := envFiles.Load(cfg, filenameEnv)
if err != nil {
return nil, fmt.Errorf("lets: failed to resolve env_file for command '%s': %w", c.Name, err)
}

resolvedEnv := envs.Dump()
for key, value := range envFileEnv {
resolvedEnv[key] = value
}

return resolvedEnv, nil
}

func (c *Command) Clone() *Command {
Expand All @@ -144,6 +176,7 @@ func (c *Command) Clone() *Command {
WorkDir: c.WorkDir,
Description: c.Description,
Env: c.Env.Clone(),
EnvFiles: c.EnvFiles.Clone(),
Docopts: c.Docopts,
SkipDocopts: c.SkipDocopts,
Options: cloneMap(c.Options),
Expand Down
Loading
Loading