284 lines
10 KiB
Bash
284 lines
10 KiB
Bash
|
|
#!/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 du dépôt"
|
|||
|
|
need_file "checks/check_life_noc_probe.py"
|
|||
|
|
need_file "icinga/commands/check_life_noc_probe.conf"
|
|||
|
|
need_file "scripts/generate_services.py"
|
|||
|
|
need_file "domains.yaml"
|
|||
|
|
|
|||
|
|
echo "==> Sauvegarde"
|
|||
|
|
mkdir -p "$ROOT/.patch-backup-days-until-due"
|
|||
|
|
cp -a "$ROOT/checks/check_life_noc_probe.py" "$ROOT/.patch-backup-days-until-due/check_life_noc_probe.py.bak"
|
|||
|
|
cp -a "$ROOT/icinga/commands/check_life_noc_probe.conf" "$ROOT/.patch-backup-days-until-due/check_life_noc_probe.conf.bak"
|
|||
|
|
cp -a "$ROOT/scripts/generate_services.py" "$ROOT/.patch-backup-days-until-due/generate_services.py.bak"
|
|||
|
|
cp -a "$ROOT/domains.yaml" "$ROOT/.patch-backup-days-until-due/domains.yaml.bak"
|
|||
|
|
|
|||
|
|
echo "==> Réécriture de checks/check_life_noc_probe.py"
|
|||
|
|
cat > "$ROOT/checks/check_life_noc_probe.py" <<'PYEOF'
|
|||
|
|
#!/usr/bin/env python3
|
|||
|
|
|
|||
|
|
import argparse
|
|||
|
|
from datetime import datetime, timezone
|
|||
|
|
from pathlib import Path
|
|||
|
|
import yaml
|
|||
|
|
|
|||
|
|
|
|||
|
|
def exit_with(code: int, text: str) -> None:
|
|||
|
|
print(text)
|
|||
|
|
raise SystemExit(code)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def parse_date(value: str) -> datetime:
|
|||
|
|
return datetime.strptime(value, "%Y-%m-%d").replace(tzinfo=timezone.utc)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def load_input_value(inputs_file: str, item_key: str) -> str:
|
|||
|
|
path = Path(inputs_file)
|
|||
|
|
if not path.exists():
|
|||
|
|
raise ValueError(f"inputs file introuvable: {inputs_file}")
|
|||
|
|
|
|||
|
|
data = yaml.safe_load(path.read_text(encoding="utf-8"))
|
|||
|
|
if not isinstance(data, dict):
|
|||
|
|
raise ValueError(f"inputs file invalide: {inputs_file}")
|
|||
|
|
|
|||
|
|
if item_key not in data:
|
|||
|
|
raise ValueError(f"item_key introuvable dans le store: {item_key}")
|
|||
|
|
|
|||
|
|
item = data[item_key]
|
|||
|
|
if not isinstance(item, dict):
|
|||
|
|
raise ValueError(f"entrée invalide pour item_key: {item_key}")
|
|||
|
|
|
|||
|
|
if "value" not in item:
|
|||
|
|
raise ValueError(f"champ 'value' absent pour item_key: {item_key}")
|
|||
|
|
|
|||
|
|
return str(item["value"]).strip()
|
|||
|
|
|
|||
|
|
|
|||
|
|
def main() -> int:
|
|||
|
|
ap = argparse.ArgumentParser()
|
|||
|
|
ap.add_argument("--probe-type", required=True)
|
|||
|
|
ap.add_argument("--source-type", required=True)
|
|||
|
|
ap.add_argument("--source-value")
|
|||
|
|
ap.add_argument("--inputs-file")
|
|||
|
|
ap.add_argument("--item-key")
|
|||
|
|
ap.add_argument("--unit", required=True)
|
|||
|
|
|
|||
|
|
ap.add_argument("--unknown-lt", type=float)
|
|||
|
|
ap.add_argument("--unknown-gte", type=float)
|
|||
|
|
|
|||
|
|
ap.add_argument("--ok-gte", type=float)
|
|||
|
|
ap.add_argument("--ok-lt", type=float)
|
|||
|
|
|
|||
|
|
ap.add_argument("--warning-gte", type=float)
|
|||
|
|
ap.add_argument("--warning-lt", type=float)
|
|||
|
|
|
|||
|
|
ap.add_argument("--critical-gte", type=float)
|
|||
|
|
ap.add_argument("--critical-lt", type=float)
|
|||
|
|
|
|||
|
|
ap.add_argument("--on-error", default="critical")
|
|||
|
|
ap.add_argument("--label", default="life-noc")
|
|||
|
|
args = ap.parse_args()
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
if args.source_type != "manual_date":
|
|||
|
|
exit_with(2, f"CRITICAL - source_type non supporté: {args.source_type}")
|
|||
|
|
|
|||
|
|
if args.unit != "days":
|
|||
|
|
exit_with(2, f"CRITICAL - unité non supportée: {args.unit}")
|
|||
|
|
|
|||
|
|
if args.inputs_file and args.item_key:
|
|||
|
|
source_value = load_input_value(args.inputs_file, args.item_key)
|
|||
|
|
elif args.source_value:
|
|||
|
|
source_value = args.source_value
|
|||
|
|
else:
|
|||
|
|
raise ValueError("aucune source fournie: inputs-file/item-key ou source-value requis")
|
|||
|
|
|
|||
|
|
dt = parse_date(source_value)
|
|||
|
|
now = datetime.now(timezone.utc)
|
|||
|
|
|
|||
|
|
if args.probe_type == "elapsed_time":
|
|||
|
|
metric = (now - dt).total_seconds() / 86400.0
|
|||
|
|
|
|||
|
|
if args.critical_gte is not None and metric >= args.critical_gte:
|
|||
|
|
exit_with(2, f"CRITICAL - {args.label}: {metric:.1f} jours")
|
|||
|
|
if args.warning_gte is not None and metric >= args.warning_gte:
|
|||
|
|
exit_with(1, f"WARNING - {args.label}: {metric:.1f} jours")
|
|||
|
|
if args.ok_gte is not None and metric >= args.ok_gte:
|
|||
|
|
exit_with(0, f"OK - {args.label}: {metric:.1f} jours")
|
|||
|
|
if args.unknown_lt is not None and metric < args.unknown_lt:
|
|||
|
|
exit_with(3, f"UNKNOWN - {args.label}: {metric:.1f} jours")
|
|||
|
|
|
|||
|
|
exit_with(2, f"CRITICAL - {args.label}: seuils incohérents")
|
|||
|
|
|
|||
|
|
elif args.probe_type == "days_until_due":
|
|||
|
|
metric = (dt - now).total_seconds() / 86400.0
|
|||
|
|
|
|||
|
|
if args.critical_lt is not None and metric < args.critical_lt:
|
|||
|
|
exit_with(2, f"CRITICAL - {args.label}: {metric:.1f} jours restants")
|
|||
|
|
if args.warning_lt is not None and metric < args.warning_lt:
|
|||
|
|
exit_with(1, f"WARNING - {args.label}: {metric:.1f} jours restants")
|
|||
|
|
if args.ok_lt is not None and metric < args.ok_lt:
|
|||
|
|
exit_with(0, f"OK - {args.label}: {metric:.1f} jours restants")
|
|||
|
|
if args.unknown_gte is not None and metric >= args.unknown_gte:
|
|||
|
|
exit_with(3, f"UNKNOWN - {args.label}: {metric:.1f} jours restants")
|
|||
|
|
|
|||
|
|
exit_with(2, f"CRITICAL - {args.label}: seuils incohérents")
|
|||
|
|
|
|||
|
|
else:
|
|||
|
|
exit_with(2, f"CRITICAL - probe_type non supporté: {args.probe_type}")
|
|||
|
|
|
|||
|
|
except Exception as exc:
|
|||
|
|
if args.on_error == "critical":
|
|||
|
|
exit_with(2, f"CRITICAL - erreur sonde: {exc}")
|
|||
|
|
exit_with(3, f"UNKNOWN - erreur sonde: {exc}")
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
raise SystemExit(main())
|
|||
|
|
PYEOF
|
|||
|
|
chmod +x "$ROOT/checks/check_life_noc_probe.py"
|
|||
|
|
|
|||
|
|
echo "==> Réécriture de icinga/commands/check_life_noc_probe.conf"
|
|||
|
|
cat > "$ROOT/icinga/commands/check_life_noc_probe.conf" <<'CONFEOF'
|
|||
|
|
object CheckCommand "check_life_noc_probe" {
|
|||
|
|
command = [ PluginDir + "/check_life_noc_probe.py" ]
|
|||
|
|
|
|||
|
|
arguments = {
|
|||
|
|
"--probe-type" = "$life_noc_probe_type$"
|
|||
|
|
"--source-type" = "$life_noc_source_type$"
|
|||
|
|
"--source-value" = "$life_noc_source_value$"
|
|||
|
|
"--inputs-file" = "$life_noc_inputs_file$"
|
|||
|
|
"--item-key" = "$life_noc_item_key$"
|
|||
|
|
"--unit" = "$life_noc_metric_unit$"
|
|||
|
|
|
|||
|
|
"--unknown-lt" = "$life_noc_threshold_unknown_lt$"
|
|||
|
|
"--unknown-gte" = "$life_noc_threshold_unknown_gte$"
|
|||
|
|
|
|||
|
|
"--ok-gte" = "$life_noc_threshold_ok_gte$"
|
|||
|
|
"--ok-lt" = "$life_noc_threshold_ok_lt$"
|
|||
|
|
|
|||
|
|
"--warning-gte" = "$life_noc_threshold_warning_gte$"
|
|||
|
|
"--warning-lt" = "$life_noc_threshold_warning_lt$"
|
|||
|
|
|
|||
|
|
"--critical-gte" = "$life_noc_threshold_critical_gte$"
|
|||
|
|
"--critical-lt" = "$life_noc_threshold_critical_lt$"
|
|||
|
|
|
|||
|
|
"--on-error" = "$life_noc_on_error$"
|
|||
|
|
"--label" = "$life_noc_label$"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
CONFEOF
|
|||
|
|
|
|||
|
|
echo "==> Patch de scripts/generate_services.py"
|
|||
|
|
python3 - "$ROOT/scripts/generate_services.py" <<'PYEOF'
|
|||
|
|
from pathlib import Path
|
|||
|
|
import sys
|
|||
|
|
|
|||
|
|
path = Path(sys.argv[1])
|
|||
|
|
text = path.read_text(encoding="utf-8")
|
|||
|
|
|
|||
|
|
old = ' if "critical_gte" in thresholds:\n lines.append(f\' vars.life_noc_threshold_critical_gte = "{icinga_escape(str(thresholds["critical_gte"]))}"\')\n'
|
|||
|
|
new = old + ' 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 old not in text:
|
|||
|
|
raise SystemExit("Bloc seuils introuvable dans generate_services.py")
|
|||
|
|
text = text.replace(old, new, 1)
|
|||
|
|
|
|||
|
|
old2 = ' probe_type = str(probe.get("type", "")).strip()\n if probe_type not in {"elapsed_time"}:\n raise ValueError("seul probe.type=elapsed_time est supporté en v1")\n'
|
|||
|
|
new2 = ' probe_type = str(probe.get("type", "")).strip()\n if probe_type not in {"elapsed_time", "days_until_due"}:\n raise ValueError("seuls probe.type=elapsed_time et days_until_due sont supportés")\n'
|
|||
|
|
if old2 in text:
|
|||
|
|
text = text.replace(old2, new2, 1)
|
|||
|
|
else:
|
|||
|
|
old3 = ' probe_type = str(probe.get("type", "")).strip()\n if probe_type not in {"elapsed_time", "days_until_due"}:\n raise ValueError("seuls probe.type=elapsed_time et days_until_due sont supportés")\n'
|
|||
|
|
if old3 not in text:
|
|||
|
|
raise SystemExit("Bloc validate_probe introuvable ou inattendu dans generate_services.py")
|
|||
|
|
|
|||
|
|
path.write_text(text, encoding="utf-8")
|
|||
|
|
print("generate_services.py patché")
|
|||
|
|
PYEOF
|
|||
|
|
|
|||
|
|
echo "==> Création du store data/inputs/obligations-legales-personnelles.yaml"
|
|||
|
|
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 dans domains.yaml")
|
|||
|
|
|
|||
|
|
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"
|
|||
|
|
echo
|
|||
|
|
echo "Test utile ensuite :"
|
|||
|
|
echo " python3 scripts/life_noc_input.py set obligations-legales-personnelles renouvellement-permis-conduire 2026-03-20"
|