Skip to content

Commit 3d27007

Browse files
authored
fix formatting issues with output (#11)
- rich print was causing line wrapping breaking some formats - multi line secrets like ssh key weren't being print correctly for yaml or dotenv
1 parent ac5c21c commit 3d27007

3 files changed

Lines changed: 75 additions & 3 deletions

File tree

src/envars/cli.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
from .cloud_utils import get_default_location_name
1313
from .main import (
14+
PrettyDumper,
1415
Secret,
1516
_check_for_circular_dependencies,
1617
_get_decrypted_value,
@@ -392,11 +393,16 @@ def output_command(
392393
resolved_vars = _get_resolved_variables(manager, loc, env, decrypt=True)
393394
if format == "dotenv":
394395
for k, v in resolved_vars.items():
395-
console.print(f"{k}={v}")
396+
if "\n" in v:
397+
# Escape newlines and wrap in quotes for dotenv format
398+
escaped_v = v.replace("\n", "\\n")
399+
print(f'{k}="{escaped_v}"')
400+
else:
401+
print(f"{k}={v}")
396402
elif format == "yaml":
397-
console.print(yaml.dump({"envars": resolved_vars}, sort_keys=False))
403+
print(yaml.dump({"envars": resolved_vars}, sort_keys=False, Dumper=PrettyDumper))
398404
elif format == "json":
399-
console.print(json.dumps({"envars": resolved_vars}, indent=2))
405+
print(json.dumps({"envars": resolved_vars}, indent=2))
400406
else:
401407
error_console.print(f"[bold red]Error:[/] Invalid output format: {format}")
402408
raise typer.Exit(code=1)

src/envars/main.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,21 @@ def secret_constructor(loader, node):
4040
return Secret(value)
4141

4242

43+
def str_representer(dumper, data):
44+
"""Use the literal block style for multi-line strings."""
45+
if "\n" in data:
46+
return dumper.represent_scalar("tag:yaml.org,2002:str", data, style="|")
47+
return dumper.represent_scalar("tag:yaml.org,2002:str", data)
48+
49+
50+
class PrettyDumper(yaml.Dumper):
51+
pass
52+
53+
54+
yaml.add_representer(str, str_representer, Dumper=PrettyDumper)
55+
yaml.add_representer(Secret, secret_representer, Dumper=PrettyDumper)
56+
57+
4358
class SafeLoaderWithDuplicatesCheck(yaml.SafeLoader):
4459
def construct_mapping(self, node, deep=False):
4560
mapping = {}

tests/test_cli.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1363,3 +1363,54 @@ def test_validate_command_with_invalid_value(self, tmp_path):
13631363
result = runner.invoke(app, ["--file", file_path, "validate"])
13641364
assert result.exit_code == 1
13651365
assert "Value 'invalid_value' for variable 'MY_VAR' does not match validation regex" in result.stderr
1366+
1367+
1368+
def test_output_multiline_secret_dotenv(tmp_path):
1369+
encrypted_string = base64.b64encode(b"some_encrypted_bytes").decode("utf-8")
1370+
initial_content = f"""
1371+
configuration:
1372+
app: MyApp
1373+
kms_key: \"arn:aws:kms:us-east-1:123456789012:key/mrk-12345\"
1374+
environments:
1375+
- dev
1376+
locations:
1377+
- my_loc: \"loc123\"
1378+
environment_variables:
1379+
MY_MULTILINE_SECRET:
1380+
dev:
1381+
my_loc: !secret {encrypted_string}
1382+
"""
1383+
file_path = create_envars_file(tmp_path, initial_content)
1384+
1385+
multiline_value = "line1\nline2\nline3"
1386+
1387+
kms_client = boto3.client("kms", region_name="us-east-1")
1388+
with Stubber(kms_client) as stubber:
1389+
stubber.add_response(
1390+
"decrypt",
1391+
{"Plaintext": multiline_value.encode("utf-8")},
1392+
{
1393+
"CiphertextBlob": b"some_encrypted_bytes",
1394+
"EncryptionContext": {"app": "MyApp", "env": "dev", "location": "my_loc"},
1395+
},
1396+
)
1397+
with patch("boto3.client", return_value=kms_client):
1398+
result = runner.invoke(
1399+
app,
1400+
[
1401+
"--file",
1402+
file_path,
1403+
"output",
1404+
"--format",
1405+
"dotenv",
1406+
"--env",
1407+
"dev",
1408+
"--loc",
1409+
"my_loc",
1410+
],
1411+
)
1412+
assert result.exit_code == 0, result.stderr
1413+
escaped_multiline_value = multiline_value.replace("\n", "\\n")
1414+
expected_output = f'MY_MULTILINE_SECRET="{escaped_multiline_value}"'
1415+
assert expected_output in result.stdout
1416+
stubber.assert_no_pending_responses()

0 commit comments

Comments
 (0)