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 {
"namespace": settings.hermes_demo_namespace,
"fixture_pvc": settings.hermes_demo_fixture_pvc,
"fixture_configmap": settings.hermes_demo_fixture_configmap,
"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]:
"""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
names the Job. Outputs: {"job_name", "succeeded", "error"}; a 409 on
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},
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 {
"apiVersion": "batch/v1",
"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"}},
"spec": {
"restartPolicy": "Never",
"serviceAccountName": "hermes-demo-repair",
"nodeSelector": {"node-role.kubernetes.io/worker": "true"},
"containers": [
{
"name": "repair",
"image": str(cfg.get("image") or ""),
"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_run_timeout_seconds: float
hermes_demo_namespace: str
hermes_demo_fixture_pvc: str
hermes_demo_fixture_configmap: str
hermes_repair_image: 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_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_fixture_pvc": _env("ARIADNE_HERMES_DEMO_FIXTURE_PVC", "hermes-triage-demo-fixture"),
"hermes_repair_image": _env("ARIADNE_HERMES_REPAIR_IMAGE", "busybox:1.37"),
"hermes_demo_fixture_configmap": _env("ARIADNE_HERMES_DEMO_FIXTURE_CONFIGMAP", "hermes-triage-demo-fixture"),
"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_run_timeout_seconds": 420.0,
"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",
"jenkins_base_url": "https://ci.example",
"jenkins_api_user": "user",
@ -261,7 +261,7 @@ def test_new_failure_full_happy_path(monkeypatch) -> None:
(
{
"namespace": "hermes-triage-demo",
"fixture_pvc": "hermes-triage-demo-fixture",
"fixture_configmap": "hermes-triage-demo-fixture",
"image": "busybox:1.37",
},
INCIDENT_ID,

View File

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