diff --git a/src/gitea_runner_manager/i18n.py b/src/gitea_runner_manager/i18n.py index 2ed1ba1..e511481 100644 --- a/src/gitea_runner_manager/i18n.py +++ b/src/gitea_runner_manager/i18n.py @@ -205,6 +205,13 @@ TRANSLATIONS: dict[str, dict[str, str]] = { "ru": "Проверка состояния Gitea Runner {name} на {host}", "zh": "正在检查 {host} 上 Gitea Runner {name} 的状态", }, + "Checking status of Gitea Runner {name} on {host} as {user} (sudo required)": { + "en": "Checking status of Gitea Runner {name} on {host} as {user} (sudo required)", + "bg": "Проверка на състоянието на Gitea Runner {name} на {host} като {user} (необходим е sudo)", + "de": "Prüfe Status von Gitea Runner {name} auf {host} als {user} (sudo erforderlich)", + "ru": "Проверка состояния Gitea Runner {name} на {host} как {user} (требуется sudo)", + "zh": "正在检查 {host} 上 Gitea Runner {name} 的状态(用户 {user},需要 sudo)", + }, "Removing Gitea Runner {name} from {host}": { "en": "Removing Gitea Runner {name} from {host}", "bg": "Премахване на Gitea Runner {name} от {host}", diff --git a/src/gitea_runner_manager/runner_manager.py b/src/gitea_runner_manager/runner_manager.py index 52607a6..a482336 100644 --- a/src/gitea_runner_manager/runner_manager.py +++ b/src/gitea_runner_manager/runner_manager.py @@ -9,6 +9,7 @@ from .executor import AnsibleExecutor from .i18n import _ from .registry import RunnerRegistry from .report import track_steps +from .ui import say class RunnerManager: @@ -280,23 +281,48 @@ class RunnerManager: user = info["user"] key = info.get("key") mode = info.get("mode", "docker") - try: - stdout = self._executor.run_ad_hoc( - host, - user, - key, - "shell", - f"systemctl is-active gitea-runner@{name}", - become=True, - ask_become_pass=True, - check=False, + say( + _( + "Checking status of Gitea Runner {name} on {host} as {user} (sudo required)", + name=name, + host=host, + user=user, ) - # Filter out ansible noise (SSH warnings etc) - lines = [ln for ln in stdout.splitlines() if ln.strip() and not ln.startswith(("[WARNING]", "ssh:"))] + ) + try: + if mode == "docker": + stdout = self._executor.run_ad_hoc( + host, + user, + key, + "shell", + f"docker inspect -f '{{{{.State.Status}}}}' gitea-runner-{name} 2>/dev/null", + become=True, + ask_become_pass=True, + check=False, + ) + else: + stdout = self._executor.run_ad_hoc( + host, + user, + key, + "shell", + f"systemctl is-active gitea-runner@{name} 2>/dev/null", + become=True, + ask_become_pass=True, + check=False, + ) + # Filter out ansible noise (headers, warnings, SSH messages) + ansible_noise = (" | CHANGED | ", " | FAILED | ", " | UNREACHABLE | ", "[WARNING]", "ssh:", ">>") + lines = [ln for ln in stdout.splitlines() if ln.strip() and not any(p in ln for p in ansible_noise)] service_status = lines[-1].strip() if lines else "unknown" + # Docker statuses need mapping to systemd vocabulary + if mode == "docker": + docker_map = {"running": "active", "exited": "inactive", "dead": "failed"} + service_status = docker_map.get(service_status, "unknown") except Exception: service_status = "unknown" - # Translate known systemd status values + # Translate known status values translated_status = _( service_status if service_status in {"active", "inactive", "failed", "unknown"} else "unknown" ) diff --git a/tests/unit/test_runner_manager.py b/tests/unit/test_runner_manager.py index c36754f..f19d5a2 100644 --- a/tests/unit/test_runner_manager.py +++ b/tests/unit/test_runner_manager.py @@ -392,14 +392,14 @@ class TestRunnerManager: mock_executor.run.assert_not_called() mock_registry.remove.assert_called_once_with("r1") - def test_list_runners(self) -> None: + def test_list_runners_docker(self) -> None: mock_registry = MagicMock() mock_registry.list.return_value = { "r1": {"host": "10.0.0.1", "user": "ubuntu", "key": "/key", "mode": "docker"}, } manager = RunnerManager(registry=mock_registry) mock_executor = MagicMock() - mock_executor.run_ad_hoc.return_value = "active" + mock_executor.run_ad_hoc.return_value = "running" manager._executor = mock_executor runners = manager.list_runners() @@ -412,7 +412,31 @@ class TestRunnerManager: "ubuntu", "/key", "shell", - "systemctl is-active gitea-runner@r1", + "docker inspect -f '{{.State.Status}}' gitea-runner-r1 2>/dev/null", + become=True, + ask_become_pass=True, + check=False, + ) + + def test_list_runners_binary(self) -> None: + mock_registry = MagicMock() + mock_registry.list.return_value = { + "r1": {"host": "10.0.0.1", "user": "ubuntu", "key": "/key", "mode": "binary"}, + } + manager = RunnerManager(registry=mock_registry) + mock_executor = MagicMock() + mock_executor.run_ad_hoc.return_value = "active" + manager._executor = mock_executor + + runners = manager.list_runners() + assert len(runners) == 1 + assert runners[0]["status"] == "active" + mock_executor.run_ad_hoc.assert_called_once_with( + "10.0.0.1", + "ubuntu", + "/key", + "shell", + "systemctl is-active gitea-runner@r1 2>/dev/null", become=True, ask_become_pass=True, check=False, @@ -424,6 +448,19 @@ class TestRunnerManager: manager = RunnerManager(registry=mock_registry) assert manager.list_runners() == [] + def test_list_runners_docker_exited(self) -> None: + mock_registry = MagicMock() + mock_registry.list.return_value = { + "r1": {"host": "10.0.0.1", "user": "ubuntu", "key": None, "mode": "docker"}, + } + manager = RunnerManager(registry=mock_registry) + mock_executor = MagicMock() + mock_executor.run_ad_hoc.return_value = "exited" + manager._executor = mock_executor + + runners = manager.list_runners() + assert runners[0]["status"] == "inactive" + def test_list_runners_unknown_status(self) -> None: mock_registry = MagicMock() mock_registry.list.return_value = {