"""Inventory CRUD, host maintenance, datastore files, DVS.""" from __future__ import annotations from typing import Any from fastapi import APIRouter, Depends, Query, Response from app.db.pool import Database from app.dependencies import get_database from app.vsphere import inventory from app.vsphere.domain import content, inventory_ops from app.vsphere.errors import invalid_argument from app.vsphere.security.authz import require_privilege, require_read from app.vsphere.security.session import SessionInfo router = APIRouter(tags=["vSphere Inventory Ext"]) def _require_name(body: dict[str, Any]) -> str: name = body.get("name") if not name: raise invalid_argument("name is required") return str(name) @router.post("/api/vcenter/datacenter") async def create_dc( body: dict[str, Any], database: Database = Depends(get_database), _: SessionInfo = Depends(require_privilege("Datacenter.Create")), ) -> str: return await inventory_ops.create_datacenter( database, name=_require_name(body), folder=str(body.get("folder") or "group-d1") ) @router.delete("/api/vcenter/datacenter/{datacenter}") async def delete_dc( datacenter: str, database: Database = Depends(get_database), _: SessionInfo = Depends(require_privilege("Datacenter.Delete")), ) -> Response: await inventory_ops.delete_managed(database, datacenter) return Response(status_code=204) @router.post("/api/vcenter/cluster") async def create_cluster( body: dict[str, Any], database: Database = Depends(get_database), _: SessionInfo = Depends(require_privilege("Cluster.Create")), ) -> str: return await inventory_ops.create_cluster( database, name=_require_name(body), folder=str(body.get("folder") or "group-h23"), drs_enabled=bool((body.get("drs") or {}).get("enabled", True)), ha_enabled=bool((body.get("ha") or {}).get("enabled", True)), ) @router.delete("/api/vcenter/cluster/{cluster}") async def delete_cluster( cluster: str, database: Database = Depends(get_database), _: SessionInfo = Depends(require_privilege("Cluster.Delete")), ) -> Response: await inventory_ops.delete_managed(database, cluster) return Response(status_code=204) @router.post("/api/vcenter/folder") async def create_folder( body: dict[str, Any], database: Database = Depends(get_database), _: SessionInfo = Depends(require_privilege("Folder.Create")), ) -> str: spec = body.get("create_spec") if isinstance(body.get("create_spec"), dict) else body return await inventory_ops.create_folder( database, name=_require_name(spec), parent=str(spec.get("parent") or spec.get("folder") or "group-v23"), folder_type=str(spec.get("type") or "VIRTUAL_MACHINE"), ) @router.post("/api/vcenter/folder/{folder}") async def folder_action( folder: str, action: str = Query(...), body: dict[str, Any] | None = None, database: Database = Depends(get_database), _: SessionInfo = Depends(require_privilege("Folder.Rename")), ) -> Response: body = body or {} if action == "rename": await inventory_ops.rename_object(database, folder, _require_name(body)) elif action == "move": parent = body.get("parent") if not parent: raise invalid_argument("parent is required") await inventory_ops.move_object(database, folder, str(parent)) else: raise invalid_argument(f"unsupported action {action}") return Response(status_code=204) @router.delete("/api/vcenter/folder/{folder}") async def delete_folder( folder: str, database: Database = Depends(get_database), _: SessionInfo = Depends(require_privilege("Folder.Delete")), ) -> Response: await inventory_ops.delete_managed(database, folder) return Response(status_code=204) @router.post("/api/vcenter/resource-pool") async def create_rp( body: dict[str, Any], database: Database = Depends(get_database), _: SessionInfo = Depends(require_privilege("Resource.CreatePool")), ) -> str: return await inventory_ops.create_resource_pool( database, name=_require_name(body), parent=str(body.get("parent") or "resgroup-22") ) @router.delete("/api/vcenter/resource-pool/{resource_pool}") async def delete_rp( resource_pool: str, database: Database = Depends(get_database), _: SessionInfo = Depends(require_privilege("Resource.DeletePool")), ) -> Response: await inventory_ops.delete_managed(database, resource_pool) return Response(status_code=204) @router.post("/api/vcenter/host/{host}/maintenance") async def host_maintenance( host: str, action: str = Query(...), database: Database = Depends(get_database), _: SessionInfo = Depends(require_privilege("Host.Config.Maintenance")), ) -> dict[str, Any]: if action not in {"enter", "exit"}: raise invalid_argument("action must be enter|exit") return await inventory_ops.set_host_maintenance(database, host, enabled=(action == "enter")) @router.get("/api/vcenter/datastore/{datastore}/files") async def ds_files( datastore: str, database: Database = Depends(get_database), _: SessionInfo = Depends(require_read), ) -> list[dict[str, Any]]: return await content.list_datastore_files(database, datastore) @router.post("/api/vcenter/datastore/{datastore}/files") async def ds_put_file( datastore: str, body: dict[str, Any], database: Database = Depends(get_database), _: SessionInfo = Depends(require_privilege("Datastore.FileManagement")), ) -> Response: path = body.get("path") if not path: raise invalid_argument("path is required") await content.put_datastore_file( database, datastore, str(path), size=int(body.get("size") or 0), file_type=str(body.get("type") or "FILE"), ) return Response(status_code=204) @router.get("/api/vcenter/network/dvs") async def list_dvs( database: Database = Depends(get_database), _: SessionInfo = Depends(require_read) ) -> list[dict[str, Any]]: objects = await inventory.list_objects(database, type_name="VmwareDistributedVirtualSwitch") return [{"dvs": o.moid, "name": o.name} for o in objects] @router.post("/api/vcenter/network/dvs") async def create_dvs( body: dict[str, Any], database: Database = Depends(get_database), _: SessionInfo = Depends(require_privilege("Network.Assign")), ) -> str: return await inventory_ops.create_dvs( database, name=_require_name(body), folder=str(body.get("folder") or "group-n23") ) @router.post("/api/vcenter/network/dvpg") async def create_dvpg( body: dict[str, Any], database: Database = Depends(get_database), _: SessionInfo = Depends(require_privilege("Network.Assign")), ) -> str: dvs = body.get("dvs") if not dvs: raise invalid_argument("dvs is required") return await inventory_ops.create_dvpg( database, name=_require_name(body), dvs=str(dvs), vlan_id=int(body.get("vlan_id") or 0), )