Skip to content

Commit 04c71e3

Browse files
authored
Merge pull request #74 from rsgalloway/v0.9.1
Version 0.9.1
2 parents b08a289 + 6370ca5 commit 04c71e3

9 files changed

Lines changed: 113 additions & 72 deletions

File tree

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ venv*/
1616
.vscode/
1717
keys.env
1818
encrypted.env
19-
baketest.env
19+
baketest.env
20+
out.env

README.md

Lines changed: 23 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,6 @@ Environment variable management system.
1212
[Python API](#python-api) |
1313
[Running Commands](#running-commands)
1414

15-
16-
| Feature | Description |
17-
|---------|-------------|
18-
| Namespaced environments | Environments in envstack are namespaced, allowing you to organize and manage variables based on different contexts or projects. Each environment stack can have its own set of variables, providing a clean separation and avoiding conflicts between different environments. |
19-
| Environment stacks | Allows you to manage environment variables using .env files called environment stacks. These stacks provide a hierarchical and contextual approach to managing variables. |
20-
| Encryption support | Secure encryption, including AES-GCM, Fernet, and Base64. This allows you to securely encrypt and decrypt sensitive environment variables. |
21-
| Hierarchical structure | Stacks can be combined and have a defined order of priority. Variables defined in higher scope stacks flow from higher scope to lower scope, left to right. |
22-
| Variable expansion modifiers | Supports bash-like variable expansion modifiers, allowing you to set default values for variables and override them in the environment or by higher scope stacks. |
23-
| Platform-specific variables | Stacks can have platform-specific variables and values. This allows you to define different values for variables based on the platform. |
24-
| Variable references | Variables can reference other variables, allowing for more flexibility and dynamic value assignment. |
25-
| Multi-line values | Supports variables with multi-line values. |
26-
| Includes | Stack files can include other stacks, making it easy to reuse and combine different stacks. |
27-
| Python API | Provides a Python API that allows you to initialize and work with environment stacks programmatically. Easily initialize pre-defined environments with Python scripts, tools, and wrappers. |
28-
| Running commands | Allows you to run command line executables inside an environment stack, providing a convenient way to execute commands with a pre-defined environment. |
29-
| Wrappers | Supports wrappers, which are command line executable scripts that automatically run a given command in the environment stack. This allows for easy customization and management of environments. |
30-
| Shell integration | Provides instructions for sourcing the environment stack in your current shell, allowing you to set and clear the environment easily. |
31-
3215
## Installation
3316

3417
The easiest way to install:
@@ -70,15 +53,13 @@ $ curl -o default.env https://raw.githubusercontent.com/rsgalloway/envstack/mast
7053
Alternatively, set `${ENVPATH}` to the directory containing your environment
7154
stack files:
7255

73-
#### bash
7456
```bash
7557
$ export ENVPATH=/path/to/env/files
7658
```
7759

7860
Define as many paths as you want, and envstack will search for stack files in
7961
order from left to right, for example:
8062

81-
#### bash
8263
```bash
8364
$ export ENVPATH=/mnt/pipe/dev/env:/mnt/pipe/prod/env
8465
```
@@ -209,50 +190,34 @@ $ envstack -s HELLO=world -o hello.env
209190
You can convert existing `.env` files to envstack by piping them into envstack:
210191
211192
```bash
212-
$ cat .env | envstack --set -o dev.env
193+
$ cat .env | envstack --set -o out.env
213194
```
214195
215-
## Creating Stacks
196+
## Creating Environments
216197
217198
Several example or starter stacks are available in the [env folder of the
218199
envstack repo](https://github.com/rsgalloway/envstack/tree/master/env).
219200
220-
To create a new environment stack, create an envstack file and declare some
221-
variables.
201+
To create a new environment file, use `--set` to declare some variables:
222202
223203
```bash
224-
$ envstack foobar -o foobar.env
225-
```
226-
227-
Add the `${FOO}` and `${BAR}` env vars to the foobar.env environment stack file:
228-
229-
```yaml
230-
#!/usr/bin/env envstack
231-
all: &all
232-
FOO: bar
233-
BAR: ${FOO}
234-
darwin:
235-
<<: *all
236-
linux:
237-
<<: *all
238-
windows:
239-
<<: *all
204+
$ envstack -s FOO=bar BAR=\${FOO} -o out.env
240205
```
241206
242-
Or using Python:
207+
Using Python:
243208
244209
```python
245210
>>> env = Env({"FOO": "bar", "BAR": "${FOO}"})
246-
>>> env.write("foobar.env")
211+
>>> env.write("out.env")
247212
```
248213
249-
Get the resolved environment for the `foobar` stack:
214+
Get the resolved values back:
250215
251216
```bash
252-
$ ./foobar.env -r
217+
$ ./out.env -r
253218
BAR=bar
254219
FOO=bar
255-
STACK=foobar
220+
STACK=out
256221
```
257222
258223
#### More Details
@@ -330,7 +295,7 @@ nodes look for keys in the following order, favoring AES-GCM over Fernet:
330295
| Fernet | ${ENVSTACK_FERNET_KEY} |
331296
332297
If no encryption keys are found in the environment, envstack will default to
333-
using Base64 encryption:
298+
using Base64 encoding:
334299
335300
```bash
336301
$ envstack --encrypt
@@ -357,7 +322,7 @@ $ source <(envstack --keygen --export)
357322
Once the keys are in the environment, you can encrypt the env stack:
358323
359324
```bash
360-
$ envstack --encrypt -o encrypted.env
325+
$ envstack -o encrypted.env --encrypt
361326
```
362327
363328
Encrypted variables will resolve as long as the key is in the environment:
@@ -369,17 +334,17 @@ HELLO=world
369334
370335
#### Storing Keys
371336
372-
Keys can be stored in other environment stacks, e.g. a `keys.env` file. To
373-
generate keys and store them in a `keys.env` env stack file:
337+
Keys can be stored in other environment stacks, e.g. a `keys.env` file
338+
(keys are automatically base64 encoded):
374339
375340
```bash
376341
$ envstack --keygen -o keys.env
377342
```
378343
379-
Then use the `keys.env` env stack to encrypt any other env stack:
344+
Then use `keys.env` to encrypt any other environment files:
380345
381346
```bash
382-
$ envstack keys -- envstack --encrypt -o encrypted.env
347+
$ ./keys.env -- envstack -eo encrypted.env
383348
```
384349
385350
To decrypt, add `keys` to the env stack:
@@ -389,7 +354,14 @@ $ envstack keys encrypted -r HELLO
389354
HELLO=world
390355
```
391356
392-
Or add the `keys` env stack to `include` to automatically decrypt:
357+
Or run the command inside the `keys` environment like this:
358+
359+
```bash
360+
$ ./keys.env -- envsatck encrypted -r HELLO
361+
HELLO=world
362+
```
363+
364+
Or include `keys` in environments to automatically decrypt:
393365
394366
```yaml
395367
include: [keys]

lib/envstack/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,6 @@
3434
"""
3535

3636
__prog__ = "envstack"
37-
__version__ = "0.9.0"
37+
__version__ = "0.9.1"
3838

3939
from envstack.env import clear, init, revert, save # noqa: F401

lib/envstack/cli.py

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -205,8 +205,14 @@ def parse_args():
205205
"--set",
206206
nargs="*",
207207
action=StoreOnce,
208-
metavar="VAR=VALUE",
209-
help="convert KEY=VALUE pairs to envstack environment variables",
208+
metavar="KEY=VALUE",
209+
help="overlay KEY=VALUE pairs to envstack environments",
210+
)
211+
parser.add_argument(
212+
"-b",
213+
"--bare",
214+
action="store_true",
215+
help="create a bare environment",
210216
)
211217
parser.add_argument(
212218
"--scope",
@@ -234,6 +240,12 @@ def parse_args():
234240
action="store_true",
235241
help="list the env stack file sources",
236242
)
243+
export_group.add_argument(
244+
"-q",
245+
"--quiet",
246+
action="store_true",
247+
help="print values only",
248+
)
237249

238250
args = parser.parse_args(args_before_dash)
239251

@@ -278,6 +290,13 @@ def main():
278290
elif args.set is not None:
279291
force_stdin = args.set == [] or args.set == ["-"]
280292
using_pipe = args.set == [] and not sys.stdin.isatty()
293+
294+
# load the environment if not in bare mode
295+
if args.bare:
296+
env = Env()
297+
else:
298+
env = load_environ(args.namespace, platform=args.platform)
299+
281300
# interactive mode
282301
if force_stdin and sys.stdin.isatty():
283302
print(
@@ -302,15 +321,27 @@ def main():
302321
else:
303322
data = _parse_keyvals(args.set)
304323

324+
# encrypt the new data only
305325
if args.encrypt:
306-
data = encrypt_environ(data, encrypt=(not args.out))
326+
data = encrypt_environ(data)
327+
328+
# update the environment with the new data
329+
env.update(data)
330+
307331
if args.export:
308-
print(export_env_to_shell(data))
332+
print(export_env_to_shell(env))
309333
elif args.out:
310-
Env(data).write(args.out)
334+
Env(env).write(args.out)
311335
else:
312-
for key, value in data.items():
313-
print(f"{key}={value}")
336+
for key, val in env.items():
337+
if args.quiet:
338+
if len(env) > 1:
339+
print("error: --quiet requires exactly one KEY")
340+
return 2
341+
else:
342+
print(val)
343+
else:
344+
print(f"{key}={val}")
314345

315346
elif args.out:
316347
bake_environ(
@@ -327,14 +358,30 @@ def main():
327358
keys = args.resolve or resolved.keys()
328359
for key in sorted(str(k) for k in keys):
329360
val = resolved.get(key)
330-
print(f"{key}={val}")
361+
if key in resolved:
362+
if args.quiet:
363+
if len(keys) > 1:
364+
print("error: --quiet requires exactly one KEY")
365+
return 2
366+
else:
367+
print(val)
368+
else:
369+
print(f"{key}={val}")
331370

332371
elif args.trace is not None:
333372
if len(args.trace) == 0:
334373
args.trace = load_environ(args.namespace).keys()
335374
for trace in args.trace:
336375
path = trace_var(*args.namespace, var=trace)
337-
print("{0}: {1}".format(trace, path))
376+
if path:
377+
if args.quiet:
378+
if len(args.trace) > 1:
379+
print("error: --quiet requires exactly one KEY")
380+
return 2
381+
else:
382+
print(path)
383+
else:
384+
print("{0}={1}".format(trace, path))
338385

339386
elif args.sources:
340387
env = load_environ(args.namespace, platform=args.platform)

lib/envstack/encrypt.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -313,10 +313,12 @@ def generate_keys():
313313
314314
:returns: Dictionary containing Fernet and AES-GCM keys.
315315
"""
316+
from envstack.node import Base64Node
317+
316318
symmetric_key = AESGCMEncryptor.generate_key()
317319
fernet_key = FernetEncryptor.generate_key()
318320

319321
return {
320-
AESGCMEncryptor.KEY_VAR_NAME: symmetric_key,
321-
FernetEncryptor.KEY_VAR_NAME: fernet_key,
322+
AESGCMEncryptor.KEY_VAR_NAME: Base64Node(symmetric_key),
323+
FernetEncryptor.KEY_VAR_NAME: Base64Node(fernet_key),
322324
}

lib/envstack/env.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,10 @@ def get_node_class(value):
344344

345345
# write the baked environment to the file
346346
if filename:
347-
baked.write()
347+
try:
348+
baked.write()
349+
except Exception as err:
350+
raise WriteError(f"Failed to write {filename}", err)
348351

349352
# create the baked environment from the baked source
350353
baked_env = Env()
@@ -791,6 +794,7 @@ def encrypt_environ(
791794
node = node_class(v)
792795
if encrypt:
793796
node.value = node.encryptor(env=resolved_env).encrypt(str(v))
797+
node.original_value = node.value
794798
encrypted_env[k] = node
795799
else:
796800
encrypted_env[k] = v

lib/envstack/exceptions.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,9 @@ class TemplateNotFound(Exception):
7474
"""Custom exception class for missing Templates."""
7575

7676
pass
77+
78+
79+
class WriteError(Exception):
80+
"""Custom exception class for errors during export operations."""
81+
82+
pass

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040

4141
setup(
4242
name="envstack",
43-
version="0.9.0",
43+
version="0.9.1",
4444
description="Stacked environment variable management system",
4545
long_description=long_description,
4646
long_description_content_type="text/markdown",

0 commit comments

Comments
 (0)