Files
Unraid-AppOps-Gateway-Public/backend/app/services/runner_fleet.py
T

83 lines
3.1 KiB
Python

"""Compose bounded Gitea and controller runner-fleet telemetry.
AppOps is intentionally a consumer of this projection. The root-owned
controller remains the only component that may perform an allowlisted recovery
or cleanup inside the isolated CI Docker daemon.
"""
from __future__ import annotations
from typing import Any
from ..connectors.gitea_actions import GiteaActionsConnector
from .deployment_broker import DeploymentBrokerClient
_CAPABILITIES = (
"linux-validation",
"docker-validation",
"browser-validation",
"security-validation",
"windows-native",
)
_SEVERITY_RANK = {"healthy": 0, "warning": 1, "critical": 2, "unknown": 3}
def _severity(*states: object) -> str:
normalized = [str(state) for state in states]
return max(normalized, key=lambda value: _SEVERITY_RANK.get(value, 3), default="unknown")
def _capabilities(runners: list[dict[str, Any]]) -> list[dict[str, object]]:
rows = []
for capability in _CAPABILITIES:
compatible = [runner for runner in runners if capability in runner.get("labels", [])]
online = [
runner
for runner in compatible
if runner.get("status") == "online" and not runner.get("disabled")
]
rows.append(
{
"name": capability,
"total": len(compatible),
"online": len(online),
"busy": sum(bool(runner.get("busy")) for runner in online),
"status": "healthy" if online else "critical",
}
)
return rows
class RunnerFleetService:
"""Provide a read-only, explainable CI fleet projection."""
def snapshot(self) -> dict[str, object]:
actions = GiteaActionsConnector().snapshot()
controller = DeploymentBrokerClient().runner_fleet_status()
runners = actions.get("runners") if isinstance(actions.get("runners"), list) else []
typed_runners = [runner for runner in runners if isinstance(runner, dict)]
capabilities = _capabilities(typed_runners)
controller_status = str(controller.get("status") or "unavailable")
controller_severity = (
controller_status
if controller_status in _SEVERITY_RANK
else "warning" if controller_status != "unavailable" else "unknown"
)
action_severity = str(actions.get("severity") or "unknown")
capability_severity = _severity(*(row["status"] for row in capabilities))
return {
"status": _severity(action_severity, controller_severity, capability_severity),
"generatedAt": actions.get("generatedAt"),
"runners": typed_runners,
"capabilities": capabilities,
"queue": actions.get("queue") if isinstance(actions.get("queue"), dict) else {},
"controller": controller,
"actions": [
{"intent": "runner.inspect", "mode": "read_only"},
{"intent": "runner.cleanup.preview", "mode": "read_only"},
{"intent": "runner.restart", "mode": "root_allowlisted_and_bounded"},
{"intent": "runner.drain", "mode": "operator_controlled"},
],
}