Files
attack-defense-platform/panel/sync_all_challenges.py
T

73 lines
2.7 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
"""Batch-apply the enabled challenge set to every live team, one challenge at a
time, then report per-challenge results.
This is the CLI equivalent of hitting PATCH /api/challenges/<name> for each
registry entry, but it survives panel restarts and reports progress, which
matters because a full sync is N challenges x 4 teams x (docker build ~1-3 min).
Usage:
python3 panel/sync_all_challenges.py # sync registry -> runtime
python3 panel/sync_all_challenges.py --status # just show what is up
"""
from __future__ import annotations
import json
import subprocess
import sys
import time
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
import teams as orch # noqa: E402
def container_count(name: str) -> int:
out = subprocess.run(
["docker", "ps", "--format", "{{.Names}}"],
capture_output=True, text=True,
).stdout.splitlines()
return sum(1 for n in out if n.startswith(f"{name}_container_team"))
def main() -> int:
if "--status" in sys.argv:
reg = orch.load_registry()
enabled = [c["name"] for c in reg["challenges"] if c.get("enabled")]
print(f"registry enabled ({len(enabled)}): {', '.join(enabled)}\n")
running = 0
for name in enabled:
n = container_count(name)
running += 1 if n else 0
print(f" {name:16s} {n}/4 teams {'OK' if n == 4 else ('PARTIAL' if n else 'MISSING')}")
print(f"\n{running}/{len(enabled)} challenges have at least one container")
return 0
reg = orch.load_registry()
enabled = [c for c in reg["challenges"] if c.get("enabled")]
print(f"syncing {len(enabled)} enabled challenges across live teams\n")
t_all = time.time()
for c in enabled:
name = c["name"]
if container_count(name) == 4:
print(f"== {name}: already up on 4 teams, skipping")
continue
t0 = time.time()
print(f"== {name}: applying (this builds one image, then reuses it per team)", flush=True)
try:
report = orch.sync_challenge_runtime(name, True)
except Exception as e: # a single bad challenge must not abort the batch
print(f" FAILED after {time.time()-t0:.0f}s: {e}", flush=True)
continue
for t in report.get("teams", []):
last = (t.get("detail") or "").strip().splitlines()[-1:] or [""]
mark = "ok" if t.get("ok") else "FAIL"
print(f" team{t.get('team')}: {mark} {last[0][:110]}", flush=True)
print(f" done in {time.time()-t0:.0f}s", flush=True)
print(f"\nall done in {(time.time()-t_all)/60:.1f} min")
return 0
if __name__ == "__main__":
raise SystemExit(main())