envstack 0.9.0__tar.gz → 0.9.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. {envstack-0.9.0/lib/envstack.egg-info → envstack-0.9.2}/PKG-INFO +24 -52
  2. {envstack-0.9.0 → envstack-0.9.2}/README.md +23 -51
  3. {envstack-0.9.0 → envstack-0.9.2}/dist.json +2 -2
  4. {envstack-0.9.0 → envstack-0.9.2}/lib/envstack/__init__.py +1 -1
  5. {envstack-0.9.0 → envstack-0.9.2}/lib/envstack/cli.py +104 -27
  6. {envstack-0.9.0 → envstack-0.9.2}/lib/envstack/encrypt.py +4 -2
  7. {envstack-0.9.0 → envstack-0.9.2}/lib/envstack/env.py +56 -25
  8. {envstack-0.9.0 → envstack-0.9.2}/lib/envstack/exceptions.py +6 -0
  9. {envstack-0.9.0 → envstack-0.9.2/lib/envstack.egg-info}/PKG-INFO +24 -52
  10. {envstack-0.9.0 → envstack-0.9.2}/setup.py +1 -1
  11. {envstack-0.9.0 → envstack-0.9.2}/tests/test_cmds.py +44 -44
  12. {envstack-0.9.0 → envstack-0.9.2}/tests/test_env.py +3 -2
  13. {envstack-0.9.0 → envstack-0.9.2}/LICENSE +0 -0
  14. {envstack-0.9.0 → envstack-0.9.2}/lib/envstack/config.py +0 -0
  15. {envstack-0.9.0 → envstack-0.9.2}/lib/envstack/logger.py +0 -0
  16. {envstack-0.9.0 → envstack-0.9.2}/lib/envstack/node.py +0 -0
  17. {envstack-0.9.0 → envstack-0.9.2}/lib/envstack/path.py +0 -0
  18. {envstack-0.9.0 → envstack-0.9.2}/lib/envstack/util.py +0 -0
  19. {envstack-0.9.0 → envstack-0.9.2}/lib/envstack/wrapper.py +0 -0
  20. {envstack-0.9.0 → envstack-0.9.2}/lib/envstack.egg-info/SOURCES.txt +0 -0
  21. {envstack-0.9.0 → envstack-0.9.2}/lib/envstack.egg-info/dependency_links.txt +0 -0
  22. {envstack-0.9.0 → envstack-0.9.2}/lib/envstack.egg-info/entry_points.txt +0 -0
  23. {envstack-0.9.0 → envstack-0.9.2}/lib/envstack.egg-info/not-zip-safe +0 -0
  24. {envstack-0.9.0 → envstack-0.9.2}/lib/envstack.egg-info/requires.txt +0 -0
  25. {envstack-0.9.0 → envstack-0.9.2}/lib/envstack.egg-info/top_level.txt +0 -0
  26. {envstack-0.9.0 → envstack-0.9.2}/setup.cfg +0 -0
  27. {envstack-0.9.0 → envstack-0.9.2}/tests/test_encrypt.py +0 -0
  28. {envstack-0.9.0 → envstack-0.9.2}/tests/test_node.py +0 -0
  29. {envstack-0.9.0 → envstack-0.9.2}/tests/test_util.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: envstack
3
- Version: 0.9.0
3
+ Version: 0.9.2
4
4
  Summary: Stacked environment variable management system
5
5
  Home-page: http://github.com/rsgalloway/envstack
6
6
  Author: Ryan Galloway
