Default Example

Start by downloading some example files:

curl -o \
default.env \
https://raw.githubusercontent.com/rsgalloway/envstack/master/examples/default/default.env
curl -o \
dev.env \
https://raw.githubusercontent.com/rsgalloway/envstack/master/examples/default/dev.env
curl -o \
data.env \
https://raw.githubusercontent.com/rsgalloway/envstack/master/examples/default/data.env

See the unresolved environment variable values for the default.env environment:

$ envstack -u
DEPLOY_ROOT=${ROOT}/${ENV}
ENV=prod
ENVPATH=${DEPLOY_ROOT}/env:${ENVPATH}
LOG_LEVEL=${LOG_LEVEL:=INFO}
PATH=${DEPLOY_ROOT}/bin:${PATH}
PS1=\[\e[32m\](../${ENV})\[\e[0m\] \w\$ 
PYTHONPATH=${DEPLOY_ROOT}/lib/python:${PYTHONPATH}
ROOT=/mnt/pipe
STACK=default

Running envstack launches a new shell session with the resolved environment:

$ envstack
🚀 Launching envstack shell... (CTRL+D or "exit" to quit)
(prod) ~$ echo $ROOT
/mnt/pipe

Includes

Environment stack files can include other namespaced environments:

include: [default, test]

In practice, most derived stacks should include default.

Loading Environments With Inheritance

Load the dev.env environment’s unresolved values:

$ envstack dev -u
DEPLOY_ROOT=${ROOT}/dev
ENV=dev
ENVPATH=${ROOT}/dev/env:${ROOT}/prod/env:${ENVPATH}
LOG_LEVEL=DEBUG
PATH=${ROOT}/dev/bin:${ROOT}/prod/bin:${PATH}
PS1=\[\e[32m\](../${ENV})\[\e[0m\] \w\$ 
PYTHONPATH=${ROOT}/dev/lib/python:${ROOT}/prod/lib/python:${PYTHONPATH}
ROOT=/mnt/pipe
STACK=dev

Note how ROOT is undefined in dev.env, and inherited from default.env. The dev.env environment also overrides some of the values in default.env, including PYTHONPATH and PATH. Here, dev paths take precedence over prod paths because dev.env sets ENVPATH with the higher-priority directory first:

$ envstack dev
🚀 Launching envstack shell... (CTRL+D or "exit" to quit)
(dev) $ echo $PATH
/mnt/pipe/dev/bin:/mnt/pipe/prod/bin:

Storing Data Types

You can store complex data types in envstack, including dict and list types. Values can themselves reference other vars:

$ envstack data -u
CHAR_LIST=['a', 'b', 'c', '${HELLO}']
DICT={'a': 1, 'b': 2, 'c': '${INT}'}
FLOAT=1.0
HELLO=world
INT=5
LOG_LEVEL=${LOG_LEVEL:=INFO}
NUMBER_LIST=[1, 2, 3]
STACK=data
$ envstack data -r CHAR_LIST
CHAR_LIST=['a', 'b', 'c', 'world']

Data types are also automatically converted using safe_eval when loading environments in Python:

>>> import envstack
>>> env = envstack.load_environ("data")
>>> env.get("DICT")
{'a': 1, 'b': 2, 'c': '${INT}'}
>>> resolved = envstack.resolve_environ(env)
>>> resolved.get("DICT")
{'a': '1', 'b': '2', 'c': '5'}

Executing Environments

envstack files are scripts that can be executed:

$ envstack test -s FOO=bar -o test.env
$ ./test.env -- echo {FOO}
bar

Or exported:

$ ./test.env --export
export FOO=bar
export STACK=test

Command Substitution and Derived Values

The test.env example shows how a stack can derive a value from a command at resolution time:

PYVERSION: $(python -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')")
PYTHONPATH: ${DEPLOY_ROOT}/lib/python${PYVERSION}
NUKESCRIPT: ${ROOT}/projects/{seq}/{shot}/comp/{show}_{seq}_{shot}.{version}.nk

This is useful when a path or tool setting depends on the local runtime. For example, PYTHONPATH can follow the active interpreter’s major/minor version without hard-coding it into every stack file.

To inspect it:

$ envstack test -u
PYVERSION=$(python -c "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}')")
PYTHONPATH=${DEPLOY_ROOT}/lib/python${PYVERSION}
NUKESCRIPT=${ROOT}/projects/{seq}/{shot}/comp/{show}_{seq}_{shot}.{version}.nk
$ envstack test -r PYVERSION

That returns the major/minor interpreter version detected on the local machine, and PYTHONPATH resolves using that value.

More Details

Variables can be platform specific:

darwin:
  HELLO: olleh
linux:
  HELLO: world
windows:
  HELLO: goodbye

Variables can reference other variables:

all: &all
  FOO: ${BAR}
  BAR: ${BAZ}
  BAZ: ${BIZ}
  BIZ: ${BIZ:=foo}

As you might expect, the above resolves to:

$ envstack -r
BAR=foo
BAZ=foo
BIZ=foo
FOO=foo

Here is an example using nested variable expansion:

FOO: ${BIZ:=${BAR:=${BAZ:=baz}}}

Resolves to:

$ envstack -r
FOO=baz

For the complete expansion syntax reference, including ${VAR}, ${VAR:=default}, ${VAR:-default}, and ${VAR:?message}, see Variable expansion.