import logging from typing import List from fastapi import APIRouter, Depends from sqlalchemy.orm import Session from .. import models, schemas, proxmox_client as pve from ..database import get_db from ..deps import get_current_user, require_admin logger = logging.getLogger(__name__) router = APIRouter(prefix="/templates", tags=["templates"]) def _normalize_lxc_source(value: str | None) -> str | None: """Приводит источник LXC к корректному Proxmox volid. Принимает как полный volid: ``local:vztmpl/debian-13-standard_13.6-1_amd64.tar.zst`` так и короткое имя: ``debian-13-standard_13.6-1_amd64``. """ if not value: return None source = value.strip() if not source: return None # Если уже передан полный volid, оставляем его без изменений. if ":" in source: return source # Сначала пытаемся найти точный volid среди шаблонов Proxmox. # Это позволяет корректно обработать .tar.zst, .tar.gz и .tar.xz. try: for item in pve.list_lxc_templates(): if item.get("name") == source: return item["volid"] volid_name = item.get("volid", "").split("/")[-1] if volid_name: for extension in (".tar.zst", ".tar.gz", ".tar.xz"): if volid_name.removesuffix(extension) == source: return item["volid"] except Exception as exc: logger.warning("Не удалось сопоставить LXC-шаблон с Proxmox: %s", exc) # Fallback для локального storage: короткое имя превращаем в volid. for extension in (".tar.zst", ".tar.gz", ".tar.xz"): if source.endswith(extension): return f"local:vztmpl/{source}" return f"local:vztmpl/{source}.tar.zst" @router.get("", response_model=List[schemas.TemplateOut]) def list_templates(db: Session = Depends(get_db), _=Depends(get_current_user)): """Возвращает активные шаблоны, сохранённые в панели.""" return db.query(models.Template).filter(models.Template.is_active == True).all() # noqa: E712 @router.post("", response_model=schemas.TemplateOut) def create_template( payload: schemas.TemplateCreate, db: Session = Depends(get_db), _=Depends(require_admin), ): """Добавляет шаблон в каталог панели. Для LXC автоматически нормализует короткое имя архива в Proxmox volid. """ data = payload.model_dump() if data.get("guest_type") == models.GuestType.lxc: data["source_vmid"] = None data["source_template"] = _normalize_lxc_source(data.get("source_template")) if not data["source_template"]: from fastapi import HTTPException raise HTTPException(status_code=400, detail="Для LXC нужно указать архивный шаблон") else: data["source_template"] = None tpl = models.Template(**data) db.add(tpl) db.commit() db.refresh(tpl) return tpl @router.get("/from-proxmox") def list_proxmox_templates(_=Depends(require_admin)): """Возвращает VM-шаблоны и архивные LXC-шаблоны с Proxmox.""" try: vm_templates = pve.list_vm_templates() except Exception as exc: logger.warning("Ошибка получения VM-шаблонов из Proxmox: %s", exc) vm_templates = [] try: lxc_templates = pve.list_lxc_templates() except Exception as exc: logger.warning("Ошибка получения архивов LXC-шаблонов из Proxmox: %s", exc) lxc_templates = [] for template in lxc_templates: template["source_kind"] = "archive" return { "vm_templates": vm_templates, "lxc_templates": lxc_templates, } @router.delete("/{template_id}") def deactivate_template( template_id: int, db: Session = Depends(get_db), _=Depends(require_admin) ): """Деактивирует шаблон в каталоге панели.""" tpl = db.query(models.Template).filter(models.Template.id == template_id).first() if tpl: tpl.is_active = False db.commit() return {"ok": True}