275 lines
9.4 KiB
Python
275 lines
9.4 KiB
Python
"""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 []
|