from __future__ import annotations import subprocess from dataclasses import dataclass from pathlib import Path from infra_controller.config import DockerComposeConfig @dataclass(frozen=True) class ServiceResult: returncode: int stdout: str stderr: str class ServiceManager: def __init__(self, docker: DockerComposeConfig): self._docker = docker def service_dir_for_service(self, service_name: str) -> Path: return self._docker.base_dir / service_name def _resolve_compose_file(self, service_dir: Path) -> Path: configured = service_dir / self._docker.compose_file if configured.exists(): return configured candidates = list(service_dir.glob("docker-compose*.yml")) + list(service_dir.glob("docker-compose*.yaml")) candidates = [p for p in candidates if p.is_file()] if not candidates: raise FileNotFoundError( f"Compose file not found in {service_dir} (expected {self._docker.compose_file} or docker-compose*.yml/.yaml)" ) def rank(p: Path) -> tuple[int, str]: name = p.name if name == "docker-compose.yml": return (0, name) if name == "docker-compose.yaml": return (1, name) if name.endswith(".yml"): return (2, name) return (3, name) return sorted(candidates, key=rank)[0] def apply_service(self, service_name: str) -> ServiceResult: service_dir = self.service_dir_for_service(service_name) if not service_dir.exists(): raise FileNotFoundError(f"Service directory not found: {service_dir}") compose_file = self._resolve_compose_file(service_dir) cmd = [ "docker", "compose", "-f", str(compose_file), "up", "-d", ] proc = subprocess.run(cmd, capture_output=True, text=True, cwd=str(service_dir)) return ServiceResult(returncode=proc.returncode, stdout=proc.stdout, stderr=proc.stderr)