Corrige l’intégration Icinga Web 2 et le transport de commandes

This commit is contained in:
Daniel Allaire 2026-03-14 17:02:35 -04:00
parent f865013a54
commit 3ea21ef27e
10 changed files with 247 additions and 19 deletions

View file

@ -48,6 +48,14 @@
- validate icinga2
- restart icinga2
- name: enable command feature
ansible.builtin.command: icinga2 feature enable command
args:
creates: /etc/icinga2/features-enabled/command.conf
notify:
- validate icinga2
- restart icinga2
- name: enable and start icinga2
ansible.builtin.service:
name: icinga2

View file

@ -1,3 +1,3 @@
[icinga2]
transport = "local"
path = "{{ life_noc_icinga2_command_pipe }}"
path = "/run/icinga2/cmd/icinga2.cmd"

View file

@ -1,6 +1,7 @@
[global]
show_stacktraces = "0"
config_backend = "ini"
config_backend = "db"
config_resource = "icingaweb2"
[logging]
log = "syslog"

View file

@ -187,6 +187,18 @@
- validate icinga
- reload icinga
- name: install generic life-noc probe plugin
ansible.builtin.copy:
src: "{{ life_noc_project_root }}/checks/check_life_noc_probe.py"
dest: /usr/lib/nagios/plugins/check_life_noc_probe.py
remote_src: true
owner: root
group: root
mode: "0755"
notify:
- validate icinga
- reload icinga
- name: ensure bpm destination directory exists
ansible.builtin.file:
path: "{{ life_noc_bpm_destination_dir }}"

View file

@ -0,0 +1,61 @@
#!/usr/bin/env python3
import argparse
from datetime import datetime, timezone
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 main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--probe-type", required=True)
ap.add_argument("--source-type", required=True)
ap.add_argument("--source-value", required=True)
ap.add_argument("--unit", required=True)
ap.add_argument("--unknown-lt", type=float)
ap.add_argument("--ok-gte", type=float)
ap.add_argument("--warning-gte", type=float)
ap.add_argument("--critical-gte", type=float)
ap.add_argument("--on-error", default="critical")
ap.add_argument("--label", default="life-noc")
args = ap.parse_args()
try:
if args.probe_type != "elapsed_time":
exit_with(2, f"CRITICAL - probe_type non supporté: {args.probe_type}")
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}")
dt = parse_date(args.source_value)
now = datetime.now(timezone.utc)
delta_days = (now - dt).total_seconds() / 86400.0
if args.critical_gte is not None and delta_days >= args.critical_gte:
exit_with(2, f"CRITICAL - {args.label}: {delta_days:.1f} jours")
if args.warning_gte is not None and delta_days >= args.warning_gte:
exit_with(1, f"WARNING - {args.label}: {delta_days:.1f} jours")
if args.ok_gte is not None and delta_days >= args.ok_gte:
exit_with(0, f"OK - {args.label}: {delta_days:.1f} jours")
if args.unknown_lt is not None and delta_days < args.unknown_lt:
exit_with(3, f"UNKNOWN - {args.label}: {delta_days:.1f} jours")
exit_with(2, f"CRITICAL - {args.label}: seuils incohérents")
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())

View file

@ -2,12 +2,40 @@ domains:
revue:
- name: revue-quotidienne-life-noc
date: 2026-03-07
notes: Revue quotidienne du tableau Life-NOC
date: "2026-03-13"
notes: Vérifier le tableau de bord Life-NOC
probe:
type: elapsed_time
source:
type: manual_date
value: "2026-03-13"
metric:
unit: days
thresholds:
unknown_lt: 1
ok_gte: 1
warning_gte: 2
critical_gte: 3
policy:
on_error: critical
- name: revue-hebdomadaire-priorites
date: 2026-03-08
notes: Revue hebdomadaire des priorités personnelles et professionnelles
date: "2026-03-10"
notes: Revoir les priorités de la semaine
probe:
type: elapsed_time
source:
type: manual_date
value: "2026-03-10"
metric:
unit: days
thresholds:
unknown_lt: 5
ok_gte: 5
warning_gte: 7
critical_gte: 10
policy:
on_error: critical
- name: revue-mensuelle-systeme-vie
date: 2026-04-01

View file

@ -0,0 +1,16 @@
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$"
"--unit" = "$life_noc_metric_unit$"
"--unknown-lt" = "$life_noc_threshold_unknown_lt$"
"--ok-gte" = "$life_noc_threshold_ok_gte$"
"--warning-gte" = "$life_noc_threshold_warning_gte$"
"--critical-gte" = "$life_noc_threshold_critical_gte$"
"--on-error" = "$life_noc_on_error$"
"--label" = "$life_noc_label$"
}
}

View file

