150 lines
4.5 KiB
Python
150 lines
4.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Discover available Gitea Actions runners for dynamic job distribution.
|
|
|
|
Queries the Gitea API for registered runners at three levels:
|
|
1. Repository level: GET /repos/{owner}/{repo}/actions/runners
|
|
2. Organization level: GET /orgs/{org}/actions/runners
|
|
3. Instance (admin) level: GET /admin/actions/runners
|
|
|
|
Falls back to the ``MOLECULE_RUNNERS`` repo variable or environment
|
|
variable, then to ``DEFAULT_MAX_RUNNERS`` (3).
|
|
|
|
Outputs:
|
|
- ``--count``: prints the number of available runners
|
|
- ``--indices``: prints a JSON array [0, 1, ..., N-1] for use as a
|
|
dynamic matrix in Gitea Actions
|
|
- (default): prints both as ``count=N`` and ``indices=[0,1,...]``
|
|
|
|
Usage:
|
|
python3 scripts/ci/discover_runners.py --owner oblachno-oss --repo grm
|
|
python3 scripts/ci/discover_runners.py --indices
|
|
python3 scripts/ci/discover_runners.py --count
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
|
|
import click
|
|
import requests
|
|
|
|
from gitea_runner_manager.config import GITEA_API_URL
|
|
|
|
DEFAULT_MAX_RUNNERS = 3
|
|
|
|
|
|
def query_runners(api_url: str, token: str, owner: str, repo: str) -> int:
|
|
"""Query the Gitea API for registered runners at all levels.
|
|
|
|
Returns the total count of active runners. If the API call fails
|
|
(e.g., no admin access for instance-level runners), falls back to
|
|
what we can see.
|
|
"""
|
|
headers = {"Authorization": f"token {token}"}
|
|
total = 0
|
|
|
|
# 1. Repository-level runners
|
|
try:
|
|
r = requests.get(
|
|
f"{api_url}/repos/{owner}/{repo}/actions/runners",
|
|
headers=headers,
|
|
timeout=10,
|
|
)
|
|
if r.status_code == 200:
|
|
data = r.json()
|
|
total += data.get("total_count", 0)
|
|
except (requests.RequestException, ValueError):
|
|
pass
|
|
|
|
# 2. Organization-level runners
|
|
try:
|
|
r = requests.get(
|
|
f"{api_url}/orgs/{owner}/actions/runners",
|
|
headers=headers,
|
|
timeout=10,
|
|
)
|
|
if r.status_code == 200:
|
|
data = r.json()
|
|
total += data.get("total_count", 0)
|
|
except (requests.RequestException, ValueError):
|
|
pass
|
|
|
|
# 3. Instance-level runners (requires admin scope)
|
|
try:
|
|
r = requests.get(
|
|
f"{api_url}/admin/actions/runners",
|
|
headers=headers,
|
|
timeout=10,
|
|
)
|
|
if r.status_code == 200:
|
|
data = r.json()
|
|
total += data.get("total_count", 0)
|
|
except (requests.RequestException, ValueError):
|
|
pass
|
|
|
|
return total
|
|
|
|
|
|
def get_runner_count(api_url: str, token: str, owner: str, repo: str) -> int:
|
|
"""Determine the number of available runners.
|
|
|
|
Tries the Gitea API first, then falls back to env vars, then default.
|
|
"""
|
|
# Try API query if we have a token
|
|
if token:
|
|
api_count = query_runners(api_url, token, owner, repo)
|
|
if api_count > 0:
|
|
return api_count
|
|
|
|
# Fall back to MOLECULE_RUNNERS env var (set by CI from repo variable)
|
|
env_count = os.environ.get("MOLECULE_RUNNERS")
|
|
if env_count:
|
|
try:
|
|
count = int(env_count)
|
|
if count > 0:
|
|
return count
|
|
except ValueError:
|
|
pass
|
|
|
|
# Fall back to default
|
|
return DEFAULT_MAX_RUNNERS
|
|
|
|
|
|
def generate_indices(count: int) -> list[int]:
|
|
"""Generate a list of runner indices [0, 1, ..., count-1]."""
|
|
return list(range(count))
|
|
|
|
|
|
@click.command()
|
|
@click.option("--owner", default=None, help="Repository owner (for API query).")
|
|
@click.option("--repo", default=None, help="Repository name (for API query).")
|
|
@click.option("--count", "output_count", is_flag=True, help="Output only the count.")
|
|
@click.option("--indices", "output_indices", is_flag=True, help="Output only the JSON indices array.")
|
|
def main(owner: str | None, repo: str | None, output_count: bool, output_indices: bool) -> None:
|
|
token = os.environ.get("REPO_TOKEN", "")
|
|
|
|
if owner is None:
|
|
owner = os.environ.get("GRM_REPO_OWNER", "oblachno-oss")
|
|
if repo is None:
|
|
repo = os.environ.get("GRM_REPO_NAME", "grm")
|
|
|
|
count = get_runner_count(GITEA_API_URL, token, owner, repo)
|
|
indices = generate_indices(count)
|
|
|
|
if output_count:
|
|
click.echo(str(count))
|
|
return
|
|
|
|
if output_indices:
|
|
click.echo(json.dumps(indices))
|
|
return
|
|
|
|
# Default: output both as key=value pairs for CI consumption
|
|
click.echo(f"count={count}")
|
|
click.echo(f"indices={json.dumps(indices)}")
|
|
|
|
|
|
if __name__ == "__main__": # pragma: no cover
|
|
main()
|