refactor(hermes-autotriage): repair via ConfigMap patch, not PVC write

Longhorn RWO attach latency and per-node engine availability made the
PVC fixture unreliable for a fast repeatable demo. The fixture is now a
ConfigMap; the repair Job runs bitnami/kubectl under the least-privilege
hermes-demo-repair SA and patches state=healthy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
codex 2026-08-05 17:42:00 -03:00
parent 8314bd1843
commit 1cbed3f6a5
6 changed files with 23 additions and 23 deletions

View File

@ -477,7 +477,7 @@ def _repair_config() -> dict[str, Any]:
return { return {
"namespace": settings.hermes_demo_namespace, "namespace": settings.hermes_demo_namespace,
"fixture_pvc": settings.hermes_demo_fixture_pvc, "fixture_configmap": settings.hermes_demo_fixture_configmap,
"image": settings.hermes_repair_image, "image": settings.hermes_repair_image,
} }

View File

@ -24,7 +24,7 @@ _REPAIR_MESSAGE = "fixture state reset to healthy"
def execute_repair(cfg: dict, incident_id: str, build_number: int) -> dict[str, Any]: def execute_repair(cfg: dict, incident_id: str, build_number: int) -> dict[str, Any]:
"""Run the hardcoded demo-fixture repair Job and wait for its outcome. """Run the hardcoded demo-fixture repair Job and wait for its outcome.
Inputs: `cfg` with namespace, fixture_pvc, and image (plus optional Inputs: `cfg` with namespace, fixture_configmap, and image (plus optional
wait_timeout_seconds); the incident id and the failed build number that wait_timeout_seconds); the incident id and the failed build number that
names the Job. Outputs: {"job_name", "succeeded", "error"}; a 409 on names the Job. Outputs: {"job_name", "succeeded", "error"}; a 409 on
creation is reported as a "duplicate job" failure. Never raises. creation is reported as a "duplicate job" failure. Never raises.
@ -80,7 +80,13 @@ def _job_payload(cfg: dict, job_name: str, incident_id: str) -> dict[str, Any]:
{"event": "hermes_demo_repair", "incident_id": incident_id, "message": _REPAIR_MESSAGE}, {"event": "hermes_demo_repair", "incident_id": incident_id, "message": _REPAIR_MESSAGE},
separators=(",", ":"), separators=(",", ":"),
) )
script = f"printf 'healthy' > /fixture/state && echo '{marker}'" namespace = str(cfg.get("namespace") or "")
fixture = str(cfg.get("fixture_configmap") or "hermes-triage-demo-fixture")
patch = '{"data":{"state":"healthy"}}'
script = (
f"kubectl -n {namespace} patch configmap {fixture} "
f"--type merge -p '{patch}' && echo '{marker}'"
)
return { return {
"apiVersion": "batch/v1", "apiVersion": "batch/v1",
"kind": "Job", "kind": "Job",
@ -99,19 +105,13 @@ def _job_payload(cfg: dict, job_name: str, incident_id: str) -> dict[str, Any]:
"metadata": {"labels": {"app.kubernetes.io/part-of": "hermes-triage-demo"}}, "metadata": {"labels": {"app.kubernetes.io/part-of": "hermes-triage-demo"}},
"spec": { "spec": {
"restartPolicy": "Never", "restartPolicy": "Never",
"serviceAccountName": "hermes-demo-repair",
"nodeSelector": {"node-role.kubernetes.io/worker": "true"}, "nodeSelector": {"node-role.kubernetes.io/worker": "true"},
"containers": [ "containers": [
{ {
"name": "repair", "name": "repair",
"image": str(cfg.get("image") or ""), "image": str(cfg.get("image") or ""),
"command": ["sh", "-c", script], "command": ["sh", "-c", script],
"volumeMounts": [{"name": "fixture", "mountPath": "/fixture"}],
}
],
"volumes": [
{
"name": "fixture",
"persistentVolumeClaim": {"claimName": str(cfg.get("fixture_pvc") or "")},
} }
], ],
}, },

View File

@ -190,7 +190,7 @@ class Settings:
hermes_api_key: str hermes_api_key: str
hermes_run_timeout_seconds: float hermes_run_timeout_seconds: float
hermes_demo_namespace: str hermes_demo_namespace: str
hermes_demo_fixture_pvc: str hermes_demo_fixture_configmap: str
hermes_repair_image: str hermes_repair_image: str
vaultwarden_namespace: str vaultwarden_namespace: str

View File

@ -277,8 +277,8 @@ def _hermes_autotriage_config() -> dict[str, Any]:
"hermes_api_key": _env("ARIADNE_HERMES_API_KEY", ""), "hermes_api_key": _env("ARIADNE_HERMES_API_KEY", ""),
"hermes_run_timeout_seconds": _env_float("ARIADNE_HERMES_RUN_TIMEOUT_SECONDS", 420.0), "hermes_run_timeout_seconds": _env_float("ARIADNE_HERMES_RUN_TIMEOUT_SECONDS", 420.0),
"hermes_demo_namespace": _env("ARIADNE_HERMES_DEMO_NAMESPACE", "hermes-triage-demo"), "hermes_demo_namespace": _env("ARIADNE_HERMES_DEMO_NAMESPACE", "hermes-triage-demo"),
"hermes_demo_fixture_pvc": _env("ARIADNE_HERMES_DEMO_FIXTURE_PVC", "hermes-triage-demo-fixture"), "hermes_demo_fixture_configmap": _env("ARIADNE_HERMES_DEMO_FIXTURE_CONFIGMAP", "hermes-triage-demo-fixture"),
"hermes_repair_image": _env("ARIADNE_HERMES_REPAIR_IMAGE", "busybox:1.37"), "hermes_repair_image": _env("ARIADNE_HERMES_REPAIR_IMAGE", "bitnami/kubectl@sha256:554ab88b1858e8424c55de37ad417b16f2a0e65d1607aa0f3fe3ce9b9f10b131"),
} }

