kb-audit: fix парсер — ловит table-rows и раздел 🗑️ удалённых

This commit is contained in:
dttb
2026-04-18 00:24:20 +03:00
parent 6368738ade
commit c8cf27df08

View File

@@ -53,20 +53,38 @@ def parse_live():
def parse_inventory(path: Path): def parse_inventory(path: Path):
"""Парсит inventory-файл. Извлекает упомянутые VMID + связанный контекст.""" """Парсит inventory-файл. Извлекает упомянутые VMID + связанный контекст.
Ловит два формата:
1. "LXC 132" / "VM 250" — в заголовках и тексте
2. Table rows: | 132 | debian | ... | — в таблицах
"""
text = path.read_text() text = path.read_text()
found = {} found = {}
# Ищем все упоминания "LXC NNN" или "VM NNN" в заголовках и таблицах
for m in re.finditer(r"(?:LXC|VM)\s+(\d{2,4})\b", text): def add(vmid: str, idx: int):
vmid = m.group(1)
if vmid not in found: if vmid not in found:
# контекст: 80 символов вокруг start = max(0, idx - 20)
start = max(0, m.start() - 20) end = min(len(text), idx + 80)
end = min(len(text), m.end() + 60)
found[vmid] = text[start:end].replace("\n", " ").strip() found[vmid] = text[start:end].replace("\n", " ").strip()
for m in re.finditer(r"(?:LXC|VM)\s+(\d{2,4})\b", text):
add(m.group(1), m.start())
# table rows: строка начинается с `| NNN |` (игнорируем header-row с тире)
for m in re.finditer(r"^\s*\|\s*(\d{2,4})\s*\|", text, re.MULTILINE):
add(m.group(1), m.start())
return found return found
def find_deleted_section(path: Path) -> set:
"""Ищет VMID в секции '## 🗑️ Удалённые' чтобы не флагать их как missing."""
text = path.read_text()
# блок между '🗑️ Удалённые' и следующим '##'
m = re.search(r"##\s*🗑[^\n]*Удал[^\n]*\n(.*?)(?=\n##|\Z)", text, re.DOTALL)
if not m:
return set()
return set(re.findall(r"\|\s*(\d{2,4})\s*\|", m.group(1)))
def compare(live: dict, inventory: dict): def compare(live: dict, inventory: dict):
live_ids = set(live.keys()) live_ids = set(live.keys())
inv_ids = set(inventory.keys()) inv_ids = set(inventory.keys())
@@ -84,7 +102,13 @@ def main():
live = parse_live() live = parse_live()
inventory = parse_inventory(INVENTORY) inventory = parse_inventory(INVENTORY)
only_live, only_inv, common = compare(live, inventory) deleted = find_deleted_section(INVENTORY)
only_live, only_inv_raw, common = compare(live, inventory)
# разделяем "в inventory но не в live" на 2 группы:
# - known-deleted (есть в секции "🗑️ Удалённые") — это ок
# - truly missing (нет и в live, и не в секции deleted) — проблема
only_inv = [v for v in only_inv_raw if v not in deleted]
known_deleted = [v for v in only_inv_raw if v in deleted]
lines = [ lines = [
"---", "---",
@@ -100,7 +124,8 @@ def main():
"", "",
f"- Живых гостей Proxmox: **{len(live)}**", f"- Живых гостей Proxmox: **{len(live)}**",
f"- Упомянуто в inventory: **{len(inventory)}**", f"- Упомянуто в inventory: **{len(inventory)}**",
f"- В обоих: {len(common)} / только в live: {len(only_live)} / только в inventory: {len(only_inv)}", f"- В обоих: {len(common)} / только в live: {len(only_live)} / отсутствуют в live: {len(only_inv)}",
f"- Известны как удалённые: {len(known_deleted)} (в `## 🗑️ Удалённые`)",
"", "",
] ]
@@ -113,13 +138,16 @@ def main():
lines += [""] lines += [""]
if only_inv: if only_inv:
lines += ["## 🗑 В inventory есть, в Proxmox НЕТ (удалён? переименован?)", ""] lines += ["## В inventory есть, в Proxmox НЕТ (не в секции 🗑️ — проверить вручную)", ""]
lines += ["| VMID | Контекст из inventory |", "|---|---|"] lines += ["| VMID | Контекст из inventory |", "|---|---|"]
for vmid in only_inv: for vmid in only_inv:
ctx = inventory[vmid][:100] ctx = inventory[vmid][:100]
lines += [f"| {vmid} | `...{ctx}...` |"] lines += [f"| {vmid} | `...{ctx}...` |"]
lines += [""] lines += [""]
if known_deleted:
lines += [f"## ✓ Удалённые хосты (задокументированы): {', '.join(sorted(known_deleted, key=int))}", ""]
if not only_live and not only_inv: if not only_live and not only_inv:
lines += ["## ✓ Inventory полностью совпадает с живой инфраструктурой", ""] lines += ["## ✓ Inventory полностью совпадает с живой инфраструктурой", ""]