Support VPS local preflight collection
Some checks are pending
CI / lint-and-test (push) Waiting to run
Some checks are pending
CI / lint-and-test (push) Waiting to run
This commit is contained in:
parent
637d285ecb
commit
0d70c09f6f
5 changed files with 55 additions and 17 deletions
|
|
@ -27,7 +27,7 @@
|
|||
"rio_strategy_nodes": 2,
|
||||
"sources": 28
|
||||
},
|
||||
"generated_at_utc": "2026-07-10T01:39:08.583122+00:00",
|
||||
"generated_at_utc": "2026-07-10T01:41:16.353240+00:00",
|
||||
"mode": "operator_runbook_generation_only",
|
||||
"operator_environment": {
|
||||
"operator_commands_run_sql_if_executed": true,
|
||||
|
|
@ -337,8 +337,8 @@
|
|||
"pre_apply_validation_commands": [
|
||||
{
|
||||
"applies_production_packet": false,
|
||||
"command_template": ".venv/bin/python scripts/collect_working_leo_production_preflight.py --execute --out docs/reports/leo-working-state-20260709/working-leo-production-preflight-current.json --markdown-out docs/reports/leo-working-state-20260709/working-leo-production-preflight-current.md",
|
||||
"description": "Collect a production baseline for every preflight SQL file through a read-only transaction wrapper. During an authorized apply window, still run each phase preflight again immediately before its phase.",
|
||||
"command_template": ".venv/bin/python scripts/collect_working_leo_production_preflight.py --execute --transport ssh --out docs/reports/leo-working-state-20260709/working-leo-production-preflight-current.json --markdown-out docs/reports/leo-working-state-20260709/working-leo-production-preflight-current.md",
|
||||
"description": "Collect a production baseline for every preflight SQL file through a read-only transaction wrapper. During an authorized apply window, still run each phase preflight again immediately before its phase. Use --transport local-docker when running this collector directly on the VPS.",
|
||||
"expected_artifact": "docs/reports/leo-working-state-20260709/working-leo-production-preflight-current.md",
|
||||
"id": "readonly_production_preflight_collector",
|
||||
"mutates_production_db": false
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Working Leo Authorized Apply Runbook
|
||||
|
||||
Generated UTC: `2026-07-10T01:39:08.583122+00:00`
|
||||
Generated UTC: `2026-07-10T01:41:16.353240+00:00`
|
||||
Mode: `operator_runbook_generation_only`
|
||||
Status: `ready_not_executed`
|
||||
Ready for authorized operator: `True`
|
||||
|
|
@ -94,8 +94,8 @@ Do not run apply commands unless the operator explicitly authorizes production D
|
|||
|
||||
## Pre-Apply Validation Commands
|
||||
|
||||
- `readonly_production_preflight_collector`: Collect a production baseline for every preflight SQL file through a read-only transaction wrapper. During an authorized apply window, still run each phase preflight again immediately before its phase.
|
||||
- command: `.venv/bin/python scripts/collect_working_leo_production_preflight.py --execute --out docs/reports/leo-working-state-20260709/working-leo-production-preflight-current.json --markdown-out docs/reports/leo-working-state-20260709/working-leo-production-preflight-current.md`
|
||||
- `readonly_production_preflight_collector`: Collect a production baseline for every preflight SQL file through a read-only transaction wrapper. During an authorized apply window, still run each phase preflight again immediately before its phase. Use --transport local-docker when running this collector directly on the VPS.
|
||||
- command: `.venv/bin/python scripts/collect_working_leo_production_preflight.py --execute --transport ssh --out docs/reports/leo-working-state-20260709/working-leo-production-preflight-current.json --markdown-out docs/reports/leo-working-state-20260709/working-leo-production-preflight-current.md`
|
||||
- artifact: `docs/reports/leo-working-state-20260709/working-leo-production-preflight-current.md`
|
||||
- mutates production DB: `False`
|
||||
- applies production packet: `False`
|
||||
|
|
|
|||
|
|
@ -138,10 +138,11 @@ PRE_APPLY_VALIDATION_COMMANDS: list[dict[str, Any]] = [
|
|||
"id": "readonly_production_preflight_collector",
|
||||
"description": (
|
||||
"Collect a production baseline for every preflight SQL file through a read-only transaction wrapper. "
|
||||
"During an authorized apply window, still run each phase preflight again immediately before its phase."
|
||||
"During an authorized apply window, still run each phase preflight again immediately before its phase. "
|
||||
"Use --transport local-docker when running this collector directly on the VPS."
|
||||
),
|
||||
"command_template": (
|
||||
".venv/bin/python scripts/collect_working_leo_production_preflight.py --execute "
|
||||
".venv/bin/python scripts/collect_working_leo_production_preflight.py --execute --transport ssh "
|
||||
"--out docs/reports/leo-working-state-20260709/working-leo-production-preflight-current.json "
|
||||
"--markdown-out docs/reports/leo-working-state-20260709/working-leo-production-preflight-current.md"
|
||||
),
|
||||
|
|
|
|||
|
|
@ -86,8 +86,11 @@ def preflight_entries(report_dir: Path = REPO_REPORT_DIR) -> list[dict[str, Any]
|
|||
return generated["phases"]["preflight_order"]
|
||||
|
||||
|
||||
def build_ssh_command(args: argparse.Namespace) -> list[str]:
|
||||
command = [
|
||||
def build_command(args: argparse.Namespace) -> list[str]:
|
||||
docker_psql = f"docker exec -i {args.container} psql -U {args.db_user} -d {args.db} -v ON_ERROR_STOP=1 -P pager=off"
|
||||
if args.transport == "local-docker":
|
||||
return docker_psql.split()
|
||||
return [
|
||||
"ssh",
|
||||
"-i",
|
||||
str(args.ssh_key),
|
||||
|
|
@ -98,15 +101,16 @@ def build_ssh_command(args: argparse.Namespace) -> list[str]:
|
|||
"-o",
|
||||
f"ConnectTimeout={args.connect_timeout}",
|
||||
args.ssh_target,
|
||||
(
|
||||
f"docker exec -i {args.container} "
|
||||
f"psql -U {args.db_user} -d {args.db} -v ON_ERROR_STOP=1 -P pager=off"
|
||||
),
|
||||
docker_psql,
|
||||
]
|
||||
return command
|
||||
|
||||
|
||||
def command_for_report(args: argparse.Namespace) -> str:
|
||||
if args.transport == "local-docker":
|
||||
return (
|
||||
f"docker exec -i {args.container} psql -U {args.db_user} -d {args.db} "
|
||||
"-v ON_ERROR_STOP=1 -P pager=off"
|
||||
)
|
||||
return (
|
||||
"ssh -i [SSH_KEY] -o BatchMode=yes -o StrictHostKeyChecking=accept-new "
|
||||
f"-o ConnectTimeout={args.connect_timeout} {args.ssh_target} "
|
||||
|
|
@ -119,7 +123,7 @@ def collect_preflight(args: argparse.Namespace) -> dict[str, Any]:
|
|||
entries = preflight_entries(args.report_dir)
|
||||
results: list[dict[str, Any]] = []
|
||||
mode = "live_vps_readonly_preflight" if args.execute else "validated_not_executed"
|
||||
command = build_ssh_command(args)
|
||||
command = build_command(args)
|
||||
|
||||
for index, entry in enumerate(entries, start=1):
|
||||
path = args.report_dir / entry["file"]
|
||||
|
|
@ -176,6 +180,7 @@ def collect_preflight(args: argparse.Namespace) -> dict[str, Any]:
|
|||
"production_apply_executed": False,
|
||||
"readonly_transaction_wrapper": True,
|
||||
"ssh_target": args.ssh_target if args.execute else None,
|
||||
"transport": args.transport,
|
||||
"command_redacted": command_for_report(args) if args.execute else None,
|
||||
"preflight_count": len(results),
|
||||
"passes": sum(1 for item in results if item["ok"]),
|
||||
|
|
@ -234,6 +239,12 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
|||
parser.add_argument("--out", type=Path, default=DEFAULT_OUT)
|
||||
parser.add_argument("--markdown-out", type=Path, default=DEFAULT_MARKDOWN_OUT)
|
||||
parser.add_argument("--execute", action="store_true", help="run the preflight SQL against the VPS")
|
||||
parser.add_argument(
|
||||
"--transport",
|
||||
choices=["ssh", "local-docker"],
|
||||
default="ssh",
|
||||
help="Use ssh from an operator machine, or local-docker when already running on the VPS.",
|
||||
)
|
||||
parser.add_argument("--ssh-target", default=DEFAULT_SSH_TARGET)
|
||||
parser.add_argument("--ssh-key", type=Path, default=DEFAULT_SSH_KEY)
|
||||
parser.add_argument("--connect-timeout", type=int, default=15)
|
||||
|
|
|
|||
|
|
@ -12,12 +12,13 @@ sys.path.insert(0, str(REPO_ROOT / "scripts"))
|
|||
import collect_working_leo_production_preflight as preflight # noqa: E402
|
||||
|
||||
|
||||
def _args(tmp_path: Path, *, execute: bool = False) -> argparse.Namespace:
|
||||
def _args(tmp_path: Path, *, execute: bool = False, transport: str = "ssh") -> argparse.Namespace:
|
||||
return argparse.Namespace(
|
||||
report_dir=REPO_ROOT / preflight.REPO_REPORT_DIR,
|
||||
out=tmp_path / "preflight.json",
|
||||
markdown_out=tmp_path / "preflight.md",
|
||||
execute=execute,
|
||||
transport=transport,
|
||||
ssh_target="root@example.invalid",
|
||||
ssh_key=tmp_path / "ssh-key",
|
||||
connect_timeout=3,
|
||||
|
|
@ -89,6 +90,31 @@ def test_execute_mode_wraps_sql_readonly_and_uses_ssh(monkeypatch, tmp_path: Pat
|
|||
assert call["input"].rstrip().endswith("commit;")
|
||||
|
||||
|
||||
def test_execute_mode_can_run_local_docker_on_vps(monkeypatch, tmp_path: Path):
|
||||
calls = []
|
||||
|
||||
class Completed:
|
||||
returncode = 0
|
||||
stdout = "ok\n"
|
||||
stderr = ""
|
||||
|
||||
def fake_run(command, *, input, text, capture_output, timeout, check):
|
||||
calls.append({"command": command, "input": input})
|
||||
return Completed()
|
||||
|
||||
monkeypatch.setattr(preflight.subprocess, "run", fake_run)
|
||||
|
||||
report = preflight.collect_preflight(_args(tmp_path, execute=True, transport="local-docker"))
|
||||
|
||||
assert report["status"] == "pass"
|
||||
assert report["transport"] == "local-docker"
|
||||
assert len(calls) == 5
|
||||
for call in calls:
|
||||
assert call["command"][:3] == ["docker", "exec", "-i"]
|
||||
assert "ssh" not in call["command"]
|
||||
assert call["input"].startswith("\\set ON_ERROR_STOP on\nbegin transaction read only;\n")
|
||||
|
||||
|
||||
def test_markdown_preserves_claim_ceiling(tmp_path: Path):
|
||||
report = preflight.collect_preflight(_args(tmp_path))
|
||||
out = tmp_path / "preflight.md"
|
||||
|
|
|
|||
Loading…
Reference in a new issue