Files
computer-playbook/utils/update/runner.py
T
kevinveenbirkenbachandClaude Opus 5.5 b055eece05 feat(ci): keep GitHub-hosted runner labels current through the updater
The workflows pin `runs-on: ubuntu-NN.NN`, so no job moves to a new Ubuntu
release behind our back, but nothing raised the pin either. A sixth job of
`🔄 Update versions` closes that: `utils/update/runner.py` reads the image
table of actions/runner-images, takes the newest x64 Ubuntu label whose
image carries no preview or beta badge, and raises every older bare
`runs-on: ubuntu-NN.NN` to it; arch-suffixed labels stay untouched. The job
opens the change as one PR over `.github/workflows`, so its CI run tests
the new image before it reaches main.

This commit carries the updater's first result: every job now runs on
ubuntu-26.04 (49 `runs-on`).

actionlint 1.7.12, the newest release, does not know `ubuntu-26.04` yet
(rhysd/actionlint#682, fix proposed in rhysd/actionlint#743), so
`.github/actionlint.yaml` declares the label until a release knows it.

act maps each `ubuntu-NN.NN` label the workflows use to the local platform
image, derived from `.github/workflows`, instead of a fixed list that did
not know ubuntu-26.04.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 16:56:52 +02:00

78 lines
2.5 KiB
Python

"""Bump the pinned GitHub-hosted Ubuntu runner label of every workflow job.
GitHub lists its hosted images in the runner-images README; the newest x64
Ubuntu label whose image carries no preview or beta badge is the target.
"""
from __future__ import annotations
import re
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from pathlib import Path
IMAGES_README = "https://raw.githubusercontent.com/actions/runner-images/main/README.md"
_LABEL = re.compile(r"`ubuntu-(\d{2})\.(\d{2})`")
_RUNS_ON = re.compile(r"(runs-on:\s*)ubuntu-(\d{2})\.(\d{2})(?![\w.-])")
_PRERELEASE = re.compile(r"preview|beta", re.IGNORECASE)
def newest_ubuntu_label(readme: str) -> str | None:
"""Return the newest GA x64 Ubuntu label listed in the images table.
Args:
readme: text of the runner-images README.
"""
versions: list[tuple[int, int]] = []
for row in readme.splitlines():
cells = [cell.strip() for cell in row.split("|")]
if len(cells) < 5 or not cells[1].startswith("Ubuntu"):
continue
if _PRERELEASE.search(cells[1]) or cells[2] != "x64":
continue
versions += [
(int(major), int(minor)) for major, minor in _LABEL.findall(cells[3])
]
if not versions:
return None
major, minor = max(versions)
return f"ubuntu-{major:02d}.{minor:02d}"
def bump_text(text: str, label: str) -> str:
"""Raise every older ``runs-on: ubuntu-NN.NN`` in *text* to *label*.
Args:
text: workflow source.
label: target label, ``ubuntu-NN.NN``.
"""
target = tuple(int(part) for part in label.removeprefix("ubuntu-").split("."))
def _raise(match: re.Match[str]) -> str:
if (int(match.group(2)), int(match.group(3))) < target:
return match.group(1) + label
return match.group(0)
return _RUNS_ON.sub(_raise, text)
def bump_workflows(workflows_dir: Path, label: str) -> list[Path]:
"""Rewrite the workflows under *workflows_dir* to *label*; return the changed files.
Args:
workflows_dir: directory holding the workflow YAML files.
label: target label, ``ubuntu-NN.NN``.
"""
changed: list[Path] = []
for path in sorted(workflows_dir.glob("*.y*ml")):
text = path.read_text( # nocheck: cache-read — read-then-write of the same file; cached read would go stale
encoding="utf-8"
)
bumped = bump_text(text, label)
if bumped != text:
path.write_text(bumped, encoding="utf-8")
changed.append(path)
return changed