Skip to content

Commit 6874fda

Browse files
authored
Merge pull request #79 from rsgalloway/v0.9.3
Version 0.9.3
2 parents 4cb88bf + b3a62bf commit 6874fda

19 files changed

Lines changed: 291 additions & 85 deletions

README.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,9 @@ STACK=default
8888
If you are not seeing the above output, make sure the `default.env` stack file
8989
is in `${ENVPATH}` or the current working directory.
9090

91-
> NOTE: The name of the current stack will always be stored in `${STACK}`
91+
> NOTE: The name of the current stack will always be stored in `${STACK}`.
92+
93+
ENV is the tier, STACK is the namespace.
9294

9395
Environments can be combined, or stacked, in order of priority (variables
9496
defined in stacks flow from higher scope to lower scope, left to right):
@@ -347,21 +349,21 @@ Then use `keys.env` to encrypt any other environment files:
347349
$ ./keys.env -- envstack -eo encrypted.env
348350
```
349351
350-
To decrypt, add `keys` to the env stack:
352+
To decrypt, run the command inside the `keys` environment again:
351353
352354
```bash
353-
$ envstack keys encrypted -r HELLO
355+
$ ./keys.env -- envstack encrypted -r HELLO
354356
HELLO=world
355357
```
356358
357-
Or run the command inside the `keys` environment like this:
359+
Or add `keys` to the env stack:
358360
359361
```bash
360-
$ ./keys.env -- envsatck encrypted -r HELLO
362+
$ envstack keys encrypted -r HELLO
361363
HELLO=world
362364
```
363365
364-
Or include `keys` in environments to automatically decrypt:
366+
Or automatically include `keys`:
365367
366368
```yaml
367369
include: [keys]

bin/envshell

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#!/usr/bin/env python3
2+
#
3+
# Copyright (c) 2024-2025, Ryan Galloway (ryan@rsgalloway.com)
4+
#
5+
# Redistribution and use in source and binary forms, with or without
6+
# modification, are permitted provided that the following conditions are met:
7+
#
8+
# - Redistributions of source code must retain the above copyright notice,
9+
# this list of conditions and the following disclaimer.
10+
#
11+
# - Redistributions in binary form must reproduce the above copyright notice,
12+
# this list of conditions and the following disclaimer in the documentation
13+
# and/or other materials provided with the distribution.
14+
#
15+
# - Neither the name of the software nor the names of its contributors
16+
# may be used to endorse or promote products derived from this software
17+
# without specific prior written permission.
18+
#
19+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29+
# POSSIBILITY OF SUCH DAMAGE.
30+
#
31+
32+
__doc__ = """
33+
Contains a simple executable for the envstack cli.py module.
34+
"""
35+
36+
import re
37+
import sys
38+
from envstack.cli import envshell
39+
40+
if __name__ == "__main__":
41+
sys.argv[0] = re.sub(r"(-script\.pyw|\.exe)?$", "", sys.argv[0])
42+
sys.exit(envshell(sys.argv[1:]))

bin/envshell.bat

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@echo off
2+
python %~dp0\envshell %*

