mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-09 12:17:13 +00:00
GitHub cuts display_title at 512 UTF-8 bytes and closes it with a literal '...'. The run-name template renders priority second-to-last and whitelist last, and on the --failed path the whitelist is empty, so the cut always lands inside the priority list: 21 of 69 roles survived the source run's title, the 21st a half-token. untriggered_priority parsed that title and passed undecodable names through verbatim, so the fragment reached the dispatched priority input, became the orchestrator's whitelist, and hit the complexity filter's lexer, whose word class has no rule for CJK. Sequencing died with argparse exit 2 and took the run with it. The retrigger that is supposed to repair a red run was breaking its successor. Read the priority from the ##[group] Inputs block a called job's log records instead: it carries all 69 roles. Depth-1 jobs only -- a deeper one echoes its own workflow's inputs, which have no priority. Drop the decode passthrough now that the source is lossless. The loud failure is only affordable once a whole value exists; before, it would have aborted every large retrigger. dispatched_priority cross-checks against the title: it still proves a priority line existed even when truncation mangled it, so an empty log read is a broken reader -- expired logs, a moved block -- and not a run without priority. Without that check the carry-over would vanish silently, which is what the rest of this change exists to prevent. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
147 lines
5.1 KiB
Python
147 lines
5.1 KiB
Python
"""Trigger the manual CI run (entry-manual-steer.yml) for the current branch.
|
|
|
|
entry-manual-steer.yml reads the "__ALL__" whitelist sentinel as "force a full
|
|
deploy across all roles".
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import sys
|
|
|
|
from cli.administration.deploy.ci import runs
|
|
|
|
_WORKFLOW = "entry-manual-steer.yml"
|
|
_ALL = "__ALL__"
|
|
|
|
|
|
def _fetch(run: str, repo: str) -> dict:
|
|
"""The jobs and title of *run*, given as a URL or a bare id (which
|
|
resolves against *repo*, the current branch's own)."""
|
|
if run.isdigit():
|
|
return runs.fetch_run(run, repo=repo)
|
|
return runs.fetch_run(runs.run_id_from_url(run), repo=runs.slug_from_url(run))
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
p = argparse.ArgumentParser(
|
|
prog="infinito administration deploy ci trigger",
|
|
description=(
|
|
"Dispatch the manual CI workflow for the branch you are on. "
|
|
"Default: trigger every role. With --failed: the roles that "
|
|
"failed in the last run form the priority line and the full "
|
|
"run follows once they are green. With --apps: an explicit "
|
|
"role list as whitelist. Whenever a source run is read, its "
|
|
"configuration is carried over so the retrigger reproduces it."
|
|
),
|
|
)
|
|
group = p.add_mutually_exclusive_group()
|
|
group.add_argument(
|
|
"--failed",
|
|
nargs="?",
|
|
const="total",
|
|
default=None,
|
|
choices=("total", "swarm", "compose", "docker", "host"),
|
|
metavar="{total,swarm,compose,host}",
|
|
help=(
|
|
"Re-trigger roles that were not green in the source run as the "
|
|
"priority line, together with the priority roles that run never "
|
|
"deployed at all; the remaining roles follow once they succeed. "
|
|
"Optional scope: 'total' (default; failed in any mode), "
|
|
"'swarm', 'compose', or 'host'."
|
|
),
|
|
)
|
|
group.add_argument(
|
|
"--apps",
|
|
default=None,
|
|
metavar='"app1 app2 ..."',
|
|
help="Explicit space-separated role ids to trigger.",
|
|
)
|
|
p.add_argument(
|
|
"--run",
|
|
default=None,
|
|
metavar="URL|ID",
|
|
help=(
|
|
"Run URL or bare run id to reproduce: its configuration (distros, "
|
|
"modes, lifecycles, filesystem, sequencing, mode_fail_fast, "
|
|
"workspace, instructions) is carried over, and with --failed its "
|
|
"results also pick the apps. A bare id resolves against the current "
|
|
"branch's repo. With --failed and no --run: the latest deploy run "
|
|
"on the current branch."
|
|
),
|
|
)
|
|
p.add_argument(
|
|
"--strict",
|
|
action="store_true",
|
|
help=(
|
|
"With --failed: re-trigger only roles with a hard failure (❌), "
|
|
"not cancelled/aborted (🚫) or still-running (⏳)."
|
|
),
|
|
)
|
|
args = p.parse_args(argv)
|
|
if args.strict and args.failed is None:
|
|
p.error("--strict only applies with --failed")
|
|
|
|
branch = runs.current_branch()
|
|
repo = runs.resolve_repo()
|
|
|
|
source = None
|
|
if args.run:
|
|
source = _fetch(args.run, repo)
|
|
elif args.failed is not None:
|
|
run = runs.find_last_deploy_run(branch, repo=repo)
|
|
if run is None:
|
|
print(
|
|
f"No CI run with deploy jobs found on {repo}@{branch}.",
|
|
file=sys.stderr,
|
|
)
|
|
return 1
|
|
source = {"jobs": run["_jobs"], "displayTitle": run.get("displayTitle", "")}
|
|
|
|
whitelist = ""
|
|
priority = ""
|
|
if args.apps is not None:
|
|
apps = " ".join(args.apps.split())
|
|
if not apps:
|
|
p.error("--apps was empty")
|
|
whitelist = apps
|
|
elif args.failed is not None:
|
|
scope = "docker" if args.failed == "compose" else args.failed
|
|
statuses = runs.parse_role_statuses(source["jobs"])
|
|
failed = runs.failed_roles(statuses, scope, strict=args.strict)
|
|
untriggered = runs.untriggered_priority(
|
|
runs.dispatched_priority(source, repo), statuses
|
|
)
|
|
if not failed and not untriggered:
|
|
print(f"Nothing failed ({args.failed}) in that run; not triggering.")
|
|
return 0
|
|
if untriggered:
|
|
print(f"Priority roles that never deployed: {' '.join(untriggered)}")
|
|
priority = " ".join(sorted(set(failed) | set(untriggered)))
|
|
else:
|
|
whitelist = _ALL
|
|
|
|
config: dict[str, str] = {}
|
|
if source is not None:
|
|
config = runs.config_from_run(source["displayTitle"], source["jobs"])
|
|
|
|
if priority:
|
|
label = f"priority {priority}, then the remaining roles"
|
|
elif whitelist == _ALL:
|
|
label = "all roles"
|
|
else:
|
|
label = whitelist
|
|
carried = ", ".join(f"{name}={value}" for name, value in config.items())
|
|
print(f"Triggering {_WORKFLOW} on {repo}@{branch} for: {label}")
|
|
if carried:
|
|
print(f"Carrying over from the source run: {carried}")
|
|
runs.dispatch_workflow(
|
|
_WORKFLOW, branch, whitelist, priority=priority, config=config, repo=repo
|
|
)
|
|
print("Dispatched. Watch with: infinito administration deploy ci status")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|