letsbe-orchestrator/app/models/agent.py

93 lines
2.6 KiB
Python

"""Agent model for SysAdmin automation workers."""
import uuid
from datetime import datetime
from enum import Enum
from typing import TYPE_CHECKING
from sqlalchemy import DateTime, ForeignKey, String, Text
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.models.base import Base, TimestampMixin, UUIDMixin
if TYPE_CHECKING:
from app.models.registration_token import RegistrationToken
from app.models.task import Task
from app.models.tenant import Tenant
class AgentStatus(str, Enum):
"""Agent status values."""
ONLINE = "online"
OFFLINE = "offline"
INVALID = "invalid" # Agent with NULL tenant_id, must re-register
class Agent(UUIDMixin, TimestampMixin, Base):
"""
Agent model representing a SysAdmin automation worker.
Agents register with the orchestrator and receive tasks to execute.
"""
__tablename__ = "agents"
tenant_id: Mapped[uuid.UUID | None] = mapped_column(
ForeignKey("tenants.id", ondelete="CASCADE"),
nullable=True,
index=True,
)
name: Mapped[str] = mapped_column(
String(255),
nullable=False,
)
version: Mapped[str] = mapped_column(
String(50),
nullable=False,
default="",
)
status: Mapped[str] = mapped_column(
String(20),
nullable=False,
default=AgentStatus.OFFLINE.value,
index=True,
)
last_heartbeat: Mapped[datetime | None] = mapped_column(
DateTime(timezone=True),
nullable=True,
)
# Legacy field - kept for backward compatibility during migration
# Will be removed after all agents migrate to new auth scheme
token: Mapped[str] = mapped_column(
Text,
nullable=False,
default="",
)
# New secure credential storage - SHA-256 hash of agent secret
secret_hash: Mapped[str] = mapped_column(
String(64),
nullable=False,
default="",
comment="SHA-256 hash of the agent secret",
)
# Reference to the registration token used to create this agent
registration_token_id: Mapped[uuid.UUID | None] = mapped_column(
ForeignKey("registration_tokens.id", ondelete="SET NULL"),
nullable=True,
index=True,
)
# Relationships
tenant: Mapped["Tenant | None"] = relationship(
back_populates="agents",
)
tasks: Mapped[list["Task"]] = relationship(
back_populates="agent",
lazy="selectin",
)
registration_token: Mapped["RegistrationToken | None"] = relationship()
def __repr__(self) -> str:
return f"<Agent(id={self.id}, name={self.name}, status={self.status})>"