envstack 0.9.6__tar.gz → 1.0.1__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.6 → envstack-1.0.1}/LICENSE +1 -1
- {envstack-0.9.6/lib/envstack.egg-info → envstack-1.0.1}/PKG-INFO +36 -10
- {envstack-0.9.6 → envstack-1.0.1}/README.md +35 -9
- {envstack-0.9.6 → envstack-1.0.1}/lib/envstack/__init__.py +3 -3
- {envstack-0.9.6 → envstack-1.0.1}/lib/envstack/cli.py +1 -1
- {envstack-0.9.6 → envstack-1.0.1}/lib/envstack/config.py +22 -2
- {envstack-0.9.6 → envstack-1.0.1}/lib/envstack/encrypt.py +1 -1
- {envstack-0.9.6 → envstack-1.0.1}/lib/envstack/env.py +1 -1
- {envstack-0.9.6 → envstack-1.0.1}/lib/envstack/envshell.py +2 -2
- {envstack-0.9.6 → envstack-1.0.1}/lib/envstack/exceptions.py +1 -1
- {envstack-0.9.6 → envstack-1.0.1}/lib/envstack/logger.py +1 -1
- {envstack-0.9.6 → envstack-1.0.1}/lib/envstack/node.py +25 -20
- envstack-1.0.1/lib/envstack/path.py +448 -0
- {envstack-0.9.6 → envstack-1.0.1}/lib/envstack/util.py +49 -7
- {envstack-0.9.6 → envstack-1.0.1}/lib/envstack/wrapper.py +63 -1
- {envstack-0.9.6 → envstack-1.0.1/lib/envstack.egg-info}/PKG-INFO +36 -10
- {envstack-0.9.6 → envstack-1.0.1}/lib/envstack.egg-info/SOURCES.txt +1 -0
- {envstack-0.9.6 → envstack-1.0.1}/pyproject.toml +1 -1
- {envstack-0.9.6 → envstack-1.0.1}/tests/test_cmds.py +27 -4
- {envstack-0.9.6 → envstack-1.0.1}/tests/test_encrypt.py +1 -1
- {envstack-0.9.6 → envstack-1.0.1}/tests/test_env.py +8 -3
- {envstack-0.9.6 → envstack-1.0.1}/tests/test_node.py +54 -1
- envstack-1.0.1/tests/test_path.py +189 -0
- {envstack-0.9.6 → envstack-1.0.1}/tests/test_util.py +82 -3
- envstack-1.0.1/tests/test_wrapper.py +313 -0
- envstack-0.9.6/lib/envstack/path.py +0 -375
- envstack-0.9.6/tests/test_wrapper.py +0 -161
- {envstack-0.9.6 → envstack-1.0.1}/dist.json +0 -0
- {envstack-0.9.6 → envstack-1.0.1}/lib/envstack.egg-info/dependency_links.txt +0 -0
- {envstack-0.9.6 → envstack-1.0.1}/lib/envstack.egg-info/entry_points.txt +0 -0
- {envstack-0.9.6 → envstack-1.0.1}/lib/envstack.egg-info/not-zip-safe +0 -0
- {envstack-0.9.6 → envstack-1.0.1}/lib/envstack.egg-info/requires.txt +0 -0
- {envstack-0.9.6 → envstack-1.0.1}/lib/envstack.egg-info/top_level.txt +0 -0
- {envstack-0.9.6 → envstack-1.0.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
envstack
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2024-
|
|
3
|
+
Copyright (c) 2024-2026, Ryan Galloway (ryan@rsgalloway.com)
|
|
4
4
|
All rights reserved.
|
|
5
5
|
|
|
6
6
|
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: envstack
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.1
|
|
4
4
|
Summary: Environment variable composition layer for tools and processes.
|
|
5
5
|
Author-email: Ryan Galloway <ryan@rsgalloway.com>
|
|
6
6
|
License: BSD 3-Clause License
|
|
@@ -41,9 +41,22 @@ Environment variable composition and activation layer for tools and processes.
|
|
|
41
41
|
- Shared, policy-driven environments
|
|
42
42
|
- Inspectable and deterministic behavior
|
|
43
43
|
|
|
44
|
-
envstack
|
|
44
|
+
envstack environments are layered hierarchically, with later layers inheriting
|
|
45
|
+
from and overriding earlier ones.
|
|
45
46
|
|
|
46
|
-
|
|
47
|
+
```mermaid
|
|
48
|
+
flowchart LR
|
|
49
|
+
default[default.env] --> prod[prod.env]
|
|
50
|
+
prod --> dev[dev.env]
|
|
51
|
+
prod --> test[test.env]
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Later layers override earlier ones. Use envstack -t VAR to trace where a value
|
|
55
|
+
comes from. envstack focuses on **configuration and activation**, not dependency
|
|
56
|
+
resolution.
|
|
57
|
+
|
|
58
|
+
For the core concepts, see
|
|
59
|
+
[docs/index.md](https://github.com/rsgalloway/envstack/blob/master/docs/index.md).
|
|
47
60
|
|
|
48
61
|
## Installation
|
|
49
62
|
|
|
@@ -55,7 +68,9 @@ pip install -U envstack
|
|
|
55
68
|
|
|
56
69
|
## Quickstart
|
|
57
70
|
|
|
58
|
-
Start by getting the latest
|
|
71
|
+
Start by getting the latest
|
|
72
|
+
[default.env](https://github.com/rsgalloway/envstack/blob/master/examples/default/default.env)
|
|
73
|
+
example file:
|
|
59
74
|
|
|
60
75
|
```bash
|
|
61
76
|
curl -o \
|
|
@@ -79,7 +94,6 @@ $ envstack -u
|
|
|
79
94
|
DEPLOY_ROOT=${ROOT}/${ENV}
|
|
80
95
|
ENV=prod
|
|
81
96
|
ENVPATH=${DEPLOY_ROOT}/env:${ENVPATH}
|
|
82
|
-
HELLO=${HELLO:=world}
|
|
83
97
|
LOG_LEVEL=${LOG_LEVEL:=INFO}
|
|
84
98
|
PATH=${DEPLOY_ROOT}/bin:${PATH}
|
|
85
99
|
PS1=\[\e[32m\](${ENV})\[\e[0m\] \w\$
|
|
@@ -138,10 +152,22 @@ $ envstack -- node index.js
|
|
|
138
152
|
Hello prod
|
|
139
153
|
```
|
|
140
154
|
|
|
155
|
+
## Secrets and encryption
|
|
156
|
+
|
|
157
|
+
envstack supports optional encryption of environment values when writing
|
|
158
|
+
environment files, allowing sensitive configuration to be safely stored,
|
|
159
|
+
committed, or distributed.
|
|
160
|
+
|
|
161
|
+
Encryption protects values **at rest** and integrates with environment stacks and
|
|
162
|
+
includes. envstack does not attempt to be a full secret management system.
|
|
163
|
+
|
|
164
|
+
See [docs/secrets.md](https://github.com/rsgalloway/envstack/blob/master/docs/secrets.md) for details.
|
|
165
|
+
|
|
141
166
|
## Documentation
|
|
142
167
|
|
|
143
|
-
- Design & philosophy
|
|
144
|
-
- Examples & patterns
|
|
145
|
-
- Tool comparisons
|
|
146
|
-
-
|
|
147
|
-
-
|
|
168
|
+
- [Design & philosophy](https://github.com/rsgalloway/envstack/blob/master/docs/design.md)
|
|
169
|
+
- [Examples & patterns](https://github.com/rsgalloway/envstack/blob/master/docs/examples.md)
|
|
170
|
+
- [Tool comparisons](https://github.com/rsgalloway/envstack/blob/master/docs/comparison.md)
|
|
171
|
+
- [Secrets and encryption](https://github.com/rsgalloway/envstack/blob/master/docs/secrets.md)
|
|
172
|
+
- [FAQ & gotchas](https://github.com/rsgalloway/envstack/blob/master/docs/faq.md)
|
|
173
|
+
- [API docs](https://github.com/rsgalloway/envstack/blob/master/docs/api.md)
|
|
@@ -13,9 +13,22 @@ Environment variable composition and activation layer for tools and processes.
|
|
|
13
13
|
- Shared, policy-driven environments
|
|
14
14
|
- Inspectable and deterministic behavior
|
|
15
15
|
|
|
16
|
-
envstack
|
|
16
|
+
envstack environments are layered hierarchically, with later layers inheriting
|
|
17
|
+
from and overriding earlier ones.
|
|
18
|
+
|
|
19
|
+
```mermaid
|
|
20
|
+
flowchart LR
|
|
21
|
+
default[default.env] --> prod[prod.env]
|
|
22
|
+
prod --> dev[dev.env]
|
|
23
|
+
prod --> test[test.env]
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Later layers override earlier ones. Use envstack -t VAR to trace where a value
|
|
27
|
+
comes from. envstack focuses on **configuration and activation**, not dependency
|
|
28
|
+
resolution.
|
|
17
29
|
|
|
18
|
-
For the core concepts, see
|
|
30
|
+
For the core concepts, see
|
|
31
|
+
[docs/index.md](https://github.com/rsgalloway/envstack/blob/master/docs/index.md).
|
|
19
32
|
|
|
20
33
|
## Installation
|
|
21
34
|
|
|
@@ -27,7 +40,9 @@ pip install -U envstack
|
|
|
27
40
|
|
|
28
41
|
## Quickstart
|
|
29
42
|
|
|
30
|
-
Start by getting the latest
|
|
43
|
+
Start by getting the latest
|
|
44
|
+
[default.env](https://github.com/rsgalloway/envstack/blob/master/examples/default/default.env)
|
|
45
|
+
example file:
|
|
31
46
|
|
|
32
47
|
```bash
|
|
33
48
|
curl -o \
|
|
@@ -51,7 +66,6 @@ $ envstack -u
|
|
|
51
66
|
DEPLOY_ROOT=${ROOT}/${ENV}
|
|
52
67
|
ENV=prod
|
|
53
68
|
ENVPATH=${DEPLOY_ROOT}/env:${ENVPATH}
|
|
54
|
-
HELLO=${HELLO:=world}
|
|
55
69
|
LOG_LEVEL=${LOG_LEVEL:=INFO}
|
|
56
70
|
PATH=${DEPLOY_ROOT}/bin:${PATH}
|
|
57
71
|
PS1=\[\e[32m\](${ENV})\[\e[0m\] \w\$
|
|
@@ -110,10 +124,22 @@ $ envstack -- node index.js
|
|
|
110
124
|
Hello prod
|
|
111
125
|
```
|
|
112
126
|
|
|
127
|
+
## Secrets and encryption
|
|
128
|
+
|
|
129
|
+
envstack supports optional encryption of environment values when writing
|
|
130
|
+
environment files, allowing sensitive configuration to be safely stored,
|
|
131
|
+
committed, or distributed.
|
|
132
|
+
|
|
133
|
+
Encryption protects values **at rest** and integrates with environment stacks and
|
|
134
|
+
includes. envstack does not attempt to be a full secret management system.
|
|
135
|
+
|
|
136
|
+
See [docs/secrets.md](https://github.com/rsgalloway/envstack/blob/master/docs/secrets.md) for details.
|
|
137
|
+
|
|
113
138
|
## Documentation
|
|
114
139
|
|
|
115
|
-
- Design & philosophy
|
|
116
|
-
- Examples & patterns
|
|
117
|
-
- Tool comparisons
|
|
118
|
-
-
|
|
119
|
-
-
|
|
140
|
+
- [Design & philosophy](https://github.com/rsgalloway/envstack/blob/master/docs/design.md)
|
|
141
|
+
- [Examples & patterns](https://github.com/rsgalloway/envstack/blob/master/docs/examples.md)
|
|
142
|
+
- [Tool comparisons](https://github.com/rsgalloway/envstack/blob/master/docs/comparison.md)
|
|
143
|
+
- [Secrets and encryption](https://github.com/rsgalloway/envstack/blob/master/docs/secrets.md)
|
|
144
|
+
- [FAQ & gotchas](https://github.com/rsgalloway/envstack/blob/master/docs/faq.md)
|
|
145
|
+
- [API docs](https://github.com/rsgalloway/envstack/blob/master/docs/api.md)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
#
|
|
3
|
-
# Copyright (c) 2024-
|
|
3
|
+
# Copyright (c) 2024-2026, Ryan Galloway (ryan@rsgalloway.com)
|
|
4
4
|
#
|
|
5
5
|
# Redistribution and use in source and binary forms, with or without
|
|
6
6
|
# modification, are permitted provided that the following conditions are met:
|
|
@@ -34,7 +34,7 @@ Stacked environment variable management system.
|
|
|
34
34
|
"""
|
|
35
35
|
|
|
36
36
|
__prog__ = "envstack"
|
|
37
|
-
__version__ = "0.
|
|
37
|
+
__version__ = "1.0.1"
|
|
38
38
|
|
|
39
39
|
from envstack.env import clear, init, revert, save # noqa: F401
|
|
40
|
-
from envstack.env import load_environ, resolve_environ # noqa: F401
|
|
40
|
+
from envstack.env import load_environ, resolve_environ # noqa: F401
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
#
|
|
3
|
-
# Copyright (c) 2024-
|
|
3
|
+
# Copyright (c) 2024-2026, Ryan Galloway (ryan@rsgalloway.com)
|
|
4
4
|
#
|
|
5
5
|
# Redistribution and use in source and binary forms, with or without
|
|
6
6
|
# modification, are permitted provided that the following conditions are met:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
#
|
|
3
|
-
# Copyright (c) 2024-
|
|
3
|
+
# Copyright (c) 2024-2026, Ryan Galloway (ryan@rsgalloway.com)
|
|
4
4
|
#
|
|
5
5
|
# Redistribution and use in source and binary forms, with or without
|
|
6
6
|
# modification, are permitted provided that the following conditions are met:
|
|
@@ -57,12 +57,32 @@ def detect_shell():
|
|
|
57
57
|
return "/usr/bin/bash"
|
|
58
58
|
|
|
59
59
|
|
|
60
|
+
# debug mode
|
|
60
61
|
DEBUG = os.getenv("DEBUG")
|
|
62
|
+
|
|
63
|
+
# default namespace
|
|
61
64
|
DEFAULT_NAMESPACE = os.getenv("DEFAULT_ENV_STACK", "default")
|
|
65
|
+
|
|
66
|
+
# allow embedded commands
|
|
67
|
+
ALLOW_COMMANDS = os.getenv("ALLOW_COMMANDS", "0") in ("1", "true", "True", "TRUE")
|
|
68
|
+
|
|
69
|
+
# embedded command timeout in seconds
|
|
70
|
+
try:
|
|
71
|
+
COMMAND_TIMEOUT = int(os.getenv("COMMAND_TIMEOUT", 5))
|
|
72
|
+
except ValueError:
|
|
73
|
+
COMMAND_TIMEOUT = 5
|
|
74
|
+
|
|
75
|
+
# default environment variables
|
|
62
76
|
ENV = os.getenv("ENV", "prod")
|
|
63
77
|
HOME = os.getenv("HOME")
|
|
64
|
-
|
|
78
|
+
|
|
79
|
+
# Ignore missing stack files when resolving environments
|
|
80
|
+
IGNORE_MISSING = os.getenv("IGNORE_MISSING", "1") in ("1", "true", "True", "TRUE")
|
|
81
|
+
|
|
82
|
+
# logging level
|
|
65
83
|
LOG_LEVEL = os.environ.get("LOG_LEVEL", "INFO")
|
|
84
|
+
|
|
85
|
+
# platform and shell info
|
|
66
86
|
ON_POSIX = "posix" in sys.builtin_module_names
|
|
67
87
|
PLATFORM = platform.system().lower()
|
|
68
88
|
PYTHON_VERSION = sys.version_info[0]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
#
|
|
3
|
-
# Copyright (c) 2024-
|
|
3
|
+
# Copyright (c) 2024-2026, Ryan Galloway (ryan@rsgalloway.com)
|
|
4
4
|
#
|
|
5
5
|
# Redistribution and use in source and binary forms, with or without
|
|
6
6
|
# modification, are permitted provided that the following conditions are met:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
#
|
|
3
|
-
# Copyright (c) 2024-
|
|
3
|
+
# Copyright (c) 2024-2026, Ryan Galloway (ryan@rsgalloway.com)
|
|
4
4
|
#
|
|
5
5
|
# Redistribution and use in source and binary forms, with or without
|
|
6
6
|
# modification, are permitted provided that the following conditions are met:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
#
|
|
3
|
-
# Copyright (c) 2024-
|
|
3
|
+
# Copyright (c) 2024-2026, Ryan Galloway (ryan@rsgalloway.com)
|
|
4
4
|
#
|
|
5
5
|
# Redistribution and use in source and binary forms, with or without
|
|
6
6
|
# modification, are permitted provided that the following conditions are met:
|
|
@@ -131,7 +131,7 @@ class EnvshellWrapper(Wrapper):
|
|
|
131
131
|
if os.name == "nt":
|
|
132
132
|
return ("PROMPT", "$E[32m(${ENV:=${STACK}})$E[0m $P$G ")
|
|
133
133
|
else:
|
|
134
|
-
return ("PS1", "\[\e[32m\](${ENV:=${STACK}})\[\e[0m\] \w\$ ")
|
|
134
|
+
return ("PS1", r"\[\e[32m\](${ENV:=${STACK}})\[\e[0m\] \w\$ ")
|
|
135
135
|
|
|
136
136
|
def get_subprocess_env(self):
|
|
137
137
|
"""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
#
|
|
3
|
-
# Copyright (c) 2024-
|
|
3
|
+
# Copyright (c) 2024-2026, Ryan Galloway (ryan@rsgalloway.com)
|
|
4
4
|
#
|
|
5
5
|
# Redistribution and use in source and binary forms, with or without
|
|
6
6
|
# modification, are permitted provided that the following conditions are met:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
#
|
|
3
|
-
# Copyright (c) 2024-
|
|
3
|
+
# Copyright (c) 2024-2026, Ryan Galloway (ryan@rsgalloway.com)
|
|
4
4
|
#
|
|
5
5
|
# Redistribution and use in source and binary forms, with or without
|
|
6
6
|
# modification, are permitted provided that the following conditions are met:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
#
|
|
3
|
-
# Copyright (c) 2024-
|
|
3
|
+
# Copyright (c) 2024-2026, Ryan Galloway (ryan@rsgalloway.com)
|
|
4
4
|
#
|
|
5
5
|
# Redistribution and use in source and binary forms, with or without
|
|
6
6
|
# modification, are permitted provided that the following conditions are met:
|
|
@@ -250,30 +250,35 @@ class FernetNode(BaseNode):
|
|
|
250
250
|
|
|
251
251
|
|
|
252
252
|
class CustomLoader(yaml.SafeLoader):
|
|
253
|
+
"""Custom Loader class to preserve order of keys and ensure required
|
|
254
|
+
keys are first in the mapping."""
|
|
255
|
+
|
|
253
256
|
required_keys = {"include", "all", "darwin", "linux", "windows"}
|
|
254
257
|
|
|
255
258
|
def construct_mapping(self, node: yaml.Node, deep: bool = False):
|
|
256
|
-
mapping
|
|
257
|
-
|
|
259
|
+
"""Construct a mapping from a YAML node, preserving the order of keys
|
|
260
|
+
and ensuring"""
|
|
261
|
+
# keep YAML merge keys (<<) working
|
|
262
|
+
self.flatten_mapping(node)
|
|
263
|
+
|
|
264
|
+
mapping = {}
|
|
265
|
+
|
|
266
|
+
for key_node, value_node in node.value:
|
|
267
|
+
# never implicit-resolve scalar KEYS (YES/NO/ON/OFF/null/date/etc)
|
|
268
|
+
if isinstance(key_node, yaml.ScalarNode):
|
|
269
|
+
key = key_node.value
|
|
270
|
+
else:
|
|
271
|
+
key = self.construct_object(key_node, deep=deep)
|
|
272
|
+
|
|
273
|
+
value = self.construct_object(value_node, deep=deep)
|
|
274
|
+
mapping[key] = value
|
|
275
|
+
|
|
276
|
+
# preserve order of keys and ensure required keys are first in the mapping
|
|
277
|
+
for key, value in list(mapping.items()):
|
|
258
278
|
if key in self.required_keys:
|
|
259
279
|
continue
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
mapping[key] = Base64Node(value)
|
|
263
|
-
elif node.tag == EncryptedNode.yaml_tag:
|
|
264
|
-
mapping[key] = EncryptedNode(value)
|
|
265
|
-
elif node.tag == AESGCMNode.yaml_tag:
|
|
266
|
-
mapping[key] = AESGCMNode(value)
|
|
267
|
-
elif node.tag == FernetNode.yaml_tag:
|
|
268
|
-
mapping[key] = FernetNode(value)
|
|
269
|
-
elif node.tag == MD5Node.yaml_tag:
|
|
270
|
-
mapping[key] = MD5Node(value)
|
|
271
|
-
else:
|
|
272
|
-
mapping[key] = Template(value)
|
|
273
|
-
except Exception as e:
|
|
274
|
-
raise yaml.constructor.ConstructorError(
|
|
275
|
-
None, None, f"Error parsing template: {e}", node.start_mark
|
|
276
|
-
)
|
|
280
|
+
mapping[key] = value
|
|
281
|
+
|
|
277
282
|
return mapping
|
|
278
283
|
|
|
279
284
|
|