Build Proxmox visualizer app
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
import asyncio
|
||||
import re
|
||||
from collections import defaultdict
|
||||
from typing import Dict, Iterable, List, Tuple
|
||||
|
||||
from pydantic import ValidationError
|
||||
|
||||
from app.models.schemas import (
|
||||
DuplicateGroup,
|
||||
ProxmoxServerConfig,
|
||||
ScanResponse,
|
||||
ServerScanStatus,
|
||||
VmRecord,
|
||||
)
|
||||
from app.services.proxmox_api import ProxmoxClient
|
||||
|
||||
|
||||
def parse_server_configs(raw_servers: Iterable[dict]) -> Tuple[List[ProxmoxServerConfig], List[str]]:
|
||||
servers: List[ProxmoxServerConfig] = []
|
||||
errors: List[str] = []
|
||||
|
||||
for index, raw_server in enumerate(raw_servers, start=1):
|
||||
try:
|
||||
servers.append(ProxmoxServerConfig.model_validate(raw_server))
|
||||
except ValidationError as exc:
|
||||
errors.append(f"serveur #{index}: {exc.errors()[0]['msg']}")
|
||||
|
||||
return servers, errors
|
||||
|
||||
|
||||
async def scan_servers(raw_servers: Iterable[dict]) -> ScanResponse:
|
||||
servers, config_errors = parse_server_configs(raw_servers)
|
||||
if not servers:
|
||||
return ScanResponse(
|
||||
servers=[
|
||||
ServerScanStatus(
|
||||
name="configuration",
|
||||
url="",
|
||||
ok=False,
|
||||
errors=config_errors or ["aucun serveur Proxmox configure"],
|
||||
)
|
||||
],
|
||||
items=[],
|
||||
duplicates=[],
|
||||
)
|
||||
|
||||
results = await asyncio.gather(*[_scan_one(server) for server in servers])
|
||||
statuses: List[ServerScanStatus] = []
|
||||
items: List[VmRecord] = []
|
||||
|
||||
if config_errors:
|
||||
statuses.append(
|
||||
ServerScanStatus(
|
||||
name="configuration",
|
||||
url="",
|
||||
ok=False,
|
||||
errors=config_errors,
|
||||
)
|
||||
)
|
||||
|
||||
for status, server_items in results:
|
||||
statuses.append(status)
|
||||
items.extend(server_items)
|
||||
|
||||
return ScanResponse(
|
||||
servers=statuses,
|
||||
items=sorted(items, key=lambda vm: (vm.server, vm.node, vm.vmid)),
|
||||
duplicates=find_duplicates(items),
|
||||
)
|
||||
|
||||
|
||||
async def _scan_one(server: ProxmoxServerConfig) -> tuple[ServerScanStatus, List[VmRecord]]:
|
||||
async with ProxmoxClient(server) as client:
|
||||
return await client.scan()
|
||||
|
||||
|
||||
def find_duplicates(items: List[VmRecord]) -> List[DuplicateGroup]:
|
||||
groups: List[DuplicateGroup] = []
|
||||
seen_item_sets = set()
|
||||
|
||||
by_metadata: Dict[int, List[VmRecord]] = defaultdict(list)
|
||||
for item in items:
|
||||
if item.duplicate_id is not None:
|
||||
by_metadata[item.duplicate_id].append(item)
|
||||
|
||||
for duplicate_id, members in sorted(by_metadata.items()):
|
||||
if len(members) < 2:
|
||||
continue
|
||||
groups.append(
|
||||
DuplicateGroup(
|
||||
id=f"metadata:{duplicate_id}",
|
||||
label=f"duplicate_id={duplicate_id}",
|
||||
reason="metadata",
|
||||
count=len(members),
|
||||
items=members,
|
||||
)
|
||||
)
|
||||
seen_item_sets.add(frozenset(item.id for item in members))
|
||||
|
||||
by_name: Dict[str, List[VmRecord]] = defaultdict(list)
|
||||
for item in items:
|
||||
normalized = normalize_name(item.name)
|
||||
if normalized:
|
||||
by_name[normalized].append(item)
|
||||
|
||||
for normalized, members in sorted(by_name.items()):
|
||||
if len(members) < 2:
|
||||
continue
|
||||
item_set = frozenset(item.id for item in members)
|
||||
if item_set in seen_item_sets:
|
||||
continue
|
||||
groups.append(
|
||||
DuplicateGroup(
|
||||
id=f"name:{normalized}",
|
||||
label=members[0].name,
|
||||
reason="name",
|
||||
count=len(members),
|
||||
items=members,
|
||||
)
|
||||
)
|
||||
|
||||
return groups
|
||||
|
||||
|
||||
def normalize_name(name: str) -> str:
|
||||
normalized = name.strip().lower()
|
||||
normalized = re.sub(r"\s+", " ", normalized)
|
||||
return normalized
|
||||
Reference in New Issue
Block a user