@ -5,22 +5,36 @@
*/
apply Service "revue-revue-quotidienne-life-noc" {
import "service_echeance"
vars.date_echeance = "2026-03-07"
vars.mock_state = "OK"
vars.mock_message = "Sous contrôle"
import "service_life_noc_probe"
vars.life_noc_probe_type = "elapsed_time"
vars.life_noc_source_type = "manual_date"
vars.life_noc_source_value = "2026-03-13"
vars.life_noc_metric_unit = "days"
vars.life_noc_label = "revue-quotidienne-life-noc"
groups = [ "REVUE" ]
notes = "Revue quotidienne du tableau Life-NOC"
vars.life_noc_threshold_unknown_lt = "1"
vars.life_noc_threshold_ok_gte = "1"
vars.life_noc_threshold_warning_gte = "2"
vars.life_noc_threshold_critical_gte = "3"
vars.life_noc_on_error = "critical"
notes = "Vérifier le tableau de bord Life-NOC"
assign where host.name == "life-noc"
}
apply Service "revue-revue-hebdomadaire-priorites" {
import "service_echeance"
vars.date_echeance = "2026-03-08"
vars.mock_state = "OK"
vars.mock_message = "Sous contrôle"
import "service_life_noc_probe"
vars.life_noc_probe_type = "elapsed_time"
vars.life_noc_source_type = "manual_date"
vars.life_noc_source_value = "2026-03-10"
vars.life_noc_metric_unit = "days"
vars.life_noc_label = "revue-hebdomadaire-priorites"
groups = [ "REVUE" ]
notes = "Revue hebdomadaire des priorités personnelles et professionnelles"
vars.life_noc_threshold_unknown_lt = "5"
vars.life_noc_threshold_ok_gte = "5"
vars.life_noc_threshold_warning_gte = "7"
vars.life_noc_threshold_critical_gte = "10"
vars.life_noc_on_error = "critical"
notes = "Revoir les priorités de la semaine"
assign where host.name == "life-noc"
}

View file

@ -0,0 +1,4 @@
template Service "service_life_noc_probe" {
check_command = "check_life_noc_probe"
vars.life_noc_on_error = "critical"
}

View file

@ -11,6 +11,7 @@ SERVICEGROUPS_DIR = Path("icinga/servicegroups")
SERVICEGROUPS_FILE = SERVICEGROUPS_DIR / "life-noc.conf"
HOST_NAME = "life-noc"
SERVICE_TEMPLATE = "service_echeance"
PROBE_TEMPLATE = "service_life_noc_probe"
DEFAULT_MOCK_STATE = "OK"
DEFAULT_MOCK_MESSAGE = "Sous contrôle"
@ -73,7 +74,7 @@ def normalize_mock_state(value: str) -> str:
return state
def render_service(domain: str, group_name: str, item: dict) -> str:
def render_mock_service(domain: str, group_name: str, item: dict) -> str:
name = item["name"].strip()
date = str(item["date"]).strip()
notes = item.get("notes", "").strip()
@ -107,6 +108,85 @@ def render_service(domain: str, group_name: str, item: dict) -> str:
return "\n".join(lines)
def render_probe_service(domain: str, group_name: str, item: dict) -> str:
name = item["name"].strip()
notes = item.get("notes", "").strip()
notes_url = item.get("notes_url", "").strip()
action_url = item.get("action_url", "").strip()
probe = item["probe"]
probe_type = str(probe["type"]).strip()
source = probe["source"]
metric = probe["metric"]
thresholds = probe["thresholds"]
policy = probe.get("policy", {})
service_name = f"{domain}-{name}"
lines = [
f'apply Service "{icinga_escape(service_name)}" {{',
f' import "{PROBE_TEMPLATE}"',
f' vars.life_noc_probe_type = "{icinga_escape(probe_type)}"',
f' vars.life_noc_source_type = "{icinga_escape(str(source["type"]).strip())}"',
f' vars.life_noc_source_value = "{icinga_escape(str(source["value"]).strip())}"',
f' vars.life_noc_metric_unit = "{icinga_escape(str(metric["unit"]).strip())}"',
f' vars.life_noc_label = "{icinga_escape(name)}"',
f' groups = [ "{icinga_escape(group_name)}" ]',
]
if "unknown_lt" in thresholds:
lines.append(f' vars.life_noc_threshold_unknown_lt = "{icinga_escape(str(thresholds["unknown_lt"]))}"')
if "ok_gte" in thresholds:
lines.append(f' vars.life_noc_threshold_ok_gte = "{icinga_escape(str(thresholds["ok_gte"]))}"')
if "warning_gte" in thresholds:
lines.append(f' vars.life_noc_threshold_warning_gte = "{icinga_escape(str(thresholds["warning_gte"]))}"')
if "critical_gte" in thresholds:
lines.append(f' vars.life_noc_threshold_critical_gte = "{icinga_escape(str(thresholds["critical_gte"]))}"')
on_error = str(policy.get("on_error", "critical")).strip()
lines.append(f' vars.life_noc_on_error = "{icinga_escape(on_error)}"')
if notes:
lines.append(f' notes = "{icinga_escape(notes)}"')
if notes_url:
lines.append(f' notes_url = "{icinga_escape(notes_url)}"')
if action_url:
lines.append(f' action_url = "{icinga_escape(action_url)}"')
lines.append(f' assign where host.name == "{HOST_NAME}"')
lines.append("}")
lines.append("")
return "\n".join(lines)
def validate_probe(item: dict) -> None:
probe = item["probe"]
if not isinstance(probe, dict):
raise ValueError("probe doit être un objet")
if str(probe.get("type", "")).strip() != "elapsed_time":
raise ValueError("seul probe.type=elapsed_time est supporté en v1")
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")
if "value" not in source:
raise ValueError("probe.source.value est 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")
def render_servicegroup(group_name: str) -> str:
return "\n".join([
f'object ServiceGroup "{icinga_escape(group_name)}" {{',
@ -186,7 +266,11 @@ def main() -> int:
return 1
try:
content.append(render_service(domain, group_name, item))
if "probe" in item:
validate_probe(item)
content.append(render_probe_service(domain, group_name, item))
else:
content.append(render_mock_service(domain, group_name, item))
except ValueError as exc:
print(f"Erreur dans '{raw_domain}', entrée {idx}: {exc}", file=sys.stderr)
return 1