View File

@ -52,7 +52,7 @@ def _settings(**overrides) -> SimpleNamespace: # type: ignore[no-untyped-def]
"hermes_api_key": "key", "hermes_api_key": "key",
"hermes_run_timeout_seconds": 420.0, "hermes_run_timeout_seconds": 420.0,
"hermes_demo_namespace": "hermes-triage-demo", "hermes_demo_namespace": "hermes-triage-demo",
"hermes_demo_fixture_pvc": "hermes-triage-demo-fixture", "hermes_demo_fixture_configmap": "hermes-triage-demo-fixture",
"hermes_repair_image": "busybox:1.37", "hermes_repair_image": "busybox:1.37",
"jenkins_base_url": "https://ci.example", "jenkins_base_url": "https://ci.example",
"jenkins_api_user": "user", "jenkins_api_user": "user",
@ -261,7 +261,7 @@ def test_new_failure_full_happy_path(monkeypatch) -> None:
( (
{ {
"namespace": "hermes-triage-demo", "namespace": "hermes-triage-demo",
"fixture_pvc": "hermes-triage-demo-fixture", "fixture_configmap": "hermes-triage-demo-fixture",
"image": "busybox:1.37", "image": "busybox:1.37",
}, },
INCIDENT_ID, INCIDENT_ID,

View File

@ -29,8 +29,8 @@ class FakeResponse:
def _cfg(**overrides) -> dict: # type: ignore[no-untyped-def] def _cfg(**overrides) -> dict: # type: ignore[no-untyped-def]
base = { base = {
"namespace": "hermes-triage-demo", "namespace": "hermes-triage-demo",
"fixture_pvc": "hermes-triage-demo-fixture", "fixture_configmap": "hermes-triage-demo-fixture",
"image": "busybox:1.37", "image": "bitnami/kubectl@sha256:554ab88b1858e8424c55de37ad417b16f2a0e65d1607aa0f3fe3ce9b9f10b131",
} }
base.update(overrides) base.update(overrides)
return base return base
@ -93,18 +93,18 @@ def test_repair_job_payload_contract(monkeypatch) -> None:
pod = spec["template"]["spec"] pod = spec["template"]["spec"]
assert pod["restartPolicy"] == "Never" assert pod["restartPolicy"] == "Never"
assert pod["nodeSelector"] == {"node-role.kubernetes.io/worker": "true"} assert pod["nodeSelector"] == {"node-role.kubernetes.io/worker": "true"}
assert pod["serviceAccountName"] == "hermes-demo-repair"
container = pod["containers"][0] container = pod["containers"][0]
assert container["image"] == "busybox:1.37" assert container["image"].startswith("bitnami/kubectl@sha256:")
assert container["command"][:2] == ["sh", "-c"] assert container["command"][:2] == ["sh", "-c"]
script = container["command"][2] script = container["command"][2]
assert "printf 'healthy' > /fixture/state" in script assert "kubectl -n hermes-triage-demo patch configmap hermes-triage-demo-fixture" in script
assert '{"data":{"state":"healthy"}}' in script
assert '"event":"hermes_demo_repair"' in script assert '"event":"hermes_demo_repair"' in script
assert f'"incident_id":"{INCIDENT_ID}"' in script assert f'"incident_id":"{INCIDENT_ID}"' in script
assert '"message":"fixture state reset to healthy"' in script assert '"message":"fixture state reset to healthy"' in script
assert container["volumeMounts"] == [{"name": "fixture", "mountPath": "/fixture"}] assert "volumeMounts" not in container
assert pod["volumes"] == [ assert "volumes" not in pod
{"name": "fixture", "persistentVolumeClaim": {"claimName": "hermes-triage-demo-fixture"}}
]
def test_execute_repair_waits_through_active_polls(monkeypatch) -> None: def test_execute_repair_waits_through_active_polls(monkeypatch) -> None: