import logging from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy import select from sqlalchemy.orm import Session from .. import models, proxmox_client as pve, schemas from ..config import settings from ..database import get_db from ..deps import get_current_user from ..proxmox_actions import delete_guest logger = logging.getLogger(__name__) router = APIRouter(prefix="/instances", tags=["instances"]) @router.get("", response_model=list[schemas.InstanceOut]) def list_instances(db: Session = Depends(get_db), user: models.User = Depends(get_current_user)): """Клиент видит только свои инстансы, администратор — все.""" query = select(models.Instance).where(models.Instance.status != models.InstanceStatus.deleted) if user.role != models.UserRole.admin: query = query.where(models.Instance.owner_id == user.id) return list(db.scalars(query.order_by(models.Instance.created_at.desc()))) def _save_error(db: Session, instance: models.Instance, exc: Exception) -> None: """Помечает инстанс ошибочным без сохранения деталей Proxmox пользователю.""" instance.status = models.InstanceStatus.error db.commit() logger.exception("Ошибка провижининга instance_id=%s: %s", instance.id, exc) @router.post("", response_model=schemas.InstanceCreateOut, status_code=status.HTTP_201_CREATED) def create_instance(payload: schemas.InstanceCreate, db: Session = Depends(get_db), user: models.User = Depends(get_current_user)): """Создаёт и запускает VM/LXC из активного шаблона.""" template = db.scalar(select(models.Template).where( models.Template.id == payload.template_id, models.Template.is_active.is_(True), )) if template is None: raise HTTPException(status_code=404, detail="Активный шаблон не найден") try: vmid = pve.next_vmid() except Exception as exc: logger.exception("Не удалось получить VMID из Proxmox: %s", exc) raise HTTPException(status_code=502, detail="Proxmox недоступен") instance = models.Instance( name=payload.name, vmid=vmid, node=settings.pve_node, guest_type=template.guest_type, status=models.InstanceStatus.creating, owner_id=user.id, template_id=template.id, ) db.add(instance) db.commit() db.refresh(instance) try: if template.guest_type == models.GuestType.vm: pve.provision_vm( source_vmid=template.source_vmid, vmid=vmid, name=payload.name, cores=template.cores, memory_mb=template.memory_mb, disk_gb=template.disk_gb, username=payload.username, password=payload.password, ) else: pve.provision_lxc( source_template=template.source_template, vmid=vmid, name=payload.name, cores=template.cores, memory_mb=template.memory_mb, disk_gb=template.disk_gb, password=payload.password, ) except Exception as exc: _save_error(db, instance, exc) raise HTTPException(status_code=502, detail="Не удалось создать инстанс в Proxmox") instance.status = models.InstanceStatus.running db.commit() db.refresh(instance) return schemas.InstanceCreateOut( **schemas.InstanceOut.model_validate(instance).model_dump(), initial_password=payload.password, ) @router.delete("/{instance_id}") def delete_instance(instance_id: int, db: Session = Depends(get_db), user: models.User = Depends(get_current_user)): """Удаляет принадлежащую пользователю VM/LXC и отмечает запись deleted.""" instance = db.get(models.Instance, instance_id) if instance is None or instance.status == models.InstanceStatus.deleted: raise HTTPException(status_code=404, detail="Инстанс не найден") if user.role != models.UserRole.admin and instance.owner_id != user.id: raise HTTPException(status_code=404, detail="Инстанс не найден") if instance.vmid is None: raise HTTPException(status_code=409, detail="У инстанса отсутствует VMID") instance.status = models.InstanceStatus.deleting db.commit() try: delete_guest(instance.guest_type.value, instance.vmid) except Exception as exc: instance.status = models.InstanceStatus.error db.commit() logger.exception("Ошибка удаления instance_id=%s: %s", instance.id, exc) raise HTTPException(status_code=502, detail="Не удалось удалить инстанс в Proxmox") instance.status = models.InstanceStatus.deleted db.commit() return {"ok": True}