mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-09 12:17:13 +00:00
The retrigger sent only whitelist and priority, so entry-manual.yml applied its own distros default of 'debian'. A run that swept five distros came back as one, and a failure that only reproduces on centos returned green with nothing fixed. The dispatch inputs are not recoverable from the API - it answers inputs: null for a finished workflow_dispatch - so they are read back from the run title. The title format is not copied here: run_name.py parses the workflow's own run-name declaration and derives the literals framing an input, and the tests build their titles through the same helper instead of spelling one out. Known gap: a run from another entry point carries an unrelated title and yields no distros, leaving the retrigger on the workflow default. Job step names carry the list for every entry point and arrive in the same gh call, so that is where to look when it matters. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
95 lines
3.3 KiB
Python
95 lines
3.3 KiB
Python
"""Recover a manual CI run's inputs from its run name.
|
|
|
|
``entry-manual.yml`` declares a ``run-name:`` that interpolates the dispatch
|
|
inputs. Once a run has started, that title is the only record of them: the
|
|
REST API answers ``inputs: null`` for a workflow_dispatch run. Anything that
|
|
wants to know what a run was dispatched with therefore parses the title — and
|
|
parsing it against a hand-copied format would drift the moment the workflow
|
|
changes, silently and without a failing test.
|
|
|
|
So the format is read from the workflow itself and the literals that frame an
|
|
input are derived, never spelled out here.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
|
|
from utils.cache.files import PROJECT_ROOT
|
|
from utils.cache.yaml import load_yaml
|
|
|
|
WORKFLOW = PROJECT_ROOT / ".github" / "workflows" / "entry-manual.yml"
|
|
|
|
_EXPRESSION = re.compile(r"\$\{\{(.*?)\}\}", re.DOTALL)
|
|
|
|
|
|
def template() -> str:
|
|
"""The raw ``run-name:`` string declared by the manual CI workflow."""
|
|
declared = load_yaml(str(WORKFLOW)).get("run-name")
|
|
if not declared:
|
|
raise ValueError(f"{WORKFLOW} declares no run-name")
|
|
return str(declared)
|
|
|
|
|
|
def frame(input_name: str, tpl: str | None = None) -> tuple[str, str]:
|
|
"""Literal text surrounding ``inputs.<input_name>`` in the run name.
|
|
|
|
Args:
|
|
input_name: dispatch input interpolated on its own, e.g. ``distros``.
|
|
tpl: run-name to read; defaults to the declared one.
|
|
|
|
Returns:
|
|
``(before, after)``. Either side is empty when the expression sits at
|
|
a boundary of the template.
|
|
|
|
Raises:
|
|
ValueError: the input is not interpolated bare anywhere in the run
|
|
name, so no literal frame identifies its value.
|
|
"""
|
|
tpl = template() if tpl is None else tpl
|
|
wanted = f"inputs.{input_name}"
|
|
cursor = 0
|
|
literals: list[str] = []
|
|
expressions: list[str] = []
|
|
for match in _EXPRESSION.finditer(tpl):
|
|
literals.append(tpl[cursor : match.start()])
|
|
expressions.append(match.group(1).strip())
|
|
cursor = match.end()
|
|
literals.append(tpl[cursor:])
|
|
|
|
for index, expression in enumerate(expressions):
|
|
if expression == wanted:
|
|
return literals[index], literals[index + 1]
|
|
raise ValueError(f"{wanted} is not interpolated on its own in: {tpl}")
|
|
|
|
|
|
def title_with(
|
|
input_name: str, value: str, tail: str = "", tpl: str | None = None
|
|
) -> str:
|
|
"""A run name in which *input_name* holds *value*.
|
|
|
|
Args:
|
|
input_name: dispatch input to place.
|
|
value: what it held.
|
|
tail: whatever the workflow renders after this input's own separator.
|
|
tpl: run-name to read; defaults to the declared one.
|
|
"""
|
|
before, after = frame(input_name, tpl)
|
|
return f"{before}{value}{after}{tail}"
|
|
|
|
|
|
def value_from_title(title: str, input_name: str, tpl: str | None = None) -> str:
|
|
"""Value ``input_name`` held when the run named *title* was dispatched.
|
|
|
|
Returns an empty string when *title* does not follow the run name at all —
|
|
a run from another entry point carries an unrelated title, and guessing a
|
|
value for it would be worse than admitting ignorance.
|
|
"""
|
|
before, after = frame(input_name, tpl)
|
|
if not title.startswith(before):
|
|
return ""
|
|
rest = title[len(before) :]
|
|
if after:
|
|
rest = rest.split(after, 1)[0]
|
|
return " ".join(rest.split())
|