@@ -36,23 +36,6 @@ Environment variable management system.
36
36
  [Python API](#python-api) |
37
37
  [Running Commands](#running-commands)
38
38
 
39
-
40
- | Feature | Description |
41
- |---------|-------------|
42
- | 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. |
43
- | 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. |
44
- | Encryption support | Secure encryption, including AES-GCM, Fernet, and Base64. This allows you to securely encrypt and decrypt sensitive environment variables. |
45
- | 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. |
46
- | 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. |
47
- | Platform-specific variables | Stacks can have platform-specific variables and values. This allows you to define different values for variables based on the platform. |
48
- | Variable references | Variables can reference other variables, allowing for more flexibility and dynamic value assignment. |
49
- | Multi-line values | Supports variables with multi-line values. |
50
- | Includes | Stack files can include other stacks, making it easy to reuse and combine different stacks. |
51
- | 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. |
52
- | 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. |
53
- | 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. |
54
- | Shell integration | Provides instructions for sourcing the environment stack in your current shell, allowing you to set and clear the environment easily. |
55
-
56
39
  ## Installation
57
40
 
58
41
  The easiest way to install:
@@ -94,7 +77,6 @@ $ curl -o default.env https://raw.githubusercontent.com/rsgalloway/envstack/mast
94
77
  Alternatively, set `${ENVPATH}` to the directory containing your environment
95
78
  stack files:
96
79
 
97
- #### bash
98
80
  ```bash
99
81
  $ export ENVPATH=/path/to/env/files
100
82
  ```
@@ -102,7 +84,6 @@ $ export ENVPATH=/path/to/env/files
102
84
  Define as many paths as you want, and envstack will search for stack files in
103
85
  order from left to right, for example:
104
86
 
105
- #### bash
106
87
  ```bash
107
88
  $ export ENVPATH=/mnt/pipe/dev/env:/mnt/pipe/prod/env
108
89
  ```
@@ -233,50 +214,34 @@ $ envstack -s HELLO=world -o hello.env
233
214
  You can convert existing `.env` files to envstack by piping them into envstack:
234
215
 
235
216
  ```bash
236
- $ cat .env | envstack --set -o dev.env
217
+ $ cat .env | envstack --set -o out.env
237
218
  ```
238
219
 
239
- ## Creating Stacks
220
+ ## Creating Environments
240
221
 
241
222
  Several example or starter stacks are available in the [env folder of the
242
223
  envstack repo](https://github.com/rsgalloway/envstack/tree/master/env).
243
224
 
244
- To create a new environment stack, create an envstack file and declare some
245
- variables.
225
+ To create a new environment file, use `--set` to declare some variables:
246
226
 
247
227
  ```bash
248
- $ envstack foobar -o foobar.env
249
- ```
250
-
251
- Add the `${FOO}` and `${BAR}` env vars to the foobar.env environment stack file:
252
-
253
- ```yaml
254
- #!/usr/bin/env envstack
255
- all: &all
256
- FOO: bar
257
- BAR: ${FOO}
258
- darwin:
259
- <<: *all
260
- linux:
261
- <<: *all
262
- windows:
263
- <<: *all
228
+ $ envstack -s FOO=bar BAR=\${FOO} -o out.env
264
229
  ```
265
230
 
266
- Or using Python:
231
+ Using Python:
267
232
 
268
233
  ```python
269
234
  >>> env = Env({"FOO": "bar", "BAR": "${FOO}"})
270
- >>> env.write("foobar.env")
235
+ >>> env.write("out.env")
271
236
  ```
272
237
 
273
- Get the resolved environment for the `foobar` stack:
238
+ Get the resolved values back:
274
239
 
275
240
  ```bash
276
- $ ./foobar.env -r
241
+ $ ./out.env -r
277
242
  BAR=bar
278
243
  FOO=bar
279
- STACK=foobar
244
+ STACK=out
280
245
  ```
281
246
 
282
247
  #### More Details
@@ -354,7 +319,7 @@ nodes look for keys in the following order, favoring AES-GCM over Fernet:
354
319
  | Fernet | ${ENVSTACK_FERNET_KEY} |
355
320
 
356
321
  If no encryption keys are found in the environment, envstack will default to
357
- using Base64 encryption:
322
+ using Base64 encoding:
358
323
 
359
324
  ```bash
360
325
  $ envstack --encrypt
@@ -381,7 +346,7 @@ $ source <(envstack --keygen --export)
381
346
  Once the keys are in the environment, you can encrypt the env stack:
382
347
 
383
348
  ```bash
384
- $ envstack --encrypt -o encrypted.env
349
+ $ envstack -o encrypted.env --encrypt
385
350
  ```
386
351
 
387
352
  Encrypted variables will resolve as long as the key is in the environment:
@@ -393,17 +358,17 @@ HELLO=world
393
358
 
394
359
  #### Storing Keys
395
360
 
396
- Keys can be stored in other environment stacks, e.g. a `keys.env` file. To
397
- generate keys and store them in a `keys.env` env stack file:
361
+ Keys can be stored in other environment stacks, e.g. a `keys.env` file
362
+ (keys are automatically base64 encoded):
398
363
 
399
364
  ```bash
400
365
  $ envstack --keygen -o keys.env
401
366
  ```
402
367
 
403
- Then use the `keys.env` env stack to encrypt any other env stack:
368
+ Then use `keys.env` to encrypt any other environment files:
404
369
 
405
370
  ```bash
406
- $ envstack keys -- envstack --encrypt -o encrypted.env
371
+ $ ./keys.env -- envstack -eo encrypted.env
407
372
  ```
408
373
 
409
374
  To decrypt, add `keys` to the env stack:
@@ -413,7 +378,14 @@ $ envstack keys encrypted -r HELLO
413
378
  HELLO=world
414
379
  ```
415
380
 
416
- Or add the `keys` env stack to `include` to automatically decrypt:
381
+ Or run the command inside the `keys` environment like this:
382
+
383
+ ```bash
384
+ $ ./keys.env -- envsatck encrypted -r HELLO
385
+ HELLO=world
386
+ ```
387
+
388
+ Or include `keys` in environments to automatically decrypt:
417
389
 
418
390
  ```yaml
419
391
  include: [keys]
@@ -12,23 +12,6 @@ Environment variable management system.
12
12
  [Python API](#python-api) |
13
13
  [Running Commands](#running-commands)
14
14
 
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
-
32
15
  ## Installation
33
16
 
34
17
  The easiest way to install:
@@ -70,7 +53,6 @@ $ curl -o default.env https://raw.githubusercontent.com/rsgalloway/envstack/mast
70
53
  Alternatively, set `${ENVPATH}` to the directory containing your environment
71
54
  stack files:
72
55
 
73
- #### bash
74
56
  ```bash
75
57
  $ export ENVPATH=/path/to/env/files
76
58
  ```
@@ -78,7 +60,6 @@ $ export ENVPATH=/path/to/env/files
78
60
  Define as many paths as you want, and envstack will search for stack files in
79
61
  order from left to right, for example:
80
62
 
81
- #### bash
82
63
  ```bash
83
64
  $ export ENVPATH=/mnt/pipe/dev/env:/mnt/pipe/prod/env
84
65
  ```
@@ -209,50 +190,34 @@ $ envstack -s HELLO=world -o hello.env
209
190
  You can convert existing `.env` files to envstack by piping them into envstack:
210
191
 
211
192
  ```bash
212
- $ cat .env | envstack --set -o dev.env
193
+ $ cat .env | envstack --set -o out.env
213
194
  ```
214
195
 
215
- ## Creating Stacks
196
+ ## Creating Environments
216
197
 
217
198
  Several example or starter stacks are available in the [env folder of the
218
199
  envstack repo](https://github.com/rsgalloway/envstack/tree/master/env).
219
200
 
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:
222
202
 
223
203
  ```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
240
205
  ```
241
206
 
242
- Or using Python:
207
+ Using Python:
243
208
 
244
209
  ```python
245
210
  >>> env = Env({"FOO": "bar", "BAR": "${FOO}"})
246
- >>> env.write("foobar.env")
211
+ >>> env.write("out.env")
247
212
  ```
248
213
 
249
- Get the resolved environment for the `foobar` stack:
214
+ Get the resolved values back:
250
215
 
251
216
  ```bash
252
- $ ./foobar.env -r
217
+ $ ./out.env -r
253
218
  BAR=bar
254
219
  FOO=bar
255
- STACK=foobar
220
+ STACK=out
256
221
  ```
257
222
 
258
223
  #### More Details
@@ -330,7 +295,7 @@ nodes look for keys in the following order, favoring AES-GCM over Fernet:
330
295
  | Fernet | ${ENVSTACK_FERNET_KEY} |
331
296
 
332
297
  If no encryption keys are found in the environment, envstack will default to
333
- using Base64 encryption:
298
+ using Base64 encoding:
334
299
 
335
300
  ```bash
336
301
  $ envstack --encrypt
@@ -357,7 +322,7 @@ $ source <(envstack --keygen --export)
357
322
  Once the keys are in the environment, you can encrypt the env stack:
358
323
 
359
324
  ```bash
360
- $ envstack --encrypt -o encrypted.env
325
+ $ envstack -o encrypted.env --encrypt
361
326
  ```
362
327
 
363
328
  Encrypted variables will resolve as long as the key is in the environment:
@@ -369,17 +334,17 @@ HELLO=world
369
334
 
370
335
  #### Storing Keys
371
336
 
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):
374
339
 
375
340
  ```bash
376
341
  $ envstack --keygen -o keys.env
377
342
  ```
378
343
 
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:
380
345
 
381
346
  ```bash
382
- $ envstack keys -- envstack --encrypt -o encrypted.env
347
+ $ ./keys.env -- envstack -eo encrypted.env
383
348
  ```
384
349
 
385
350
  To decrypt, add `keys` to the env stack:
@@ -389,7 +354,14 @@ $ envstack keys encrypted -r HELLO
389
354
  HELLO=world
390
355
  ```
391
356
 
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:
393
365
 
394
366
  ```yaml
395
367
  include: [keys]
@@ -9,11 +9,11 @@
9
9
  "source": "bin/*",
10
10
  "destination": "{DEPLOY_ROOT}/bin/%1"
11
11
  },
12
- "envstack_completion": {
12
+ "bash_completion": {
13
13
  "source": "bin/envstack_completion.sh",
14
14
  "destination": "/etc/bash_completion.d/envstack"
15
15
  },
16
- "lib-envstack": {
16
+ "lib": {
17
17
  "source": "lib/envstack",
18
18
  "destination": "{DEPLOY_ROOT}/lib/python/envstack"
19
19
  }
@@ -34,6 +34,6 @@ Stacked environment variable management system.
34
34
  """
35
35
 
36
36
  __prog__ = "envstack"
37
- __version__ = "0.9.0"
37
+ __version__ = "0.9.2"
38
38
 
39
39
  from envstack.env import clear, init, revert, save # noqa: F401
@@ -147,12 +147,19 @@ def parse_args():
147
147
  action="version",
148
148
  version=f"envstack {__version__}",
149
149
  )
150
- parser.add_argument(
150
+ group = parser.add_mutually_exclusive_group(required=False)
151
+ group.add_argument(
151
152
  "namespace",
152
153
  metavar="STACK",
153
154
  nargs="*",
154
155
  default=[config.DEFAULT_NAMESPACE],
155
- help="the environment stacks to use (default '%s')" % config.DEFAULT_NAMESPACE,
156
+ help="the environment stacks to use",
157
+ )
158
+ group.add_argument(
159
+ "-b",
160
+ "--bare",
161
+ action="store_true",
162
+ help="create a bare environment",
156
163
  )
157
164
  encrypt_group = parser.add_argument_group("encryption options")
158
165
  encrypt_group.add_argument(
@@ -175,10 +182,11 @@ def parse_args():
175
182
  help="save the environment to an env file",
176
183
  )
177
184
  bake_group.add_argument(
185
+ "-d",
178
186
  "--depth",
179
187
  type=int,
180
188
  default=0,
181
- help="depth of environment stack to bake",
189
+ help="depth of environment stack to bake (default: 0 = flatten)",
182
190
  )
183
191
  parser.add_argument_group(bake_group)
184
192
  export_group = parser.add_argument_group("export options")
@@ -205,8 +213,8 @@ def parse_args():
205
213
  "--set",
206
214
  nargs="*",
207
215
  action=StoreOnce,
208
- metavar="VAR=VALUE",
209
- help="convert KEY=VALUE pairs to envstack environment variables",
216
+ metavar="KEY=VALUE",
217
+ help="overlay KEY=VALUE pairs to envstack environments",
210
218
  )
211
219
  parser.add_argument(
212
220
  "--scope",
@@ -234,6 +242,12 @@ def parse_args():
234
242
  action="store_true",
235
243
  help="list the env stack file sources",
236
244
  )
245
+ export_group.add_argument(
246
+ "-q",
247
+ "--quiet",
248
+ action="store_true",
249
+ help="print the value of an environment variable only (no key)",
250
+ )
237
251
 
238
252
  args = parser.parse_args(args_before_dash)
239
253
 
@@ -275,9 +289,24 @@ def main():
275
289
  for key, value in data.items():
276
290
  print(f"{key}={value}")
277
291
 
278
- elif args.set is not None:
292
+ elif args.clear:
293
+ from envstack.env import clear
294
+
295
+ print(clear(args.namespace, shell=config.SHELL))
296
+
297
+ elif args.export and args.resolve is None and args.set is None:
298
+ print(export(args.namespace, shell=config.SHELL, encrypt=args.encrypt))
299
+
300
+ elif args.set is not None and args.resolve is None:
279
301
  force_stdin = args.set == [] or args.set == ["-"]
280
302
  using_pipe = args.set == [] and not sys.stdin.isatty()
303
+
304
+ # load the environment if not in bare mode
305
+ if args.bare:
306
+ env = Env()
307
+ else:
308
+ env = load_environ(args.namespace, platform=args.platform)
309
+
281
310
  # interactive mode
282
311
  if force_stdin and sys.stdin.isatty():
283
312
  print(
@@ -302,53 +331,101 @@ def main():
302
331
  else:
303
332
  data = _parse_keyvals(args.set)
304
333
 
334
+ # encrypt the new data only
305
335
  if args.encrypt:
306
- data = encrypt_environ(data, encrypt=(not args.out))
336
+ data = encrypt_environ(data)
337
+
338
+ # update the environment with the new data
339
+ env.update(data)
340
+
307
341
  if args.export:
308
- print(export_env_to_shell(data))
342
+ print(export_env_to_shell(env))
309
343
  elif args.out:
310
- Env(data).write(args.out)
344
+ env.write(args.out, depth=args.depth)
311
345
  else:
312
- for key, value in data.items():
313
- print(f"{key}={value}")
314
-
315
- elif args.out:
346
+ for key, val in env.items():
347
+ if args.quiet:
348
+ if len(env) > 1:
349
+ print("error: --quiet requires exactly one KEY")
350
+ return 2
351
+ else:
352
+ print(val)
353
+ else:
354
+ print(f"{key}={val}")
355
+
356
+ elif args.out and args.resolve is None:
316
357
  bake_environ(
317
358
  args.namespace,
318
359
  filename=args.out,
319
- depth=args.depth or 0,
360
+ depth=args.depth,
320
361
  encrypt=args.encrypt,
321
362
  )
322
363
 
323
364
  elif args.resolve is not None:
365
+ if args.depth:
366
+ print("error: --depth is not valid with --resolve")
367
+ return 2
324
368
  resolved = resolve_environ(
325
369
  load_environ(args.namespace, platform=args.platform)
326
370
  )
327
- keys = args.resolve or resolved.keys()
328
- for key in sorted(str(k) for k in keys):
329
- val = resolved.get(key)
330
- print(f"{key}={val}")
371
+ if args.set:
372
+ resolved.update(_parse_keyvals(args.set))
373
+ if args.encrypt:
374
+ resolved = encrypt_environ(resolved)
375
+ if args.out:
376
+ if len(args.resolve) == 0:
377
+ resolved.write(args.out, depth=0)
378
+ else:
379
+ keys = args.resolve or resolved.keys()
380
+ if args.set:
381
+ keys = set(keys).union(_parse_keyvals(args.set).keys())
382
+ env = Env({key: resolved[key] for key in keys})
383
+ env.write(args.out, depth=0)
384
+ elif args.export:
385
+ if len(args.resolve) == 0:
386
+ print(export_env_to_shell(resolved, shell=config.SHELL))
387
+ else:
388
+ keys = args.resolve or resolved.keys()
389
+ if args.set:
390
+ keys = set(keys).union(_parse_keyvals(args.set).keys())
391
+ env = Env({key: resolved[key] for key in keys})
392
+ print(export_env_to_shell(env, shell=config.SHELL))
393
+ else:
394
+ keys = args.resolve or resolved.keys()
395
+ if args.set:
396
+ keys = set(keys).union(_parse_keyvals(args.set).keys())
397
+ for key in sorted(str(k) for k in keys):
398
+ val = resolved.get(key)
399
+ if key in resolved:
400
+ if args.quiet:
401
+ if len(keys) > 1:
402
+ print("error: --quiet requires exactly one KEY")
403
+ return 2
404
+ else:
405
+ print(val)
406
+ else:
407
+ print(f"{key}={val}")
331
408
 
332
409
  elif args.trace is not None:
333
410
  if len(args.trace) == 0:
334
411
  args.trace = load_environ(args.namespace).keys()
335
412
  for trace in args.trace:
336
413
  path = trace_var(*args.namespace, var=trace)
337
- print("{0}: {1}".format(trace, path))
414
+ if path:
415
+ if args.quiet:
416
+ if len(args.trace) > 1:
417
+ print("error: --quiet requires exactly one KEY")
418
+ return 2
419
+ else:
420
+ print(path)
421
+ else:
422
+ print("{0}={1}".format(trace, path))
338
423
 
339
424
  elif args.sources:
340
425
  env = load_environ(args.namespace, platform=args.platform)
341
426
  for source in env.sources:
342
427
  print(source.path)
343
428
 
344
- elif args.clear:
345
- from envstack.env import clear
346
-
347
- print(clear(args.namespace, config.SHELL))
348
-
349
- elif args.export:
350
- print(export(args.namespace, config.SHELL))
351
-
352
429
  else:
353
430
  env = load_environ(
354
431
  args.namespace, platform=args.platform, encrypt=args.encrypt
@@ -313,10 +313,12 @@ def generate_keys():
313
313
 
314
314
  :returns: Dictionary containing Fernet and AES-GCM keys.
315
315
  """
316
+ from envstack.node import Base64Node
317
+
316
318
  symmetric_key = AESGCMEncryptor.generate_key()
317
319
  fernet_key = FernetEncryptor.generate_key()
318
320
 
319
321
  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),
322
324
  }
@@ -114,7 +114,7 @@ class Source(object):
114
114
  """Returns the char length of the path"""
115
115
  return len(self.path)
116
116
 
117
- def load(self, platform=config.PLATFORM):
117
+ def load(self, platform: str = config.PLATFORM):
118
118
  """Reads .env from .path, and returns an Env class object"""
119
119
  if self.path and not self.data:
120
120
  self.data = load_file(self.path)
@@ -305,17 +305,12 @@ class Env(dict):
305
305
  >>> env.bake("baked.env")
306
306
 
307
307
  :param filename: path to save the baked environment.
308
- :param depth: depth of source files to incldue (default: all).
309
- :param encrypt: encrypt the values.
308
+ :param depth: depth of source files to include (optional).
309
+ :param encrypt: encrypt the values (optional).
310
310
  :returns: baked environment.
311
311
  """
312
- # get the sources for the given environment
313
312
  sources = self.sources
314
-
315
- # look for encryption keys in the environment
316
- os.environ.update(get_keys_from_env(self))
317
-
318
- # create a baked source
313
+ os.environ.update(get_keys_from_env(self)) # for encryption
319
314
  baked = Source(filename)
320
315
 
321
316
  def get_node_class(value):
@@ -327,32 +322,63 @@ class Env(dict):
327
322
  return EncryptedNode
328
323
  return value.__class__
329
324
 
330
- # merge the sources into the outfile
325
+ # track included files and seen keys
326
+ includes = []
327
+ seen_keys = set()
328
+
329
+ for source in sources[:depth]:
330
+ for key, value in source.data.items():
331
+ if isinstance(value, dict):
332
+ for k, v in value.items():
333
+ if k in self:
334
+ v = self[k]
335
+ node_class = get_node_class(v)
336
+ seen_keys.add(k)
337
+ else:
338
+ seen_keys.add(key)
339
+
340
+ current_depth = 0
331
341
  for source in sources[-depth:]:
332
342
  for key, value in source.data.items():
343
+ if key == "include" and current_depth <= depth:
344
+ includes = value
345
+ continue
333
346
  if isinstance(value, dict):
334
347
  for k, v in value.items():
348
+ if k in self and key == "all": # override only in "all"
349
+ v = self[k]
335
350
  node_class = get_node_class(v)
336
351
  baked.data.setdefault(key, {})[k] = node_class(v)
352
+ seen_keys.add(k)
337
353
  else:
338
- node_class = get_node_class(value)
339
- baked.data[key] = node_class(value)
354
+ baked.data[key] = get_node_class(value)(value)
355
+ seen_keys.add(key)
356
+ current_depth += 1
357
+
358
+ # add/override with values from the current environment
359
+ for key, value in self.items():
360
+ if key == "STACK" or key in seen_keys:
361
+ continue
362
+ baked.data["all"][key] = get_node_class(value)(value)
340
363
 
341
364
  # clear includes if environment stack is fully baked
342
- if depth <= 0:
365
+ if depth <= 0 or depth >= len(sources):
343
366
  baked.data["include"] = []
344
-
345
- # write the baked environment to the file
346
- if filename:
347
- baked.write()
367
+ else:
368
+ baked.data["include"] = includes
348
369
 
349
370
  # create the baked environment from the baked source
350
371
  baked_env = Env()
351
372
  baked_env.load_source(baked)
373
+ if filename:
374
+ try:
375
+ baked.write()
376
+ except Exception as err:
377
+ raise WriteError(f"Failed to write {filename}", err)
352
378
 
353
379
  return baked_env
354
380
 
355
- def write(self, filename: str = None):
381
+ def write(self, filename: str, depth: int = 0, encrypt: bool = False):
356
382
  """Writes the environment to an env file.
357
383
 
358
384
  >>> env = Env({"FOO": "${BAR}", "BAR": "bar"})
@@ -364,11 +390,13 @@ class Env(dict):
364
390
  >>> env.write("encrypted.env")
365
391
 
366
392
  :param filename: path to save the baked environment.
393
+ :param depth: depth of source files to include (optional).
394
+ :param encrypt: encrypt the values (optional).
367
395
  :returns: Source object.
368
396
  """
369
397
  # the environment was loaded from one or more sources
370
398
  if self.sources:
371
- baked = self.bake(filename)
399
+ baked = self.bake(filename, depth=depth, encrypt=encrypt)
372
400
  return baked.sources[0]
373
401
 
374
402
  # the environment was created from scratch
@@ -631,6 +659,7 @@ def export(
631
659
  name: str = config.DEFAULT_NAMESPACE,
632
660
  shell: str = config.SHELL,
633
661
  scope: str = None,
662
+ encrypt: bool = False,
634
663
  ):
635
664
  """Returns shell commands that can be sourced to set environment stack
636
665
  environment variables.
@@ -638,12 +667,13 @@ def export(
638
667
  Supported shells: bash, sh, tcsh, cmd, pwsh (see config.detect_shell()).
639
668
 
640
669
  :param name: stack namespace.
641
- :param shell: name of shell (default: current shell).
642
- :param scope: environment scope (default: cwd).
670
+ :param shell: name of shell (optional).
671
+ :param scope: environment scope (optional).
672
+ :param encrypt: encrypt the values (optional).
643
673
  :returns: shell commands as string.
644
674
  """
645
- resolved_env = resolve_environ(load_environ(name, scope=scope))
646
- return export_env_to_shell(resolved_env, shell)
675
+ env = load_environ(name, scope=scope, encrypt=encrypt)
676
+ return export_env_to_shell(env, shell)
647
677
 
648
678
 
649
679
  def save():
@@ -744,8 +774,8 @@ def bake_environ(
744
774
  $ envstack [STACK] -o <filename>
745
775
 
746
776
  :param name: stack namespace.
747
- :param scope: environment scope (default: cwd).
748
- :param depth: depth of source files to incldue (default: all).
777
+ :param scope: environment scope (optional).
778
+ :param depth: depth of source files to include (optional).
749
779
  :param filename: path to save the baked environment.
750
780
  :param encrypt: encrypt the values.
751
781
  :returns: baked environment.
@@ -791,6 +821,7 @@ def encrypt_environ(
791
821
  node = node_class(v)
792
822
  if encrypt:
793
823
  node.value = node.encryptor(env=resolved_env).encrypt(str(v))
824
+ node.original_value = node.value
794
825
  encrypted_env[k] = node
795
826
  else:
796
827
  encrypted_env[k] = v
@@ -74,3 +74,9 @@ class TemplateNotFound(Exception):
74
74
  """Custom exception class for missing Templates."""
75
75
 
76
76
  pass
77
+
78
+
79
+ class WriteError(Exception):
80
+ """Custom exception class for errors during export operations."""
81
+
82
+ pass
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: envstack
3
- Version: 0.9.0
3
+ Version: 0.9.2
4
4
  Summary: Stacked environment variable management system
5
5
  Home-page: http://github.com/rsgalloway/envstack
6
6
  Author: Ryan Galloway
@@ -36,23 +36,6 @@ Environment variable management system.
36
36
  [Python API](#python-api) |
37
37
  [Running Commands](#running-commands)
38
38
 
39
-
40
- | Feature | Description |
41
- |---------|-------------|
42
- | 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. |
43
- | 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. |
44
- | Encryption support | Secure encryption, including AES-GCM, Fernet, and Base64. This allows you to securely encrypt and decrypt sensitive environment variables. |
45
- | 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. |
46
- | 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. |
47
- | Platform-specific variables | Stacks can have platform-specific variables and values. This allows you to define different values for variables based on the platform. |
48
- | Variable references | Variables can reference other variables, allowing for more flexibility and dynamic value assignment. |
49
- | Multi-line values | Supports variables with multi-line values. |
50
- | Includes | Stack files can include other stacks, making it easy to reuse and combine different stacks. |
51
- | 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. |
52
- | 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. |
53
- | 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. |
54
- | Shell integration | Provides instructions for sourcing the environment stack in your current shell, allowing you to set and clear the environment easily. |
55
-
56
39
  ## Installation
57
40
 
58
41
  The easiest way to install:
@@ -94,7 +77,6 @@ $ curl -o default.env https://raw.githubusercontent.com/rsgalloway/envstack/mast
94
77
  Alternatively, set `${ENVPATH}` to the directory containing your environment
95
78
  stack files:
96
79
 
97
- #### bash
98
80
  ```bash
99
81
  $ export ENVPATH=/path/to/env/files
100
82
  ```
@@ -102,7 +84,6 @@ $ export ENVPATH=/path/to/env/files
102
84
  Define as many paths as you want, and envstack will search for stack files in
103
85
  order from left to right, for example:
104
86
 
105
- #### bash
106
87
  ```bash
107
88
  $ export ENVPATH=/mnt/pipe/dev/env:/mnt/pipe/prod/env
108
89
  ```
@@ -233,50 +214,34 @@ $ envstack -s HELLO=world -o hello.env
233
214
  You can convert existing `.env` files to envstack by piping them into envstack:
234
215
 
235
216
  ```bash
236
- $ cat .env | envstack --set -o dev.env
217
+ $ cat .env | envstack --set -o out.env
237
218
  ```
238
219
 
239
- ## Creating Stacks
220
+ ## Creating Environments
240
221
 
241
222
  Several example or starter stacks are available in the [env folder of the
242
223
  envstack repo](https://github.com/rsgalloway/envstack/tree/master/env).
243
224
 
244
- To create a new environment stack, create an envstack file and declare some
245
- variables.
225
+ To create a new environment file, use `--set` to declare some variables:
246
226
 
247
227
  ```bash
248
- $ envstack foobar -o foobar.env
249
- ```
250
-
251
- Add the `${FOO}` and `${BAR}` env vars to the foobar.env environment stack file:
252
-
253
- ```yaml
254
- #!/usr/bin/env envstack
255
- all: &all
256
- FOO: bar
257
- BAR: ${FOO}
258
- darwin:
259
- <<: *all
260
- linux:
261
- <<: *all
262
- windows:
263
- <<: *all
228
+ $ envstack -s FOO=bar BAR=\${FOO} -o out.env
264
229
  ```
265
230
 
266
- Or using Python:
231
+ Using Python:
267
232
 
268
233
  ```python
269
234
  >>> env = Env({"FOO": "bar", "BAR": "${FOO}"})
270
- >>> env.write("foobar.env")
235
+ >>> env.write("out.env")
271
236
  ```
272
237
 
273
- Get the resolved environment for the `foobar` stack:
238
+ Get the resolved values back:
274
239
 
275
240
  ```bash
276
- $ ./foobar.env -r
241
+ $ ./out.env -r
277
242
  BAR=bar
278
243
  FOO=bar
279
- STACK=foobar
244
+ STACK=out
280
245
  ```
281
246
 
282
247
  #### More Details
@@ -354,7 +319,7 @@ nodes look for keys in the following order, favoring AES-GCM over Fernet:
354
319
  | Fernet | ${ENVSTACK_FERNET_KEY} |
355
320
 
356
321
  If no encryption keys are found in the environment, envstack will default to
357
- using Base64 encryption:
322
+ using Base64 encoding:
358
323
 
359
324
  ```bash
360
325
  $ envstack --encrypt
@@ -381,7 +346,7 @@ $ source <(envstack --keygen --export)
381
346
  Once the keys are in the environment, you can encrypt the env stack:
382
347
 
383
348
  ```bash
384
- $ envstack --encrypt -o encrypted.env
349
+ $ envstack -o encrypted.env --encrypt
385
350
  ```
386
351
 
387
352
  Encrypted variables will resolve as long as the key is in the environment:
@@ -393,17 +358,17 @@ HELLO=world
393
358
 
394
359
  #### Storing Keys
395
360
 
396
- Keys can be stored in other environment stacks, e.g. a `keys.env` file. To
397
- generate keys and store them in a `keys.env` env stack file:
361
+ Keys can be stored in other environment stacks, e.g. a `keys.env` file
362
+ (keys are automatically base64 encoded):
398
363
 
399
364
  ```bash
400
365
  $ envstack --keygen -o keys.env
401
366
  ```
402
367
 
403
- Then use the `keys.env` env stack to encrypt any other env stack:
368
+ Then use `keys.env` to encrypt any other environment files:
404
369
 
405
370
  ```bash
406
- $ envstack keys -- envstack --encrypt -o encrypted.env
371
+ $ ./keys.env -- envstack -eo encrypted.env
407
372
  ```
408
373
 
409
374
  To decrypt, add `keys` to the env stack:
@@ -413,7 +378,14 @@ $ envstack keys encrypted -r HELLO
413
378
  HELLO=world
414
379
  ```
415
380
 
416
- Or add the `keys` env stack to `include` to automatically decrypt:
381
+ Or run the command inside the `keys` environment like this:
382
+
383
+ ```bash
384
+ $ ./keys.env -- envsatck encrypted -r HELLO
385
+ HELLO=world
386
+ ```
387
+
388
+ Or include `keys` in environments to automatically decrypt:
417
389
 
418
390
  ```yaml
419
391
  include: [keys]
@@ -40,7 +40,7 @@ with open(os.path.join(here, "README.md")) as f:
40
40
 
41
41
  setup(
42
42
  name="envstack",
43
- version="0.9.0",
43
+ version="0.9.2",
44
44
  description="Stacked environment variable management system",
45
45
  long_description=long_description,
46
46
  long_description_content_type="text/markdown",
@@ -52,7 +52,10 @@ def make_command(envstack_bin: str, filename: str, *args: str):
52
52
  Build a cross-platform shell command that runs envstack (with args)
53
53
  and then prints the output file.
54
54
  """
55
- envstack_cmd = f'{envstack_bin} {" ".join(args)} -o "{filename}"'
55
+
56
+ envstack_cmd = f'{envstack_bin} {" ".join(args)}'
57
+ if filename:
58
+ envstack_cmd += f' -o "{filename}"'
56
59
 
57
60
  if os.name == "nt" or platform.system().lower().startswith("win"):
58
61
  return f'{envstack_cmd} & type "{filename}"'
@@ -131,14 +134,14 @@ STACK=distman
131
134
 
132
135
  def test_hello(self):
133
136
  expected_output = (
134
- """DEPLOY_ROOT=${ROOT}/${ENV}
135
- ENV=prod
136
- ENVPATH=${DEPLOY_ROOT}/env:${ENVPATH}
137
+ """DEPLOY_ROOT=${ROOT}/dev
138
+ ENV=dev
139
+ ENVPATH=${ROOT}/dev/env:${ROOT}/prod/env:${ENVPATH}
137
140
  HELLO=${HELLO:=world}
138
141
  LOG_LEVEL=${LOG_LEVEL:=INFO}
139
- PATH=${DEPLOY_ROOT}/bin:${PATH}
142
+ PATH=${ROOT}/dev/bin:${ROOT}/prod/bin:${PATH}
140
143
  PYEXE=/usr/bin/python
141
- PYTHONPATH=${DEPLOY_ROOT}/lib/python:${PYTHONPATH}
144
+ PYTHONPATH=${ROOT}/dev/lib/python:${ROOT}/prod/lib/python:${PYTHONPATH}
142
145
  ROOT=%s
143
146
  STACK=hello
144
147
  """
@@ -212,33 +215,36 @@ STACK=ZGVmYXVsdA==
212
215
  """Test that the AESGCM encryption works, and resolves vars since the
213
216
  encrypted values change every time."""
214
217
  os.environ[AESGCMEncryptor.KEY_VAR_NAME] = AESGCMEncryptor.generate_key()
215
- expected_output = f"""DEPLOY_ROOT={self.root}/prod
216
- ENV=prod
217
- ROOT={self.root}
218
- """
219
- command = "%s --encrypt -r ENV ROOT DEPLOY_ROOT" % self.envstack_bin
218
+ expected_output = f"""{self.root}/prod\n"""
219
+ command = "%s -e -- echo {DEPLOY_ROOT}" % self.envstack_bin
220
220
  output = subprocess.check_output(
221
221
  command, shell=True, env=os.environ, universal_newlines=True
222
222
  )
223
223
  self.assertEqual(output, expected_output)
224
224
 
225
225
  def test_default_resolve(self):
226
- expected_output = f"""DEPLOY_ROOT={self.root}/prod
227
- ENV=prod
228
- ROOT={self.root}
229
- """
230
- command = "%s --encrypt -r ENV ROOT DEPLOY_ROOT" % self.envstack_bin
226
+ """Get and encrypt default stack values, and test they are resolved in a subprocess."""
227
+ expected_output = f"""{self.root}/prod\n"""
228
+ command = "%s --encrypt -- echo {DEPLOY_ROOT}" % self.envstack_bin
231
229
  output = subprocess.check_output(command, shell=True, universal_newlines=True)
232
230
  self.assertEqual(output, expected_output)
233
231
 
234
232
  def test_default_command_echo(self):
235
- expected_output = f"""{self.root}/prod
236
- """
233
+ """Tests that the default stack works with encrypted values."""
234
+ expected_output = f"""{self.root}/prod\n"""
237
235
  command = "%s --encrypt -- echo {DEPLOY_ROOT}" % self.envstack_bin
238
236
  output = subprocess.check_output(command, shell=True, universal_newlines=True)
239
237
  self.assertEqual(output, expected_output)
240
238
 
239
+ def test_hello_command_echo(self):
240
+ """Tests that resolved and encrypted values resolve in subprocesses."""
241
+ expected_output = f"""goodbye\n"""
242
+ command = "%s thing -r HELLO -e -- echo {HELLO}" % self.envstack_bin
243
+ output = subprocess.check_output(command, shell=True, universal_newlines=True)
244
+ self.assertEqual(output, expected_output)
245
+
241
246
  def test_dev(self):
247
+ """Tests encrypting values in the dev stack."""
242
248
  expected_output = """DEPLOY_ROOT=JHtST09UfS9kZXY=
243
249
  ENV=ZGV2
244
250
  ENVPATH=JHtST09UfS9kZXYvZW52OiR7Uk9PVH0vcHJvZC9lbnY6JHtFTlZQQVRIfQ==
@@ -253,18 +259,9 @@ STACK=ZGV2
253
259
  output = subprocess.check_output(command, shell=True, universal_newlines=True)
254
260
  self.assertEqual(output, expected_output)
255
261
 
256
- def test_dev_resolve(self):
257
- expected_output = f"""DEPLOY_ROOT={self.root}/dev
258
- ENV=dev
259
- ROOT={self.root}
260
- """
261
- command = "%s dev --encrypt -r ENV ROOT DEPLOY_ROOT" % self.envstack_bin
262
- output = subprocess.check_output(command, shell=True, universal_newlines=True)
263
- self.assertEqual(output, expected_output)
264
-
265
- def test_dev_command_echo(self):
266
- expected_output = f"""{self.root}/dev
267
- """
262
+ def test_dev_command(self):
263
+ """Tests that encrypted values resolve in subprocess in the dev stack."""
264
+ expected_output = f"""{self.root}/dev\n"""
268
265
  command = "%s dev --encrypt -- echo {DEPLOY_ROOT}" % self.envstack_bin
269
266
  output = subprocess.check_output(command, shell=True, universal_newlines=True)
270
267
  self.assertEqual(output, expected_output)
@@ -421,26 +418,22 @@ windows:
421
418
 
422
419
  def test_dev(self):
423
420
  """Tests baking the dev stack."""
424
- command = make_command(self.envstack_bin, self.filename, "dev")
421
+ command = make_command(self.envstack_bin, self.filename, "dev", "-d 1")
425
422
  expected_output = """#!/usr/bin/env envstack
426
- include: []
423
+ include: [default]
427
424
  all: &all
428
425
  DEPLOY_ROOT: ${ROOT}/dev
429
426
  ENV: dev
430
427
  ENVPATH: ${ROOT}/dev/env:${ROOT}/prod/env:${ENVPATH}
431
- HELLO: ${HELLO:=world}
432
428
  LOG_LEVEL: DEBUG
433
429
  PATH: ${ROOT}/dev/bin:${ROOT}/prod/bin:${PATH}
434
430
  PYTHONPATH: ${ROOT}/dev/lib/python:${ROOT}/prod/lib/python:${PYTHONPATH}
435
431
  darwin:
436
432
  <<: *all
437
- ROOT: /Volumes/pipe
438
433
  linux:
439
434
  <<: *all
440
- ROOT: /mnt/pipe
441
435
  windows:
442
436
  <<: *all
443
- ROOT: X:/pipe
444
437
  """
445
438
  output = subprocess.check_output(
446
439
  command,
@@ -650,7 +643,7 @@ class TestSet(unittest.TestCase):
650
643
 
651
644
  def test_hello_world(self):
652
645
  """Tests setting HELLO to world."""
653
- command = "%s --set HELLO:world" % self.envstack_bin
646
+ command = "%s --set HELLO=world --bare" % self.envstack_bin
654
647
  expected_output = "HELLO=world\n"
655
648
  output = subprocess.check_output(
656
649
  command,
@@ -661,7 +654,7 @@ class TestSet(unittest.TestCase):
661
654
 
662
655
  def test_hello_world_encrypted(self):
663
656
  """Tests setting HELLO to world encrypted."""
664
- command = "%s --set HELLO:world --encrypt" % self.envstack_bin
657
+ command = "%s --set HELLO:world --encrypt --bare" % self.envstack_bin
665
658
  expected_output = "HELLO=d29ybGQ=\n"
666
659
  output = subprocess.check_output(
667
660
  command,
@@ -672,7 +665,7 @@ class TestSet(unittest.TestCase):
672
665
 
673
666
  def test_foo_bar(self):
674
667
  """Tests setting FOO and BAR."""
675
- command = r"%s -s FOO:foo BAR:\${FOO}" % self.envstack_bin
668
+ command = r"%s -s FOO:foo BAR:\${FOO} --bare" % self.envstack_bin
676
669
  expected_output = "FOO=foo\nBAR=${FOO}\n"
677
670
  output = subprocess.check_output(
678
671
  command,
@@ -683,7 +676,7 @@ class TestSet(unittest.TestCase):
683
676
 
684
677
  def test_foo_bar_encrypted(self):
685
678
  """Tests setting FOO and BAR encrypted."""
686
- command = r"%s -s FOO:foo BAR:\${FOO} --encrypt" % self.envstack_bin
679
+ command = r"%s -s FOO:foo BAR:\${FOO} --encrypt --bare" % self.envstack_bin
687
680
  expected_output = "FOO=Zm9v\nBAR=JHtGT099\n"
688
681
  output = subprocess.check_output(
689
682
  command,
@@ -695,7 +688,12 @@ class TestSet(unittest.TestCase):
695
688
  def test_foo_bar_bake(self):
696
689
  """Tests setting FOO and BAR and bake it out to a file."""
697
690
  command = make_command(
698
- self.envstack_bin, self.filename, "--set", "FOO:foo", "BAR:\${FOO}"
691
+ self.envstack_bin,
692
+ self.filename,
693
+ "--set",
694
+ "FOO:foo",
695
+ "BAR:\${FOO}", # not a typo, need to escape $ for shell
696
+ "--bare",
699
697
  )
700
698
  expected_output = """#!/usr/bin/env envstack
701
699
  include: []
@@ -723,8 +721,9 @@ windows:
723
721
  self.filename,
724
722
  "--set",
725
723
  "FOO:foo",
726
- "BAR:\${FOO}",
724
+ "BAR:\${FOO}", # not a typo, need to escape $ for shell
727
725
  "--encrypt",
726
+ "--bare",
728
727
  )
729
728
  expected_output = """#!/usr/bin/env envstack
730
729
  include: []
@@ -893,9 +892,10 @@ class TestIssues(unittest.TestCase):
893
892
  hello_env_file = os.path.join(self.root, "dev", "env", "hello.env")
894
893
  update_env_file(hello_env_file, "PYEXE", "/usr/bin/foobar")
895
894
 
896
- # test "default" should only include prod sources
895
+ # 'envstack hello' should only include prod sources
897
896
  command = "%s hello --sources" % self.envstack_bin
898
897
  expected_output = f"""{self.root}/prod/env/default.env
898
+ {self.root}/prod/env/dev.env
899
899
  {self.root}/prod/env/hello.env
900
900
  """
901
901
  output = subprocess.check_output(
@@ -903,7 +903,7 @@ class TestIssues(unittest.TestCase):
903
903
  )
904
904
  self.assertEqual(output, expected_output)
905
905
 
906
- # test "dev" should include prod and dev sources
906
+ # 'envstack dev hello' should include prod and dev sources
907
907
  command = "%s dev hello --sources" % self.envstack_bin
908
908
  expected_output = f"""{self.root}/prod/env/default.env
909
909
  {self.root}/prod/env/dev.env
@@ -186,7 +186,7 @@ class TestEnv(unittest.TestCase):
186
186
 
187
187
  env1 = load_environ("thing")
188
188
  self.filename = "test_bake_out.env"
189
- env1.write(self.filename)
189
+ env1.write(self.filename, depth=0)
190
190
  env2 = load_environ(self.filename)
191
191
  for k, v in env1.items():
192
192
  if k == "STACK":
@@ -809,7 +809,7 @@ class TestEncryptEnviron(unittest.TestCase):
809
809
  if key == "STACK": # skip the stack name
810
810
  continue
811
811
  encrypted_value = encrypted[key]
812
- self.assertTrue(isinstance(encrypted_value, EncryptedNode))
812
+ self.assertTrue(isinstance(encrypted_value, EncryptedNode), f"type is {type(encrypted_value)}")
813
813
  self.assertEqual(encrypted_value.original_value, None)
814
814
  # self.assertNotEqual(encrypted_value.original_value, value) # from_yaml only
815
815
  self.assertEqual(encrypted_value.value, value)
@@ -955,6 +955,7 @@ class TestIssues(unittest.TestCase):
955
955
 
956
956
  expected_paths = [
957
957
  os.path.join(self.root, "prod", "env", "default.env"),
958
+ os.path.join(self.root, "prod", "env", "dev.env"),
958
959
  os.path.join(self.root, "prod", "env", "hello.env"),
959
960
  ]
960
961
  self.assertEqual(paths, expected_paths)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes