Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions scripts/ci/runner_label_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from __future__ import annotations

import re
import json
from functools import cache
from pathlib import Path

Expand Down Expand Up @@ -333,11 +334,44 @@ def forbidden_reason(label: str) -> str | None:
NO_HOSTED_MAC_VARIABLES = frozenset({
"CI_CONTROL_RUNNER", "CI_IOS_INTERNAL_RUNNER", "CI_NIGHTLY_NEXT_BUILD_RUNNER", "CI_SIDE_LANE_RUNNER",
"LINUX_RUNNER",
"RUNNER_POOLS",
"MACOS_RUNNER_15", "MACOS_RUNNER_26", "NIGHTLY_NEXT_TRAIN_RUNNER",
})
HOSTED_MAC_LABEL = re.compile(r"macos-(?:latest|\d+)(?:-[a-z0-9]+)*")


def runner_pools_reason(value: str) -> str | None:
"""Validate the label values in the structured RUNNER_POOLS variable.

RUNNER_POOLS is JSON rather than a scalar runner label. Its `_dead`
metadata is timestamps and is deliberately ignored; every other pool
member is a runner label and must not smuggle in a hosted Mac or a
self-hosted fleet label.
"""
if not value.strip():
return None
try:
pools = json.loads(value)
except (TypeError, ValueError):
return "is not valid JSON, so its runner labels cannot be checked"
if not isinstance(pools, dict):
return "is not a JSON object of runner pool labels"
for pool, label in pools.items():
if str(pool).startswith("_"):
continue
if not isinstance(label, str):
return f"pool `{pool}` is not a string runner label"
if not label.strip():
continue
original = label.strip()
if HOSTED_MAC_LABEL.fullmatch(original.lower()):
return f"pool `{pool}` names a GitHub-hosted Mac `{original}`"
reason = forbidden_reason(original)
if reason is not None:
return f"pool `{pool}` {reason}"
return None


def drifted_runner_variables(
variables: dict[str, str],
) -> list[tuple[str, str, str]]:
Expand All @@ -350,6 +384,11 @@ def drifted_runner_variables(
for name, value in variables.items():
if not isinstance(value, str):
continue
if name == "RUNNER_POOLS":
reason = runner_pools_reason(value)
if reason is not None:
drifted.append((name, value.strip(), reason))
continue
if name in NO_HOSTED_MAC_VARIABLES and HOSTED_MAC_LABEL.fullmatch(value.strip().lower()):
drifted.append((name, value.strip(), "names a GitHub-hosted Mac; the nightly path takes the fleet, then Blacksmith"))
continue
Expand Down
23 changes: 23 additions & 0 deletions tests/test_runner_label_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,29 @@ def test_every_runner_variable_a_workflow_reads_is_reported(self) -> None:
)


class RunnerPoolsVariable(unittest.TestCase):
def test_json_pool_members_reject_hosted_macs_and_fleet_labels(self) -> None:
for value in (
'{"macos_26":"macos-26"}',
'{"linux":"glaeda-std-xcode-26.6"}',
'{"linux":"self-hosted"}',
'{"linux":["self-hosted","Linux"]}',
'{"linux":"macOS"}',
):
with self.subTest(value=value):
self.assertEqual([name for name, _, _ in drifted_runner_variables({"RUNNER_POOLS": value})],
["RUNNER_POOLS"])

def test_json_pool_members_allow_trusted_blacksmith_labels_and_metadata(self) -> None:
value = '{"linux":"blacksmith-16vcpu-ubuntu-2404","macos_26":"blacksmith-6vcpu-macos-26","_dead":{"x":"2026-11-01T00:00:00Z"}}'
self.assertEqual(drifted_runner_variables({"RUNNER_POOLS": value}), [])
self.assertEqual(drifted_runner_variables({"RUNNER_POOLS": ""}), [])

def test_invalid_pool_json_fails_closed(self) -> None:
self.assertEqual([name for name, _, _ in drifted_runner_variables({"RUNNER_POOLS": "not-json"})],
["RUNNER_POOLS"])


class NightlyNextBuildVariable(unittest.TestCase):
def test_an_owned_side_or_dev_label_or_blacksmith_is_allowed(self) -> None:
name = "CI_NIGHTLY_NEXT_BUILD_RUNNER"
Expand Down
Loading