Files
computer-playbook/cli/deploy/development/run.py
T
kevinveenbirkenbach bdd6587bcc fix(ci): show deploy stdout/stderr and pass inventory_dir in run orchestrator
- Capture and print deploy stdout/stderr on failure (development/deploy.py)
- Forward INVENTORY_DIR into init and deploy handlers (development/run.py)
- Add missing when_down flag for up handler
- Print full traceback on orchestrator failure for CI visibility

https://chatgpt.com/share/6970fcf5-5860-800f-89ae-0d601405489b
2026-01-21 17:21:02 +01:00

160 lines
5.0 KiB
Python

from __future__ import annotations
import argparse
import os
import sys
import traceback
from pathlib import Path
from .common import make_compose, resolve_deploy_ids_for_app
from .deploy import handler as deploy_handler
from .init import handler as init_handler
from .log_utils import append_text, log_path, write_text
from .up import handler as up_handler
def add_parser(sub: argparse._SubParsersAction) -> None:
p = sub.add_parser(
"run", help="Orchestrator: up -> init -> deploy -> down (optional keep)."
)
p.add_argument(
"--type",
required=True,
choices=["server", "workstation", "universal"],
help="Deploy type.",
)
p.add_argument(
"--distro",
default=os.environ.get("INFINITO_DISTRO", "arch"),
choices=["arch", "debian", "ubuntu", "fedora", "centos"],
help="Target distro (compose env INFINITO_DISTRO).",
)
p.add_argument(
"--skip-entry-init",
action="store_true",
help="Do not run entry.sh init after compose up.",
)
p.add_argument(
"--app", required=True, help="Run for this single application_id (plus deps)."
)
p.add_argument(
"--logs-dir", default="logs", help="Directory for log files (default: logs)."
)
p.add_argument(
"--keep-stack-on-failure",
action="store_true",
help="Do not tear down compose stack on failure.",
)
p.add_argument(
"--debug",
action=argparse.BooleanOptionalAction,
default=False,
help="Enable/disable Ansible debug mode (default: disabled).",
)
p.add_argument(
"ansible_args",
nargs=argparse.REMAINDER,
help="Passthrough args appended to `cli.deploy.dedicated` (use `--` to separate).",
)
p.set_defaults(_handler=handler)
def handler(args: argparse.Namespace) -> int:
logs_dir = Path(args.logs_dir).resolve()
logs_dir.mkdir(parents=True, exist_ok=True)
main_log = log_path(logs_dir, args.type, args.distro, "orchestrator")
write_text(
main_log,
f"deploy_type={args.type}\ndistro={args.distro}\napp={args.app}\ndebug={args.debug}\n",
)
# Drop leading '--' from REMAINDER
passthrough = list(args.ansible_args or [])
if passthrough and passthrough[0] == "--":
passthrough = passthrough[1:]
# Inventory dir is required for init+deploy (both default to $INVENTORY_DIR)
inv_dir = os.environ.get("INVENTORY_DIR")
if not inv_dir:
raise SystemExit("INVENTORY_DIR must be set for run (used by init/deploy)")
inv_dir = str(inv_dir).rstrip("/")
compose = None
exit_code = 0
try:
# 1) up (re-use up handler so build-missing stays SPOT)
class _UpArgs:
distro = args.distro
skip_entry_init = bool(args.skip_entry_init)
when_down = False # required by up.handler()
rc_up = int(up_handler(_UpArgs()))
if rc_up != 0:
return rc_up
# Create compose AFTER up so we have a fresh instance for the remainder.
compose = make_compose(distro=args.distro)
# 2) resolve deps
deploy_ids = resolve_deploy_ids_for_app(compose, args.app)
append_text(main_log, f"\nresolved_ids={','.join(deploy_ids)}\n")
# 3) init subcommand (re-using its handler via a tiny args object)
class _InitArgs:
distro = args.distro
app = args.app
include = None
inventory_dir = inv_dir
threshold_gib = 100
force_storage_constrained = None
init_handler(_InitArgs())
# 4) deploy subcommand
class _DeployArgs:
distro = args.distro
type = args.type
app = args.app
id = None
inventory_dir = inv_dir
debug = bool(args.debug)
ansible_args = ["--", *passthrough] if passthrough else []
rc = deploy_handler(_DeployArgs())
exit_code = int(rc)
per_app_log = log_path(logs_dir, args.type, args.distro, f"app-{args.app}")
write_text(
per_app_log, f"app={args.app}\nids={','.join(deploy_ids)}\nrc={exit_code}\n"
)
if exit_code != 0:
append_text(
per_app_log,
"\n--- hint ---\n"
"Search workflow logs for the failing task output.\n"
f"Also run: INFINITO_DISTRO={args.distro} docker compose --profile ci logs --tail=200\n",
)
return exit_code
except Exception as exc:
exit_code = 1
# Make the failure visible in CI output (not only in logs file)
print(f">>> ERROR: {exc}", file=sys.stderr)
traceback.print_exc()
append_text(main_log, f"\nERROR: {exc}\n")
return 1
finally:
should_keep = bool(args.keep_stack_on_failure) and exit_code != 0
if not should_keep and compose is not None:
try:
compose.down()
except Exception:
pass