Files
SkillCompiler/data/skills-bench/skillsbench_agentbeats/ghcr_preflight.py
T
2026-09-04 14:58:42 +08:00

143 lines
4.9 KiBLFS
Python

"""GHCR publication preflight checks for AgentBeats public image evidence."""
from __future__ import annotations
import argparse
import os
import re
import subprocess
import sys
REQUIRED_GHCR_SCOPES = frozenset({"read:packages", "write:packages"})
SCOPE_RE = re.compile(r"'([^']+)'")
HEADER_SCOPE_RE = re.compile(r"^x-oauth-scopes:\s*(.*)$", re.IGNORECASE | re.MULTILINE)
def active_account_scopes(gh_auth_status_output: str) -> set[str]:
"""Parse `gh auth status` output and return scopes for the active account."""
for block in _account_blocks(gh_auth_status_output):
if "Active account: true" not in block:
continue
scope_line = next((line for line in block.splitlines() if "Token scopes:" in line), "")
return set(SCOPE_RE.findall(scope_line))
raise ValueError("could not find an active GitHub account in `gh auth status` output")
def missing_ghcr_package_scopes(scopes: set[str]) -> set[str]:
"""Return GHCR package scopes that are missing from the active token."""
return set(REQUIRED_GHCR_SCOPES - scopes)
def ensure_ghcr_package_scopes(gh_auth_status_output: str) -> set[str]:
"""Validate active GitHub token package scopes and return the parsed scopes."""
scopes = active_account_scopes(gh_auth_status_output)
missing = missing_ghcr_package_scopes(scopes)
if missing:
missing_list = ", ".join(sorted(missing))
raise ValueError(
f"active GitHub token is missing GHCR package scopes: {missing_list}. "
"Run `gh auth refresh -h github.com --scopes write:packages,read:packages` "
"with an operator-controlled browser/device-code flow before pushing images."
)
return scopes
def run_gh_auth_status() -> str:
"""Run `gh auth status` and return its combined output."""
completed = subprocess.run(
["gh", "auth", "status"],
check=False,
capture_output=True,
text=True,
)
output = completed.stdout + completed.stderr
if completed.returncode != 0:
raise RuntimeError(output.strip() or "`gh auth status` failed")
return output
def run_gh_token_scope_status() -> set[str]:
"""Inspect scopes for a PAT supplied through GH_TOKEN or GITHUB_TOKEN."""
token = os.environ.get("GH_TOKEN") or os.environ.get("GITHUB_TOKEN")
if not token:
raise ValueError("set GH_TOKEN or GITHUB_TOKEN to validate a browser-created GitHub token")
env = os.environ.copy()
env.pop("GH_TOKEN", None)
env.pop("GITHUB_TOKEN", None)
env["GH_TOKEN"] = token
completed = subprocess.run(
["gh", "api", "-i", "user"],
check=False,
capture_output=True,
text=True,
env=env,
)
output = completed.stdout + completed.stderr
if completed.returncode != 0:
raise RuntimeError(_redact_token(output.strip() or "`gh api -i user` failed", token))
return _scopes_from_api_header(output)
def _scopes_from_api_header(output: str) -> set[str]:
match = HEADER_SCOPE_RE.search(output)
if match is None:
raise ValueError("could not find X-OAuth-Scopes header in `gh api -i user` output")
scopes = {scope.strip() for scope in match.group(1).split(",") if scope.strip()}
return scopes
def _redact_token(message: str, token: str) -> str:
return message.replace(token, "[REDACTED_TOKEN]")
def _account_blocks(output: str) -> list[str]:
blocks: list[list[str]] = []
current: list[str] = []
for line in output.splitlines():
if "Logged in to github.com account" in line:
if current:
blocks.append(current)
current = [line]
elif current:
current.append(line)
if current:
blocks.append(current)
return ["\n".join(block) for block in blocks]
def _main() -> None:
parser = argparse.ArgumentParser(description="Check GitHub CLI scopes before pushing SkillsBench AgentBeats images to GHCR.")
parser.add_argument("--print-scopes", action="store_true", help="Print the active account scopes after validation.")
parser.add_argument(
"--token-from-env",
action="store_true",
help="Validate GH_TOKEN or GITHUB_TOKEN scopes instead of the stored active gh account.",
)
args = parser.parse_args()
try:
scopes = run_gh_token_scope_status() if args.token_from_env else ensure_ghcr_package_scopes(run_gh_auth_status())
missing = missing_ghcr_package_scopes(scopes)
if missing:
missing_list = ", ".join(sorted(missing))
source = "environment GitHub token" if args.token_from_env else "active GitHub token"
raise ValueError(f"{source} is missing GHCR package scopes: {missing_list}")
except (RuntimeError, ValueError) as exc:
print(str(exc), file=sys.stderr)
raise SystemExit(1) from exc
if args.print_scopes:
print(",".join(sorted(scopes)))
else:
print("GHCR package scopes available")
if __name__ == "__main__":
_main()