"""CIS-style asynchronous tasks shared by REST and SOAP.""" from __future__ import annotations import json import secrets from datetime import UTC, datetime from typing import Any from app.db.pool import Database def _pool(database: Database) -> Any: return database.pool # type: ignore[attr-defined] async def create_task( database: Database, *, description: str, service: str, operation: str, status: str = "SUCCEEDED", result: dict[str, Any] | None = None, error: dict[str, Any] | None = None, task_id: str | None = None, ) -> str: resolved_id = task_id or f"task-{secrets.token_hex(8)}" now = datetime.now(UTC) completed = now if status in {"SUCCEEDED", "FAILED"} else None pool = _pool(database) async with pool.acquire() as conn: await conn.execute( """ INSERT INTO vsphere_tasks (id, description, status, service, operation, result, error, completed_at) VALUES ($1, $2, $3, $4, $5, $6::jsonb, $7::jsonb, $8) ON CONFLICT (id) DO UPDATE SET description = EXCLUDED.description, status = EXCLUDED.status, service = EXCLUDED.service, operation = EXCLUDED.operation, result = EXCLUDED.result, error = EXCLUDED.error, completed_at = EXCLUDED.completed_at """, resolved_id, description, status, service, operation, json.dumps(result) if result is not None else None, json.dumps(error) if error is not None else None, completed, ) return resolved_id async def get_task(database: Database, task_id: str) -> dict[str, Any] | None: pool = _pool(database) async with pool.acquire() as conn: row = await conn.fetchrow("SELECT * FROM vsphere_tasks WHERE id = $1", task_id) if row is None: return None return _row(row) async def list_tasks(database: Database) -> list[dict[str, Any]]: pool = _pool(database) async with pool.acquire() as conn: rows = await conn.fetch("SELECT * FROM vsphere_tasks ORDER BY created_at DESC LIMIT 200") return [_row(row) for row in rows] def _localizable(message: str, *, message_id: str = "com.vmware.cis.task.description") -> dict[str, Any]: return { "id": message_id, "default_message": message, "args": [], "localized": message, } def _row(row: Any) -> dict[str, Any]: result = row["result"] error = row["error"] if isinstance(result, str): result = json.loads(result) if isinstance(error, str): error = json.loads(error) status = row["status"] completed = 100 if status in {"SUCCEEDED", "FAILED"} else 50 description_text = row["description"] or row["operation"] or "task" # Cis Task Info wire shape (Automation) plus lab-friendly aliases used by cookbooks. return { "task": row["id"], "description": _localizable(description_text), "status": status, "state": status, "service": row["service"], "operation": row["operation"], "cancelable": False, "progress": { "total": 100, "completed": completed, "message": _localizable( f"{completed}%", message_id="com.vmware.cis.task.progress", ), }, "result": result, "error": error, "start_time": row["created_at"].isoformat() if row["created_at"] else None, "end_time": row["completed_at"].isoformat() if row["completed_at"] else None, "created_at": row["created_at"].isoformat() if row["created_at"] else None, "completed_at": row["completed_at"].isoformat() if row["completed_at"] else None, }