#!/usr/bin/env python3 """Guard Gitea workflows from reintroducing 110 runner pressure triggers. This is a source guard only. It reads committed `.gitea/workflows` files and fails when automatic branch events target 110 incident runner labels, or when a generic runner label is reintroduced. """ from __future__ import annotations import argparse import re import sys from dataclasses import dataclass from pathlib import Path AUTO_BRANCH_EVENTS = {"push", "pull_request", "pull_request_target"} INCIDENT_RUNNER_LABELS = {"awoooi-ubuntu", "awoooi-host"} GENERIC_LABELS = {"ubuntu-latest", "self-hosted"} RUNS_ON_RE = re.compile(r"^\s*runs-on:\s*(?P.+?)\s*$") ON_RE = re.compile(r"^on:\s*(?P.*)$") ON_CHILD_EVENT_RE = re.compile(r"^(?P\s+)(?P[A-Za-z_][A-Za-z0-9_-]*)\s*:") @dataclass(frozen=True) class WorkflowInfo: path: Path events: set[str] labels: list[tuple[int, str]] @dataclass(frozen=True) class Violation: path: Path line: int label: str reason: str events: set[str] def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--root", type=Path, default=Path.cwd(), help="Repository root") parser.add_argument( "--workflow-dir", default=".gitea/workflows", help="Workflow directory relative to --root", ) return parser.parse_args() def remove_inline_comment(value: str) -> str: return value.split("#", 1)[0].rstrip() def strip_inline_comment(value: str) -> str: return remove_inline_comment(value).strip() def parse_inline_events(value: str) -> set[str]: clean = strip_inline_comment(value).strip() if not clean: return set() if clean.startswith("[") and clean.endswith("]"): return {part.strip().strip("'\"") for part in clean[1:-1].split(",") if part.strip()} if clean.startswith("{") and clean.endswith("}"): return { part.split(":", 1)[0].strip().strip("'\"") for part in clean[1:-1].split(",") if part.strip() } return {clean.strip().strip("'\"")} def parse_events(lines: list[str]) -> set[str]: events: set[str] = set() for index, line in enumerate(lines): match = ON_RE.match(remove_inline_comment(line)) if not match: continue inline_events = parse_inline_events(match.group("value")) if inline_events: events.update(inline_events) continue for child in lines[index + 1 :]: if not child.strip() or child.lstrip().startswith("#"): continue if child[0] not in {" ", "\t"}: break child_match = ON_CHILD_EVENT_RE.match(remove_inline_comment(child)) if child_match: events.add(child_match.group("event")) return events def normalize_runs_on(value: str) -> list[str]: clean = strip_inline_comment(value).strip() if clean.startswith("[") and clean.endswith("]"): return [part.strip().strip("'\"") for part in clean[1:-1].split(",") if part.strip()] return [clean.strip().strip("'\"")] def parse_workflow(path: Path) -> WorkflowInfo: lines = path.read_text(encoding="utf-8").splitlines() labels: list[tuple[int, str]] = [] for line_number, line in enumerate(lines, start=1): match = RUNS_ON_RE.match(line) if not match: continue for label in normalize_runs_on(match.group("value")): labels.append((line_number, label)) return WorkflowInfo(path=path, events=parse_events(lines), labels=labels) def label_is_generic(label: str) -> bool: if label in GENERIC_LABELS: return True return label.startswith("ubuntu-") def inspect(workflow_dir: Path) -> tuple[list[WorkflowInfo], list[Violation]]: workflows = [parse_workflow(path) for path in sorted(workflow_dir.glob("*.y*ml"))] violations: list[Violation] = [] for workflow in workflows: auto_events = workflow.events & AUTO_BRANCH_EVENTS for line, label in workflow.labels: if label_is_generic(label): violations.append( Violation( path=workflow.path, line=line, label=label, reason="generic_runner_label_reintroduced", events=workflow.events, ) ) if auto_events and label in INCIDENT_RUNNER_LABELS: violations.append( Violation( path=workflow.path, line=line, label=label, reason="auto_branch_event_targets_110_incident_runner", events=workflow.events, ) ) return workflows, violations def main() -> int: args = parse_args() root = args.root.expanduser().resolve() workflow_dir = root / args.workflow_dir if not workflow_dir.exists(): print(f"GITEA_RUNNER_PRESSURE_GUARD_BLOCKED workflow_dir_missing={workflow_dir}") return 2 workflows, violations = inspect(workflow_dir) if violations: print("GITEA_RUNNER_PRESSURE_GUARD_BLOCKED") for item in violations: rel_path = item.path.relative_to(root) events = ",".join(sorted(item.events)) or "none" print( f"{rel_path}:{item.line} label={item.label} " f"reason={item.reason} events={events}" ) return 1 scheduled = sum(1 for workflow in workflows if "schedule" in workflow.events) print( "GITEA_RUNNER_PRESSURE_GUARD_OK " f"workflow_files={len(workflows)} " f"scheduled_workflows={scheduled} " "auto_branch_events_on_110=0 " "generic_runner_labels=0" ) return 0 if __name__ == "__main__": sys.exit(main())