mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 02:54:26 +00:00
The swarm deploy job now renders the same summary page as compose: role runtimes, a swarm structure section (topology roles plus live node list and service placement from the manager) and the Playwright table. The matrix deploy tees its ansible output and prints the compose matrix-deploy round/PASS markers, so the runtime parser segments swarm runs per variant round too. The runtime table unifies tasks and roles with a Type column (tasks first), appends the recap total row and flags a mismatch between the parsed sum and the recap total; rows are read from ROLES RECAP sections only, which removes the double-counted TASKS RECAP task rows. Each role row carries one column per host: executed, skipped or failed, with ignored failures downgraded. The Playwright variant column drops the redundant variant- prefix. Verified against the logs of runs 29656268771 (swarm) and 29620158441 (compose): every segment's parsed sum matches the recap total within 0.05s. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
57 lines
1.4 KiB
Python
57 lines
1.4 KiB
Python
from __future__ import annotations
|
|
|
|
import csv
|
|
import io
|
|
from pathlib import Path
|
|
from typing import IO
|
|
|
|
from .model import RoleRuntime
|
|
|
|
CSV_HEADER = ["round", "rounds_total", "pass", "pass_mode", "role", "seconds", "hosts"]
|
|
|
|
|
|
def _to_row(r: RoleRuntime) -> list[str]:
|
|
return [
|
|
r.round,
|
|
r.rounds_total,
|
|
r.pass_num,
|
|
r.pass_mode,
|
|
r.role,
|
|
f"{r.seconds:.2f}",
|
|
r.hosts,
|
|
]
|
|
|
|
|
|
def dump_csv(records: list[RoleRuntime], fh: IO[str]) -> None:
|
|
writer = csv.writer(fh)
|
|
writer.writerow(CSV_HEADER)
|
|
for r in records:
|
|
writer.writerow(_to_row(r))
|
|
|
|
|
|
def to_csv_string(records: list[RoleRuntime]) -> str:
|
|
buf = io.StringIO()
|
|
dump_csv(records, buf)
|
|
return buf.getvalue()
|
|
|
|
|
|
def write_csv(path: str | Path, records: list[RoleRuntime]) -> None:
|
|
with Path(path).open("w", encoding="utf-8", newline="") as fh:
|
|
dump_csv(records, fh)
|
|
|
|
|
|
def read_csv(path: str | Path) -> list[RoleRuntime]:
|
|
with Path(path).open(encoding="utf-8", newline="") as fh:
|
|
return [
|
|
RoleRuntime(
|
|
role=row["role"],
|
|
seconds=float(row["seconds"]),
|
|
round=row.get("round", ""),
|
|
rounds_total=row.get("rounds_total", ""),
|
|
pass_num=row.get("pass", ""),
|
|
pass_mode=row.get("pass_mode", ""),
|
|
hosts=row.get("hosts", "") or "",
|
|
)
|
|
for row in csv.DictReader(fh)
|
|
]
|