import os import subprocess import json from typing import List, Dict, Optional from pathlib import Path import logging logger = logging.getLogger(__name__) class DockerComposeManager: """Manager pour gérer les docker-compose localisés dans /home/innotex/Docker Inspiré de TrueNAS Scale avec support pour: - Découverte automatique des docker-compose - Gestion centralisée des conteneurs - Mise à jour des images """ DOCKER_COMPOSE_DIR = "/home/innotex/Docker" def __init__(self): self.docker_dir = Path(self.DOCKER_COMPOSE_DIR) if not self.docker_dir.exists(): logger.warning(f"Répertoire Docker absent: {self.DOCKER_COMPOSE_DIR}") def discover_compose_files(self) -> List[Dict[str, str]]: """Découvre tous les docker-compose.*.yml dans le répertoire""" compose_files = [] if not self.docker_dir.exists(): return compose_files try: for file in sorted(self.docker_dir.glob("docker-compose.*.yml")): app_name = file.stem.replace("docker-compose.", "") compose_files.append({ "name": app_name, "file": file.name, "path": str(file), "exists": file.exists() }) except Exception as e: logger.error(f"Erreur lors de la découverte des docker-compose: {e}") return compose_files def get_compose_status(self, compose_file: str) -> Dict: """Récupère l'état des conteneurs d'un docker-compose""" try: file_path = self.docker_dir / compose_file if not file_path.exists(): return {"status": "error", "message": f"Fichier non trouvé: {compose_file}"} # Exécuter docker-compose ps result = subprocess.run( ["docker-compose", "-f", str(file_path), "ps", "--format=json"], capture_output=True, text=True, cwd=str(self.docker_dir), timeout=10 ) if result.returncode != 0: return {"status": "error", "message": result.stderr} try: containers = json.loads(result.stdout) if result.stdout else [] return { "status": "success", "file": compose_file, "containers": containers, "count": len(containers) } except json.JSONDecodeError: # Fallback si le format JSON n'est pas disponible return { "status": "success", "file": compose_file, "output": result.stdout, "count": len(result.stdout.strip().split('\n')) } except subprocess.TimeoutExpired: return {"status": "error", "message": "Timeout lors de la récupération du statut"} except Exception as e: logger.error(f"Erreur: {e}") return {"status": "error", "message": str(e)} def start_compose(self, compose_file: str) -> Dict: """Démarre les conteneurs d'un docker-compose""" try: file_path = self.docker_dir / compose_file if not file_path.exists(): return {"status": "error", "message": f"Fichier non trouvé: {compose_file}"} result = subprocess.run( ["docker-compose", "-f", str(file_path), "up", "-d"], capture_output=True, text=True, cwd=str(self.docker_dir), timeout=60 ) if result.returncode == 0: return { "status": "success", "message": f"Services démarrés pour {compose_file}", "output": result.stdout } else: return {"status": "error", "message": result.stderr} except subprocess.TimeoutExpired: return {"status": "error", "message": "Timeout lors du démarrage des services"} except Exception as e: logger.error(f"Erreur: {e}") return {"status": "error", "message": str(e)} def stop_compose(self, compose_file: str) -> Dict: """Arrête les conteneurs d'un docker-compose""" try: file_path = self.docker_dir / compose_file if not file_path.exists(): return {"status": "error", "message": f"Fichier non trouvé: {compose_file}"} result = subprocess.run( ["docker-compose", "-f", str(file_path), "stop"], capture_output=True, text=True, cwd=str(self.docker_dir), timeout=60 ) if result.returncode == 0: return { "status": "success", "message": f"Services arrêtés pour {compose_file}", "output": result.stdout } else: return {"status": "error", "message": result.stderr} except subprocess.TimeoutExpired: return {"status": "error", "message": "Timeout lors de l'arrêt des services"} except Exception as e: logger.error(f"Erreur: {e}") return {"status": "error", "message": str(e)} def down_compose(self, compose_file: str) -> Dict: """Arrête et supprime les conteneurs d'un docker-compose""" try: file_path = self.docker_dir / compose_file if not file_path.exists(): return {"status": "error", "message": f"Fichier non trouvé: {compose_file}"} result = subprocess.run( ["docker-compose", "-f", str(file_path), "down"], capture_output=True, text=True, cwd=str(self.docker_dir), timeout=60 ) if result.returncode == 0: return { "status": "success", "message": f"Services arrêtés et supprimés pour {compose_file}", "output": result.stdout } else: return {"status": "error", "message": result.stderr} except subprocess.TimeoutExpired: return {"status": "error", "message": "Timeout lors de la suppression des services"} except Exception as e: logger.error(f"Erreur: {e}") return {"status": "error", "message": str(e)} def restart_compose(self, compose_file: str) -> Dict: """Redémarre les conteneurs d'un docker-compose""" try: file_path = self.docker_dir / compose_file if not file_path.exists(): return {"status": "error", "message": f"Fichier non trouvé: {compose_file}"} result = subprocess.run( ["docker-compose", "-f", str(file_path), "restart"], capture_output=True, text=True, cwd=str(self.docker_dir), timeout=60 ) if result.returncode == 0: return { "status": "success", "message": f"Services redémarrés pour {compose_file}", "output": result.stdout } else: return {"status": "error", "message": result.stderr} except subprocess.TimeoutExpired: return {"status": "error", "message": "Timeout lors du redémarrage des services"} except Exception as e: logger.error(f"Erreur: {e}") return {"status": "error", "message": str(e)} def pull_compose_images(self, compose_file: str) -> Dict: """Pull les images d'un docker-compose""" try: file_path = self.docker_dir / compose_file if not file_path.exists(): return {"status": "error", "message": f"Fichier non trouvé: {compose_file}"} result = subprocess.run( ["docker-compose", "-f", str(file_path), "pull"], capture_output=True, text=True, cwd=str(self.docker_dir), timeout=300 ) if result.returncode == 0: return { "status": "success", "message": f"Images téléchargées pour {compose_file}", "output": result.stdout } else: return {"status": "error", "message": result.stderr} except subprocess.TimeoutExpired: return {"status": "error", "message": "Timeout lors du téléchargement des images"} except Exception as e: logger.error(f"Erreur: {e}") return {"status": "error", "message": str(e)} def logs_compose(self, compose_file: str, tail: int = 100) -> Dict: """Récupère les logs d'un docker-compose""" try: file_path = self.docker_dir / compose_file if not file_path.exists(): return {"status": "error", "message": f"Fichier non trouvé: {compose_file}"} result = subprocess.run( ["docker-compose", "-f", str(file_path), "logs", "--tail", str(tail)], capture_output=True, text=True, cwd=str(self.docker_dir), timeout=10 ) if result.returncode == 0: return { "status": "success", "file": compose_file, "logs": result.stdout } else: return {"status": "error", "message": result.stderr} except subprocess.TimeoutExpired: return {"status": "error", "message": "Timeout lors de la récupération des logs"} except Exception as e: logger.error(f"Erreur: {e}") return {"status": "error", "message": str(e)}