build / Garde-fou (pull_request) Failing after 7s
build / Images Harbor (catalog-sync, Dockerfile.catalog-sync) (pull_request) Skipped
build / Images Harbor (web, Dockerfile) (pull_request) Skipped
build / Garde-fou (push) Failing after 8s
build / Images Harbor (catalog-sync, Dockerfile.catalog-sync) (push) Skipped
build / Images Harbor (web, Dockerfile) (push) Skipped
183 lines
5.8 KiB
Python
183 lines
5.8 KiB
Python
"""Chargement du catalogue de packs de supervision.
|
|
|
|
Un pack est un fichier YAML decrivant, pour une technologie donnee :
|
|
- un modele d'hote (host template) et ses macros,
|
|
- une liste de services, chacun portant sa ligne de commande et ses macros.
|
|
|
|
Le catalogue est volontairement en donnees et non en code : ajouter un pack
|
|
revient a deposer un fichier YAML dans catalog/<categorie>/.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from dataclasses import dataclass, field
|
|
from typing import Any
|
|
|
|
import yaml
|
|
|
|
CATEGORIES = {
|
|
"system": "Systemes d'exploitation",
|
|
"hardware": "Materiel",
|
|
"application": "Applications",
|
|
}
|
|
|
|
|
|
@dataclass
|
|
class Macro:
|
|
name: str
|
|
value: str = ""
|
|
description: str = ""
|
|
is_password: bool = False
|
|
|
|
@classmethod
|
|
def from_dict(cls, raw: dict[str, Any]) -> "Macro":
|
|
return cls(
|
|
name=str(raw["name"]).strip().upper(),
|
|
value="" if raw.get("value") is None else str(raw.get("value")),
|
|
description=str(raw.get("description", "")),
|
|
is_password=bool(raw.get("is_password", False)),
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class Service:
|
|
name: str
|
|
alias: str
|
|
description: str
|
|
line: str
|
|
macros: list[Macro] = field(default_factory=list)
|
|
enabled_by_default: bool = True
|
|
check_interval: int | None = None
|
|
retry_interval: int | None = None
|
|
max_attempts: int | None = None
|
|
|
|
@classmethod
|
|
def from_dict(cls, raw: dict[str, Any]) -> "Service":
|
|
return cls(
|
|
name=str(raw["name"]).strip(),
|
|
alias=str(raw.get("alias") or raw["name"]).strip(),
|
|
description=str(raw.get("description", "")),
|
|
line=str(raw["line"]).strip(),
|
|
macros=[Macro.from_dict(m) for m in raw.get("macros") or []],
|
|
enabled_by_default=bool(raw.get("default", True)),
|
|
check_interval=raw.get("check_interval"),
|
|
retry_interval=raw.get("retry_interval"),
|
|
max_attempts=raw.get("max_attempts"),
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class HostTemplate:
|
|
name: str
|
|
alias: str
|
|
parents: list[str] = field(default_factory=list)
|
|
check_command: str = ""
|
|
check_command_args: str = ""
|
|
macros: list[Macro] = field(default_factory=list)
|
|
|
|
@classmethod
|
|
def from_dict(cls, raw: dict[str, Any]) -> "HostTemplate":
|
|
return cls(
|
|
name=str(raw["name"]).strip(),
|
|
alias=str(raw.get("alias") or raw["name"]).strip(),
|
|
parents=[str(p).strip() for p in raw.get("parents") or []],
|
|
check_command=str(raw.get("check_command", "")),
|
|
check_command_args=str(raw.get("check_command_args", "")),
|
|
macros=[Macro.from_dict(m) for m in raw.get("macros") or []],
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class Pack:
|
|
id: str
|
|
name: str
|
|
category: str
|
|
description: str
|
|
plugin: str
|
|
status: str
|
|
host_template: HostTemplate
|
|
services: list[Service]
|
|
prerequis: dict[str, Any] = field(default_factory=dict)
|
|
source_file: str = ""
|
|
|
|
@property
|
|
def prefix(self) -> str:
|
|
"""Racine des noms de modeles de service, deduite du modele d'hote.
|
|
|
|
OS-Linux-SNMP-custom -> OS-Linux-SNMP
|
|
"""
|
|
base = self.host_template.name
|
|
return base[: -len("-custom")] if base.endswith("-custom") else base
|
|
|
|
@property
|
|
def category_label(self) -> str:
|
|
return CATEGORIES.get(self.category, self.category)
|
|
|
|
def service(self, name: str) -> Service | None:
|
|
for svc in self.services:
|
|
if svc.name == name:
|
|
return svc
|
|
return None
|
|
|
|
@classmethod
|
|
def from_dict(cls, raw: dict[str, Any], source_file: str = "") -> "Pack":
|
|
return cls(
|
|
id=str(raw["id"]).strip(),
|
|
name=str(raw["name"]).strip(),
|
|
category=str(raw.get("category", "system")).strip(),
|
|
description=str(raw.get("description", "")),
|
|
plugin=str(raw.get("plugin", "")),
|
|
status=str(raw.get("status", "to-verify")),
|
|
host_template=HostTemplate.from_dict(raw["host_template"]),
|
|
services=[Service.from_dict(s) for s in raw.get("services") or []],
|
|
prerequis=raw.get("prerequis") or {},
|
|
source_file=source_file,
|
|
)
|
|
|
|
|
|
class Catalog:
|
|
def __init__(self, root: str):
|
|
self.root = root
|
|
self.packs: dict[str, Pack] = {}
|
|
self.errors: list[str] = []
|
|
self.load()
|
|
|
|
def load(self) -> None:
|
|
self.packs = {}
|
|
self.errors = []
|
|
for dirpath, _dirnames, filenames in os.walk(self.root):
|
|
for filename in sorted(filenames):
|
|
if not filename.endswith((".yml", ".yaml")):
|
|
continue
|
|
path = os.path.join(dirpath, filename)
|
|
try:
|
|
with open(path, encoding="utf-8") as handle:
|
|
raw = yaml.safe_load(handle)
|
|
pack = Pack.from_dict(raw, source_file=path)
|
|
except Exception as exc: # noqa: BLE001 - remonte tel quel a l'UI
|
|
self.errors.append(f"{path} : {exc}")
|
|
continue
|
|
if pack.id in self.packs:
|
|
self.errors.append(
|
|
f"{path} : identifiant deja utilise par "
|
|
f"{self.packs[pack.id].source_file}"
|
|
)
|
|
continue
|
|
self.packs[pack.id] = pack
|
|
|
|
def get(self, pack_id: str) -> Pack | None:
|
|
return self.packs.get(pack_id)
|
|
|
|
def by_category(self) -> dict[str, list[Pack]]:
|
|
grouped: dict[str, list[Pack]] = {key: [] for key in CATEGORIES}
|
|
for pack in self.packs.values():
|
|
grouped.setdefault(pack.category, []).append(pack)
|
|
for packs in grouped.values():
|
|
packs.sort(key=lambda p: p.name.lower())
|
|
return grouped
|
|
|
|
@property
|
|
def service_count(self) -> int:
|
|
return sum(len(p.services) for p in self.packs.values())
|