#!/usr/bin/env bash set -euo pipefail ROOT="${1:-.}" need_file() { local f="$1" if [[ ! -f "$ROOT/$f" ]]; then echo "Fichier introuvable: $f" >&2 exit 1 fi } echo "==> Vérification" need_file "scripts/generate_services.py" need_file "domains.yaml" echo "==> Sauvegarde" mkdir -p "$ROOT/.patch-backup-days-until-due-v3" cp -a "$ROOT/scripts/generate_services.py" "$ROOT/.patch-backup-days-until-due-v3/generate_services.py.bak" cp -a "$ROOT/domains.yaml" "$ROOT/.patch-backup-days-until-due-v3/domains.yaml.bak" echo "==> Patch robuste de scripts/generate_services.py" python3 - "$ROOT/scripts/generate_services.py" <<'PYEOF' from pathlib import Path import re import sys path = Path(sys.argv[1]) text = path.read_text(encoding="utf-8") # 1) Ajouter les seuils lt/gte manquants dans render_probe_service, juste avant on_error if 'vars.life_noc_threshold_unknown_gte' not in text: marker = ' on_error = str(policy.get("on_error", "critical")).strip()\n' inject = ( ' if "unknown_gte" in thresholds:\n' ' lines.append(f\' vars.life_noc_threshold_unknown_gte = "{icinga_escape(str(thresholds["unknown_gte"]))}"\')\n' ' if "ok_lt" in thresholds:\n' ' lines.append(f\' vars.life_noc_threshold_ok_lt = "{icinga_escape(str(thresholds["ok_lt"]))}"\')\n' ' if "warning_lt" in thresholds:\n' ' lines.append(f\' vars.life_noc_threshold_warning_lt = "{icinga_escape(str(thresholds["warning_lt"]))}"\')\n' ' if "critical_lt" in thresholds:\n' ' lines.append(f\' vars.life_noc_threshold_critical_lt = "{icinga_escape(str(thresholds["critical_lt"]))}"\')\n' ) if marker not in text: raise SystemExit("Impossible de trouver le point d'injection des seuils dans render_probe_service") text = text.replace(marker, inject + marker, 1) # 2) Remplacer entièrement validate_probe(...) new_validate = '''def validate_probe(item: dict) -> None: probe = item["probe"] if not isinstance(probe, dict): raise ValueError("probe doit être un objet") probe_type = str(probe.get("type", "")).strip() if probe_type not in {"elapsed_time", "days_until_due"}: raise ValueError("seuls probe.type=elapsed_time et days_until_due sont supportés") source = probe.get("source") if not isinstance(source, dict): raise ValueError("probe.source doit être un objet") if str(source.get("type", "")).strip() != "manual_date": raise ValueError("seul probe.source.type=manual_date est supporté en v1") has_inline_value = "value" in source has_store_ref = "inputs_file" in source and "item_key" in source if not has_inline_value and not has_store_ref: raise ValueError("probe.source.value ou probe.source.inputs_file + item_key requis") metric = probe.get("metric") if not isinstance(metric, dict): raise ValueError("probe.metric doit être un objet") if str(metric.get("unit", "")).strip() != "days": raise ValueError("seul probe.metric.unit=days est supporté en v1") thresholds = probe.get("thresholds") if not isinstance(thresholds, dict): raise ValueError("probe.thresholds doit être un objet") ''' pattern = r'def validate_probe\(item: dict\) -> None:\n(?: .*\n)+?(?=\ndef render_servicegroup|\ndef main\()' new_text, count = re.subn(pattern, new_validate + "\n", text, flags=re.MULTILINE) if count == 0: raise SystemExit("Impossible de remplacer validate_probe(...) dans generate_services.py") text = new_text path.write_text(text, encoding="utf-8") print("generate_services.py patché") PYEOF echo "==> Création du store" mkdir -p "$ROOT/data/inputs" cat > "$ROOT/data/inputs/obligations-legales-personnelles.yaml" <<'YAMLEOF' renouvellement-permis-conduire: value: "2026-09-20" captured_at: "2026-03-14T23:30:00Z" origin: manual YAMLEOF echo "==> Mise à jour de domains.yaml" python3 - "$ROOT/domains.yaml" <<'PYEOF' from pathlib import Path import sys import yaml path = Path(sys.argv[1]) data = yaml.safe_load(path.read_text(encoding="utf-8")) domain = "obligations-legales-personnelles" items = data.get("domains", {}).get(domain) if not isinstance(items, list): raise SystemExit(f"Domaine introuvable ou invalide: {domain}") target = None for item in items: if isinstance(item, dict) and item.get("name") == "renouvellement-permis-conduire": target = item break if target is None: raise SystemExit("Item 'renouvellement-permis-conduire' introuvable") target["date"] = "2026-09-20" target["notes"] = "Vérifier la proximité de l’échéance du permis" target["probe"] = { "type": "days_until_due", "source": { "type": "manual_date", "inputs_file": "/opt/life-noc/data/inputs/obligations-legales-personnelles.yaml", "item_key": "renouvellement-permis-conduire", }, "metric": { "unit": "days", }, "thresholds": { "critical_lt": 7, "warning_lt": 30, "ok_lt": 90, "unknown_gte": 90, }, "policy": { "on_error": "critical", }, } path.write_text(yaml.safe_dump(data, sort_keys=False, allow_unicode=True), encoding="utf-8") print("domains.yaml mis à jour") PYEOF echo "==> Régénération" ( cd "$ROOT" python3 scripts/generate_services.py ) echo echo "Patch terminé." echo "Étapes suivantes :" echo " python3 scripts/generate_services.py" echo " make check" echo " make deploy-with-bpm"