Corrige l’intégration Icinga Web 2 et le transport de commandes
This commit is contained in:
parent
f865013a54
commit
3ea21ef27e
10 changed files with 247 additions and 19 deletions
|
|
@ -48,6 +48,14 @@
|
||||||
- validate icinga2
|
- validate icinga2
|
||||||
- restart 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
|
- name: enable and start icinga2
|
||||||
ansible.builtin.service:
|
ansible.builtin.service:
|
||||||
name: icinga2
|
name: icinga2
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
[icinga2]
|
[icinga2]
|
||||||
transport = "local"
|
transport = "local"
|
||||||
path = "{{ life_noc_icinga2_command_pipe }}"
|
path = "/run/icinga2/cmd/icinga2.cmd"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
[global]
|
[global]
|
||||||
show_stacktraces = "0"
|
show_stacktraces = "0"
|
||||||
config_backend = "ini"
|
config_backend = "db"
|
||||||
|
config_resource = "icingaweb2"
|
||||||
|
|
||||||
[logging]
|
[logging]
|
||||||
log = "syslog"
|
log = "syslog"
|
||||||
|
|
|
||||||
|
|
@ -187,6 +187,18 @@
|
||||||
- validate icinga
|
- validate icinga
|
||||||
- reload 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
|
- name: ensure bpm destination directory exists
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
path: "{{ life_noc_bpm_destination_dir }}"
|
path: "{{ life_noc_bpm_destination_dir }}"
|
||||||
|
|
|
||||||
61
checks/check_life_noc_probe.py
Normal file
61
checks/check_life_noc_probe.py
Normal 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())
|
||||||
38
domains.yaml
38
domains.yaml
|
|
@ -2,12 +2,40 @@ domains:
|
||||||
|
|
||||||
revue:
|
revue:
|
||||||
- name: revue-quotidienne-life-noc
|
- name: revue-quotidienne-life-noc
|
||||||
date: 2026-03-07
|
date: "2026-03-13"
|
||||||
notes: Revue quotidienne du tableau Life-NOC
|
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
|
- name: revue-hebdomadaire-priorites
|
||||||
date: 2026-03-08
|
date: "2026-03-10"
|
||||||
notes: Revue hebdomadaire des priorités personnelles et professionnelles
|
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
|
- name: revue-mensuelle-systeme-vie
|
||||||
date: 2026-04-01
|
date: 2026-04-01
|
||||||
|
|
|
||||||
16
icinga/commands/check_life_noc_probe.conf
Normal file
16
icinga/commands/check_life_noc_probe.conf
Normal 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$"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,22 +5,36 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
apply Service "revue-revue-quotidienne-life-noc" {
|
apply Service "revue-revue-quotidienne-life-noc" {
|
||||||
import "service_echeance"
|
import "service_life_noc_probe"
|
||||||
vars.date_echeance = "2026-03-07"
|
vars.life_noc_probe_type = "elapsed_time"
|
||||||
vars.mock_state = "OK"
|
vars.life_noc_source_type = "manual_date"
|
||||||
vars.mock_message = "Sous contrôle"
|
vars.life_noc_source_value = "2026-03-13"
|
||||||
|
vars.life_noc_metric_unit = "days"
|
||||||
|
vars.life_noc_label = "revue-quotidienne-life-noc"
|
||||||
groups = [ "REVUE" ]
|
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"
|
assign where host.name == "life-noc"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply Service "revue-revue-hebdomadaire-priorites" {
|
apply Service "revue-revue-hebdomadaire-priorites" {
|
||||||
import "service_echeance"
|
import "service_life_noc_probe"
|
||||||
vars.date_echeance = "2026-03-08"
|
vars.life_noc_probe_type = "elapsed_time"
|
||||||
vars.mock_state = "OK"
|
vars.life_noc_source_type = "manual_date"
|
||||||
vars.mock_message = "Sous contrôle"
|
vars.life_noc_source_value = "2026-03-10"
|
||||||
|
vars.life_noc_metric_unit = "days"
|
||||||
|
vars.life_noc_label = "revue-hebdomadaire-priorites"
|
||||||
groups = [ "REVUE" ]
|
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"
|
assign where host.name == "life-noc"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
4
icinga/templates/service_life_noc_probe.conf
Normal file
4
icinga/templates/service_life_noc_probe.conf
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
template Service "service_life_noc_probe" {
|
||||||
|
check_command = "check_life_noc_probe"
|
||||||
|
vars.life_noc_on_error = "critical"
|
||||||
|
}
|
||||||
|
|
@ -11,6 +11,7 @@ SERVICEGROUPS_DIR = Path("icinga/servicegroups")
|
||||||
SERVICEGROUPS_FILE = SERVICEGROUPS_DIR / "life-noc.conf"
|
SERVICEGROUPS_FILE = SERVICEGROUPS_DIR / "life-noc.conf"
|
||||||
HOST_NAME = "life-noc"
|
HOST_NAME = "life-noc"
|
||||||
SERVICE_TEMPLATE = "service_echeance"
|
SERVICE_TEMPLATE = "service_echeance"
|
||||||
|
PROBE_TEMPLATE = "service_life_noc_probe"
|
||||||
DEFAULT_MOCK_STATE = "OK"
|
DEFAULT_MOCK_STATE = "OK"
|
||||||
DEFAULT_MOCK_MESSAGE = "Sous contrôle"
|
DEFAULT_MOCK_MESSAGE = "Sous contrôle"
|
||||||
|
|
||||||
|
|
@ -73,7 +74,7 @@ def normalize_mock_state(value: str) -> str:
|
||||||
return state
|
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()
|
name = item["name"].strip()
|
||||||
date = str(item["date"]).strip()
|
date = str(item["date"]).strip()
|
||||||
notes = item.get("notes", "").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)
|
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:
|
def render_servicegroup(group_name: str) -> str:
|
||||||
return "\n".join([
|
return "\n".join([
|
||||||
f'object ServiceGroup "{icinga_escape(group_name)}" {{',
|
f'object ServiceGroup "{icinga_escape(group_name)}" {{',
|
||||||
|
|
@ -186,7 +266,11 @@ def main() -> int:
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
try:
|
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:
|
except ValueError as exc:
|
||||||
print(f"Erreur dans '{raw_domain}', entrée {idx}: {exc}", file=sys.stderr)
|
print(f"Erreur dans '{raw_domain}', entrée {idx}: {exc}", file=sys.stderr)
|
||||||
return 1
|
return 1
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue