import asyncio from pathlib import Path import yaml from app.integrations.docker.models import DockerContainer, DockerPortBinding from app.integrations.docker.ports import _get_service_binding COMPOSE_NAMES = ( "compose.yml", "compose.yaml", "docker-compose.yml", "docker-compose.yaml", ) type ComposeDocument = dict[str, object] def _get_visible_folders(directory: Path) -> list[Path]: try: entries = list(Path(directory).iterdir()) except OSError: return [] folders: list[Path] = [] for entry in entries: if entry.name.startswith("."): continue try: if entry.is_dir(): folders.append(entry) except OSError: continue return folders def find_docker_compose(path: Path) -> tuple[ComposeDocument, Path] | None: try: if not path or not path.is_dir(): return None except OSError: return None for name in COMPOSE_NAMES: file = path / name try: if file.is_file(): content = yaml.safe_load(file.read_text()) or {} return content, file except OSError: continue return None def get_services(folder: Path) -> list[DockerContainer] | None: services: list[DockerContainer] = [] compose_data = find_docker_compose(folder) if compose_data is None: return None docker_compose, _ = compose_data compose_services = docker_compose.get("services") or {} if not isinstance(compose_services, dict) or not compose_services: return None for service_name, config in compose_services.items(): mappings = config.get("ports") if isinstance(config, dict) else None if not isinstance(mappings, list) or not mappings: continue bindings: list[DockerPortBinding] = [ {"published": published, "host": host} for published, host in ( _get_service_binding(mapping) for mapping in mappings ) if published is not None ] if not bindings: continue services.append( { "name": service_name, "ports": [binding["published"] for binding in bindings], "bindings": bindings, "folder": folder, } ) return services def get_all_services_in_dir( directory: str | Path, ) -> list[DockerContainer]: path = Path(directory) try: if not path.is_dir(): return [] except OSError: return [] services: list[DockerContainer] = [] for sub_folder in _get_visible_folders(path): sub_services = get_services(sub_folder) if sub_services: services.extend(sub_services) return services async def run_docker_command( file: Path, command: list[str], ) -> dict[str, str] | None: if not command: return None full_command = ["docker", "compose", "-f", f"{file}", *command] process = await asyncio.create_subprocess_exec( *full_command, cwd=str(file.parent), stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) stdout, stderr = await process.communicate() if process.returncode == 0: return {"status": "success", "output": stdout.decode()} else: return {"status": "error", "reason": stderr.decode()}