118 lines
3.8 KiB
Python
118 lines
3.8 KiB
Python
"""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,
|
|
}
|