66 lines
2.1 KiB
Python
66 lines
2.1 KiB
Python
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)
|