269 lines
8.3 KiB
Python
269 lines
8.3 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Any, Literal
|
|
|
|
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
|
|
|
from app.utils.validators import (
|
|
validate_app_id,
|
|
validate_container_name,
|
|
validate_docker_digest,
|
|
validate_docker_image,
|
|
validate_docker_tag,
|
|
validate_env_name,
|
|
validate_package_name,
|
|
validate_port_mapping,
|
|
validate_restart_policy,
|
|
validate_service_name,
|
|
validate_sha256,
|
|
validate_volume_mapping,
|
|
validate_version,
|
|
)
|
|
|
|
|
|
TaskType = Literal["install", "update", "remove"]
|
|
TaskStatus = Literal["queued", "running", "success", "failed", "cancelled"]
|
|
ComponentType = Literal["deb", "docker", "docker_compose"]
|
|
|
|
|
|
class CamelModel(BaseModel):
|
|
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
|
|
|
|
class HealthResponse(CamelModel):
|
|
status: str
|
|
agent_version: str = Field(alias="agentVersion")
|
|
hostname: str
|
|
os: str
|
|
architecture: str
|
|
|
|
|
|
class SystemInfoResponse(CamelModel):
|
|
hostname: str
|
|
os: str
|
|
kernel: str
|
|
architecture: str
|
|
disk_free: str = Field(alias="diskFree")
|
|
memory_total: str = Field(alias="memoryTotal")
|
|
|
|
|
|
class InstallRequest(CamelModel):
|
|
app_id: str = Field(alias="appId")
|
|
version: str | None = None
|
|
app_name: str | None = Field(default=None, alias="appName")
|
|
package_name: str | None = Field(default=None, alias="packageName")
|
|
service_name: str | None = Field(default=None, alias="serviceName")
|
|
download_url: str | None = Field(default=None, alias="downloadUrl")
|
|
checksum: str | None = None
|
|
sha256: str | None = None
|
|
|
|
@field_validator("app_id")
|
|
@classmethod
|
|
def _app_id(cls, value: str) -> str:
|
|
return validate_app_id(value)
|
|
|
|
@field_validator("version")
|
|
@classmethod
|
|
def _version(cls, value: str | None) -> str | None:
|
|
return validate_version(value) if value else None
|
|
|
|
@field_validator("package_name")
|
|
@classmethod
|
|
def _package_name(cls, value: str | None) -> str | None:
|
|
return validate_package_name(value) if value else None
|
|
|
|
@field_validator("service_name")
|
|
@classmethod
|
|
def _service_name(cls, value: str | None) -> str | None:
|
|
return validate_service_name(value)
|
|
|
|
@model_validator(mode="after")
|
|
def _direct_package_fields(self) -> "InstallRequest":
|
|
digest = self.sha256 or self.checksum
|
|
if digest:
|
|
normalized = validate_sha256(digest)
|
|
self.sha256 = normalized
|
|
self.checksum = normalized
|
|
|
|
has_direct_package = any([self.download_url, self.package_name, self.checksum, self.sha256])
|
|
if has_direct_package and not all([self.download_url, self.package_name, self.checksum or self.sha256]):
|
|
raise ValueError("direct install requires packageName, downloadUrl, and checksum/sha256")
|
|
return self
|
|
|
|
|
|
class UpdateRequest(InstallRequest):
|
|
current_version: str | None = Field(default=None, alias="currentVersion")
|
|
target_version: str | None = Field(default=None, alias="targetVersion")
|
|
|
|
@model_validator(mode="after")
|
|
def _normalize_target(self) -> "UpdateRequest":
|
|
if self.target_version:
|
|
self.target_version = validate_version(self.target_version)
|
|
self.version = self.target_version
|
|
return self
|
|
|
|
|
|
class RemoveRequest(CamelModel):
|
|
app_id: str = Field(alias="appId")
|
|
package_name: str | None = Field(default=None, alias="packageName")
|
|
service_name: str | None = Field(default=None, alias="serviceName")
|
|
purge: bool = False
|
|
|
|
@field_validator("app_id")
|
|
@classmethod
|
|
def _app_id(cls, value: str) -> str:
|
|
return validate_app_id(value)
|
|
|
|
@field_validator("package_name")
|
|
@classmethod
|
|
def _package_name(cls, value: str | None) -> str | None:
|
|
return validate_package_name(value) if value else None
|
|
|
|
@field_validator("service_name")
|
|
@classmethod
|
|
def _service_name(cls, value: str | None) -> str | None:
|
|
return validate_service_name(value)
|
|
|
|
|
|
class ServiceRequest(CamelModel):
|
|
service_name: str = Field(alias="serviceName")
|
|
|
|
@field_validator("service_name")
|
|
@classmethod
|
|
def _service_name(cls, value: str) -> str:
|
|
return validate_service_name(value) or value
|
|
|
|
|
|
class TaskQueuedResponse(CamelModel):
|
|
task_id: str = Field(alias="taskId")
|
|
status: str
|
|
|
|
|
|
class DebComponent(CamelModel):
|
|
component_id: str = Field(alias="componentId")
|
|
type: Literal["deb"] = "deb"
|
|
install_order: int = Field(default=10, alias="installOrder")
|
|
required: bool = True
|
|
package_name: str = Field(alias="packageName")
|
|
version: str
|
|
download_url: str = Field(alias="downloadUrl")
|
|
sha256: str
|
|
service_name: str | None = Field(default=None, alias="serviceName")
|
|
|
|
@field_validator("component_id")
|
|
@classmethod
|
|
def _component_id(cls, value: str) -> str:
|
|
return validate_app_id(value)
|
|
|
|
@field_validator("package_name")
|
|
@classmethod
|
|
def _package_name(cls, value: str) -> str:
|
|
return validate_package_name(value)
|
|
|
|
@field_validator("version")
|
|
@classmethod
|
|
def _version(cls, value: str) -> str:
|
|
return validate_version(value)
|
|
|
|
@field_validator("sha256")
|
|
@classmethod
|
|
def _sha256(cls, value: str) -> str:
|
|
return validate_sha256(value)
|
|
|
|
@field_validator("service_name")
|
|
@classmethod
|
|
def _service_name(cls, value: str | None) -> str | None:
|
|
return validate_service_name(value)
|
|
|
|
|
|
class DockerComponent(CamelModel):
|
|
component_id: str = Field(alias="componentId")
|
|
type: Literal["docker"] = "docker"
|
|
install_order: int = Field(default=10, alias="installOrder")
|
|
required: bool = True
|
|
image: str
|
|
tag: str | None = None
|
|
digest: str | None = None
|
|
container_name: str | None = Field(default=None, alias="containerName")
|
|
restart_policy: str = Field(default="unless-stopped", alias="restartPolicy")
|
|
ports: list[str] = Field(default_factory=list)
|
|
volumes: list[str] = Field(default_factory=list)
|
|
env: dict[str, str] = Field(default_factory=dict)
|
|
|
|
@field_validator("component_id")
|
|
@classmethod
|
|
def _component_id(cls, value: str) -> str:
|
|
return validate_app_id(value)
|
|
|
|
@field_validator("image")
|
|
@classmethod
|
|
def _image(cls, value: str) -> str:
|
|
return validate_docker_image(value)
|
|
|
|
@field_validator("tag")
|
|
@classmethod
|
|
def _tag(cls, value: str | None) -> str | None:
|
|
return validate_docker_tag(value)
|
|
|
|
@field_validator("digest")
|
|
@classmethod
|
|
def _digest(cls, value: str | None) -> str | None:
|
|
return validate_docker_digest(value)
|
|
|
|
@field_validator("restart_policy")
|
|
@classmethod
|
|
def _restart_policy(cls, value: str | None) -> str:
|
|
return validate_restart_policy(value)
|
|
|
|
@field_validator("ports")
|
|
@classmethod
|
|
def _ports(cls, values: list[str]) -> list[str]:
|
|
return [validate_port_mapping(value) for value in values]
|
|
|
|
@field_validator("volumes")
|
|
@classmethod
|
|
def _volumes(cls, values: list[str]) -> list[str]:
|
|
return [validate_volume_mapping(value) for value in values]
|
|
|
|
@field_validator("env")
|
|
@classmethod
|
|
def _env(cls, values: dict[str, Any]) -> dict[str, str]:
|
|
normalized: dict[str, str] = {}
|
|
for key, value in values.items():
|
|
normalized[validate_env_name(str(key))] = str(value)
|
|
return normalized
|
|
|
|
@model_validator(mode="after")
|
|
def _container_name(self) -> "DockerComponent":
|
|
self.container_name = validate_container_name(self.container_name or self.component_id)
|
|
return self
|
|
|
|
|
|
class RawComponent(CamelModel):
|
|
component_id: str = Field(alias="componentId")
|
|
type: ComponentType
|
|
install_order: int = Field(default=10, alias="installOrder")
|
|
required: bool = True
|
|
payload: dict[str, Any] = Field(default_factory=dict)
|
|
|
|
|
|
class AppManifest(CamelModel):
|
|
schema_version: str = Field(default="1.0", alias="schemaVersion")
|
|
app_id: str = Field(alias="appId")
|
|
app_name: str = Field(alias="appName")
|
|
version: str
|
|
open_url: str | None = Field(default=None, alias="openUrl")
|
|
architecture: str = "amd64"
|
|
components: list[dict[str, Any]]
|
|
signature: str | None = None
|
|
|
|
@field_validator("app_id")
|
|
@classmethod
|
|
def _app_id(cls, value: str) -> str:
|
|
return validate_app_id(value)
|
|
|
|
@field_validator("version")
|
|
@classmethod
|
|
def _version(cls, value: str) -> str:
|
|
return validate_version(value)
|