Каталог шаблонов переведён на ручной импорт как в исходной панели

This commit is contained in:
2026-08-09 17:00:54 +03:00
parent 45c2addac4
commit 55a5f6e67f
+3 -87
View File
@@ -1,6 +1,4 @@
import logging import logging
import re
from pathlib import PurePosixPath
from fastapi import APIRouter, Depends, HTTPException, status from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy import select from sqlalchemy import select
@@ -16,79 +14,15 @@ logger = logging.getLogger(__name__)
router = APIRouter(prefix="/templates", tags=["templates"]) router = APIRouter(prefix="/templates", tags=["templates"])
def _safe_name(value: str, suffix: str = "") -> str:
"""Преобразует имя Proxmox в допустимое имя шаблона панели."""
name = re.sub(r"[^a-zA-Z0-9-]+", "-", value.lower()).strip("-") or "template"
return f"{name}-{suffix}"[:64].strip("-")
def _unique_name(db: Session, desired: str, source_key: str) -> str:
"""Возвращает свободное имя, не ломая существующий каталог."""
existing = db.scalar(select(models.Template).where(models.Template.name == desired))
if existing is None or source_key in {str(existing.source_vmid), existing.source_template}:
return desired
for index in range(1, 100):
candidate = f"{desired}-{index}"[:64]
if db.scalar(select(models.Template).where(models.Template.name == candidate)) is None:
return candidate
raise RuntimeError("Не удалось подобрать уникальное имя шаблона")
def _import_discovered(db: Session, discovered: dict) -> list[models.Template]:
"""Обновляет каталог панели данными Proxmox без конфликтов имён."""
imported: list[models.Template] = []
for item in discovered.get("vm_templates", []):
source_vmid = item["vmid"]
template = db.scalar(select(models.Template).where(
models.Template.source_vmid == source_vmid,
models.Template.guest_type == models.GuestType.vm,
))
if template is None:
name = _unique_name(db, _safe_name(item["name"], str(source_vmid)), str(source_vmid))
template = models.Template(name=name, guest_type=models.GuestType.vm, source_vmid=source_vmid, description=f"Импортировано из Proxmox: {item['name']}")
db.add(template)
template.cores = item.get("cores", 1)
template.memory_mb = item.get("memory_mb", 1024)
template.disk_gb = template.disk_gb or 10
template.is_active = True
imported.append(template)
for item in discovered.get("lxc_templates", []):
source = item.get("volid", "")
template = db.scalar(select(models.Template).where(
models.Template.guest_type == models.GuestType.lxc,
models.Template.source_template == source,
))
if template is None:
base_name = PurePosixPath(item.get("name", "lxc")).name
base_name = re.sub(r"\.tar\.(zst|gz|xz)$", "", base_name)
suffix = f"ct{item['vmid']}" if item.get("source_kind") == "vmid" else ""
desired = _safe_name(base_name, suffix)
template = models.Template(name=_unique_name(db, desired, source), guest_type=models.GuestType.lxc, source_template=source, description=f"Импортировано из Proxmox: {item.get('name', source)}")
db.add(template)
template.cores = item.get("cores", template.cores or 1)
template.memory_mb = item.get("memory_mb", template.memory_mb or 1024)
template.disk_gb = template.disk_gb or 10
template.is_active = True
imported.append(template)
return imported
@router.get("", response_model=list[schemas.TemplateOut]) @router.get("", response_model=list[schemas.TemplateOut])
def list_templates(db: Session = Depends(get_db), _: models.User = Depends(get_current_user)): def list_templates(db: Session = Depends(get_db), _: models.User = Depends(get_current_user)):
"""Автоматически обновляет каталог из Proxmox и возвращает активные шаблоны.""" """Возвращает только уже импортированные шаблоны панели."""
try:
_import_discovered(db, discover_templates())
db.commit()
except Exception as error:
db.rollback()
logger.warning("Не удалось автоматически обновить шаблоны из Proxmox: %s", error)
return list(db.scalars(select(models.Template).where(models.Template.is_active.is_(True)).order_by(models.Template.created_at.desc()))) return list(db.scalars(select(models.Template).where(models.Template.is_active.is_(True)).order_by(models.Template.created_at.desc())))
@router.get("/from-proxmox") @router.get("/from-proxmox")
def list_proxmox_templates(_: models.User = Depends(require_admin)): def list_proxmox_templates(_: models.User = Depends(require_admin)):
"""Возвращает найденные на Proxmox VM- и CT-шаблоны.""" """Ищет шаблоны непосредственно на Proxmox; ничего не записывает в БД."""
try: try:
return discover_templates() return discover_templates()
except Exception: except Exception:
@@ -96,27 +30,9 @@ def list_proxmox_templates(_: models.User = Depends(require_admin)):
raise HTTPException(status_code=502, detail="Proxmox временно недоступен") raise HTTPException(status_code=502, detail="Proxmox временно недоступен")
@router.post("/sync", response_model=list[schemas.TemplateOut])
def sync_proxmox_templates(db: Session = Depends(get_db), _: models.User = Depends(require_admin)):
"""Принудительно обновляет каталог; повторный запуск безопасен."""
try:
imported = _import_discovered(db, discover_templates())
db.commit()
except IntegrityError:
db.rollback()
raise HTTPException(status_code=409, detail="Не удалось сохранить шаблоны")
except Exception:
db.rollback()
logger.exception("Не удалось синхронизировать шаблоны Proxmox")
raise HTTPException(status_code=502, detail="Proxmox временно недоступен")
for template in imported:
db.refresh(template)
return imported
@router.post("", response_model=schemas.TemplateOut, status_code=status.HTTP_201_CREATED) @router.post("", response_model=schemas.TemplateOut, status_code=status.HTTP_201_CREATED)
def create_template(payload: schemas.TemplateCreate, db: Session = Depends(get_db), _: models.User = Depends(require_admin)): def create_template(payload: schemas.TemplateCreate, db: Session = Depends(get_db), _: models.User = Depends(require_admin)):
"""Создаёт шаблон вручную.""" """Импортирует или создаёт один шаблон в каталоге панели."""
template = models.Template(**payload.model_dump()) template = models.Template(**payload.model_dump())
db.add(template) db.add(template)
try: try: