44 lines
1.8 KiB
Python
44 lines
1.8 KiB
Python
"""challenges.yml: which repos are part of this CTF, and their check exceptions."""
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
|
|
import yaml
|
|
|
|
from .challenge import SELECTIONS
|
|
from .checks import REPO_CHECKS
|
|
|
|
SELECTION_FILE = Path(__file__).parent.parent / "challenges.yml" # next to ctf_status.py
|
|
|
|
|
|
@dataclass
|
|
class Selection:
|
|
states: dict # repo path -> one of SELECTIONS
|
|
skip_checks: dict # repo path -> names of REPO_CHECKS it need not pass
|
|
|
|
|
|
def load_selection(path: Path = SELECTION_FILE) -> Selection:
|
|
"""Raises ValueError on anything off. A broken file must stop the run: without
|
|
it nothing counts as included, and challs.txt would be committed empty."""
|
|
try:
|
|
doc = yaml.safe_load(path.read_text())
|
|
except (OSError, yaml.YAMLError) as e:
|
|
raise ValueError(f"cannot read {path.name}: {e}") from None
|
|
states = doc.get("challenges") if isinstance(doc, dict) else None
|
|
if not states or not isinstance(states, dict):
|
|
raise ValueError(f"{path.name} has no `challenges:` mapping")
|
|
bad = [f"{k}: {v}" for k, v in states.items() if v not in SELECTIONS]
|
|
if bad:
|
|
raise ValueError(f"{path.name}: expected one of {', '.join(SELECTIONS)}, got " + ", ".join(bad))
|
|
|
|
skip = doc.get("skip_checks") or {}
|
|
if not isinstance(skip, dict) or not all(isinstance(v, list) for v in skip.values()):
|
|
raise ValueError(f"{path.name}: `skip_checks:` must map repos to lists of checks")
|
|
known = {check.__name__ for check in REPO_CHECKS}
|
|
bad = sorted({str(n) for names in skip.values() for n in names} - known)
|
|
if bad:
|
|
raise ValueError(f"{path.name}: no such repo check: " + ", ".join(bad))
|
|
return Selection({str(k): v for k, v in states.items()},
|
|
{str(k): [str(n) for n in v] for k, v in skip.items()})
|