Split host setup into explicit entity actions
This commit is contained in:
51 files changed
+1023
-275
No files matched your search
@@ -0,0 +1,82 @@
|
||||
"""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
|
||||
Reference in new issue
Block a user