Initial commit: VMware vSphere API simulator scaffold.
Add the FastAPI app, PostgreSQL migrations, Docker/Helm packaging, API contracts, docs, client examples, and the unit/integration/compatibility test suite for local client and tooling labs without a real vCenter.
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
"""Content library, OVF deploy, storage policies, privileges."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
|
||||
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.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 "")
|
||||
)
|
||||
|
||||
|
||||
@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/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
|
||||
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(target.get("folder") or "group-v23"),
|
||||
host=str(target.get("host") or "host-11"),
|
||||
datastore=str(target.get("datastore") or "datastore-31"),
|
||||
)
|
||||
return {"resource_id": {"id": moid, "type": "VirtualMachine"}, "task": task_id}
|
||||
|
||||
|
||||
@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/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 []
|
||||
Reference in New Issue
Block a user