env/default.env

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@ all: &all
1010
darwin:
1111
<<: *all
1212
ROOT: /Volumes/pipe
13+
PS1: '\[\e[32m\](${ENV})\[\e[0m\] \w\$ '
1314
linux:
1415
<<: *all
1516
ROOT: /mnt/pipe
17+
PS1: '\[\e[32m\](${ENV})\[\e[0m\] \w\$ '
1618
windows:
1719
<<: *all
18-
ROOT: "X:/pipe"
20+
ROOT: //tools/pipe
21+
PROMPT: $E[32m(${ENV})$E[0m $P$G

env/hello.env

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ all: &all
55
PYEXE: /usr/bin/python
66
darwin:
77
<<: *all
8+
PS1: '\[\e[33m\](${STACK})\[\e[0m\] \w\$ '
89
linux:
910
<<: *all
11+
PS1: '\[\e[33m\](${STACK})\[\e[0m\] \w\$ '
1012
windows:
11-
<<: *all
13+
<<: *all
14+
PROMPT: $E[33m(${ENV})$E[0m $P$G

env/project.env

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/usr/bin/env envstack
2+
include: [default]
3+
all: &all
4+
ENV: ${STACK:=${ENV}}
5+
DEPLOY_ROOT: ${ROOT}/${ENV}
6+
ENVPATH: ${ROOT}/${ENV}/env:${ROOT}/prod/env
7+
PATH: ${ROOT}/${ENV}/bin:${ROOT}/prod/bin:${PATH}
8+
PYTHONPATH: ${ROOT}/${ENV}/lib/python:${ROOT}/prod/lib/python:${PYTHONPATH}
9+
darwin:
10+
<<: *all
11+
linux:
12+
<<: *all
13+
windows:
14+
<<: *all

env/test.env

Lines changed: 0 additions & 15 deletions
This file was deleted.

lib/envstack/__init__.py

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

3636
__prog__ = "envstack"
37-
__version__ = "0.9.2"
37+
__version__ = "0.9.3"
3838

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

lib/envstack/cli.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import re
3838
import sys
3939
import traceback
40+
from typing import List
4041

4142
from envstack import __version__, config
4243
from envstack.env import (
@@ -147,20 +148,24 @@ def parse_args():
147148
action="version",
148149
version=f"envstack {__version__}",
149150
)
150-
group = parser.add_mutually_exclusive_group(required=False)
151-
group.add_argument(
151+
parser.add_argument(
152152
"namespace",
153153
metavar="STACK",
154154
nargs="*",
155155
default=[config.DEFAULT_NAMESPACE],
156156
help="the environment stacks to use",
157157
)
158-
group.add_argument(
158+
parser.add_argument(
159159
"-b",
160160
"--bare",
161161
action="store_true",
162162
help="create a bare environment",
163163
)
164+
parser.add_argument(
165+
"--shell",
166+
action="store_true",
167+
help="drop into a shell with the environment loaded",
168+
)
164169
encrypt_group = parser.add_argument_group("encryption options")
165170
encrypt_group.add_argument(
166171
"-e",
@@ -254,9 +259,20 @@ def parse_args():
254259
return args, args_after_dash
255260

256261

262+
def envshell(namespace: List[str] = None):
263+
"""Run a shell in the given environment stack."""
264+
from .envshell import EnvshellWrapper
265+
266+
print("\U0001F680 Launching envshell... CTRL+D to exit")
267+
268+
name = (namespace or [config.DEFAULT_NAMESPACE])[:]
269+
envshell = EnvshellWrapper(name)
270+
return envshell.launch()
271+
272+
257273
def whichenv():
258274
"""Entry point for the whichenv command line tool. Finds {VAR}s."""
259-
from envstack.util import findenv
275+
from .util import findenv
260276

261277
if len(sys.argv) != 2:
262278
print("Usage: whichenv [VAR]")
@@ -276,6 +292,9 @@ def main():
276292
if command:
277293
return run_command(command, args.namespace)
278294

295+
elif args.shell:
296+
return envshell(args.namespace)
297+
279298
elif args.keygen:
280299
from envstack.encrypt import generate_keys
281300

lib/envstack/encrypt.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,14 @@
4343

4444
# cryptography and _rust dependency may not be available everywhere
4545
# ImportError: DLL load failed while importing _rust: Module not found.
46+
Fernet = None
4647
try:
4748
import cryptography.exceptions
4849
from cryptography.fernet import Fernet, InvalidToken
4950
from cryptography.hazmat.primitives import padding
5051
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
5152
except ImportError as err:
5253
log.debug("cryptography module not available: %s", err)
53-
Fernet = None
5454

5555

5656
class Base64Encryptor(object):
@@ -78,7 +78,7 @@ class FernetEncryptor(object):
7878
KEY_VAR_NAME = "ENVSTACK_FERNET_KEY"
7979

8080
def __init__(self, key: str = None, env: dict = os.environ):
81-
if key:
81+
if key and Fernet:
8282
self.key = Fernet(key)
8383
else:
8484
self.key = self.get_key(env)
@@ -90,7 +90,8 @@ def generate_key(csl):
9090
key = Fernet.generate_key()
9191
return key.decode()
9292
else:
93-
log.error("Fernet encryption not available")
93+
log.debug("Fernet encryption not available")
94+
return ""
9495

9596
def get_key(self, env: dict = os.environ):
9697
"""Load the encryption key from the environment `env`.
@@ -99,7 +100,7 @@ def get_key(self, env: dict = os.environ):
99100
:return: encryption key.
100101
"""
101102
key = env.get(self.KEY_VAR_NAME)
102-
if key:
103+
if key and Fernet:
103104
return Fernet(key)
104105
return key
105106

@@ -313,12 +314,11 @@ def generate_keys():
313314
314315
:returns: Dictionary containing Fernet and AES-GCM keys.
315316
"""
316-
from envstack.node import Base64Node
317317

318318
symmetric_key = AESGCMEncryptor.generate_key()
319319
fernet_key = FernetEncryptor.generate_key()
320320

321321
return {
322-
AESGCMEncryptor.KEY_VAR_NAME: Base64Node(symmetric_key),
323-
FernetEncryptor.KEY_VAR_NAME: Base64Node(fernet_key),
322+
AESGCMEncryptor.KEY_VAR_NAME: symmetric_key,
323+
FernetEncryptor.KEY_VAR_NAME: fernet_key,
324324
}

0 commit comments

Comments
 (0)