diff --git a/src/gitea_runner_manager/executor.py b/src/gitea_runner_manager/executor.py index 3c910f9..ce63846 100644 --- a/src/gitea_runner_manager/executor.py +++ b/src/gitea_runner_manager/executor.py @@ -109,6 +109,15 @@ class AnsibleExecutor: capture_output=True, text=True, ) + if proc.returncode != 0: + stderr = proc.stderr.strip() if proc.stderr else "" + raise AnsibleError( + _( + "Ad-hoc command failed on {host}: {stderr}", + host=host, + stderr=stderr or _("exit code {code}", code=proc.returncode), + ) + ) return proc.stdout.strip() def _extract_status(self, log_file: Path) -> str | None: diff --git a/src/gitea_runner_manager/i18n.py b/src/gitea_runner_manager/i18n.py index 51b11df..2752880 100644 --- a/src/gitea_runner_manager/i18n.py +++ b/src/gitea_runner_manager/i18n.py @@ -474,6 +474,20 @@ TRANSLATIONS: dict[str, dict[str, str]] = { "ru": "Пропустить удаленную очистку и удалить только локальную запись реестра", "zh": "跳过远程清理,仅删除本地注册表条目", }, + "Ad-hoc command failed on {host}: {stderr}": { + "en": "Ad-hoc command failed on {host}: {stderr}", + "bg": "Ad-hoc командата неуспешна на {host}: {stderr}", + "de": "Ad-hoc-Befehl auf {host} fehlgeschlagen: {stderr}", + "ru": "Ad-hoc команда не удалась на {host}: {stderr}", + "zh": "Ad-hoc 命令在 {host} 上失败: {stderr}", + }, + "exit code {code}": { + "en": "exit code {code}", + "bg": "код за изход {code}", + "de": "Exit-Code {code}", + "ru": "код выхода {code}", + "zh": "退出代码 {code}", + }, } diff --git a/src/gitea_runner_manager/runner_manager.py b/src/gitea_runner_manager/runner_manager.py index 4873d8a..4faca9f 100644 --- a/src/gitea_runner_manager/runner_manager.py +++ b/src/gitea_runner_manager/runner_manager.py @@ -281,7 +281,9 @@ class RunnerManager: 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}") + stdout = self._executor.run_ad_hoc( + host, user, key, "shell", f"systemctl is-active gitea-runner@{name}", become=True + ) # Filter out ansible noise (SSH warnings etc) lines = [ln for ln in stdout.splitlines() if ln.strip() and not ln.startswith(("[WARNING]", "ssh:"))] service_status = lines[-1].strip() if lines else "unknown" diff --git a/tests/unit/test_executor.py b/tests/unit/test_executor.py index b8c5974..3f6258e 100644 --- a/tests/unit/test_executor.py +++ b/tests/unit/test_executor.py @@ -206,3 +206,14 @@ class TestAnsibleExecutorAdHoc: assert result == "ok" cmd = mock_run.call_args.args[0] assert "--become" in cmd + + def test_run_ad_hoc_failure_raises(self, tmp_path: Path) -> None: + executor = AnsibleExecutor(log_dir=tmp_path) + result_mock = MagicMock() + result_mock.stdout = "" + result_mock.returncode = 1 + result_mock.stderr = "SSH timeout" + + with patch("subprocess.run", return_value=result_mock): + with pytest.raises(AnsibleError, match="SSH timeout"): + executor.run_ad_hoc("10.0.0.1", "ubuntu", None, "shell", "cmd") diff --git a/tests/unit/test_runner_manager.py b/tests/unit/test_runner_manager.py index 7743c5e..80a9882 100644 --- a/tests/unit/test_runner_manager.py +++ b/tests/unit/test_runner_manager.py @@ -408,7 +408,7 @@ class TestRunnerManager: assert runners[0]["host"] == "10.0.0.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" + "10.0.0.1", "ubuntu", "/key", "shell", "systemctl is-active gitea-runner@r1", become=True ) def test_list_runners_empty(self) -> None: