Keep API not-found and 401 responses usable behind Ingress and in the Web UI.
Return native JSON with the missing id in the message, clear stale sessions on 401, and document ingress-nginx annotations so branded HTML 404/405 pages do not rewrite simulator bodies.
This commit is contained in:
@@ -218,9 +218,7 @@ def _is_seed_host_or_named_vm(moid: str, obj: Any) -> bool:
|
||||
|
||||
|
||||
async def set_host_maintenance(database: Database, host: str, enabled: bool) -> dict[str, Any]:
|
||||
obj = await inventory.get_object(database, host)
|
||||
if obj is None or obj.type != "HostSystem":
|
||||
raise not_found(f"Host {host} not found")
|
||||
obj = await inventory.require_object(database, host, type_name="HostSystem", resource="host")
|
||||
props = dict(obj.props)
|
||||
props["connection_state"] = "CONNECTED"
|
||||
props["maintenance_mode"] = enabled
|
||||
|
||||
@@ -14,10 +14,7 @@ from app.vsphere.errors import invalid_argument, not_found
|
||||
|
||||
|
||||
async def require_vm(database: Database, vm: str) -> inventory.ManagedObject:
|
||||
obj = await inventory.get_object(database, vm)
|
||||
if obj is None or obj.type != "VirtualMachine":
|
||||
raise not_found(f"VM {vm} not found")
|
||||
return obj
|
||||
return await inventory.require_object(database, vm, type_name="VirtualMachine", resource="vm")
|
||||
|
||||
|
||||
def _default_identity(name: str, moid: str) -> dict[str, str]:
|
||||
|
||||
+24
-2
@@ -41,16 +41,38 @@ def unauthenticated(message: str = "Authentication required") -> VsphereError:
|
||||
)
|
||||
|
||||
|
||||
def not_found(message: str = "Not found") -> VsphereError:
|
||||
def not_found(message: str = "Not found", *, args: list[Any] | None = None) -> VsphereError:
|
||||
return VsphereError(
|
||||
404,
|
||||
error_type="not_found",
|
||||
messages=[
|
||||
{"default_message": message, "id": "com.vmware.vapi.std.errors.not_found", "args": []}
|
||||
{
|
||||
"default_message": message,
|
||||
"id": "com.vmware.vapi.std.errors.not_found",
|
||||
"args": list(args or []),
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def no_such(resource: str, resource_id: str) -> VsphereError:
|
||||
"""Native not-found with the missing id in the message (Ingress-friendly JSON)."""
|
||||
|
||||
message = f"No such {resource} ('{resource_id}')"
|
||||
return VsphereError(
|
||||
404,
|
||||
error_type="not_found",
|
||||
messages=[
|
||||
{
|
||||
"default_message": message,
|
||||
"id": "com.vmware.vapi.std.errors.not_found",
|
||||
"args": [resource_id],
|
||||
}
|
||||
],
|
||||
data={resource: message},
|
||||
)
|
||||
|
||||
|
||||
def already_exists(message: str = "Already exists") -> VsphereError:
|
||||
return VsphereError(
|
||||
400,
|
||||
|
||||
@@ -60,6 +60,36 @@ async def get_object(database: Database, moid: str) -> ManagedObject | None:
|
||||
return None if row is None else _row(row)
|
||||
|
||||
|
||||
_TYPE_RESOURCE: dict[str, str] = {
|
||||
"HostSystem": "host",
|
||||
"VirtualMachine": "vm",
|
||||
"Datastore": "datastore",
|
||||
"Datacenter": "datacenter",
|
||||
"Folder": "folder",
|
||||
"ClusterComputeResource": "cluster",
|
||||
"ResourcePool": "resource_pool",
|
||||
"Network": "network",
|
||||
}
|
||||
|
||||
|
||||
async def require_object(
|
||||
database: Database,
|
||||
moid: str,
|
||||
*,
|
||||
type_name: str | None = None,
|
||||
resource: str | None = None,
|
||||
) -> ManagedObject:
|
||||
"""Return the managed object or raise a native not-found with the bad id."""
|
||||
|
||||
from app.vsphere.errors import no_such
|
||||
|
||||
obj = await get_object(database, moid)
|
||||
label = resource or (type_name and _TYPE_RESOURCE.get(type_name)) or "object"
|
||||
if obj is None or (type_name is not None and obj.type != type_name):
|
||||
raise no_such(label, moid)
|
||||
return obj
|
||||
|
||||
|
||||
async def upsert_object(
|
||||
database: Database,
|
||||
*,
|
||||
|
||||
@@ -464,9 +464,7 @@ async def host_storage(
|
||||
database: Database = Depends(get_database),
|
||||
_: SessionInfo = Depends(require_read),
|
||||
) -> list[dict[str, Any]]:
|
||||
obj = await inventory.get_object(database, host)
|
||||
if obj is None or obj.type != "HostSystem":
|
||||
raise not_found(f"Host {host} not found")
|
||||
obj = await inventory.require_object(database, host, type_name="HostSystem", resource="host")
|
||||
devices = obj.props.get("storage_devices")
|
||||
return list(devices) if isinstance(devices, list) else []
|
||||
|
||||
@@ -477,9 +475,7 @@ async def host_networking(
|
||||
database: Database = Depends(get_database),
|
||||
_: SessionInfo = Depends(require_read),
|
||||
) -> dict[str, Any]:
|
||||
obj = await inventory.get_object(database, host)
|
||||
if obj is None or obj.type != "HostSystem":
|
||||
raise not_found(f"Host {host} not found")
|
||||
obj = await inventory.require_object(database, host, type_name="HostSystem", resource="host")
|
||||
networking = obj.props.get("networking")
|
||||
return networking if isinstance(networking, dict) else {}
|
||||
|
||||
@@ -490,9 +486,7 @@ async def folder_children(
|
||||
database: Database = Depends(get_database),
|
||||
_: SessionInfo = Depends(require_read),
|
||||
) -> list[dict[str, str]]:
|
||||
parent = await inventory.get_object(database, folder)
|
||||
if parent is None:
|
||||
raise not_found(f"Folder {folder} not found")
|
||||
await inventory.require_object(database, folder, resource="folder")
|
||||
children = [obj for obj in await inventory.list_objects(database) if obj.parent_moid == folder]
|
||||
return [{"moid": c.moid, "type": c.type, "name": c.name} for c in children]
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ from app.db.pool import Database
|
||||
from app.dependencies import get_database
|
||||
from app.vsphere import inventory
|
||||
from app.vsphere.domain import vm_ops
|
||||
from app.vsphere.errors import invalid_argument, not_found, unauthenticated
|
||||
from app.vsphere.errors import invalid_argument, unauthenticated
|
||||
from app.vsphere.rest import mappers
|
||||
from app.vsphere.security.authz import require_privilege, require_read
|
||||
from app.vsphere.security.session import (
|
||||
@@ -200,9 +200,7 @@ async def get_vm(
|
||||
database: Database = Depends(get_database),
|
||||
_: SessionInfo = Depends(require_read),
|
||||
) -> dict[str, Any]:
|
||||
obj = await inventory.get_object(database, vm)
|
||||
if obj is None or obj.type != "VirtualMachine":
|
||||
raise not_found(f"VM {vm} not found")
|
||||
obj = await inventory.require_object(database, vm, type_name="VirtualMachine", resource="vm")
|
||||
return mappers.vm_info(obj)
|
||||
|
||||
|
||||
@@ -286,9 +284,7 @@ async def delete_vm(
|
||||
database: Database = Depends(get_database),
|
||||
_: SessionInfo = Depends(require_privilege("VirtualMachine.Inventory.Delete")),
|
||||
) -> Response:
|
||||
obj = await inventory.get_object(database, vm)
|
||||
if obj is None or obj.type != "VirtualMachine":
|
||||
raise not_found(f"VM {vm} not found")
|
||||
obj = await inventory.require_object(database, vm, type_name="VirtualMachine", resource="vm")
|
||||
if obj.props.get("power_state") == "POWERED_ON":
|
||||
raise invalid_argument("VM must be powered off before delete")
|
||||
await inventory.delete_object(database, vm)
|
||||
@@ -301,9 +297,7 @@ async def get_vm_power(
|
||||
database: Database = Depends(get_database),
|
||||
_: SessionInfo = Depends(require_read),
|
||||
) -> dict[str, str]:
|
||||
obj = await inventory.get_object(database, vm)
|
||||
if obj is None or obj.type != "VirtualMachine":
|
||||
raise not_found(f"VM {vm} not found")
|
||||
obj = await inventory.require_object(database, vm, type_name="VirtualMachine", resource="vm")
|
||||
state = str(obj.props.get("power_state") or "POWERED_OFF")
|
||||
return {"state": state}
|
||||
|
||||
@@ -334,9 +328,7 @@ async def get_host(
|
||||
database: Database = Depends(get_database),
|
||||
_: SessionInfo = Depends(require_read),
|
||||
) -> dict[str, Any]:
|
||||
obj = await inventory.get_object(database, host)
|
||||
if obj is None or obj.type != "HostSystem":
|
||||
raise not_found(f"Host {host} not found")
|
||||
obj = await inventory.require_object(database, host, type_name="HostSystem", resource="host")
|
||||
return mappers.host_info(obj)
|
||||
|
||||
|
||||
@@ -355,9 +347,9 @@ async def get_datastore(
|
||||
database: Database = Depends(get_database),
|
||||
_: SessionInfo = Depends(require_read),
|
||||
) -> dict[str, Any]:
|
||||
obj = await inventory.get_object(database, datastore)
|
||||
if obj is None or obj.type != "Datastore":
|
||||
raise not_found(f"Datastore {datastore} not found")
|
||||
obj = await inventory.require_object(
|
||||
database, datastore, type_name="Datastore", resource="datastore"
|
||||
)
|
||||
props = obj.props
|
||||
return {
|
||||
"name": obj.name,
|
||||
@@ -421,9 +413,7 @@ async def vm_guest_identity(
|
||||
database: Database = Depends(get_database),
|
||||
_: SessionInfo = Depends(require_read),
|
||||
) -> dict[str, Any]:
|
||||
obj = await inventory.get_object(database, vm)
|
||||
if obj is None or obj.type != "VirtualMachine":
|
||||
raise not_found(f"VM {vm} not found")
|
||||
obj = await inventory.require_object(database, vm, type_name="VirtualMachine", resource="vm")
|
||||
return {
|
||||
"name": obj.name,
|
||||
"family": "LINUX",
|
||||
|
||||
@@ -19,7 +19,6 @@ from app.dependencies import get_database
|
||||
from app.vsphere import inventory
|
||||
from app.vsphere.domain import api_state, tagging
|
||||
from app.vsphere.domain import content as content_domain
|
||||
from app.vsphere.errors import not_found
|
||||
from app.vsphere.rest.coverage import IMPLEMENTED
|
||||
from app.vsphere.security.authz import require_read
|
||||
from app.vsphere.security.session import SessionInfo
|
||||
@@ -61,9 +60,7 @@ async def _live_get(database: Database, template: str, concrete: str) -> Any | N
|
||||
|
||||
vm = params.get("vm")
|
||||
if vm and "/hardware/" in template:
|
||||
obj = await inventory.get_object(database, vm)
|
||||
if obj is None:
|
||||
raise not_found(f"VM {vm} not found")
|
||||
obj = await inventory.require_object(database, vm, type_name="VirtualMachine", resource="vm")
|
||||
props = obj.props or {}
|
||||
|
||||
def _live_list(key: str) -> list[Any] | None:
|
||||
@@ -107,9 +104,7 @@ async def _live_get(database: Database, template: str, concrete: str) -> Any | N
|
||||
return nic
|
||||
|
||||
if vm and "/guest/" in template:
|
||||
obj = await inventory.get_object(database, vm)
|
||||
if obj is None:
|
||||
raise not_found(f"VM {vm} not found")
|
||||
obj = await inventory.require_object(database, vm, type_name="VirtualMachine", resource="vm")
|
||||
props = obj.props or {}
|
||||
if template.endswith("/guest/local-filesystem"):
|
||||
if "guest_filesystems" in props:
|
||||
@@ -130,15 +125,11 @@ async def _live_get(database: Database, template: str, concrete: str) -> Any | N
|
||||
|
||||
host = params.get("host")
|
||||
if host and template.endswith("/networking"):
|
||||
obj = await inventory.get_object(database, host)
|
||||
if obj is None:
|
||||
raise not_found(f"Host {host} not found")
|
||||
obj = await inventory.require_object(database, host, type_name="HostSystem", resource="host")
|
||||
networking = (obj.props or {}).get("networking")
|
||||
return networking if networking is not None else None
|
||||
if host and "storage-device" in template:
|
||||
obj = await inventory.get_object(database, host)
|
||||
if obj is None:
|
||||
raise not_found(f"Host {host} not found")
|
||||
obj = await inventory.require_object(database, host, type_name="HostSystem", resource="host")
|
||||
devices = (obj.props or {}).get("storage_devices")
|
||||
return devices if devices is not None else None
|
||||
|
||||
@@ -156,9 +147,7 @@ async def _mutate_vm_hardware(
|
||||
vm = params.get("vm")
|
||||
if not vm or "/hardware/" not in template:
|
||||
return False
|
||||
obj = await inventory.get_object(database, vm)
|
||||
if obj is None:
|
||||
raise not_found(f"VM {vm} not found")
|
||||
obj = await inventory.require_object(database, vm, type_name="VirtualMachine", resource="vm")
|
||||
props = dict(obj.props or {})
|
||||
|
||||
if verb == "POST" and template.endswith("/hardware/cdrom"):
|
||||
|
||||
Reference in New Issue
Block a user