From b07a6816d407353b7c366c8508b236a4ed2d36da Mon Sep 17 00:00:00 2001 From: Admin MPCZ Date: Mon, 4 May 2026 15:37:12 +0200 Subject: [PATCH] feat(patching/iexec): check espace disque (/ >= 1.5Go, /var/log >= 1Go) + fix detection subscription-manager identity FR/EN via UUID regex --- app/services/prepatch_check_service.py | 70 +++++++++++++++++++++++++- app/templates/patching_iexec.html | 12 ++++- 2 files changed, 80 insertions(+), 2 deletions(-) diff --git a/app/services/prepatch_check_service.py b/app/services/prepatch_check_service.py index 1b95699..a5ef72c 100644 --- a/app/services/prepatch_check_service.py +++ b/app/services/prepatch_check_service.py @@ -19,6 +19,7 @@ calcule un verdict global. from __future__ import annotations import logging +import re import socket import time from typing import Callable, Dict, List, Any @@ -30,6 +31,10 @@ log = logging.getLogger("patchcenter.prepatch") # Timeout par commande SSH EXEC_TIMEOUT = 15 +# Seuils espace disque (en Mo) +DISK_MIN_ROOT_MB = 1500 # 1.5 Go sur / +DISK_MIN_VARLOG_MB = 1000 # 1 Go sur /var/log + # Satellites SANEF par zone réseau SATELLITE_LAN = "vpdsiasat2.sanef.groupe" SATELLITE_DMZ = "vpdsiasat1.sanef.groupe" @@ -127,6 +132,65 @@ def check_ssh(ctx: Dict[str, Any]) -> Dict[str, Any]: } +def _disk_avail_mb(client, path: str): + """Renvoie l'espace dispo en Mo sur le FS contenant `path`, ou None si KO.""" + r = _exec(client, f"sudo -n df -BM --output=avail {path} 2>&1 | tail -n +2") + out = (r["stdout"] or "").strip() + m = re.search(r"(\d+)\s*M", out) + if m: + return int(m.group(1)) + return None + + +def check_disk(ctx: Dict[str, Any]) -> Dict[str, Any]: + """Vérifie l'espace disque dispo : + - / >= 1.5 Go + - /var/log >= 1 Go + KO si insuffisant → pas éligible au snapshot. + """ + client = ctx.get("client") + if client is None: + return { + "name": "disk", + "label": f"Espace disque (/ ≥ {DISK_MIN_ROOT_MB}M, /var/log ≥ {DISK_MIN_VARLOG_MB}M)", + "status": "ko", + "message": "SSH KO en amont", + "details": "", + } + root_mb = _disk_avail_mb(client, "/") + var_mb = _disk_avail_mb(client, "/var/log") + issues = [] + parts = [] + if root_mb is None: + issues.append("/ : mesure impossible") + else: + parts.append(f"/ {root_mb}M") + if root_mb < DISK_MIN_ROOT_MB: + issues.append(f"/ {root_mb}M < min {DISK_MIN_ROOT_MB}M") + if var_mb is None: + issues.append("/var/log : mesure impossible") + else: + parts.append(f"/var/log {var_mb}M") + if var_mb < DISK_MIN_VARLOG_MB: + issues.append(f"/var/log {var_mb}M < min {DISK_MIN_VARLOG_MB}M") + label = f"Espace disque (/ ≥ {DISK_MIN_ROOT_MB}M, /var/log ≥ {DISK_MIN_VARLOG_MB}M)" + details = ( + f"$ sudo df -BM --output=avail / → {root_mb if root_mb is not None else 'N/A'}M\n" + f"$ sudo df -BM --output=avail /var/log → {var_mb if var_mb is not None else 'N/A'}M" + ) + if issues: + return { + "name": "disk", "label": label, "status": "ko", + "message": " · ".join(issues), + "details": details, + } + return { + "name": "disk", "label": label, "status": "ok", + "message": " · ".join(parts) + " (au-dessus seuils)", + "details": details, + } + + def check_satellite(ctx: Dict[str, Any]) -> Dict[str, Any]: """Vérifie : 1. la joignabilité du Satellite cible (LAN ou DMZ selon Domaine) @@ -154,8 +218,11 @@ def check_satellite(ctx: Dict[str, Any]) -> Dict[str, Any]: sat_reachable = http_code in ("200", "301", "302", "403") # 2) subscription-manager identity + # Locale-indépendant : on cherche un UUID dans la sortie (présent en EN comme en FR). r1 = _exec(client, "sudo -n subscription-manager identity 2>&1") - sub_ok = (r1["rc"] == 0 and "system identity" in r1["stdout"].lower()) + sub_ok = (r1["rc"] == 0 and bool(re.search( + r"\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b", + r1["stdout"], re.IGNORECASE))) # 3) yum repolist enabled --quiet r2 = _exec(client, "sudo -n yum repolist enabled --quiet 2>&1 | head -50") @@ -205,6 +272,7 @@ def check_satellite(ctx: Dict[str, Any]) -> Dict[str, Any]: CHECKS: Dict[str, Callable[[Dict[str, Any]], Dict[str, Any]]] = { "dns": check_dns, "ssh": check_ssh, + "disk": check_disk, "satellite": check_satellite, } diff --git a/app/templates/patching_iexec.html b/app/templates/patching_iexec.html index 9db36d8..09dc92a 100644 --- a/app/templates/patching_iexec.html +++ b/app/templates/patching_iexec.html @@ -46,6 +46,7 @@ Excludes DNS SSH + Disque Satellite Verdict @@ -61,11 +62,12 @@ {{ r.effective_excludes or '(aucun)' }} · · + · · en attente {% else %} - Aucune ligne éligible. + Aucune ligne éligible. {% endfor %} @@ -93,6 +95,11 @@ const detailsCard = document.getElementById('details-card'); const detailsPane = document.getElementById('details-pane'); + function escapeHTML(s){ + if (s === null || s === undefined) return ''; + return String(s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); + } + function statusBadge(st){ if (st === 'ok') return '✓ OK'; if (st === 'warn')return '⚠ WARN'; @@ -112,6 +119,7 @@ if (!isLinux(osStr)) { tr.querySelector('.cell-dns').innerHTML = statusBadge('unsupported'); tr.querySelector('.cell-ssh').innerHTML = statusBadge('unsupported'); + tr.querySelector('.cell-disk').innerHTML = statusBadge('unsupported'); tr.querySelector('.cell-sat').innerHTML = statusBadge('unsupported'); tr.querySelector('.cell-overall').innerHTML = '⊘ Windows'; return {overall: 'unsupported'}; @@ -127,6 +135,7 @@ if (j.overall === 'unsupported') { tr.querySelector('.cell-dns').innerHTML = statusBadge('unsupported'); tr.querySelector('.cell-ssh').innerHTML = statusBadge('unsupported'); + tr.querySelector('.cell-disk').innerHTML = statusBadge('unsupported'); tr.querySelector('.cell-sat').innerHTML = statusBadge('unsupported'); tr.querySelector('.cell-overall').innerHTML = '⊘ N/A'; return j; @@ -135,6 +144,7 @@ (j.checks || []).forEach(c => byName[c.name] = c); tr.querySelector('.cell-dns').innerHTML = byName.dns ? statusBadge(byName.dns.status) : '–'; tr.querySelector('.cell-ssh').innerHTML = byName.ssh ? statusBadge(byName.ssh.status) : '–'; + tr.querySelector('.cell-disk').innerHTML = byName.disk ? statusBadge(byName.disk.status) + ' ' + escapeHTML(byName.disk.message) + '' : '–'; tr.querySelector('.cell-sat').innerHTML = byName.satellite ? statusBadge(byName.satellite.status) : '–'; tr.querySelector('.cell-overall').innerHTML = statusBadge(j.overall) + ' (' + (j.duration_ms||0) + 'ms)'; tr._checkData = j;