feat: add tool catalog and installation endpoints with models for tool management

This commit is contained in:
2026-09-25 23:03:25 +05:30
parent e1b934e35e
commit 0b22c81e6c
4 changed files with 158 additions and 0 deletions
+88
View File
@@ -11,6 +11,7 @@ from .domain import VmRecord
from .errors import ValidationError
from .guest_agent import DEFAULT_TIMEOUT_SECONDS, MAX_COMMAND_BYTES, MAX_TIMEOUT_SECONDS
from .guest_exec import validate_exec
from .tool_catalog import MAX_SELECTIONS, ToolSelection
class HealthResponse(BaseModel):
@@ -42,6 +43,19 @@ class PortRuleModel(BaseModel):
protocol: Literal["TCP", "HTTP", "HTTPS"] = "TCP"
class ToolSelectionModel(BaseModel):
model_config = ConfigDict(extra="forbid")
id: str = Field(strict=True, min_length=1, max_length=32)
version: str | None = Field(default=None, strict=True, max_length=16,
description="Required for Python: 3.11, 3.12, or 3.13. Omit for other tools.")
@model_validator(mode="after")
def validate_selection(self):
ToolSelection(self.id, self.version)
return self
class VmCreateRequest(BaseModel):
model_config = ConfigDict(extra="forbid")
@@ -54,6 +68,8 @@ class VmCreateRequest(BaseModel):
name: str | None = None
ports: list[PortRuleModel] | None = None
no_default_ports: bool = Field(default=False, strict=True)
tools: list[ToolSelectionModel] = Field(default_factory=list, max_length=MAX_SELECTIONS,
description="Optional guest tools to install asynchronously after boot")
class VmResizeRequest(BaseModel):
@@ -149,6 +165,78 @@ class VmAgentInstallResponse(BaseModel):
restart_required: bool = True
class ToolCatalogResponse(BaseModel):
id: str
name: str
description: str
version_label: str
versions: list[str]
source: str
class ToolInstallRequest(BaseModel):
model_config = ConfigDict(extra="forbid")
tools: list[ToolSelectionModel] = Field(min_length=1, max_length=MAX_SELECTIONS)
start_if_stopped: bool = Field(default=False, strict=True)
restart: bool = Field(default=False, strict=True, description="Restart first to refresh the guest agent")
request_id: str | None = Field(default=None, strict=True, max_length=80, pattern=r"^[A-Za-z0-9_-]{1,80}$",
description="Optional idempotency key, retained with the most recent 10 jobs")
class ToolInventoryResponse(BaseModel):
key: str
id: str
version: str | None
installed: bool
detected_version: str | None
error: str | None
usage: str
class ToolJobItemResponse(BaseModel):
key: str
id: str
version: str | None
status: Literal["queued", "installing", "installed", "already_installed", "failed", "interrupted", "skipped"]
detected_version: str | None
error: str | None
output: str
truncated: bool
class ToolJobSummaryResponse(BaseModel):
id: str
status: Literal["queued", "starting", "waiting", "installing", "succeeded", "failed", "interrupted"]
created_at: float
completed_at: float | None
class ToolJobResponse(ToolJobSummaryResponse):
vm_id: str
source: Literal["create", "user"]
updated_at: float
start_if_stopped: bool
restart: bool
request_id: str | None
error: str | None
outcome_unknown: bool
items: list[ToolJobItemResponse]
class VmToolsResponse(BaseModel):
vm_id: str
inventory: list[ToolInventoryResponse]
checked_at: float | None
agent_ready: bool
guest_busy: bool
supported: bool | None
error: str | None
stale: bool
latest_job: ToolJobResponse | None
jobs: list[ToolJobSummaryResponse]
class DiagnosticResponse(BaseModel):
name: str
ok: bool