mirror of
https://github.com/CyberMind-FR/secubox-deb.git
synced 2026-07-28 21:17:36 +00:00
fix(profiles): _run distinguishes TimeoutExpired from could-not-run (ref #893)
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
This commit is contained in:
parent
649a544669
commit
c8e23843ed
|
|
@ -32,6 +32,15 @@ class ActuationError(Exception):
|
|||
"""Une commande d'actionnement a échoué (rc non-nul) ou n'a pas pu tourner (rc=None)."""
|
||||
|
||||
|
||||
# Sentinel returncode for a command that RAN but did not return within the
|
||||
# deadline (subprocess.TimeoutExpired), as opposed to one that could not run at
|
||||
# all (OSError -> rc is None). The actuator fast-fails only on the latter; a
|
||||
# timeout on lxc-start/lxc-stop is deferred to wait_state (observed state
|
||||
# decides). 1000 is outside every real returncode (exit codes 0-255, signal
|
||||
# codes negative), so `rc != 0` still reads it as "not success".
|
||||
TIMED_OUT = 1000
|
||||
|
||||
|
||||
def _must(run, argv: list[str]) -> None:
|
||||
rc, out = run(argv)
|
||||
if rc != 0:
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import sys
|
|||
from pathlib import Path
|
||||
|
||||
from . import apply, export
|
||||
from .actuate import TIMED_OUT
|
||||
from .audit import AUDIT_LOG
|
||||
from .diff import ProtectedViolation, plan_changes
|
||||
from .export import format_apt, format_json, format_pkglist, resolve_packages
|
||||
|
|
@ -296,16 +297,24 @@ def _cmd_scan(args) -> int:
|
|||
return 0
|
||||
|
||||
|
||||
_RUN_TIMEOUT_S = 30 # was 15: --no-block native commands return at once; the
|
||||
# extra headroom is for lxc-start/lxc-stop (no --no-block flag) so the container
|
||||
# CLI usually finishes before we give up and defer to wait_state.
|
||||
|
||||
|
||||
def _run(argv: list[str]) -> tuple[int | None, str]:
|
||||
"""rc=None signale que la commande n'a PAS pu s'exécuter (OSError, timeout) —
|
||||
à distinguer d'un rc non-nul qui est une réponse authentique de la commande.
|
||||
Même contrat que observe._run_cmd : un (1, "") fabriqué ici serait
|
||||
indistinguable d'une vraie réponse "non" de la commande (voir _cmd_scan,
|
||||
qui a besoin de cette distinction pour ne pas écrire un manifeste faux)."""
|
||||
"""rc=None = la commande n'a PAS pu s'exécuter (OSError) — jamais un faux
|
||||
succès. rc=TIMED_OUT = elle a bien démarré mais n'a pas répondu dans le
|
||||
délai (subprocess.TimeoutExpired) : pour lxc-start/lxc-stop (sans --no-block)
|
||||
ce n'est PAS un échec, c'est wait_state qui tranche sur l'état observé.
|
||||
Même contrat de lecture-seule côté observe._run_cmd (qui, lui, garde
|
||||
timeout->None : une sonde qui traîne reste indéterminée)."""
|
||||
try:
|
||||
p = subprocess.run(argv, capture_output=True, text=True, timeout=15)
|
||||
p = subprocess.run(argv, capture_output=True, text=True, timeout=_RUN_TIMEOUT_S)
|
||||
return p.returncode, p.stdout
|
||||
except (OSError, subprocess.SubprocessError):
|
||||
except subprocess.TimeoutExpired:
|
||||
return TIMED_OUT, ""
|
||||
except OSError:
|
||||
return None, ""
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -445,3 +445,24 @@ def test_apply_error_maps_to_rc3_not_traceback(tmp_path, monkeypatch):
|
|||
raise apply_mod.ApplyError("x est protégé — un STOP est refusé")
|
||||
monkeypatch.setattr(apply_mod, "apply_plan", boom)
|
||||
assert cli.main(["--root", str(root), "apply", "--yes"]) == 3
|
||||
|
||||
|
||||
def test_run_distinguishes_timeout_from_could_not_run(monkeypatch):
|
||||
# cli._run must return the actuate.TIMED_OUT sentinel on a subprocess
|
||||
# timeout (the command RAN, still working) and None only on OSError
|
||||
# (the command could not run at all) — the actuator relies on this
|
||||
# distinction to fast-fail only on genuine could-not-run.
|
||||
import subprocess
|
||||
|
||||
import api.cli as cli
|
||||
from api.actuate import TIMED_OUT
|
||||
|
||||
def raise_timeout(*a, **k):
|
||||
raise subprocess.TimeoutExpired(cmd="x", timeout=1)
|
||||
monkeypatch.setattr(cli.subprocess, "run", raise_timeout)
|
||||
assert cli._run(["whatever"]) == (TIMED_OUT, "")
|
||||
|
||||
def raise_oserror(*a, **k):
|
||||
raise OSError("no such binary")
|
||||
monkeypatch.setattr(cli.subprocess, "run", raise_oserror)
|
||||
assert cli._run(["whatever"]) == (None, "")
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user