GRM-17: fix docker inspect Jinja2 conflict and add per-iteration exception handling

- docker inspect -f "{{.State.Status}}" used Go template braces that
  conflicted with Ansible Jinja2 templating in the shell module.
  Ansible tried to parse {{.State.Status}} as a Jinja2 variable (which
  starts with a dot, making it invalid), causing a local template error.
  The outer except Exception caught this immediately, so the fallback
  loop never reached the legacy container name or systemctl checks.

- Replaced with: docker inspect <name> | python3 -c JSON parsing,
  which avoids any brace syntax and uses python3 (already required by
  Ansible on managed nodes).

- Added per-iteration try/except inside the fallback loop so a failure
  on one container name continues to the next fallback instead of
  aborting the entire check.

- Added tests for fallback behavior and binary mode exception path.

128 tests, 100% coverage, ruff + pyright clean
This commit is contained in:
Emil Simeonov
2026-06-19 02:53:26 +02:00
parent 1bbe3b4f43
commit a9adb71a08
2 changed files with 75 additions and 16 deletions
+27 -15
View File
@@ -295,27 +295,36 @@ class RunnerManager:
lines = [ln for ln in stdout.splitlines() if ln.strip() and not any(p in ln for p in ansible_noise)]
return lines[-1].strip() if lines else "unknown"
try:
if mode == "docker":
# Try expected container name first, then host-based fallback for legacy installs
for container_name in (f"gitea-runner-{name}", f"gitea-runner-{host}"):
service_status = "unknown"
if mode == "docker":
# Try expected container name first, then host-based fallback for legacy installs.
# Use python3 JSON parsing to avoid Jinja2 brace conflicts in Ansible shell args.
for container_name in (f"gitea-runner-{name}", f"gitea-runner-{host}"):
try:
stdout = self._executor.run_ad_hoc(
host,
user,
key,
"shell",
f"docker inspect -f '{{{{.State.Status}}}}' {container_name} 2>/dev/null",
(
f"docker inspect {container_name} 2>/dev/null | "
f"python3 -c \"import sys,json; print(json.load(sys.stdin)[0]['State']['Status'])\" "
f"2>/dev/null || echo unknown"
),
become=True,
ask_become_pass=True,
check=False,
)
service_status = _parse_status(stdout)
if service_status != "unknown":
docker_map = {"running": "active", "exited": "inactive", "dead": "failed"}
service_status = docker_map.get(service_status, "unknown")
break
else:
# Final fallback: check systemd service (for installs using the fixed template)
except Exception:
continue
status = _parse_status(stdout)
if status != "unknown":
docker_map = {"running": "active", "exited": "inactive", "dead": "failed"}
service_status = docker_map.get(status, "unknown")
break
else:
# Final fallback: check systemd service (for installs using the fixed template)
try:
stdout = self._executor.run_ad_hoc(
host,
user,
@@ -327,7 +336,10 @@ class RunnerManager:
check=False,
)
service_status = _parse_status(stdout)
else:
except Exception:
service_status = "unknown"
else:
try:
stdout = self._executor.run_ad_hoc(
host,
user,
@@ -339,8 +351,8 @@ class RunnerManager:
check=False,
)
service_status = _parse_status(stdout)
except Exception:
service_status = "unknown"
except Exception:
service_status = "unknown"
# Translate known status values
translated_status = _(
service_status if service_status in {"active", "inactive", "failed", "unknown"} else "unknown"