73 lines
2.7 KiB
Python
73 lines
2.7 KiB
Python
#!/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())
|