diff --git a/examples/cloud_api_examples.py b/examples/cloud_api_examples.py index 47844a55..705dca8b 100644 --- a/examples/cloud_api_examples.py +++ b/examples/cloud_api_examples.py @@ -17,6 +17,8 @@ async def main() -> None: # List VMs provider = CloudProvider(api_key=api_key, verbose=True) async with provider: + + # List all VMs vms = await provider.list_vms() print(f"Found {len(vms)} VM(s)") for vm in vms: @@ -27,31 +29,43 @@ async def main() -> None: f"vnc_url: {vm.get('vnc_url')}\n", ) - # --- Additional operations (commented out) --- - # To stop a VM by name: - # api_key = os.getenv("CUA_API_KEY") - # provider = CloudProvider(api_key=api_key, verbose=True) - # async with provider: - # name = "your-vm-name-here" - # resp = await provider.stop_vm(name) - # print("stop_vm response:", resp) + # # --- Additional operations (commented out) --- + # # To stop a VM by name: + # name = "m-linux-96lcxd2c2k" + # resp = await provider.stop_vm(name) + # print( + # "stop_vm response:\n", + # f"name: {resp['name']}\n", + # f"status: {resp['status']}\n", # stopping + # ) - # To restart a VM by name: - # api_key = os.getenv("CUA_API_KEY") - # provider = CloudProvider(api_key=api_key, verbose=True) - # async with provider: - # name = "your-vm-name-here" - # resp = await provider.restart_vm(name) - # print("restart_vm response:", resp) + # # To start a VM by name: + # name = "m-linux-96lcxd2c2k" + # resp = await provider.run_vm(name) + # print( + # "run_vm response:\n", + # f"name: {resp['name']}\n", + # f"status: {resp['status']}\n", # starting + # ) - # To probe a VM's status via its public hostname (if you know the name): - # api_key = os.getenv("CUA_API_KEY") - # provider = CloudProvider(api_key=api_key, verbose=True) - # async with provider: - # name = "your-vm-name-here" - # info = await provider.get_vm(name) - # print("get_vm info:", info) + # # To restart a VM by name: + # name = "m-linux-96lcxd2c2k" + # resp = await provider.restart_vm(name) + # print( + # "restart_vm response:\n", + # f"name: {resp['name']}\n", + # f"status: {resp['status']}\n", # restarting + # ) + # # To probe a VM's status via its public hostname (if you know the name): + name = "m-linux-96lcxd2c2k" + info = await provider.get_vm(name) + print("get_vm info:\n", + f"name: {info['name']}\n", + f"status: {info['status']}\n", # running + f"api_url: {info.get('api_url')}\n", + f"os_type: {info.get('os_type')}\n", + ) if __name__ == "__main__": asyncio.run(main()) diff --git a/libs/python/computer/computer/providers/cloud/provider.py b/libs/python/computer/computer/providers/cloud/provider.py index 0e3dfd83..5e4e7c51 100644 --- a/libs/python/computer/computer/providers/cloud/provider.py +++ b/libs/python/computer/computer/providers/cloud/provider.py @@ -78,8 +78,7 @@ class CloudProvider(BaseVMProvider): try: data = await resp.json(content_type=None) vm_status = str(data.get("status", "ok")) - if isinstance(data, dict) and "os_type" in data: - vm_os_type = str(data.get("os_type")) + vm_os_type = str(data.get("os_type")) except Exception: vm_status = "unknown" elif status_code < 500: @@ -89,23 +88,11 @@ class CloudProvider(BaseVMProvider): return { "name": name, "status": "running" if vm_status == "ok" else vm_status, - "hostname": hostname, + "api_url": f"https://{hostname}:8443", "os_type": vm_os_type, } except Exception: - # Fall back to a DNS resolve check - try: - loop = asyncio.get_event_loop() - await loop.getaddrinfo(hostname, 443) - # Host resolves, but HTTPS probe failed → treat as unknown - return { - "name": name, - "status": "unknown", - "hostname": hostname, - } - except Exception: - # Host does not resolve → not found - return {"name": name, "status": "not_found", "hostname": hostname} + return {"name": name, "status": "not_found", "api_url": f"https://{hostname}:8443"} async def list_vms(self) -> ListVMsResponse: url = f"{self.api_base}/v1/vms" @@ -130,10 +117,10 @@ class CloudProvider(BaseVMProvider): name = vm.get("name") password = vm.get("password") if isinstance(name, str) and name: - host = f"https://{name}.containers.cloud.trycua.com:8443" + host = f"{name}.containers.cloud.trycua.com" # api_url: always set if missing if not vm.get("api_url"): - vm["api_url"] = host + vm["api_url"] = f"https://{host}:8443" # vnc_url: only when password available if not vm.get("vnc_url") and isinstance(password, str) and password: vm[ @@ -151,7 +138,7 @@ class CloudProvider(BaseVMProvider): logger.error(f"list_vms failed: HTTP {resp.status} - {text}") return [] - async def run_vm(self, image: str, name: str, run_opts: Dict[str, Any], storage: Optional[str] = None) -> Dict[str, Any]: + async def run_vm(self, name: str, image: Optional[str] = None, run_opts: Optional[Dict[str, Any]] = None, storage: Optional[str] = None) -> Dict[str, Any]: """Start a VM via public API. Returns a minimal status.""" url = f"{self.api_base}/v1/vms/{name}/start" headers = { diff --git a/libs/python/computer/computer/providers/cloud/types.py b/libs/python/computer/computer/providers/cloud/types.py deleted file mode 100644 index a289701d..00000000 --- a/libs/python/computer/computer/providers/cloud/types.py +++ /dev/null @@ -1,57 +0,0 @@ -"""Pydantic models for the CUA Cloud provider API. - -Documents the response shape for the Cloud list VMs endpoint. - -List VMs -- Method: GET -- Path: `/v1/vms` -- Description: Returns all VMs owned by the API key's user. -- Responses: - - 200: Array of minimal VM objects with fields `{ name, password, status }` - - 401: Unauthorized (missing/invalid API key) - -Example curl: - curl -H "Authorization: Bearer $CUA_API_KEY" \ - "https://api.cua.ai/v1/vms" - -Response shape: -[ - { - "name": "s-windows-x4snp46ebf", - "password": "49b8daa3", - "status": "running" - } -] - -Status values: -- pending : VM deployment in progress -- running : VM is active and accessible -- stopped : VM is stopped but not terminated -- terminated: VM has been permanently destroyed -- failed : VM deployment or operation failed -""" -from __future__ import annotations - -from typing import Literal, Optional - -# Require pydantic for typed models in provider APIs -from pydantic import BaseModel - - -CloudVMStatus = Literal["pending", "running", "stopped", "terminated", "failed"] - - -class CloudVM(BaseModel): - """Minimal VM object returned by CUA Cloud list API. - - Additional optional fields (like URLs) may be filled by callers based on - their environment but are not guaranteed by the API. - """ - - name: str - password: str - status: CloudVMStatus - - # Optional, not guaranteed by the list API, but useful when known/derived - vnc_url: Optional[str] = None - api_url: Optional[str] = None