"""Content library, OVF deploy, storage policies, privileges.""" 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.domain import api_state, content from app.vsphere.security.authz import require_privilege, require_read from app.vsphere.security.session import SessionInfo router = APIRouter(tags=["vSphere Content"]) @router.get("/api/content/library") async def list_libraries( database: Database = Depends(get_database), _: SessionInfo = Depends(require_read) ) -> list[str]: return [item["id"] for item in await content.list_libraries(database)] @router.get("/api/content/local-library") async def list_local_libraries( database: Database = Depends(get_database), _: SessionInfo = Depends(require_read) ) -> list[str]: return [ item["id"] for item in await content.list_libraries(database) if str(item.get("type") or "LOCAL").upper() == "LOCAL" ] @router.post("/api/content/local-library") async def create_library( body: dict[str, Any], database: Database = Depends(get_database), _: SessionInfo = Depends(require_privilege("ContentLibrary.CreateLocalLibrary")), ) -> str: from app.vsphere.errors import invalid_argument spec = body.get("create_spec") or body name = spec.get("name") if isinstance(spec, dict) else None if not name: raise invalid_argument("create_spec.name is required") return await content.create_library( database, name=str(name), description=str(spec.get("description") or "") ) # Static /library/item* paths must win over /library/{library_id}. @router.get("/api/content/library/item") async def list_items( library_id: str = Query(...), database: Database = Depends(get_database), _: SessionInfo = Depends(require_read), ) -> list[str]: return [item["id"] for item in await content.list_library_items(database, library_id)] @router.post("/api/content/library/item") async def create_item( body: dict[str, Any], database: Database = Depends(get_database), _: SessionInfo = Depends(require_privilege("ContentLibrary.AddLibraryItem")), ) -> str: from app.vsphere.errors import invalid_argument spec = body.get("create_spec") or body if not isinstance(spec, dict): raise invalid_argument("create_spec is required") library_id = spec.get("library_id") name = spec.get("name") if not library_id: raise invalid_argument("create_spec.library_id is required") if not name: raise invalid_argument("create_spec.name is required") return await content.create_library_item( database, library_id=str(library_id), name=str(name), item_type=str(spec.get("type") or "ovf"), description=str(spec.get("description") or ""), ) @router.post("/api/content/library/item/update-session") async def create_update_session( body: dict[str, Any], database: Database = Depends(get_database), _: SessionInfo = Depends(require_privilege("ContentLibrary.AddLibraryItem")), ) -> str: create_spec = body.get("create_spec") or body library_item_id = str(create_spec.get("library_item_id") or create_spec.get("item_id") or "") if not library_item_id: from app.vsphere.errors import invalid_argument raise invalid_argument("create_spec.library_item_id is required") return await content.create_update_session(database, library_item_id=library_item_id) @router.get("/api/content/library/item/update-session/{session_id}") async def get_update_session( session_id: str, database: Database = Depends(get_database), _: SessionInfo = Depends(require_read), ) -> dict[str, Any]: return await content.get_update_session(database, session_id) @router.post("/api/content/library/item/update-session/{session_id}/file") async def add_update_session_file( session_id: str, body: dict[str, Any], database: Database = Depends(get_database), _: SessionInfo = Depends(require_privilege("ContentLibrary.AddLibraryItem")), ) -> dict[str, Any]: file_spec = body.get("file_spec") or body return await content.add_update_session_file( database, session_id, name=str(file_spec.get("name") or "upload.bin"), source_type=str(file_spec.get("source_type") or "PUSH"), size=int(file_spec.get("size") or 0), content=str(file_spec.get("content") or ""), ) @router.post("/api/content/library/item/update-session/{session_id}") async def complete_update_session( session_id: str, action: str = Query("complete"), database: Database = Depends(get_database), _: SessionInfo = Depends(require_privilege("ContentLibrary.AddLibraryItem")), ) -> None: del action await content.complete_update_session(database, session_id) @router.post("/api/content/library/item/download-session") async def create_download_session( body: dict[str, Any], database: Database = Depends(get_database), _: SessionInfo = Depends(require_read), ) -> str: create_spec = body.get("create_spec") or body library_item_id = str(create_spec.get("library_item_id") or create_spec.get("item_id") or "") if not library_item_id: from app.vsphere.errors import invalid_argument raise invalid_argument("create_spec.library_item_id is required") return await content.create_download_session(database, library_item_id=library_item_id) @router.get("/api/content/library/item/download-session/{session_id}") async def get_download_session( session_id: str, database: Database = Depends(get_database), _: SessionInfo = Depends(require_read), ) -> dict[str, Any]: return await content.get_download_session(database, session_id) @router.get("/api/content/library/item/download-session/{session_id}/file") async def list_download_session_files( session_id: str, database: Database = Depends(get_database), _: SessionInfo = Depends(require_read), ) -> list[dict[str, Any]]: return await content.list_download_session_files(database, session_id) @router.get("/api/content/library/item/{library_item_id}") async def get_item( library_item_id: str, database: Database = Depends(get_database), _: SessionInfo = Depends(require_read), ) -> dict[str, Any]: return await content.get_library_item(database, library_item_id) @router.delete("/api/content/library/item/{library_item_id}") async def delete_item( library_item_id: str, database: Database = Depends(get_database), _: SessionInfo = Depends(require_privilege("ContentLibrary.AddLibraryItem")), ) -> Response: await content.delete_library_item(database, library_item_id) return Response(status_code=204) @router.get("/api/content/library/{library_id}") async def get_library( library_id: str, database: Database = Depends(get_database), _: SessionInfo = Depends(require_read), ) -> dict[str, Any]: return await content.get_library(database, library_id) @router.get("/api/content/local-library/{library_id}") async def get_local_library( library_id: str, database: Database = Depends(get_database), _: SessionInfo = Depends(require_read), ) -> dict[str, Any]: info = await content.get_library(database, library_id) if str(info.get("type") or "").upper() not in {"LOCAL", ""}: from app.vsphere.errors import not_found raise not_found(f"Local library {library_id} not found") return info @router.delete("/api/content/local-library/{library_id}") async def delete_local_library( library_id: str, database: Database = Depends(get_database), _: SessionInfo = Depends(require_privilege("ContentLibrary.CreateLocalLibrary")), ) -> Response: await content.delete_library(database, library_id) return Response(status_code=204) @router.post("/api/vcenter/ovf/library-item/{item_id}") async def deploy_ovf( item_id: str, body: dict[str, Any], database: Database = Depends(get_database), _: SessionInfo = Depends(require_privilege("VirtualMachine.Provisioning.DeployTemplate")), ) -> dict[str, Any]: target = body.get("target") or {} deployment = body.get("deployment_spec") or body folder = ( target.get("folder_id") or target.get("folder") or deployment.get("folder") or "group-v23" ) host = target.get("host_id") or target.get("host") or "host-11" datastore = ( target.get("datastore_id") or target.get("datastore") or deployment.get("datastore") or "datastore-31" ) resource_pool = ( target.get("resource_pool_id") or target.get("resource_pool") or deployment.get("resource_pool") ) moid, task_id = await content.deploy_ovf_from_library( database, item_id=item_id, name=str(deployment.get("name") or f"ovf-{item_id[-6:]}"), folder=str(folder), host=str(host), datastore=str(datastore), resource_pool=str(resource_pool) if resource_pool else None, ) return {"resource_id": {"id": moid, "type": "VirtualMachine"}, "task": task_id} @router.get("/api/vcenter/storage/policies") async def storage_policies( database: Database = Depends(get_database), _: SessionInfo = Depends(require_read), ) -> list[dict[str, Any]]: payload = await api_state.get_payload(database, "GET", "/api/vcenter/storage/policies") if isinstance(payload, list): return payload return []