GRM-17: fix: make grm list retrieve runner status correctly

- run_ad_hoc() now raises AnsibleError on non-zero exit, surfacing
  stderr instead of silently returning empty stdout
- list_runners() passes become=True to run_ad_hoc since systemctl
  is-active requires root privileges
- Add i18n translations for ad-hoc failure messages
- Add unit test for run_ad_hoc failure case
- Update list_runners test to expect become=True
- 119 tests, 100% coverage, pyright clean, ruff clean
This commit is contained in:
Emil Simeonov
2026-06-19 02:05:06 +02:00
parent 677745ac99
commit 18760f6a2a
5 changed files with 38 additions and 2 deletions
+9
View File
@@ -109,6 +109,15 @@ class AnsibleExecutor:
capture_output=True, capture_output=True,
text=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() return proc.stdout.strip()
def _extract_status(self, log_file: Path) -> str | None: def _extract_status(self, log_file: Path) -> str | None:
+14
View File
@@ -474,6 +474,20 @@ TRANSLATIONS: dict[str, dict[str, str]] = {
"ru": "Пропустить удаленную очистку и удалить только локальную запись реестра", "ru": "Пропустить удаленную очистку и удалить только локальную запись реестра",
"zh": "跳过远程清理,仅删除本地注册表条目", "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}",
},
} }
+3 -1
View File
@@ -281,7 +281,9 @@ class RunnerManager:
key = info.get("key") key = info.get("key")
mode = info.get("mode", "docker") mode = info.get("mode", "docker")
try: 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) # Filter out ansible noise (SSH warnings etc)
lines = [ln for ln in stdout.splitlines() if ln.strip() and not ln.startswith(("[WARNING]", "ssh:"))] 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" service_status = lines[-1].strip() if lines else "unknown"
+11
View File
@@ -206,3 +206,14 @@ class TestAnsibleExecutorAdHoc:
assert result == "ok" assert result == "ok"
cmd = mock_run.call_args.args[0] cmd = mock_run.call_args.args[0]
assert "--become" in cmd 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")
+1 -1
View File
@@ -408,7 +408,7 @@ class TestRunnerManager:
assert runners[0]["host"] == "10.0.0.1" assert runners[0]["host"] == "10.0.0.1"
assert runners[0]["status"] == "active" assert runners[0]["status"] == "active"
mock_executor.run_ad_hoc.assert_called_once_with( 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: def test_list_runners_empty(self) -> None: