"""CRUD operations for Docker containers.""" from datetime import datetime from typing import List, Optional from sqlalchemy import case, delete, func, select from sqlalchemy.ext.asyncio import AsyncSession from app.models.docker_container import DockerContainer class DockerContainerRepository: """Repository for Docker container CRUD operations.""" def __init__(self, session: AsyncSession): self.session = session async def get(self, container_db_id: int) -> Optional[DockerContainer]: """Get a container by its database ID.""" result = await self.session.execute( select(DockerContainer).where(DockerContainer.id == container_db_id) ) return result.scalar_one_or_none() async def get_by_container_id(self, host_id: str, container_id: str) -> Optional[DockerContainer]: """Get a container by host ID and Docker container ID.""" result = await self.session.execute( select(DockerContainer).where( DockerContainer.host_id == host_id, DockerContainer.container_id == container_id ).limit(1) ) return result.scalars().first() async def get_by_name(self, host_id: str, name: str) -> Optional[DockerContainer]: """Get a container by host ID and container name.""" result = await self.session.execute( select(DockerContainer).where( DockerContainer.host_id == host_id, DockerContainer.name == name ).limit(1) ) return result.scalars().first() async def list_by_host( self, host_id: str, state: Optional[str] = None, compose_project: Optional[str] = None ) -> List[DockerContainer]: """List all containers for a host with optional filters.""" query = select(DockerContainer).where(DockerContainer.host_id == host_id) if state: query = query.where(DockerContainer.state == state) if compose_project: query = query.where(DockerContainer.compose_project == compose_project) query = query.order_by(DockerContainer.name) result = await self.session.execute(query) return list(result.scalars().all()) async def count_by_host(self, host_id: str) -> dict: """Count containers by state for a host.""" result = await self.session.execute( select( func.count(DockerContainer.id).label('total'), func.sum(case((DockerContainer.state == 'running', 1), else_=0)).label('running') ).where(DockerContainer.host_id == host_id) ) row = result.one() return { "total": row.total or 0, "running": row.running or 0 } async def upsert( self, host_id: str, container_id: str, name: str, image: Optional[str] = None, state: str = "unknown", status: Optional[str] = None, health: Optional[str] = None, created_at: Optional[datetime] = None, ports: Optional[dict] = None, labels: Optional[dict] = None, compose_project: Optional[str] = None ) -> DockerContainer: """Create or update a container.""" existing = await self.get_by_container_id(host_id, container_id) if existing: existing.name = name existing.image = image existing.state = state existing.status = status existing.health = health existing.created_at = created_at existing.ports = ports existing.labels = labels existing.compose_project = compose_project existing.last_update_at = datetime.utcnow() return existing container = DockerContainer( host_id=host_id, container_id=container_id, name=name, image=image, state=state, status=status, health=health, created_at=created_at, ports=ports, labels=labels, compose_project=compose_project ) self.session.add(container) return container async def delete_by_host(self, host_id: str) -> int: """Delete all containers for a host.""" result = await self.session.execute( delete(DockerContainer).where(DockerContainer.host_id == host_id) ) return result.rowcount async def delete_stale(self, host_id: str, current_container_ids: List[str]) -> int: """Delete containers that no longer exist on the host.""" if not current_container_ids: return await self.delete_by_host(host_id) result = await self.session.execute( delete(DockerContainer).where( DockerContainer.host_id == host_id, DockerContainer.container_id.notin_(current_container_ids) ) ) return result.rowcount async def list_all( self, state: Optional[str] = None, compose_project: Optional[str] = None, health: Optional[str] = None, host_ids: Optional[List[str]] = None ) -> List[DockerContainer]: """List all containers across all hosts with optional filters.""" query = select(DockerContainer) if state: query = query.where(DockerContainer.state == state) if compose_project: query = query.where(DockerContainer.compose_project == compose_project) if health: query = query.where(DockerContainer.health == health) if host_ids: query = query.where(DockerContainer.host_id.in_(host_ids)) query = query.order_by(DockerContainer.host_id, DockerContainer.name) result = await self.session.execute(query) return list(result.scalars().all()) async def count_all(self) -> dict: """Count all containers by state across all hosts.""" result = await self.session.execute( select( func.count(DockerContainer.id).label('total'), func.sum(case((DockerContainer.state == 'running', 1), else_=0)).label('running'), func.sum(case((DockerContainer.state == 'exited', 1), else_=0)).label('stopped'), func.sum(case((DockerContainer.state == 'paused', 1), else_=0)).label('paused'), func.count(func.distinct(DockerContainer.host_id)).label('hosts_count'), func.max(DockerContainer.last_update_at).label('last_update') ) ) row = result.one() return { "total": row.total or 0, "running": row.running or 0, "stopped": row.stopped or 0, "paused": row.paused or 0, "hosts_count": row.hosts_count or 0, "last_update": row.last_update }