kb-audit: fix парсер — ловит table-rows и раздел 🗑️ удалённых
This commit is contained in:
@@ -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 полностью совпадает с живой инфраструктурой", ""]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user