Files
apex.ermnvldmr.com/actions/env.py
T

83 lines
3.4 KiB
Python

"""Export synthesized APEX values or bridge a composition's environment for Compose."""
import os
import shlex
from engine.descriptor import Flag, Meta, Opt
METADATA = Meta(
summary="Export or sync synthesized APEX_* variables for shell or docker compose.",
args=[
Flag("--export", "Format as shell export statements (default)."),
Flag("--dotenv", "Print quoted generated APEX_* dotenv values only."),
Opt("--write", "Write a standalone generated dotenv file (0600).", default=None),
Flag("--sync", "Preserve user .env text and refresh the core Compose managed block."),
],
)
_START = "# BEGIN APEX MANAGED ENV"
_END = "# END APEX MANAGED ENV"
def _dotenv_value(value):
"""Quote a literal using Compose dotenv escapes, including interpolation dollars."""
value = str(value)
for old, new in (("\\", "\\\\"), ('"', '\\"'), ("$", "$$"),
("\r", "\\r"), ("\n", "\\n"), ("\t", "\\t")):
value = value.replace(old, new)
return f'"{value}"'
def _read_optional(path):
try:
with open(path, encoding="utf-8", newline="") as source:
return source.read()
except FileNotFoundError:
return ""
def _sync_body(existing, public, generated):
if any(marker in public or marker in generated for marker in (_START, _END)):
raise ValueError("Managed environment marker collides with source content.")
lines = existing.splitlines(keepends=True)
markers = [(index, line.rstrip("\r\n")) for index, line in enumerate(lines)
if _START in line or _END in line]
if markers:
if (len(markers) != 2 or markers[0][1] != _START or markers[1][1] != _END):
raise ValueError("Malformed or duplicate managed environment markers; .env unchanged.")
start, end = markers[0][0], markers[1][0]
existing = "".join(lines[:start] + lines[end + 1:])
# Keep user bytes in order, but place managed values last to preserve precedence.
separator = "" if not existing or existing.endswith("\n") else "\n"
public_separator = "" if not public or public.endswith("\n") else "\n"
return (existing + separator + _START + "\n" + public + public_separator
+ generated + _END + "\n")
def run(ctx, args):
modes = (args.export, args.dotenv, args.write is not None, args.sync)
if sum(bool(mode) for mode in modes) > 1:
ctx.log.error("Choose only one of --export, --dotenv, --write, or --sync.")
raise SystemExit(64)
variables = {k: v for k, v in ctx.vars().items() if k.startswith("APEX_")}
generated = "".join(f"{key}={_dotenv_value(value)}\n"
for key, value in sorted(variables.items()))
target = args.write
if args.sync:
core = os.path.join(ctx.paths.compositions, "apex")
target = os.path.join(core, ".env")
try:
generated = _sync_body(_read_optional(target),
_read_optional(os.path.join(core, "apex.env")), generated)
except ValueError as error:
ctx.log.error(str(error))
raise SystemExit(65) from error
if target is not None:
ctx.host.write_file(target, generated, mode="0600")
ctx.log.success(f"Wrote {len(variables)} synthesized variables to {target}.")
elif args.dotenv:
print(generated, end="")
else:
for key, value in sorted(variables.items()):
print(f"export {key}={shlex.quote(str(value))}")
return 0