# msdl/docker_manager.py import os import subprocess import sys from functools import lru_cache import yaml from msdl.config import PROJECT_ROOT, TEMP_DIR from msdl.i18n import t @lru_cache(maxsize=1) def get_docker_command(): try: subprocess.run( ["docker", "compose", "version"], check=True, capture_output=True ) return ["docker", "compose"] except subprocess.CalledProcessError: try: subprocess.run( ["docker-compose", "--version"], check=True, capture_output=True ) return ["docker-compose"] except subprocess.CalledProcessError: print(t("DOCKER_COMPOSE_NOT_FOUND")) sys.exit(1) @lru_cache(maxsize=1) def check_docker_install(): try: subprocess.run(["docker", "--version"], check=True, capture_output=True) docker_compose_cmd = get_docker_command() subprocess.run( docker_compose_cmd + ["version"], check=True, capture_output=True ) print(t("DOCKER_INSTALLED")) return True except subprocess.CalledProcessError as e: print(t("DOCKER_INSTALL_ERROR", error=str(e))) return False except FileNotFoundError: print(t("DOCKER_NOT_FOUND")) return False def stop_and_remove_containers(): docker_compose_cmd = get_docker_command() compose_file = os.path.join(TEMP_DIR, "docker-compose.yaml") # Read the docker-compose.yaml file try: with open(compose_file, "r") as file: compose_config = yaml.safe_load(file) except Exception as e: print(t("COMPOSE_FILE_READ_ERROR", error=str(e))) return # Get project name and service names project_name = compose_config.get("name", "mindsearch") service_names = list(compose_config.get("services", {}).keys()) # Use only the project name as the container prefix container_prefix = f"{project_name}_" try: # 1. Try to stop containers using the current docker-compose.yaml subprocess.run( docker_compose_cmd + ["-f", compose_file, "down", "-v", "--remove-orphans"], check=True, ) except subprocess.CalledProcessError: print(t("CURRENT_COMPOSE_STOP_FAILED")) # 2. Attempt to clean up potentially existing containers, regardless of the success of the previous step try: # List all containers (including stopped ones) result = subprocess.run( ["docker", "ps", "-a", "--format", "{{.Names}}"], check=True, capture_output=True, text=True, ) all_containers = result.stdout.splitlines() # 3. Filter out containers belonging to our project project_containers = [ c for c in all_containers if c.startswith(container_prefix) or any(c == f"{project_name}-{service}" for service in service_names) ] if project_containers: # 4. Force stop and remove these containers for container in project_containers: try: subprocess.run(["docker", "stop", container], check=True) subprocess.run(["docker", "rm", "-f", container], check=True) print(t("CONTAINER_STOPPED_AND_REMOVED", container=container)) except subprocess.CalledProcessError as e: print(t("CONTAINER_STOP_ERROR", container=container, error=str(e))) # 5. Clean up potentially leftover networks try: subprocess.run(["docker", "network", "prune", "-f"], check=True) print(t("NETWORKS_PRUNED")) except subprocess.CalledProcessError as e: print(t("NETWORK_PRUNE_ERROR", error=str(e))) except subprocess.CalledProcessError as e: print(t("DOCKER_LIST_ERROR", error=str(e))) print(t("CONTAINERS_STOPPED_AND_REMOVED")) def run_docker_compose(): docker_compose_cmd = get_docker_command() try: print(t("STARTING_CONTAINERS_WITH_BUILD")) subprocess.run( docker_compose_cmd + [ "-f", os.path.join(TEMP_DIR, "docker-compose.yaml"), "--env-file", os.path.join(TEMP_DIR, ".env"), "up", "-d", "--build", ], check=True, ) print(t("CONTAINERS_STARTED")) except subprocess.CalledProcessError as e: print(t("DOCKER_ERROR", error=str(e))) print(t("DOCKER_OUTPUT")) print(e.output.decode() if e.output else "No output") stop_and_remove_containers() sys.exit(1) def update_docker_compose_paths(project_root=PROJECT_ROOT): docker_compose_path = os.path.join(TEMP_DIR, "docker-compose.yaml") with open(docker_compose_path, "r") as file: compose_data = yaml.safe_load(file) for service in compose_data["services"].values(): if "build" in service: if "context" in service["build"]: if service["build"]["context"] == "..": service["build"]["context"] = project_root else: service["build"]["context"] = os.path.join( project_root, service["build"]["context"] ) if "dockerfile" in service["build"]: dockerfile_name = os.path.basename(service["build"]["dockerfile"]) service["build"]["dockerfile"] = os.path.join(TEMP_DIR, dockerfile_name) with open(docker_compose_path, "w") as file: yaml.dump(compose_data, file) print(t("PATHS_UPDATED")) def main(): if check_docker_install(): update_docker_compose_paths() run_docker_compose() else: sys.exit(1) if __name__ == "__main__": main()