"""Lab-grade NSX / WCP / vSAN / identity / VECS / NFC state (DB-backed).""" from __future__ import annotations import json import secrets from typing import Any from app.db.pool import Database from app.vsphere import inventory from app.vsphere.domain import api_state _IDENTITY = "/api/vcenter/identity/providers" def default_identity_providers() -> list[dict[str, Any]]: return [ { "provider": "vsphere.local", "name": "vsphere.local", "type_id": "LocalOS", "domain_names": ["vsphere.local"], "is_default": True, }, { "provider": "oidc-lab", "name": "Lab OIDC", "type_id": "Oidc", "issuer_uri": "https://idp.lab.local/realms/vsphere", "client_id": "vcenter-lab", "is_default": False, }, { "provider": "saml-lab", "name": "Lab SAML", "type_id": "Saml", "idp_entity_id": "https://idp.lab.local/saml", "sso_service_url": "https://idp.lab.local/saml/sso", "is_default": False, }, ] def default_nsx_surface() -> dict[str, Any]: return { ("GET", "/api/vcenter/namespace-management/nsx-tier0-gateway"): [ { "gateway": "nsx-tier0-1", "name": "tier0-lab", "path": "/infra/tier-0s/tier0-lab", "status": "UP", } ], ("GET", "/api/vcenter/namespace-management/networks/{network}/nsx/projects"): [ { "project": "nsx-project-1", "name": "default", "path": "/orgs/default/projects/default", "status": "READY", } ], ("GET", "/api/vcenter/namespace-management/networks/{network}/nsx/edges"): [ { "edge": "nsx-edge-1", "name": "edge-lab-1", "path": "/infra/sites/default/enforcement-points/default/edge-clusters/edge-1", "status": "UP", } ], ( "GET", "/api/vcenter/namespace-management/networks/{network}/nsx/distributed-switches", ): [ { "distributed_switch": "nsx-dvs-1", "name": "NSX-DVS", "path": "/infra/sites/default/enforcement-points/default/transport-zones/tz-1", "status": "UP", } ], ("GET", "/api/vcenter/namespace-management/networks/{network}/nsx/vpcs"): [ { "vpc": "vpc-lab-1", "name": "vpc-lab", "path": "/orgs/default/projects/default/vpcs/vpc-lab", "status": "READY", } ], ( "GET", "/api/vcenter/namespace-management/networks/{network}/nsx/vpc-connectivity-profiles", ): [ { "profile": "vpc-profile-1", "name": "default-connectivity", "status": "READY", } ], ( "GET", "/api/vcenter/namespaces/{namespace}/networks/{network}/nsx/subnets", ): [ { "subnet": "subnet-lab-1", "name": "workload", "cidr": "10.244.0.0/24", "status": "READY", } ], } def default_wcp_surface() -> dict[str, Any]: return { ("GET", "/api/vcenter/namespace-management/networks"): [ { "network": "network-41", "name": "VM Network", "mode": "NSXT_VPC", "status": "READY", } ], ("GET", "/api/vcenter/namespaces"): [ { "namespace": "ns-lab-1", "name": "ns-lab-1", "cluster": "domain-c21", "config_status": "RUNNING", "stats": {"cpu_used": 2, "memory_used_mib": 4096}, } ], ("GET", "/api/vcenter/namespace-management/virtual-machine-classes"): [ { "id": "best-effort-small", "cpu_count": 2, "memory_mb": 2048, "description": "Lab small class", }, { "id": "guaranteed-large", "cpu_count": 8, "memory_mb": 16384, "description": "Lab large class", }, ], ("GET", "/api/vcenter/namespace-management/clusters/{cluster}/nsm"): { "cluster": "domain-c21", "config_status": "RUNNING", "kubernetes_status": "READY", "network_provider": "NSXT_CONTAINER_PLUGIN", }, ( "GET", "/api/vcenter/namespace-management/supervisors/{supervisor}/identity/providers", ): [ { "provider": "oidc-supervisor", "name": "Supervisor OIDC", "issuer": "https://idp.lab.local/realms/supervisor", "type": "OIDC", "status": "READY", } ], ("GET", "/api/vcenter/namespace-management/infrastructure-policies"): [ { "policy": "infra-policy-lab-1", "name": "default-infra", "status": "ENABLED", } ], } def default_vsan_surface() -> dict[str, Any]: return { ("GET", "/api/vcenter/storage/policies"): [ { "policy": "policy-default", "name": "vSAN Default Storage Policy", "description": "Lab vSAN default", "policy_type": "VSAN", }, { "policy": "policy-thin", "name": "Thin provision", "description": "Thin disks", "policy_type": "VVOL", }, { "policy": "policy-vsan-raid1", "name": "vSAN RAID1", "description": "Failures to tolerate = 1", "policy_type": "VSAN", }, ], ("GET", "/api/vcenter/storage/policies/{policy}"): { "policy": "policy-default", "name": "vSAN Default Storage Policy", "description": "Lab vSAN default", "policy_type": "VSAN", "constraints": [{"property_name": "hostFailuresToTolerate", "value": 1}], }, } def default_vecs_surface() -> dict[str, Any]: return { ("GET", "/api/vcenter/certificate-management/vcenter/tls-csr"): { "csr": "-----BEGIN CERTIFICATE REQUEST-----\nMIIBLabCSR\n-----END CERTIFICATE REQUEST-----", "status": "AVAILABLE", "subject_dn": "CN=vcenter.lab.local", }, ("GET", "/api/vcenter/certificate-management/vcenter/signing-certificate"): { "cert": "-----BEGIN CERTIFICATE-----\nMIIBSigning\n-----END CERTIFICATE-----", "valid_from": "2026-01-01T00:00:00.000Z", "valid_to": "2030-01-01T00:00:00.000Z", "subject_dn": "CN=CA,DC=vsphere,DC=local", }, ("GET", "/api/vcenter/certificate-management/vcenter/trusted-root-chains"): [ { "chain": "chain-lab-1", "cert_chain": ["-----BEGIN CERTIFICATE-----\nMIIBRoot\n-----END CERTIFICATE-----"], "thumbprint": "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD", } ], ( "GET", "/api/vcenter/namespace-management/supervisors/{supervisor}/certificates", ): [ { "certificate": "supervisor-tls", "status": "VALID", "valid_to": "2028-01-01T00:00:00.000Z", } ], ( "GET", "/api/vcenter/namespace-management/supervisors/{supervisor}/signing-requests", ): [ { "request": "csr-supervisor-1", "status": "PENDING", "csr": "-----BEGIN CERTIFICATE REQUEST-----\nMIIBSupCSR\n-----END CERTIFICATE REQUEST-----", } ], } async def seed_platform_surface(database: Database) -> dict[str, int]: """Overlay rich lab payloads for previously shallow product areas (seed-time only).""" rows = 0 providers = default_identity_providers() await api_state.put_seed_payload(database, "GET", _IDENTITY, providers) rows += 1 for mapping in ( default_nsx_surface(), default_wcp_surface(), default_vsan_surface(), default_vecs_surface(), ): for (verb, path), payload in mapping.items(): await api_state.put_seed_payload(database, verb, path, payload) rows += 1 return {"platform_surface_rows": rows} async def list_identity_providers(database: Database) -> list[dict[str, Any]]: payload = await api_state.get_payload_or_seed(database, "GET", _IDENTITY) if not isinstance(payload, list) or not payload: return [] if not any(str(item.get("provider")) == "vsphere.local" for item in payload): restored = await api_state.restore_seed_payload(database, "GET", _IDENTITY) if isinstance(restored, list) and restored: return restored return payload async def get_identity_provider(database: Database, provider: str) -> dict[str, Any] | None: for item in await list_identity_providers(database): if provider in { str(item.get("provider")), str(item.get("name")), str(item.get("id")), }: return item return None async def upsert_identity_provider(database: Database, body: dict[str, Any]) -> dict[str, Any]: providers = await list_identity_providers(database) provider_id = str(body.get("provider") or body.get("name") or f"idp-{secrets.token_hex(3)}") entry = { "provider": provider_id, "name": str(body.get("name") or provider_id), "type_id": str(body.get("type_id") or body.get("type") or "Oidc"), "is_default": bool(body.get("is_default", False)), **{ k: v for k, v in body.items() if k not in {"provider", "name", "type_id", "type", "is_default"} }, } out: list[dict[str, Any]] = [] replaced = False for item in providers: if str(item.get("provider")) == provider_id: out.append({**item, **entry}) replaced = True else: out.append(item) if not replaced: out.append(entry) await api_state.put_payload(database, "GET", _IDENTITY, out) return entry async def delete_identity_provider(database: Database, provider: str) -> bool: providers = await list_identity_providers(database) filtered = [p for p in providers if str(p.get("provider")) != provider] if len(filtered) == len(providers): return False # Soft-delete lab baseline: restore seed document so GET stays non-empty. await api_state.restore_seed_payload(database, "GET", _IDENTITY) return True def _decode_payload(value: Any) -> dict[str, Any]: current = value while isinstance(current, str): current = json.loads(current) return current if isinstance(current, dict) else {} async def _save_nfc_lease(database: Database, lease_id: str, info: dict[str, Any]) -> None: pool = database.pool # type: ignore[attr-defined] async with pool.acquire() as conn: await conn.execute( """ INSERT INTO vsphere_nfc_leases (id, vm_moid, state, payload, updated_at) VALUES ($1, $2, $3, $4::jsonb, now()) ON CONFLICT (id) DO UPDATE SET vm_moid = EXCLUDED.vm_moid, state = EXCLUDED.state, payload = EXCLUDED.payload, updated_at = now() """, lease_id, str(info.get("entity") or ""), str(info.get("state") or "ready"), json.dumps(info), ) await inventory.upsert_object( database, moid=lease_id, type_name="HttpNfcLease", name=lease_id, parent_moid=None, props=info, ) async def create_nfc_lease( database: Database, *, vm: str, files: list[str] | None = None, ) -> dict[str, Any]: lease_id = f"lease-{secrets.token_hex(6)}" file_list = files or [f"{vm}.vmdk", f"{vm}.nvram"] info = { "lease": lease_id, "state": "ready", "entity": vm, "initializeProgress": 100, "transferProgress": 0, "info": { "deviceUrl": [ { "key": f"disk-{index}", "importKey": name, "url": f"https://localhost/nfc/{lease_id}/files/{name}", "sslThumbprint": "https://example.invalid/thumbprint", } for index, name in enumerate(file_list) ] }, "files": {name: {"uploaded": False, "size": 0} for name in file_list}, } await _save_nfc_lease(database, lease_id, info) return info async def get_nfc_lease(database: Database, lease_id: str) -> dict[str, Any] | None: pool = database.pool # type: ignore[attr-defined] async with pool.acquire() as conn: row = await conn.fetchrow( "SELECT payload FROM vsphere_nfc_leases WHERE id = $1", lease_id, ) if row is None: return None return _decode_payload(row["payload"]) async def complete_nfc_lease(database: Database, lease_id: str) -> dict[str, Any] | None: lease = await get_nfc_lease(database, lease_id) if lease is None: return None lease["state"] = "done" lease["transferProgress"] = 100 await _save_nfc_lease(database, lease_id, lease) return lease async def upload_nfc_file( database: Database, lease_id: str, filename: str, size: int, ) -> dict[str, Any] | None: lease = await get_nfc_lease(database, lease_id) if lease is None: return None files = lease.setdefault("files", {}) files[filename] = {"uploaded": True, "size": size} uploaded = sum(1 for meta in files.values() if meta.get("uploaded")) total = max(len(files), 1) lease["transferProgress"] = int(100 * uploaded / total) if uploaded >= total: lease["state"] = "done" await _save_nfc_lease(database, lease_id, lease) return lease