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.
- {envstack-0.9.0/lib/envstack.egg-info → envstack-0.9.2}/PKG-INFO +24 -52
- {envstack-0.9.0 → envstack-0.9.2}/README.md +23 -51
- {envstack-0.9.0 → envstack-0.9.2}/dist.json +2 -2
- {envstack-0.9.0 → envstack-0.9.2}/lib/envstack/__init__.py +1 -1
- {envstack-0.9.0 → envstack-0.9.2}/lib/envstack/cli.py +104 -27
- {envstack-0.9.0 → envstack-0.9.2}/lib/envstack/encrypt.py +4 -2
- {envstack-0.9.0 → envstack-0.9.2}/lib/envstack/env.py +56 -25
- {envstack-0.9.0 → envstack-0.9.2}/lib/envstack/exceptions.py +6 -0
- {envstack-0.9.0 → envstack-0.9.2/lib/envstack.egg-info}/PKG-INFO +24 -52
- {envstack-0.9.0 → envstack-0.9.2}/setup.py +1 -1
- {envstack-0.9.0 → envstack-0.9.2}/tests/test_cmds.py +44 -44
- {envstack-0.9.0 → envstack-0.9.2}/tests/test_env.py +3 -2
- {envstack-0.9.0 → envstack-0.9.2}/LICENSE +0 -0
- {envstack-0.9.0 → envstack-0.9.2}/lib/envstack/config.py +0 -0
- {envstack-0.9.0 → envstack-0.9.2}/lib/envstack/logger.py +0 -0
- {envstack-0.9.0 → envstack-0.9.2}/lib/envstack/node.py +0 -0
- {envstack-0.9.0 → envstack-0.9.2}/lib/envstack/path.py +0 -0
- {envstack-0.9.0 → envstack-0.9.2}/lib/envstack/util.py +0 -0
- {envstack-0.9.0 → envstack-0.9.2}/lib/envstack/wrapper.py +0 -0
- {envstack-0.9.0 → envstack-0.9.2}/lib/envstack.egg-info/SOURCES.txt +0 -0
- {envstack-0.9.0 → envstack-0.9.2}/lib/envstack.egg-info/dependency_links.txt +0 -0
- {envstack-0.9.0 → envstack-0.9.2}/lib/envstack.egg-info/entry_points.txt +0 -0
- {envstack-0.9.0 → envstack-0.9.2}/lib/envstack.egg-info/not-zip-safe +0 -0
- {envstack-0.9.0 → envstack-0.9.2}/lib/envstack.egg-info/requires.txt +0 -0
- {envstack-0.9.0 → envstack-0.9.2}/lib/envstack.egg-info/top_level.txt +0 -0
- {envstack-0.9.0 → envstack-0.9.2}/setup.cfg +0 -0
- {envstack-0.9.0 → envstack-0.9.2}/tests/test_encrypt.py +0 -0
- {envstack-0.9.0 → envstack-0.9.2}/tests/test_node.py +0 -0
- {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.
|
|
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
|
|
217
|
+
$ cat .env | envstack --set -o out.env
|
|
237
218
|
```
|
|
238
219
|
|
|
239
|
-
## Creating
|
|
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
|
|
245
|
-
variables.
|
|
225
|
+
To create a new environment file, use `--set` to declare some variables:
|
|
246
226
|
|
|
247
227
|
```bash
|
|
248
|
-
$ envstack
|
|
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
|
-
|
|
231
|
+
Using Python:
|
|
267
232
|
|
|
268
233
|
```python
|
|
269
234
|
>>> env = Env({"FOO": "bar", "BAR": "${FOO}"})
|
|
270
|
-
>>> env.write("
|
|
235
|
+
>>> env.write("out.env")
|
|
271
236
|
```
|
|
272
237
|
|
|
273
|
-
Get the resolved
|
|
238
|
+
Get the resolved values back:
|
|
274
239
|
|
|
275
240
|
```bash
|
|
276
|
-
$ ./
|
|
241
|
+
$ ./out.env -r
|
|
277
242
|
BAR=bar
|
|
278
243
|
FOO=bar
|
|
279
|
-
STACK=
|
|
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
|
|
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
|
|
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
|
|
397
|
-
|
|
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
|
|
368
|
+
Then use `keys.env` to encrypt any other environment files:
|
|
404
369
|
|
|
405
370
|
```bash
|
|
406
|
-
$
|
|
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
|
|
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
|
|
193
|
+
$ cat .env | envstack --set -o out.env
|
|
213
194
|
```
|
|
214
195
|
|
|
215
|
-
## Creating
|
|
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
|
|
221
|
-
variables.
|
|
201
|
+
To create a new environment file, use `--set` to declare some variables:
|
|
222
202
|
|
|
223
203
|
```bash
|
|
224
|
-
$ envstack
|
|
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
|
-
|
|
207
|
+
Using Python:
|
|
243
208
|
|
|
244
209
|
```python
|
|
245
210
|
>>> env = Env({"FOO": "bar", "BAR": "${FOO}"})
|
|
246
|
-
>>> env.write("
|
|
211
|
+
>>> env.write("out.env")
|
|
247
212
|
```
|
|
248
213
|
|
|
249
|
-
Get the resolved
|
|
214
|
+
Get the resolved values back:
|
|
250
215
|
|
|
251
216
|
```bash
|
|
252
|
-
$ ./
|
|
217
|
+
$ ./out.env -r
|
|
253
218
|
BAR=bar
|
|
254
219
|
FOO=bar
|
|
255
|
-
STACK=
|
|
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
|
|
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
|
|
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
|
|
373
|
-
|
|
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
|
|
344
|
+
Then use `keys.env` to encrypt any other environment files:
|
|
380
345
|
|
|
381
346
|
```bash
|
|
382
|
-
$
|
|
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
|
|
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
|
-
"
|
|
12
|
+
"bash_completion": {
|
|
13
13
|
"source": "bin/envstack_completion.sh",
|
|
14
14
|
"destination": "/etc/bash_completion.d/envstack"
|
|
15
15
|
},
|
|
16
|
-
"lib
|
|
16
|
+
"lib": {
|
|
17
17
|
"source": "lib/envstack",
|
|
18
18
|
"destination": "{DEPLOY_ROOT}/lib/python/envstack"
|
|
19
19
|
}
|
|
@@ -147,12 +147,19 @@ def parse_args():
|
|
|
147
147
|
action="version",
|
|
148
148
|
version=f"envstack {__version__}",
|
|
149
149
|
)
|
|
150
|
-
parser.
|
|
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
|
|
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="
|
|
209
|
-
help="
|
|
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.
|
|
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
|
|
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(
|
|
342
|
+
print(export_env_to_shell(env))
|
|
309
343
|
elif args.out:
|
|
310
|
-
|
|
344
|
+
env.write(args.out, depth=args.depth)
|
|
311
345
|
else:
|
|
312
|
-
for key,
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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
|
|
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
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
#
|
|
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
|
-
|
|
339
|
-
|
|
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
|
-
|
|
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 =
|
|
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 (
|
|
642
|
-
:param scope: environment scope (
|
|
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
|
-
|
|
646
|
-
return export_env_to_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 (
|
|
748
|
-
:param depth: depth of source files to
|
|
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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: envstack
|
|
3
|
-
Version: 0.9.
|
|
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
|
|
217
|
+
$ cat .env | envstack --set -o out.env
|
|
237
218
|
```
|
|
238
219
|
|
|
239
|
-
## Creating
|
|
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
|
|
245
|
-
variables.
|
|
225
|
+
To create a new environment file, use `--set` to declare some variables:
|
|
246
226
|
|
|
247
227
|
```bash
|
|
248
|
-
$ envstack
|
|
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
|
-
|
|
231
|
+
Using Python:
|
|
267
232
|
|
|
268
233
|
```python
|
|
269
234
|
>>> env = Env({"FOO": "bar", "BAR": "${FOO}"})
|
|
270
|
-
>>> env.write("
|
|
235
|
+
>>> env.write("out.env")
|
|
271
236
|
```
|
|
272
237
|
|
|
273
|
-
Get the resolved
|
|
238
|
+
Get the resolved values back:
|
|
274
239
|
|
|
275
240
|
```bash
|
|
276
|
-
$ ./
|
|
241
|
+
$ ./out.env -r
|
|
277
242
|
BAR=bar
|
|
278
243
|
FOO=bar
|
|
279
|
-
STACK=
|
|
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
|
|
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
|
|
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
|
|
397
|
-
|
|
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
|
|
368
|
+
Then use `keys.env` to encrypt any other environment files:
|
|
404
369
|
|
|
405
370
|
```bash
|
|
406
|
-
$
|
|
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
|
|
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.
|
|
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
|
-
|
|
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}
|
|
135
|
-
ENV=
|
|
136
|
-
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=${
|
|
142
|
+
PATH=${ROOT}/dev/bin:${ROOT}/prod/bin:${PATH}
|
|
140
143
|
PYEXE=/usr/bin/python
|
|
141
|
-
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"""
|
|
216
|
-
|
|
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
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
|
|
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
|
|
257
|
-
|
|
258
|
-
|
|
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
|
|
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,
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|