Script de mise à jour des projets Docker Compose

#!/bin/python3
 
#Appel des dépendances
import subprocess
import os
from typing import Dict, List, Tuple, Optional
 
# Fonction qui affiche le message d'accueil
def display_welcome_message():
    """
    Affiche un message d'accueil stylisé pour le script de mise à jour Docker Compose.
    """
    # Couleurs ANSI (fonctionnent dans la plupart des terminaux Linux)
    RESET = "\033[0m"
    GREEN = "\033[92m"
    BOLD = "\033[1m"
 
    # Message d'accueil personnalisé
    welcome_message = f"""
    {BOLD}{GREEN}🚀 Bienvenue dans le script de mise à jour de projets Docker Compose ! 🚀
 
    ℹ️  Pré-requis :
 
        1️⃣ Le paquet docker-compose-plugin doit être installé. La version legacy de docker-compose n'est pas compatible ❌
 
        2️⃣ Le service Docker doit être démarré normalement
 
        3️⃣ Les projets Docker Compose doivent être stockés dans le répertoire d'un des utilisateurs du système (/home/xxxx)
            Autrement, le chemin racine des projets peut être modifié à la ligne 179 de ce script (replacez le "/home")
 
        4️⃣ Le tag 'latest' doit également être mentionné dans la configuration des images à mettre à jour{RESET}
 
    """
    # Afficher le message
    print(welcome_message)
 
# Fonction pour récupérer les projets docker compose actif et le chemin de la configuration
def find_docker_compose_projects(root_dir: str = "/") -> Dict[str, str]:
    """
    Cherche les fichiers docker-compose.yml dans les répertoires et vérifie les projets actifs.
    Retourne un dictionnaire {nom_projet: chemin_du_repertoire}.
    """
    projects = {}
    for dirpath, _, filenames in os.walk(root_dir):
        if "docker-compose.yml" in filenames:
            try:
                # Vérifier si le projet est en cours d'exécution
                result = subprocess.run(
                    ["docker", "compose", "-f", os.path.join(dirpath, "docker-compose.yml"), "ps", "--services", "--filter", "status=running"],
                    capture_output=True,
                    text=True,
                )
                services = result.stdout.strip().split("\n")
                if services and services[0]:
                    project_name = os.path.basename(dirpath)
                    projects[project_name] = dirpath
            except Exception as e:
                print(f"Erreur pour {dirpath}: {e}")
    return projects
 
# Fonction qui affiche les projets docker compose numérotés
def display_project_menu(projects: Dict[str, str]) -> None:
    """
    Affiche un menu numéroté des projets Docker Compose actifs.
 
    Args:
        projects: Dictionnaire {nom_projet: chemin_du_repertoire} retourné par `find_docker_compose_projects`.
    """
    if not projects:
        print("Aucun projet Docker Compose actif trouvé.")
        return
 
    print("\nProjets Docker Compose actifs :")
    for index, (project_name, project_path) in enumerate(projects.items(), start=1):
        print(f"{index}. {project_name} ({project_path})")
 
# Fonction qui demande à l'utilisateur de choisir un projet à mettre à jour et lancer la maj si OK
def select_and_confirm_project(projects: Dict[str, str]) -> Optional[Tuple[str, str]]:
    """
    Demande à l'utilisateur de sélectionner un projet Docker Compose par son numéro,
    vérifie la validité du choix, et demande confirmation.
 
    Args:
        projects: Dictionnaire {nom_projet: chemin_du_repertoire} des projets actifs.
 
    Returns:
        Un tuple (nom_projet, chemin) si la sélection est confirmée, sinon None.
    """
    if not projects:
        print("Aucun projet disponible.")
        return None
 
    try:
        # Afficher le menu
        display_project_menu(projects)
 
        # Demander la sélection
        selection = input("\nEntrez le numéro du projet à mettre à jour (ou 'q' pour quitter) : ")
        if selection.lower() == 'q':
            return None
 
        selected_index = int(selection) - 1
        project_list = list(projects.items())
        if selected_index < 0 or selected_index >= len(project_list):
            print("Numéro invalide.")
            return None
 
        selected_project = project_list[selected_index]
 
        # Demander confirmation
        confirm = input(f"\nVous avez sélectionné : {selected_project[0]} ({selected_project[1]})\nConfirmez-vous cette sélection ? (o/n) : ")
        if confirm.lower() == 'o':
            return selected_project
        else:
            print("Sélection annulée.")
            return None
 
    except ValueError:
        print("Veuillez entrer un numéro valide.")
        return None
 
# Fonction de mise à jour    
def update_docker_compose_project(project_name: str, project_path: str) -> bool:
    """
    Met à jour un projet Docker Compose en exécutant `docker-compose pull` et `docker-compose up -d`.
 
    Args:
        project_name: Nom du projet (pour les logs).
        project_path: Chemin du répertoire contenant le fichier docker-compose.yml.
 
    Returns:
        bool: True si la mise à jour a réussi, False sinon.
    """
    try:
        print(f"\nMise à jour du projet {project_name} dans {project_path}...")
 
        # 1. Se déplacer dans le répertoire du projet
        os.chdir(project_path)
 
        # 2. Exécuter `docker-compose pull` pour récupérer les dernières images
        print("Récupération des dernières images...")
        pull_result = subprocess.run(
            ["docker", "compose", "pull"],
            capture_output=True,
            text=True,
            check=True
        )
        print(pull_result.stdout)
 
        # 3. Exécuter `docker-compose up -d` pour redémarrer les conteneurs
        print("Redémarrage des conteneurs...")
        up_result = subprocess.run(
            ["docker", "compose", "up", "-d"],
            capture_output=True,
            text=True,
            check=True
        )
        print(up_result.stdout)
 
        print(f"\nLe projet {project_name} a été mis à jour avec succès !")
        return True
 
    except subprocess.CalledProcessError as e:
        print(f"\n❌ Erreur lors de la mise à jour du projet {project_name} :")
        print(f"Commande échouée : {e.cmd}")
        print(f"Code de retour : {e.returncode}")
        print(f"Erreur : {e.stderr}")
        return False
 
    except Exception as e:
        print(f"\n❌ Erreur inattendue lors de la mise à jour du projet {project_name} : {e}")
        return False
 
# Fonction principale
def main():
 
    display_welcome_message() # Message d'accueil
 
    projects = find_docker_compose_projects("/home")  # Remplacer "/home" par le chemin racine des projets Docker Compose
    if not projects:
        print("Aucun projet Docker Compose actif trouvé.")
        return
 
    selected = select_and_confirm_project(projects)
    if selected:
        project_name, project_path = selected
        success = update_docker_compose_project(project_name, project_path) # Executer la mise à jour
 
# Executer la fonction main()
if __name__ == "__main__":
    main()