Three major improvements: 1. Rootless Docker refactor: Removes docker/binary modes, unifies to rootless Docker with per-runner system users. Each runner gets its own rootless Docker daemon, systemd user service, and isolated environment. Simplifies CLI (removes --mode option), Ansible role (single code path), and molecule scenarios (removes binary scenario). 2. Auto-merge fix: Fixes status check context mismatch in branch protection (was requiring "lint", "unit-tests", "molecule-tests" but actual contexts are "CI / quality", "CI / molecule-tests*"). Adds retry/wait logic to auto_merge.py that polls commit statuses for up to 15 minutes before attempting merge, eliminating the chicken-and-egg problem where auto-merge would fail because CI hadn't completed yet. 3. Molecule platform matrix: Adds OS platform matrix to CI — all 6 scenarios now run on all 4 supported OSes (ubuntu-2204, ubuntu-2404, debian-12, archlinux) = 24 test pairs distributed across 3 parallel runners. Updates distribute_molecule.py to distribute (scenario, platform) pairs. Updates Makefile with molecule-all target for local multi-platform testing. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
84 lines
2.6 KiB
Python
84 lines
2.6 KiB
Python
"""Local JSON registry for installed Gitea Runners.
|
|
|
|
Stores connection metadata (host, user, key, labels) on the local machine
|
|
so that subsequent lifecycle commands only need the runner name.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from datetime import UTC, datetime
|
|
from pathlib import Path
|
|
from typing import Any, cast
|
|
|
|
from .exceptions import GRMError
|
|
from .i18n import _
|
|
|
|
|
|
class RunnerRegistry:
|
|
"""Manages a local JSON file mapping runner names to connection metadata."""
|
|
|
|
def __init__(self, path: Path | None = None) -> None:
|
|
self._path = path or Path.home() / ".local" / "share" / "grm" / "runners.json"
|
|
self._data: dict[str, dict[str, Any]] = self._load()
|
|
|
|
def _load(self) -> dict[str, dict[str, Any]]:
|
|
if self._path.exists():
|
|
try:
|
|
with open(self._path) as f:
|
|
data: Any = json.load(f)
|
|
if isinstance(data, dict):
|
|
return cast(dict[str, dict[str, Any]], data)
|
|
except (json.JSONDecodeError, OSError):
|
|
pass
|
|
return {}
|
|
|
|
def _save(self) -> None:
|
|
self._path.parent.mkdir(parents=True, exist_ok=True)
|
|
with open(self._path, "w") as f:
|
|
json.dump(self._data, f, indent=2)
|
|
|
|
def add(
|
|
self,
|
|
name: str,
|
|
host: str,
|
|
user: str,
|
|
key: str | None = None,
|
|
gitea_url: str = "",
|
|
labels: str | None = None,
|
|
) -> None:
|
|
"""Register a runner in the local database."""
|
|
self._data[name] = {
|
|
"host": host,
|
|
"user": user,
|
|
"key": key,
|
|
"gitea_url": gitea_url,
|
|
"labels": labels,
|
|
"created_at": datetime.now(UTC).isoformat(),
|
|
}
|
|
self._save()
|
|
|
|
def get(self, name: str) -> dict[str, Any] | None:
|
|
"""Retrieve runner metadata by name."""
|
|
info = self._data.get(name)
|
|
if info:
|
|
return dict(info)
|
|
return None
|
|
|
|
def remove(self, name: str) -> None:
|
|
"""Remove a runner from the local database."""
|
|
if name in self._data:
|
|
del self._data[name]
|
|
self._save()
|
|
|
|
def list(self) -> dict[str, dict[str, Any]]:
|
|
"""Return a copy of all registered runners."""
|
|
return {name: dict(info) for name, info in self._data.items()}
|
|
|
|
def update(self, name: str, **kwargs: str | None) -> None:
|
|
"""Update fields for an existing runner entry."""
|
|
if name not in self._data:
|
|
raise GRMError(_("Runner '{name}' not found in registry.", name=name))
|
|
self._data[name].update({k: v for k, v in kwargs.items() if v is not None})
|
|
self._save()
|