# Fichier: python_cheats/cheatsheets/jenkins.txt
# Cheatsheet Jenkins avec Python - Guide Complet pour Débutants


[OK] QU'EST-CE QUE JENKINS ? (POUR DÉBUTANTS)

# === Introduction Simple ===

# Jenkins est un "robot" qui automatise les tâches répétitives de développement.
# Imaginez que vous devez faire ceci MANUELLEMENT chaque fois que vous modifiez votre code:
# 1. Récupérer le code depuis Git
# 2. Installer les dépendances Python
# 3. Lancer les tests
# 4. Vérifier la qualité du code
# 5. Créer un package
# 6. Déployer sur le serveur
# 
# Jenkins fait TOUT ÇA automatiquement pour vous !

# === Concepts Clés ===

# [CONSTRUCTION] JOB (ou PROJECT)
# Un "job" = une tâche automatisée
# Exemple: "Tester mon application Python"
# Un job contient des étapes (steps) à exécuter

# [OUTIL] BUILD
# Un "build" = une exécution d'un job
# Exemple: Le job "Tester mon app" a été lancé 5 fois = 5 builds
# Chaque build a un numéro (#1, #2, #3...)

# [OBJECTIF] PIPELINE
# Un "pipeline" = un job complexe avec plusieurs étapes
# Exemple: Build -> Test -> Deploy
# Comme une chaîne de montage dans une usine

# [ECRAN] NODE (ou AGENT)
# Un "node" = une machine qui exécute les jobs
# Jenkins peut distribuer le travail sur plusieurs machines

# [LISTE] QUEUE
# La "queue" = file d'attente des builds
# Si plusieurs builds sont déclenchés, ils attendent leur tour

# [DESIGN] VIEW
# Une "view" = un dossier pour organiser vos jobs
# Exemple: "Projets Python", "Projets Frontend"


# === Pourquoi utiliser Python avec Jenkins ? ===

# 1. AUTOMATISATION
# Créer 50 jobs similaires en quelques secondes au lieu de les créer manuellement

# 2. MONITORING
# Surveiller vos builds et recevoir des alertes

# 3. INTÉGRATION
# Connecter Jenkins avec d'autres outils (Slack, GitHub, AWS...)

# 4. REPORTING
# Générer des rapports personnalisés

# 5. GESTION
# Sauvegarder, restaurer, migrer vos configurations


# === Comment Jenkins fonctionne ? ===

# 1. DÉCLENCHEMENT (Trigger)
#    - Manuel: Vous cliquez sur "Build Now"
#    - Automatique: À chaque commit Git
#    - Planifié: Tous les jours à 2h du matin
#    - API: Via Python avec python-jenkins

# 2. EXÉCUTION
#    Jenkins lance les commandes que vous avez définies
#    Exemple: pytest tests/

# 3. RÉSULTAT
#    - SUCCESS [OK] : Tout s'est bien passé
#    - FAILURE [X] : Une erreur est survenue
#    - UNSTABLE [ATTENTION] : Des tests ont échoué
#    - ABORTED [STOP] : Arrêté manuellement

# 4. NOTIFICATION
#    Jenkins peut vous notifier par email, Slack, etc.


[OK] INTRODUCTION JENKINS & PYTHON

# === Intégration Python <-> Jenkins ===

# Il y a 2 façons d'utiliser Python avec Jenkins:

# 1⃣ JENKINS UTILISE PYTHON
# Jenkins exécute vos scripts Python dans les jobs
# Exemple: pytest, black, flake8...
# C'est le cas le plus courant !

# 2⃣ PYTHON CONTRÔLE JENKINS
# Vous utilisez Python pour piloter Jenkins via son API
# Bibliothèque: python-jenkins
# C'est ce que ce cheatsheet couvre !


# === La bibliothèque python-jenkins ===

# python-jenkins est un client Python pour l'API REST de Jenkins
# Elle vous permet de:
# - Créer/modifier/supprimer des jobs
# - Déclencher des builds
# - Récupérer les résultats
# - Gérer la configuration
# - Tout faire programmatiquement !

# Cas d'usage réels:
# - Automatiser création de jobs pour nouveaux projets
# - Déclencher builds depuis un script Python
# - Créer un dashboard personnalisé
# - Sauvegarder automatiquement la config Jenkins
# - Monitoring et alertes intelligentes
# - Tests automatisés de votre infrastructure CI/CD


[OK] INSTALLATION & CONFIGURATION (EXPLICATIONS DÉTAILLÉES)

# === Installer Jenkins (3 méthodes) ===

# MÉTHODE 1: Docker (RECOMMANDÉE pour débutants) *
# Pourquoi Docker? Plus simple, pas de conflits avec votre système

# Étape 1: Installer Docker
# Windows/Mac: Télécharger Docker Desktop depuis docker.com
# Ubuntu: sudo apt-get install docker.io

# Étape 2: Lancer Jenkins dans Docker
docker run -d \
  -p 8080:8080 \                    # Port web de Jenkins
  -p 50000:50000 \                  # Port pour les agents
  --name jenkins \                  # Nom du conteneur
  -v jenkins_home:/var/jenkins_home \ # Sauvegarder les données
  jenkins/jenkins:lts               # Image Jenkins LTS (Long Term Support)

# Explication des options:
# -d : Détaché (tourne en arrière-plan)
# -p 8080:8080 : Mapper le port 8080 du conteneur vers votre machine
# -v jenkins_home : Volume pour persister les données (ne pas perdre config)
# lts : Version stable, recommandée pour production

# Étape 3: Récupérer le mot de passe initial
docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword
# Copier ce mot de passe

# Étape 4: Accéder à Jenkins
# Ouvrir navigateur: http://localhost:8080
# Coller le mot de passe
# Cliquer "Install suggested plugins" (plugins recommandés)
# Créer votre compte admin


# MÉTHODE 2: Installation native Ubuntu/Debian
# Plus complexe mais meilleure performance

# Étape 1: Ajouter la clé et le repository Jenkins
wget -q -O - https://pkg.jenkins.io/debian/jenkins.io.key | sudo apt-key add -
sudo sh -c 'echo deb https://pkg.jenkins.io/debian-stable binary/ > /etc/apt/sources.list.d/jenkins.list'

# Étape 2: Installer Jenkins
sudo apt-get update
sudo apt-get install jenkins

# Étape 3: Démarrer Jenkins
sudo systemctl start jenkins
sudo systemctl enable jenkins  # Démarrage automatique

# Étape 4: Vérifier status
sudo systemctl status jenkins

# Étape 5: Récupérer mot de passe
sudo cat /var/lib/jenkins/secrets/initialAdminPassword


# MÉTHODE 3: macOS avec Homebrew
brew install jenkins-lts
brew services start jenkins-lts

# Accès: http://localhost:8080


# === Installer python-jenkins ===

# python-jenkins est la bibliothèque Python pour interagir avec Jenkins

# Dans un environnement virtuel (RECOMMANDÉ)
python -m venv jenkins_env
source jenkins_env/bin/activate  # Linux/Mac
jenkins_env\Scripts\activate     # Windows

# Installer python-jenkins
pip install python-jenkins

# Vérifier l'installation
python -c "import jenkins; print(jenkins.__version__)"

# Installer dépendances supplémentaires
pip install requests           # Pour les requêtes HTTP
pip install python-dotenv      # Pour gérer les variables d'environnement
pip install pyyaml            # Pour lire des fichiers YAML

# Créer un fichier requirements.txt
cat > requirements.txt << EOF
python-jenkins==1.8.0
requests==2.31.0
python-dotenv==1.0.0
pyyaml==6.0.1
EOF

# Installer depuis requirements.txt
pip install -r requirements.txt


# === Configuration initiale ===

# IMPORTANT: Sécurité d'abord !
# Ne JAMAIS mettre vos mots de passe dans le code

# Créer un fichier .env (variables d'environnement)
cat > .env << EOF
JENKINS_URL=http://localhost:8080
JENKINS_USER=admin
JENKINS_TOKEN=votre_token_ici
EOF

# [ATTENTION] AJOUTER .env AU .gitignore !
echo ".env" >> .gitignore


# === Créer un API Token Jenkins ===

# Pourquoi un token ? Plus sûr qu'un mot de passe
# Le token peut être révoqué sans changer le mot de passe

# Étapes pour créer un token:
# 1. Se connecter à Jenkins: http://localhost:8080
# 2. Cliquer sur votre nom (en haut à droite)
# 3. Cliquer "Configure" (Configurer)
# 4. Section "API Token"
# 5. Cliquer "Add new Token"
# 6. Donner un nom: "Python Script"
# 7. Cliquer "Generate"
# 8. COPIER LE TOKEN (on ne peut le voir qu'une fois !)
# 9. Coller dans votre fichier .env


# === Premier test de connexion ===

# Créer un fichier test_connection.py
cat > test_connection.py << 'EOF'
import jenkins
import os
from dotenv import load_dotenv

# Charger variables d'environnement
load_dotenv()

# Se connecter à Jenkins
server = jenkins.Jenkins(
    os.getenv('JENKINS_URL'),       # http://localhost:8080
    username=os.getenv('JENKINS_USER'),  # admin
    password=os.getenv('JENKINS_TOKEN')  # votre token
)

# Tester la connexion
try:
    user = server.get_whoami()
    version = server.get_version()
    
    print("[OK] Connexion réussie !")
    print(f"[UTILISATEUR] Utilisateur: {user['fullName']}")
    print(f"[PACKAGE] Version Jenkins: {version}")
except Exception as e:
    print(f"[X] Erreur de connexion: {e}")
    print("\nVérifiez:")
    print("1. Jenkins est bien démarré (http://localhost:8080)")
    print("2. Les identifiants dans .env sont corrects")
    print("3. Le token API est valide")
EOF

# Exécuter le test
python test_connection.py

# Si ça marche, vous verrez:
# [OK] Connexion réussie !
# [UTILISATEUR] Utilisateur: Admin User
# [PACKAGE] Version Jenkins: 2.387.1


# === Comprendre l'objet server ===

# L'objet 'server' est votre interface avec Jenkins
# C'est comme une télécommande pour piloter Jenkins

server = jenkins.Jenkins(url, username, password)

# Cet objet a des MÉTHODES pour tout faire:
# - server.get_jobs() : Lister les jobs
# - server.build_job() : Déclencher un build
# - server.get_build_info() : Info sur un build
# - ... et bien d'autres !

# C'est l'objet que vous utiliserez dans TOUS vos scripts


[OK] CONNEXION À JENKINS (GUIDE DÉBUTANT)

# === Méthode 1: Connexion simple (pour tester) ===

import jenkins

# Connexion basique (sans authentification)
# [ATTENTION] Ne marche QUE si Jenkins n'a pas de sécurité
server = jenkins.Jenkins('http://localhost:8080')

# En pratique, Jenkins a TOUJOURS une sécurité
# Donc cette méthode ne marchera presque jamais


# === Méthode 2: Avec username et password ===

# [ATTENTION] PAS RECOMMANDÉ (mot de passe en clair dans le code)
server = jenkins.Jenkins(
    'http://localhost:8080',
    username='admin',
    password='mon_mot_de_passe'  # Dangereux !
)


# === Méthode 3: Avec API Token (RECOMMANDÉE) * ===

# Créer un token API (voir section précédente)
# Utiliser le token au lieu du mot de passe

server = jenkins.Jenkins(
    'http://localhost:8080',
    username='admin',
    password='11234567890abcdef1234567890abcdef'  # API Token
)

# Pourquoi c'est mieux ?
# 1. Le token peut être révoqué sans changer le mot de passe
# 2. Vous pouvez créer plusieurs tokens pour différents scripts
# 3. Plus sûr si le code est partagé


# === Méthode 4: Avec variables d'environnement (LA MEILLEURE) *** ===

import os
from dotenv import load_dotenv

# Charger le fichier .env
load_dotenv()

# Récupérer les variables
server = jenkins.Jenkins(
    os.getenv('JENKINS_URL'),      # Depuis .env
    username=os.getenv('JENKINS_USER'),
    password=os.getenv('JENKINS_TOKEN')
)

# Avantages:
# 1. Pas de secrets dans le code
# 2. Facile de changer de serveur (dev/prod)
# 3. Sécurisé (le .env n'est pas commité dans Git)

# Contenu du fichier .env:
"""
JENKINS_URL=http://localhost:8080
JENKINS_USER=admin
JENKINS_TOKEN=votre_token_ici
"""


# === Vérifier que la connexion marche ===

# Méthode get_whoami() : retourne info sur l'utilisateur connecté
try:
    user = server.get_whoami()
    print("[OK] Connexion OK")
    print(f"Utilisateur: {user['fullName']}")
    print(f"ID: {user['id']}")
except jenkins.JenkinsException as e:
    print(f"[X] Erreur de connexion: {e}")
    print("Vérifiez vos identifiants !")

# Méthode get_version() : retourne version de Jenkins
try:
    version = server.get_version()
    print(f"[PACKAGE] Version Jenkins: {version}")
except Exception as e:
    print(f"[X] Impossible de récupérer la version: {e}")


# === Configuration avancée: SSL ===

# Si votre Jenkins utilise HTTPS (https://)
# et que vous avez une erreur de certificat:

# Option 1: Désactiver vérification SSL (DEV SEULEMENT !)
import ssl
import urllib3
urllib3.disable_warnings()  # Cache les warnings

server = jenkins.Jenkins(
    'https://jenkins.example.com',
    username='admin',
    password='token'
)

# Option 2: Utiliser un certificat spécifique (PRODUCTION)
import requests
session = requests.Session()
session.verify = '/chemin/vers/certificat.pem'

server = jenkins.Jenkins(
    'https://jenkins.example.com',
    username='admin',
    password='token',
    requester=session
)


# === Configuration: Timeout ===

# Par défaut, python-jenkins attend indéfiniment
# Vous pouvez définir un timeout (en secondes)

server = jenkins.Jenkins(
    'http://localhost:8080',
    username='admin',
    password='token',
    timeout=30  # Attend maximum 30 secondes
)

# Utile si Jenkins est lent ou inaccessible
# Évite que votre script se bloque


# === Structure complète d'un script ===

"""
Script complet pour se connecter à Jenkins
"""

import jenkins
import os
from dotenv import load_dotenv

def connect_to_jenkins():
    """
    Se connecter à Jenkins avec gestion d'erreurs
    """
    # Charger config
    load_dotenv()
    
    # Récupérer variables
    url = os.getenv('JENKINS_URL')
    user = os.getenv('JENKINS_USER')
    token = os.getenv('JENKINS_TOKEN')
    
    # Vérifier que les variables existent
    if not all([url, user, token]):
        raise ValueError(
            "Variables manquantes dans .env\n"
            "Assurez-vous d'avoir:\n"
            "- JENKINS_URL\n"
            "- JENKINS_USER\n"
            "- JENKINS_TOKEN"
        )
    
    # Se connecter
    try:
        server = jenkins.Jenkins(url, username=user, password=token)
        
        # Tester la connexion
        server.get_whoami()
        
        print(f"[OK] Connecté à Jenkins: {url}")
        return server
        
    except jenkins.JenkinsException as e:
        print(f"[X] Erreur Jenkins: {e}")
        raise
    except Exception as e:
        print(f"[X] Erreur inattendue: {e}")
        raise

# Utilisation
if __name__ == '__main__':
    server = connect_to_jenkins()
    
    # Maintenant vous pouvez utiliser 'server' pour tout !
    version = server.get_version()
    print(f"Version: {version}")


# === Erreurs courantes et solutions ===

# ERREUR 1: "Connection refused"
# [X] requests.exceptions.ConnectionError: Connection refused
# 
# CAUSE: Jenkins n'est pas démarré ou mauvaise URL
# 
# SOLUTIONS:
# 1. Vérifier que Jenkins tourne: http://localhost:8080 dans navigateur
# 2. Vérifier l'URL dans .env (http:// et pas https://)
# 3. Vérifier le port (8080 par défaut)


# ERREUR 2: "Unauthorized"
# [X] jenkins.JenkinsException: Unauthorized
# 
# CAUSE: Mauvais identifiants ou token expiré
# 
# SOLUTIONS:
# 1. Vérifier username dans .env
# 2. Créer un nouveau token API
# 3. Vérifier que le token est bien copié (pas d'espace)


# ERREUR 3: "Forbidden"
# [X] jenkins.JenkinsException: Forbidden
# 
# CAUSE: Utilisateur n'a pas les permissions
# 
# SOLUTIONS:
# 1. Utiliser un compte admin
# 2. Dans Jenkins: Manage Jenkins > Security > Matrix-based security
# 3. Donner les permissions nécessaires à l'utilisateur


# ERREUR 4: ".env file not found"
# [X] FileNotFoundError ou variables None
# 
# CAUSE: Fichier .env pas au bon endroit
# 
# SOLUTIONS:
# 1. Le .env doit être dans le MÊME dossier que votre script
# 2. Ou spécifier le chemin: load_dotenv('/chemin/vers/.env')


# === Résumé pour débutants ===

# 1⃣ Installer Jenkins (Docker recommandé)
# 2⃣ Créer un API Token dans Jenkins
# 3⃣ Créer un fichier .env avec vos identifiants
# 4⃣ Installer python-jenkins: pip install python-jenkins python-dotenv
# 5⃣ Se connecter avec:
"""
from dotenv import load_dotenv
import jenkins
import os

load_dotenv()
server = jenkins.Jenkins(
    os.getenv('JENKINS_URL'),
    username=os.getenv('JENKINS_USER'),
    password=os.getenv('JENKINS_TOKEN')
)
"""

# 6⃣ Tester avec: server.get_version()
# 7⃣ Vous êtes prêt ! [RAPIDE]


[OK] GESTION DES JOBS (COMPRENDRE LES JOBS)

# === C'est quoi un JOB ? ===

# Un JOB = une tâche que Jenkins peut exécuter automatiquement
# 
# Exemples de jobs:
# - "Tester mon application Python" -> Lance pytest
# - "Déployer sur le serveur" -> Copie les fichiers
# - "Vérifier la qualité du code" -> Lance flake8
# 
# Un job contient:
# 1. Un NOM (identifier)
# 2. Une DESCRIPTION (ce que fait le job)
# 3. Des ÉTAPES (commandes à exécuter)
# 4. Des DÉCLENCHEURS (quand lancer le job)
# 5. Des POST-ACTIONS (après exécution)


# === Types de jobs ===

# 1. FREESTYLE PROJECT (Simple)
# - Le plus basique
# - Interface graphique pour configurer
# - Bon pour débuter

# 2. PIPELINE (Moderne - RECOMMANDÉ)
# - Défini en code (Jenkinsfile)
# - Plus flexible et puissant
# - Peut avoir plusieurs étapes (stages)

# 3. MULTI-CONFIGURATION
# - Pour tester sur plusieurs configurations
# - Exemple: Python 3.9, 3.10, 3.11


# === Lister tous les jobs ===

# La méthode la plus simple
jobs = server.get_jobs()

# jobs est une LISTE de DICTIONNAIRES
# Chaque job a: name, url, color

# Afficher tous les jobs
for job in jobs:
    print(f"Job: {job['name']}")
    print(f"URL: {job['url']}")
    print(f"Status: {job['color']}")  # blue=OK, red=ERREUR
    print()

# Explication des "colors":
# - 'blue' : Dernier build réussi [OK]
# - 'red' : Dernier build échoué [X]
# - 'yellow' : Build instable [ATTENTION]
# - 'grey' : Jamais exécuté
# - 'disabled' : Job désactivé
# - 'blue_anime' : Build en cours (réussi)
# - 'red_anime' : Build en cours (échouait avant)


# Compter les jobs
print(f"Nombre total de jobs: {len(jobs)}")


# Filtrer les jobs échoués
failed_jobs = [job for job in jobs if job['color'] == 'red']
print(f"Jobs en échec: {len(failed_jobs)}")


# === Obtenir les détails d'un job ===

job_name = 'mon-projet-python'

# Récupérer TOUTES les infos du job
job_info = server.get_job_info(job_name)

# job_info est un GROS dictionnaire avec plein d'infos
print(f"Nom: {job_info['name']}")
print(f"Description: {job_info['description']}")
print(f"Peut être lancé: {job_info['buildable']}")  # True/False
print(f"En cours: {job_info.get('inQueue', False)}")

# Informations sur les builds
if job_info['lastBuild']:
    print(f"Dernier build: #{job_info['lastBuild']['number']}")

if job_info['lastSuccessfulBuild']:
    print(f"Dernier succès: #{job_info['lastSuccessfulBuild']['number']}")

if job_info['lastFailedBuild']:
    print(f"Dernier échec: #{job_info['lastFailedBuild']['number']}")


# === Vérifier si un job existe ===

def job_exists(server, job_name):
    """
    Vérifier si un job existe dans Jenkins
    Retourne True si existe, False sinon
    """
    try:
        server.get_job_info(job_name)
        return True
    except jenkins.NotFoundException:
        return False

# Utilisation
if job_exists(server, 'mon-projet'):
    print("[OK] Le job existe")
else:
    print("[X] Job introuvable")


# === Créer un job simple ===

# Pour créer un job, il faut fournir sa configuration en XML
# C'est un peu technique, mais on va simplifier !

# Job le plus simple possible (affiche "Hello World")
simple_job_xml = '''<?xml version='1.0' encoding='UTF-8'?>
<project>
  <description>Mon premier job créé avec Python !</description>
  <keepDependencies>false</keepDependencies>
  <properties/>
  <scm class="hudson.scm.NullSCM"/>
  <canRoam>true</canRoam>
  <disabled>false</disabled>
  <blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
  <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
  <triggers/>
  <concurrentBuild>false</concurrentBuild>
  <builders>
    <hudson.tasks.Shell>
      <command>echo "Hello World depuis Python !"</command>
    </hudson.tasks.Shell>
  </builders>
  <publishers/>
  <buildWrappers/>
</project>'''

# Créer le job
server.create_job('hello-world', simple_job_xml)
print("[OK] Job 'hello-world' créé !")

# Explications du XML:
# <description> : Description du job
# <disabled> : false = job actif, true = job désactivé
# <builders> : Les commandes à exécuter
# <hudson.tasks.Shell> : Exécuter une commande shell
# <command> : La commande à lancer


# === Créer un job Python avec tests ===

python_test_job = '''<?xml version='1.0' encoding='UTF-8'?>
<project>
  <description>Lance les tests Python avec pytest</description>
  <scm class="hudson.plugins.git.GitSCM">
    <configVersion>2</configVersion>
    <userRemoteConfigs>
      <hudson.plugins.git.UserRemoteConfig>
        <url>https://github.com/username/mon-projet.git</url>
      </hudson.plugins.git.UserRemoteConfig>
    </userRemoteConfigs>
    <branches>
      <hudson.plugins.git.BranchSpec>
        <name>*/main</name>
      </hudson.plugins.git.BranchSpec>
    </branches>
  </scm>
  <triggers>
    <hudson.triggers.SCMTrigger>
      <spec>H/5 * * * *</spec>
    </hudson.triggers.SCMTrigger>
  </triggers>
  <builders>
    <hudson.tasks.Shell>
      <command>
# Créer environnement virtuel
python -m venv venv
source venv/bin/activate

# Installer dépendances
pip install -r requirements.txt

# Lancer tests
pytest tests/ -v
      </command>
    </hudson.tasks.Shell>
  </builders>
  <publishers/>
</project>'''

server.create_job('mon-projet-tests', python_test_job)

# Explications:
# <scm class="hudson.plugins.git.GitSCM"> : Récupérer code depuis Git
# <url> : URL du repository Git
# <branches><name>*/main</name> : Branche à utiliser
# <triggers><SCMTrigger> : Vérifier Git toutes les 5 minutes (H/5 * * * *)
# <command> : Les commandes à exécuter (comme dans votre terminal)


# === Copier un job existant ===

# Très utile pour créer des jobs similaires !
server.copy_job('job-existant', 'nouveau-job')
print("[OK] Job copié")

# Exemple pratique:
# Vous avez un job "projet1-tests"
# Vous voulez créer "projet2-tests" identique
server.copy_job('projet1-tests', 'projet2-tests')
# Puis modifier la configuration du nouveau job


# === Renommer un job ===

server.rename_job('ancien-nom', 'nouveau-nom')
print("[OK] Job renommé")


# === Modifier un job existant ===

# Récupérer la config actuelle
job_config = server.get_job_config('mon-job')

# job_config est une CHAÎNE (string) avec du XML
# Vous pouvez la modifier

# Exemple: Changer la description
job_config = job_config.replace(
    '<description>Ancienne description</description>',
    '<description>Nouvelle description</description>'
)

# Appliquer les changements
server.reconfig_job('mon-job', job_config)
print("[OK] Job modifié")


# === Activer / Désactiver un job ===

# Désactiver un job (il ne peut plus être lancé)
server.disable_job('mon-job')
print("[ROUGE] Job désactivé")

# Réactiver un job
server.enable_job('mon-job')
print("[VERT] Job activé")

# Vérifier si job est désactivé
job_info = server.get_job_info('mon-job')
if job_info.get('disabled', False):
    print("Job est désactivé")
else:
    print("Job est actif")


# === Supprimer un job ===

# [ATTENTION] ATTENTION: Suppression définitive !
server.delete_job('mon-job')
print("[OK] Job supprimé")

# Avec confirmation (plus sûr)
job_name = 'mon-job-a-supprimer'
confirmation = input(f"Supprimer le job '{job_name}' ? (oui/non): ")

if confirmation.lower() == 'oui':
    server.delete_job(job_name)
    print("[OK] Job supprimé")
else:
    print("[X] Annulé")


# === Exemple complet: Gestion de jobs ===

"""
Script complet pour gérer des jobs
"""

def list_all_jobs(server):
    """Afficher tous les jobs avec leur statut"""
    jobs = server.get_jobs()
    
    print(f"[LISTE] Total: {len(jobs)} jobs\n")
    
    for job in jobs:
        # Symbole selon le statut
        if job['color'] == 'blue':
            status = '[OK]'
        elif job['color'] == 'red':
            status = '[X]'
        elif job['color'] == 'disabled':
            status = '[ROUGE]'
        else:
            status = '[BLANC]'
        
        print(f"{status} {job['name']}")

def create_simple_test_job(server, project_name, git_url):
    """
    Créer un job de test simple pour un projet Python
    """
    config = f'''<?xml version='1.0' encoding='UTF-8'?>
<project>
  <description>Tests pour {project_name}</description>
  <scm class="hudson.plugins.git.GitSCM">
    <userRemoteConfigs>
      <hudson.plugins.git.UserRemoteConfig>
        <url>{git_url}</url>
      </hudson.plugins.git.UserRemoteConfig>
    </userRemoteConfigs>
    <branches>
      <hudson.plugins.git.BranchSpec>
        <name>*/main</name>
      </hudson.plugins.git.BranchSpec>
    </branches>
  </scm>
  <builders>
    <hudson.tasks.Shell>
      <command>
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
pytest tests/ -v
      </command>
    </hudson.tasks.Shell>
  </builders>
</project>'''
    
    job_name = f'{project_name}-tests'
    
    if job_exists(server, job_name):
        print(f"[ATTENTION] Job '{job_name}' existe déjà")
        return False
    
    server.create_job(job_name, config)
    print(f"[OK] Job '{job_name}' créé")
    return True

# Utilisation
list_all_jobs(server)
create_simple_test_job(
    server,
    'mon-super-projet',
    'https://github.com/user/projet.git'
)


# === Astuces pour débutants ===

# 1. Commencez par LISTER les jobs pour comprendre
jobs = server.get_jobs()
for job in jobs:
    print(job['name'])

# 2. Regardez la config d'un job existant pour apprendre
config = server.get_job_config('un-job-existant')
print(config)  # Étudiez le XML

# 3. Utilisez COPY au lieu de CREATE pour débuter
server.copy_job('job-qui-marche', 'mon-nouveau-job')

# 4. Testez d'abord dans l'interface web de Jenkins
# Puis récupérez la config avec get_job_config()

# 5. Sauvegardez toujours avant de modifier
config_backup = server.get_job_config('mon-job')
# ... modifications ...
# Si problème: server.reconfig_job('mon-job', config_backup)

# === Créer un job ===

# Job simple (freestyle)
job_config = '''<?xml version='1.0' encoding='UTF-8'?>
<project>
  <description>Mon premier job</description>
  <keepDependencies>false</keepDependencies>
  <properties/>
  <scm class="hudson.scm.NullSCM"/>
  <canRoam>true</canRoam>
  <disabled>false</disabled>
  <blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
  <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
  <triggers/>
  <concurrentBuild>false</concurrentBuild>
  <builders>
    <hudson.tasks.Shell>
      <command>echo "Hello World"</command>
    </hudson.tasks.Shell>
  </builders>
  <publishers/>
  <buildWrappers/>
</project>'''

server.create_job('hello-world', job_config)

# Job Python
python_job_config = '''<?xml version='1.0' encoding='UTF-8'?>
<project>
  <description>Job Python</description>
  <scm class="hudson.plugins.git.GitSCM">
    <configVersion>2</configVersion>
    <userRemoteConfigs>
      <hudson.plugins.git.UserRemoteConfig>
        <url>https://github.com/user/repo.git</url>
      </hudson.plugins.git.UserRemoteConfig>
    </userRemoteConfigs>
    <branches>
      <hudson.plugins.git.BranchSpec>
        <name>*/main</name>
      </hudson.plugins.git.BranchSpec>
    </branches>
  </scm>
  <builders>
    <hudson.tasks.Shell>
      <command>
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
pytest tests/
      </command>
    </hudson.tasks.Shell>
  </builders>
</project>'''

server.create_job('python-test-job', python_job_config)

# === Créer job depuis template ===

template_config = server.get_job_config('template-job')
server.create_job('new-job-from-template', template_config)

# === Copier un job ===

server.copy_job('source-job', 'destination-job')

# === Renommer un job ===

server.rename_job('old-name', 'new-name')

# === Mettre à jour configuration ===

job_config = server.get_job_config('my-job')
# Modifier job_config (XML)
job_config = job_config.replace(
    '<description>Old</description>',
    '<description>New description</description>'
)
server.reconfig_job('my-job', job_config)

# === Activer/Désactiver un job ===

# Désactiver
server.disable_job('my-job')

# Activer
server.enable_job('my-job')

# Vérifier status
job_info = server.get_job_info('my-job')
is_disabled = job_info.get('disabled', False)
print(f"Job désactivé: {is_disabled}")

# === Supprimer un job ===

server.delete_job('my-job')

# Avec confirmation
if input("Supprimer le job? (y/n): ").lower() == 'y':
    server.delete_job('my-job')
    print("Job supprimé")


[OK] GESTION DES BUILDS (COMPRENDRE LES BUILDS)

# === C'est quoi un BUILD ? ===

# Un BUILD = une EXÉCUTION d'un job
# 
# Analogie:
# - Un JOB = une recette de cuisine
# - Un BUILD = préparer le plat selon la recette
# 
# Chaque fois que vous lancez un job, ça crée un nouveau build
# Les builds sont numérotés: #1, #2, #3, #4...


# === Anatomie d'un build ===

# Chaque build a:
# 1. Un NUMÉRO (#42)
# 2. Un RÉSULTAT (SUCCESS, FAILURE, ABORTED, UNSTABLE)
# 3. Une DURÉE (combien de temps ça a pris)
# 4. Des LOGS (tout ce qui s'est affiché pendant l'exécution)
# 5. Un TIMESTAMP (quand il a été lancé)


# === Déclencher un build (méthode simple) ===

job_name = 'mon-projet-tests'

# Lancer le job (sans paramètres)
server.build_job(job_name)
print(f"[OK] Build de '{job_name}' déclenché !")

# Le build ne démarre pas IMMÉDIATEMENT
# Il est d'abord mis en QUEUE (file d'attente)
# Puis il démarre quand un executor est libre


# === Déclencher un build avec paramètres ===

# Certains jobs acceptent des PARAMÈTRES
# Par exemple: sur quel environnement déployer ? (dev, prod)

parameters = {
    'ENVIRONMENT': 'production',   # Paramètre 1
    'VERSION': '1.2.3',           # Paramètre 2
    'SEND_NOTIFICATION': True     # Paramètre 3
}

server.build_job(job_name, parameters=parameters)
print("[OK] Build avec paramètres déclenché")

# Ces paramètres sont accessibles dans le job comme variables
# Exemple dans le job: echo $ENVIRONMENT


# === Obtenir le numéro du build déclenché ===

# Quand vous déclenchez un build, vous voulez souvent
# savoir quel numéro il a reçu

queue_item = server.build_job(job_name)
print(f"Build mis en queue: {queue_item}")

# Attendre que le build démarre
import time
time.sleep(2)  # Attendre 2 secondes

# Récupérer le numéro du build
queue_info = server.get_queue_item(queue_item)
if 'executable' in queue_info:
    build_number = queue_info['executable']['number']
    print(f"[OK] Build #{build_number} démarré")
else:
    print("[HOURGLASS_WITH_FLOWING_SAND] Build encore en queue...")


# === Obtenir les infos d'un build ===

job_name = 'mon-projet'
build_number = 42

# Récupérer TOUTES les infos du build
build_info = server.get_build_info(job_name, build_number)

# Afficher les infos importantes
print(f"Build #{build_info['number']}")
print(f"URL: {build_info['url']}")
print(f"Résultat: {build_info['result']}")  # SUCCESS, FAILURE, etc.
print(f"Durée: {build_info['duration']} ms")  # en millisecondes
print(f"En cours: {build_info['building']}")  # True ou False

# Convertir durée en secondes (plus lisible)
duration_seconds = build_info['duration'] / 1000
print(f"Durée: {duration_seconds:.1f} secondes")

# Timestamp (quand le build a été lancé)
from datetime import datetime
timestamp = build_info['timestamp'] / 1000  # Convertir en secondes
build_time = datetime.fromtimestamp(timestamp)
print(f"Lancé le: {build_time.strftime('%Y-%m-%d %H:%M:%S')}")


# === Les différents résultats possibles ===

# SUCCESS [OK]
# - Tout s'est bien passé
# - Toutes les commandes ont réussi
# - Code de sortie = 0

# FAILURE [X]
# - Une erreur est survenue
# - Une commande a échoué
# - Code de sortie != 0
# - Tests ont échoué

# UNSTABLE [ATTENTION]
# - Le build s'est terminé
# - Mais il y a des warnings
# - Exemple: Certains tests ont échoué mais pas tous

# ABORTED [STOP]
# - Build arrêté manuellement
# - Ou timeout dépassé
# - N'a pas pu se terminer normalement

# NOT_BUILT / null
# - Build n'a pas démarré
# - Ou encore en cours (building=True)


# === Récupérer les paramètres utilisés ===

build_info = server.get_build_info(job_name, build_number)

# Les paramètres sont dans 'actions'
if 'actions' in build_info:
    for action in build_info['actions']:
        # Chercher l'action 'ParametersAction'
        if action.get('_class') == 'hudson.model.ParametersAction':
            print("[LISTE] Paramètres utilisés:")
            for param in action.get('parameters', []):
                print(f"  {param['name']} = {param['value']}")

# Exemple de sortie:
# [LISTE] Paramètres utilisés:
#   ENVIRONMENT = production
#   VERSION = 1.2.3
#   SEND_NOTIFICATION = True


# === Obtenir le dernier build ===

# Dernier build (peu importe le résultat)
job_info = server.get_job_info(job_name)
if job_info['lastBuild']:
    last_build_number = job_info['lastBuild']['number']
    print(f"Dernier build: #{last_build_number}")
    
    # Récupérer ses infos
    last_build = server.get_build_info(job_name, last_build_number)
    print(f"Résultat: {last_build['result']}")

# Dernier build RÉUSSI
if job_info['lastSuccessfulBuild']:
    success_number = job_info['lastSuccessfulBuild']['number']
    print(f"Dernier succès: #{success_number}")

# Dernier build ÉCHOUÉ
if job_info['lastFailedBuild']:
    failure_number = job_info['lastFailedBuild']['number']
    print(f"Dernier échec: #{failure_number}")


# === Lire les logs du build (console output) ===

# Les logs = tout ce qui s'affiche pendant le build
console_output = server.get_build_console_output(job_name, build_number)

# Afficher les logs
print("[FICHIER] Logs du build:")
print(console_output)

# Les logs sont une LONGUE chaîne de caractères
# Vous pouvez les analyser pour trouver des erreurs

# Chercher les erreurs dans les logs
if 'error' in console_output.lower():
    print("[ATTENTION] Le mot 'error' a été trouvé dans les logs")

# Extraire les lignes avec "error"
error_lines = [
    line for line in console_output.split('\n') 
    if 'error' in line.lower()
]

print(f"[X] {len(error_lines)} lignes avec des erreurs:")
for line in error_lines[:5]:  # Afficher les 5 premières
    print(f"  {line}")


# === Suivre un build en temps réel ===

def stream_build_logs(server, job_name, build_number):
    """
    Afficher les logs d'un build en temps réel
    Comme si vous regardiez dans Jenkins web
    """
    start = 0  # Position dans les logs
    
    while True:
        # Récupérer les logs
        try:
            output = server.get_build_console_output(job_name, build_number)
            
            # Afficher seulement les NOUVEAUX logs
            new_output = output[start:]
            if new_output:
                print(new_output, end='')  # Pas de saut de ligne supplémentaire
                start = len(output)
            
            # Vérifier si le build est terminé
            build_info = server.get_build_info(job_name, build_number)
            if not build_info['building']:
                print(f"\n[OK] Build terminé: {build_info['result']}")
                break
            
            # Attendre avant de vérifier à nouveau
            time.sleep(2)
            
        except Exception as e:
            print(f"\n[X] Erreur: {e}")
            break

# Utilisation
stream_build_logs(server, 'mon-projet', 42)


# === Arrêter un build en cours ===

# Si un build tourne trop longtemps ou a un problème
server.stop_build(job_name, build_number)
print(f"[STOP] Build #{build_number} arrêté")

# Vérifier que le build est bien arrêté
time.sleep(1)
build_info = server.get_build_info(job_name, build_number)
if build_info['result'] == 'ABORTED':
    print("[OK] Build correctement arrêté")


# === Supprimer un build ===

# Supprimer un vieux build pour libérer de l'espace
server.delete_build(job_name, build_number)
print(f"[OK] Build #{build_number} supprimé")


# === Attendre qu'un build se termine ===

def wait_for_build(server, job_name, build_number, timeout=300):
    """
    Attendre qu'un build se termine
    
    Args:
        timeout: Temps maximum d'attente (en secondes)
    
    Returns:
        Les infos du build terminé
    """
    elapsed = 0
    interval = 5  # Vérifier toutes les 5 secondes
    
    print(f"[HOURGLASS_WITH_FLOWING_SAND] Attente du build #{build_number}...")
    
    while elapsed < timeout:
        build_info = server.get_build_info(job_name, build_number)
        
        # Vérifier si terminé
        if not build_info['building']:
            print(f"[OK] Build terminé après {elapsed}s")
            return build_info
        
        # Afficher progression
        print(f"  [TEMPS] {elapsed}s...", end='\r')
        
        time.sleep(interval)
        elapsed += interval
    
    # Si on arrive ici, c'est un timeout
    raise TimeoutError(f"Build #{build_number} timeout après {timeout}s")

# Utilisation
try:
    result = wait_for_build(server, 'mon-projet', 42, timeout=600)
    print(f"Résultat final: {result['result']}")
except TimeoutError as e:
    print(f"[X] {e}")


# === Déclencher un build ET attendre le résultat ===

def build_and_wait(server, job_name, parameters=None, timeout=600):
    """
    Déclencher un build et attendre qu'il se termine
    Très utile pour des scripts d'automatisation
    """
    print(f"[RAPIDE] Déclenchement de '{job_name}'...")
    
    # Déclencher
    queue_item = server.build_job(job_name, parameters=parameters)
    
    # Attendre que le build démarre
    print("[HOURGLASS_WITH_FLOWING_SAND] Attente du démarrage...")
    build_number = None
    start_time = time.time()
    
    while build_number is None:
        if time.time() - start_time > 60:
            raise TimeoutError("Le build n'a pas démarré en 60 secondes")
        
        try:
            queue_info = server.get_queue_item(queue_item)
            if 'executable' in queue_info:
                build_number = queue_info['executable']['number']
                print(f"[OK] Build #{build_number} démarré")
        except:
            pass
        
        time.sleep(2)
    
    # Attendre que le build se termine
    result = wait_for_build(server, job_name, build_number, timeout)
    
    return result

# Utilisation
result = build_and_wait(server, 'mon-projet', parameters={'ENV': 'prod'})
if result['result'] == 'SUCCESS':
    print("[BRAVO] Build réussi !")
else:
    print(f"[X] Build échoué: {result['result']}")


# === Obtenir l'historique des builds ===

def get_build_history(server, job_name, limit=10):
    """
    Récupérer l'historique des derniers builds
    """
    job_info = server.get_job_info(job_name)
    builds = job_info['builds'][:limit]  # Limiter au nombre voulu
    
    history = []
    
    for build in builds:
        build_info = server.get_build_info(job_name, build['number'])
        
        # Convertir timestamp
        build_time = datetime.fromtimestamp(build_info['timestamp'] / 1000)
        
        history.append({
            'number': build_info['number'],
            'result': build_info.get('result', 'BUILDING'),
            'duration': build_info['duration'] / 1000,  # en secondes
            'timestamp': build_time
        })
    
    return history

# Utilisation
history = get_build_history(server, 'mon-projet', limit=20)

print(f"[GRAPHIQUE] Historique des 20 derniers builds:")
for build in history:
    # Emoji selon résultat
    if build['result'] == 'SUCCESS':
        emoji = '[OK]'
    elif build['result'] == 'FAILURE':
        emoji = '[X]'
    else:
        emoji = '[ATTENTION]'
    
    print(f"{emoji} Build #{build['number']}: {build['result']} "
          f"({build['duration']:.1f}s) - {build['timestamp']}")

# Exemple de sortie:
# [OK] Build #45: SUCCESS (123.4s) - 2024-01-15 10:30:25
# [OK] Build #44: SUCCESS (118.2s) - 2024-01-15 09:15:10
# [X] Build #43: FAILURE (45.6s) - 2024-01-15 08:00:05


# === Statistiques des builds ===

def get_build_statistics(server, job_name, days=7):
    """
    Calculer des statistiques sur les builds récents
    """
    from datetime import datetime, timedelta
    
    # Date limite
    cutoff = datetime.now() - timedelta(days=days)
    
    job_info = server.get_job_info(job_name)
    
    stats = {
        'total': 0,
        'success': 0,
        'failure': 0,
        'aborted': 0,
        'unstable': 0,
        'avg_duration': 0,
        'success_rate': 0
    }
    
    durations = []
    
    # Parcourir tous les builds
    for build in job_info['builds']:
        build_info = server.get_build_info(job_name, build['number'])
        build_time = datetime.fromtimestamp(build_info['timestamp'] / 1000)
        
        # Vérifier si dans la période
        if build_time < cutoff:
            continue
        
        stats['total'] += 1
        result = build_info.get('result', 'UNKNOWN')
        
        # Compter par résultat
        if result == 'SUCCESS':
            stats['success'] += 1
        elif result == 'FAILURE':
            stats['failure'] += 1
        elif result == 'ABORTED':
            stats['aborted'] += 1
        elif result == 'UNSTABLE':
            stats['unstable'] += 1
        
        # Durée
        if build_info['duration'] > 0:
            durations.append(build_info['duration'])
    
    # Calculs
    if durations:
        stats['avg_duration'] = sum(durations) / len(durations) / 1000  # secondes
    
    if stats['total'] > 0:
        stats['success_rate'] = (stats['success'] / stats['total']) * 100
    
    return stats

# Utilisation
stats = get_build_statistics(server, 'mon-projet', days=7)

print(f"[GRAPHIQUE] Statistiques des 7 derniers jours:")
print(f"   Total builds: {stats['total']}")
print(f"   [OK] Succès: {stats['success']}")
print(f"   [X] Échecs: {stats['failure']}")
print(f"   [STOP] Arrêtés: {stats['aborted']}")
print(f"   [HAUSSE] Taux de réussite: {stats['success_rate']:.1f}%")
print(f"   [TEMPS] Durée moyenne: {stats['avg_duration']:.1f}s")


# === Exemple complet: Automatisation ===

"""
Script complet pour déclencher un build et attendre le résultat
Utile pour automatiser des déploiements
"""

def automated_deployment(server, job_name, version):
    """
    Automatiser un déploiement avec Jenkins
    """
    print(f"[RAPIDE] Démarrage du déploiement v{version}")
    
    # Paramètres du build
    parameters = {
        'VERSION': version,
        'ENVIRONMENT': 'production',
        'NOTIFICATION': True
    }
    
    try:
        # Déclencher et attendre
        result = build_and_wait(
            server, 
            job_name, 
            parameters=parameters,
            timeout=1800  # 30 minutes max
        )
        
        # Vérifier le résultat
        if result['result'] == 'SUCCESS':
            duration = result['duration'] / 1000
            print(f"[OK] Déploiement réussi en {duration:.0f}s")
            print(f"[LIEN] {result['url']}")
            return True
        else:
            print(f"[X] Déploiement échoué: {result['result']}")
            print(f"[FICHIER] Logs: {result['url']}console")
            
            # Récupérer logs d'erreur
            console = server.get_build_console_output(job_name, result['number'])
            errors = [l for l in console.split('\n') if 'error' in l.lower()]
            
            print(f"[ATTENTION] {len(errors)} erreurs trouvées:")
            for error in errors[:3]:
                print(f"   {error}")
            
            return False
            
    except TimeoutError:
        print("[X] Timeout: le déploiement prend trop de temps")
        return False
    except Exception as e:
        print(f"[X] Erreur inattendue: {e}")
        return False

# Utilisation
success = automated_deployment(server, 'deploy-production', '2.1.0')

if success:
    print("[BRAVO] Tout est OK!")
else:
    print("[IMPACT] Il y a eu un problème")


[OK] PIPELINES JENKINS (GUIDE DÉBUTANT)

# === C'est quoi un PIPELINE ? ===

# Un PIPELINE = un job avec plusieurs ÉTAPES (stages)
# 
# Analogie: Chaîne de montage d'une voiture
# Étape 1: Assembler la carrosserie
# Étape 2: Installer le moteur
# Étape 3: Peindre
# Étape 4: Contrôle qualité
# 
# En programmation:
# Étape 1: Récupérer le code (Checkout)
# Étape 2: Compiler/Builder
# Étape 3: Tester
# Étape 4: Déployer


# === Pipeline vs Freestyle Job ===

# FREESTYLE JOB (Simple)
# - Une seule liste de commandes
# - Configuration via interface graphique
# - Difficile à versionner

# PIPELINE (Moderne - RECOMMANDÉ)
# - Plusieurs étapes clairement séparées
# - Défini en CODE (Jenkinsfile)
# - Versionné avec votre code dans Git
# - Plus flexible et puissant


# === Le Jenkinsfile ===

# Un Jenkinsfile = un fichier qui décrit votre pipeline
# C'est du code Groovy (langage de Jenkins)
# Vous le mettez dans votre repository Git

# Exemple de structure:
"""
mon-projet/
├── src/
├── tests/
├── requirements.txt
└── Jenkinsfile  <- Le fichier pipeline
"""


# === Pipeline le plus simple ===

# Jenkinsfile basique
simple_pipeline = '''
pipeline {
    agent any
    
    stages {
        stage('Hello') {
            steps {
                echo 'Hello World!'
            }
        }
    }
}
'''

# Explications ligne par ligne:
# pipeline { }         : Commence la définition du pipeline
# agent any           : Exécuter sur n'importe quelle machine disponible
# stages { }          : Liste des étapes
# stage('Hello') { }  : Une étape nommée 'Hello'
# steps { }           : Les actions à faire dans cette étape
# echo                : Afficher un message


# === Pipeline Python simple ===

python_pipeline = '''
pipeline {
    agent any
    
    stages {
        stage('Checkout') {
            steps {
                echo 'Récupération du code...'
                git 'https://github.com/username/mon-projet.git'
            }
        }
        
        stage('Install') {
            steps {
                echo 'Installation des dépendances...'
                sh '''
                    python -m venv venv
                    . venv/bin/activate
                    pip install -r requirements.txt
                '''
            }
        }
        
        stage('Test') {
            steps {
                echo 'Exécution des tests...'
                sh '''
                    . venv/bin/activate
                    pytest tests/ -v
                '''
            }
        }
    }
}
'''

# Explications:
# git 'url'               : Clone le repository Git
# sh ''' commandes '''   : Exécute des commandes shell
# '''                     : Permet d'écrire sur plusieurs lignes


# === Créer un Pipeline job avec Python ===

# Configuration XML pour un pipeline
pipeline_config = '''<?xml version='1.0' encoding='UTF-8'?>
<flow-definition plugin="workflow-job">
  <description>Mon premier pipeline Python</description>
  <definition class="org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition">
    <script>
pipeline {
    agent any
    
    stages {
        stage('Build') {
            steps {
                echo 'Building...'
                sh 'python --version'
            }
        }
        
        stage('Test') {
            steps {
                echo 'Testing...'
                sh 'echo "Tests OK"'
            }
        }
    }
}
    </script>
    <sandbox>true</sandbox>
  </definition>
</flow-definition>'''

# Créer le job
server.create_job('mon-premier-pipeline', pipeline_config)
print("[OK] Pipeline créé!")


# === Pipeline avec environnement virtuel Python ===

# Pipeline complet pour projet Python
python_complete_pipeline = '''
pipeline {
    agent any
    
    environment {
        VENV_PATH = "${WORKSPACE}/venv"
    }
    
    stages {
        stage('Setup') {
            steps {
                echo 'Configuration environnement Python...'
                sh '''
                    python -m venv ${VENV_PATH}
                    . ${VENV_PATH}/bin/activate
                    pip install --upgrade pip
                    pip install -r requirements.txt
                '''
            }
        }
        
        stage('Lint') {
            steps {
                echo 'Vérification qualité du code...'
                sh '''
                    . ${VENV_PATH}/bin/activate
                    flake8 src/ tests/
                    black --check src/
                '''
            }
        }
        
        stage('Test') {
            steps {
                echo 'Exécution des tests...'
                sh '''
                    . ${VENV_PATH}/bin/activate
                    pytest tests/ -v --cov=src
                '''
            }
        }
        
        stage('Build') {
            steps {
                echo 'Construction du package...'
                sh '''
                    . ${VENV_PATH}/bin/activate
                    python setup.py sdist bdist_wheel
                '''
            }
        }
    }
    
    post {
        always {
            echo 'Nettoyage...'
            cleanWs()
        }
        success {
            echo '[OK] Pipeline réussi!'
        }
        failure {
            echo '[X] Pipeline échoué!'
        }
    }
}
'''

# Explications des nouveautés:
# environment { }           : Définir des variables d'environnement
# ${WORKSPACE}             : Dossier de travail Jenkins
# post { }                 : Actions après le pipeline
# always { }               : Toujours exécuté (succès ou échec)
# success { }              : Seulement si succès
# failure { }              : Seulement si échec
# cleanWs()                : Nettoyer le workspace


# === Pipeline avec paramètres ===

# Pipeline qui accepte des paramètres
parametrized_pipeline = '''
pipeline {
    agent any
    
    parameters {
        choice(
            name: 'ENVIRONMENT',
            choices: ['dev', 'staging', 'production'],
            description: 'Environnement de déploiement'
        )
        string(
            name: 'VERSION',
            defaultValue: '1.0.0',
            description: 'Version à déployer'
        )
        booleanParam(
            name: 'RUN_TESTS',
            defaultValue: true,
            description: 'Exécuter les tests?'
        )
    }
    
    stages {
        stage('Info') {
            steps {
                echo "Environnement: ${params.ENVIRONMENT}"
                echo "Version: ${params.VERSION}"
                echo "Tests: ${params.RUN_TESTS}"
            }
        }
        
        stage('Test') {
            when {
                expression { params.RUN_TESTS == true }
            }
            steps {
                echo 'Exécution des tests...'
                sh 'pytest tests/'
            }
        }
        
        stage('Deploy') {
            steps {
                echo "Déploiement sur ${params.ENVIRONMENT}..."
                sh "python deploy.py --env ${params.ENVIRONMENT} --version ${params.VERSION}"
            }
        }
    }
}
'''

# Explications:
# parameters { }           : Définir des paramètres
# choice()                 : Liste déroulante
# string()                 : Champ texte
# booleanParam()          : Case à cocher
# ${params.NOM}           : Accéder à un paramètre
# when { }                : Condition pour exécuter une étape
# expression { }          : Expression booléenne


# === Pipeline avec branches parallèles ===

# Exécuter plusieurs choses en même temps
parallel_pipeline = '''
pipeline {
    agent any
    
    stages {
        stage('Parallel Tests') {
            parallel {
                stage('Unit Tests') {
                    steps {
                        echo 'Tests unitaires...'
                        sh 'pytest tests/unit/'
                    }
                }
                
                stage('Integration Tests') {
                    steps {
                        echo 'Tests d\'intégration...'
                        sh 'pytest tests/integration/'
                    }
                }
                
                stage('Linting') {
                    steps {
                        echo 'Vérification du code...'
                        sh 'flake8 src/'
                    }
                }
            }
        }
        
        stage('Deploy') {
            steps {
                echo 'Déploiement...'
            }
        }
    }
}
'''

# Explications:
# parallel { }             : Exécuter en parallèle
# Les 3 stages à l'intérieur s'exécutent EN MÊME TEMPS
# Plus rapide si vous avez plusieurs executors


# === Pipeline avec Docker ===

# Exécuter dans un conteneur Docker
docker_pipeline = '''
pipeline {
    agent {
        docker {
            image 'python:3.11'
            args '-v /tmp:/tmp'
        }
    }
    
    stages {
        stage('Test') {
            steps {
                sh 'python --version'
                sh 'pip install pytest'
                sh 'pytest tests/'
            }
        }
    }
}
'''

# Explications:
# agent { docker { } }     : Utiliser un conteneur Docker
# image 'python:3.11'     : Image Docker à utiliser
# args                    : Arguments pour docker run
# 
# Avantages:
# - Environnement isolé
# - Même environnement partout (dev/CI/prod)
# - Pas besoin d'installer Python sur Jenkins


# === Pipeline depuis Git (Pipeline as Code) ===

# Au lieu de mettre le code dans Jenkins,
# on le met dans un fichier Jenkinsfile dans Git

# 1. Créer un fichier Jenkinsfile dans votre projet
"""
# Fichier: Jenkinsfile (à la racine du projet)
pipeline {
    agent any
    
    stages {
        stage('Build') {
            steps {
                sh 'python setup.py build'
            }
        }
        
        stage('Test') {
            steps {
                sh 'pytest tests/'
            }
        }
    }
}
"""

# 2. Créer le job qui lit ce Jenkinsfile
pipeline_from_git_config = '''<?xml version='1.0' encoding='UTF-8'?>
<flow-definition plugin="workflow-job">
  <description>Pipeline depuis Git</description>
  <definition class="org.jenkinsci.plugins.workflow.cps.CpsScmFlowDefinition">
    <scm class="hudson.plugins.git.GitSCM">
      <userRemoteConfigs>
        <hudson.plugins.git.UserRemoteConfig>
          <url>https://github.com/username/mon-projet.git</url>
        </hudson.plugins.git.UserRemoteConfig>
      </userRemoteConfigs>
      <branches>
        <hudson.plugins.git.BranchSpec>
          <n>*/main</n>
        </hudson.plugins.git.BranchSpec>
      </branches>
    </scm>
    <scriptPath>Jenkinsfile</scriptPath>
  </definition>
</flow-definition>'''

server.create_job('pipeline-from-git', pipeline_from_git_config)

# Avantages:
# - Le pipeline est versionné avec le code
# - Changements traçables (commits Git)
# - Facile de revenir en arrière


# === Pipeline CI/CD complet (exemple réel) ===

complete_cicd_pipeline = '''
pipeline {
    agent any
    
    environment {
        APP_NAME = 'mon-application'
        DOCKER_REGISTRY = 'registry.example.com'
    }
    
    stages {
        stage('Checkout') {
            steps {
                git branch: 'main', url: 'https://github.com/user/projet.git'
            }
        }
        
        stage('Setup') {
            steps {
                sh """
                    python -m venv venv
                    . venv/bin/activate
                    pip install -r requirements.txt
                    pip install -r requirements-dev.txt
                """
            }
        }
        
        stage('Quality Checks') {
            parallel {
                stage('Linting') {
                    steps {
                        sh """
                            . venv/bin/activate
                            flake8 src/ --max-line-length=100
                        """
                    }
                }
                
                stage('Type Checking') {
                    steps {
                        sh """
                            . venv/bin/activate
                            mypy src/
                        """
                    }
                }
                
                stage('Security') {
                    steps {
                        sh """
                            . venv/bin/activate
                            safety check
                            bandit -r src/
                        """
                    }
                }
            }
        }
        
        stage('Tests') {
            steps {
                sh """
                    . venv/bin/activate
                    pytest tests/ \\
                        --junitxml=test-results.xml \\
                        --cov=src \\
                        --cov-report=xml \\
                        --cov-report=html
                """
            }
            post {
                always {
                    junit 'test-results.xml'
                    publishHTML([
                        reportDir: 'htmlcov',
                        reportFiles: 'index.html',
                        reportName: 'Coverage Report'
                    ])
                }
            }
        }
        
        stage('Build Docker Image') {
            when {
                branch 'main'
            }
            steps {
                script {
                    def version = sh(
                        returnStdout: true, 
                        script: 'git describe --tags --always'
                    ).trim()
                    
                    sh """
                        docker build -t ${DOCKER_REGISTRY}/${APP_NAME}:${version} .
                        docker tag ${DOCKER_REGISTRY}/${APP_NAME}:${version} \\
                                   ${DOCKER_REGISTRY}/${APP_NAME}:latest
                    """
                }
            }
        }
        
        stage('Deploy to Staging') {
            when {
                branch 'main'
            }
            steps {
                sh """
                    kubectl set image deployment/${APP_NAME} \\
                        ${APP_NAME}=${DOCKER_REGISTRY}/${APP_NAME}:latest \\
                        -n staging
                    kubectl rollout status deployment/${APP_NAME} -n staging
                """
            }
        }
        
        stage('Deploy to Production') {
            when {
                branch 'main'
            }
            input {
                message "Déployer en production?"
                ok "Oui, déployer!"
            }
            steps {
                sh """
                    kubectl set image deployment/${APP_NAME} \\
                        ${APP_NAME}=${DOCKER_REGISTRY}/${APP_NAME}:latest \\
                        -n production
                    kubectl rollout status deployment/${APP_NAME} -n production
                """
            }
        }
    }
    
    post {
        always {
            cleanWs()
        }
        success {
            echo '[BRAVO] Pipeline réussi!'
            // Envoyer notification Slack
        }
        failure {
            echo '[X] Pipeline échoué!'
            // Envoyer alerte
        }
    }
}
'''

# Ce pipeline fait TOUT:
# [OK] Récupère le code
# [OK] Installe les dépendances
# [OK] Vérifie la qualité (lint, types, sécurité)
# [OK] Lance les tests avec coverage
# [OK] Build une image Docker
# [OK] Déploie en staging
# [OK] Demande confirmation pour production
# [OK] Déploie en production
# [OK] Nettoie
# [OK] Notifie


# === Créer un pipeline programmatiquement ===

def create_python_pipeline(server, project_name, git_url):
    """
    Créer un pipeline Python complet avec Python
    """
    
    pipeline_script = f'''
pipeline {{
    agent any
    
    environment {{
        PROJECT = '{project_name}'
    }}
    
    stages {{
        stage('Checkout') {{
            steps {{
                git '{git_url}'
            }}
        }}
        
        stage('Test') {{
            steps {{
                sh """
                    python -m venv venv
                    . venv/bin/activate
                    pip install -r requirements.txt
                    pytest tests/ -v
                """
            }}
        }}
    }}
    
    post {{
        success {{
            echo '[OK] Tests réussis pour {project_name}!'
        }}
        failure {{
            echo '[X] Tests échoués pour {project_name}'
        }}
    }}
}}
'''
    
    config = f'''<?xml version='1.0' encoding='UTF-8'?>
<flow-definition plugin="workflow-job">
  <description>Pipeline pour {project_name}</description>
  <definition class="org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition">
    <script>{pipeline_script}</script>
    <sandbox>true</sandbox>
  </definition>
</flow-definition>'''
    
    job_name = f'{project_name}-pipeline'
    server.create_job(job_name, config)
    print(f"[OK] Pipeline '{job_name}' créé")
    
    return job_name

# Utilisation
create_python_pipeline(
    server,
    'mon-api',
    'https://github.com/user/mon-api.git'
)


# === Astuces Pipelines pour débutants ===

# 1. COMMENCEZ SIMPLE
# Créez d'abord un pipeline avec juste un echo
# Puis ajoutez les étapes une par une

# 2. TESTEZ DANS L'INTERFACE WEB D'ABORD
# Jenkins > New Item > Pipeline
# Testez votre Jenkinsfile
# Puis récupérez-le avec Python

# 3. UTILISEZ L'ÉDITEUR JENKINS
# Jenkins a un éditeur de Jenkinsfile avec auto-complétion
# Blue Ocean > Pipeline Editor

# 4. CONSULTEZ LES EXEMPLES
# Jenkins > New Item > Pipeline
# Cliquez sur "Pipeline Syntax" en bas
# Plein d'exemples et de générateurs!

# 5. LOGS DÉTAILLÉS
# En cas d'erreur, regardez les logs du build
# Ils indiquent exactement où ça coince

# 6. VALIDATION
# Jenkins valide le Jenkinsfile avant de l'exécuter
# Les erreurs de syntaxe sont détectées rapidement


[OK] GESTION DE LA QUEUE (FILE D'ATTENTE)

# === C'est quoi la QUEUE ? ===

# La QUEUE (file d'attente) = les builds qui attendent d'être exécutés
# 
# Pourquoi un build attend ?
# 1. Pas d'executor libre (tous occupés)
# 2. Node nécessaire est offline
# 3. Attente d'une ressource
# 4. Restrictions de concurrence
# 
# C'est comme une file d'attente à la caisse :
# Les builds attendent leur tour


# === Voir ce qui est en queue ===

# Obtenir tous les items en attente
queue_info = server.get_queue_info()

print(f"[LISTE] {len(queue_info)} builds en attente\n")

for item in queue_info:
    print(f"ID: {item['id']}")
    print(f"Job: {item['task']['name']}")
    print(f"Raison: {item.get('why', 'En attente...')}")
    print(f"Bloqué: {item.get('stuck', False)}")
    print(f"Peut être construit: {item.get('buildable', False)}")
    print()

# Explications:
# id : Identifiant unique de l'item en queue
# task['name'] : Nom du job
# why : Pourquoi il attend (ex: "Waiting for executor")
# stuck : True si bloqué (problème)
# buildable : True si prêt à être exécuté


# === Info détaillée d'un item ===

# Quand vous déclenchez un build, vous obtenez un queue_item_id
queue_item_id = server.build_job('mon-job')

# Récupérer les détails
item_info = server.get_queue_item(queue_item_id)

print(f"[PACKAGE] Item #{queue_item_id}")
print(f"Job: {item_info['task']['name']}")
print(f"Bloqué: {item_info.get('blocked', False)}")

# Vérifier si le build a démarré
if 'executable' in item_info:
    build_number = item_info['executable']['number']
    print(f"[OK] Build #{build_number} a démarré!")
else:
    print("[HOURGLASS_WITH_FLOWING_SAND] Toujours en attente...")


# === Annuler un build en queue ===

# Si vous avez déclenché un build par erreur
queue_item_id = 123
server.cancel_queue(queue_item_id)
print(f"[SUPPRIMER] Item #{queue_item_id} annulé")


# === Surveiller la queue ===

def monitor_queue(server, max_size=10):
    """
    Surveiller la taille de la queue
    Alerter si trop d'items en attente
    """
    queue = server.get_queue_info()
    queue_size = len(queue)
    
    print(f"[GRAPHIQUE] Queue: {queue_size} items")
    
    if queue_size > max_size:
        print(f"[ATTENTION] ALERTE: Queue trop longue ({queue_size} > {max_size})")
        
        # Lister les jobs en attente
        jobs = {}
        for item in queue:
            job_name = item['task']['name']
            jobs[job_name] = jobs.get(job_name, 0) + 1
        
        print("Jobs en attente:")
        for job, count in jobs.items():
            print(f"  - {job}: {count} builds")
        
        return True
    
    return False

# Utilisation
monitor_queue(server, max_size=5)


[OK] GESTION DES NODES (AGENTS)

# === C'est quoi un NODE ? ===

# Un NODE (ou AGENT) = une machine qui exécute les builds
# 
# Types de nodes:
# 1. MASTER : Le serveur Jenkins principal
#    - Gère l'interface web
#    - Coordonne les builds
#    - Peut exécuter des builds (mais pas recommandé)
# 
# 2. AGENT (ou SLAVE) : Machines supplémentaires
#    - Exécutent les builds
#    - Peuvent être sur d'autres serveurs
#    - Spécialisés (Linux, Windows, MacOS...)
# 
# Pourquoi plusieurs nodes ?
# - Exécuter plusieurs builds en parallèle
# - Builds sur différents OS
# - Isoler les environnements
# - Scalabilité


# === Lister tous les nodes ===

nodes = server.get_nodes()

print(f"[ECRAN] {len(nodes)} nodes total\n")

for node in nodes:
    status = '[ROUGE] OFFLINE' if node['offline'] else '[VERT] ONLINE'
    print(f"{status} {node['name']}")

# Le master est toujours dans la liste


# === Info détaillée d'un node ===

node_name = 'agent-1'  # ou 'master' pour le master
node_info = server.get_node_info(node_name)

print(f"[GRAPHIQUE] Node: {node_name}")
print(f"Description: {node_info.get('description', 'N/A')}")
print(f"Executors: {node_info['numExecutors']}")  # Nombre de jobs simultanés
print(f"Offline: {node_info['offline']}")
print(f"Idle: {node_info.get('idle', False)}")  # Inactif
print(f"Mode: {node_info.get('mode', 'N/A')}")

# Si offline, pourquoi ?
if node_info['offline']:
    reason = node_info.get('offlineCauseReason', 'Raison inconnue')
    print(f"[ATTENTION] Raison offline: {reason}")

# Explications:
# numExecutors : Combien de builds peuvent tourner en même temps
# offline : True = hors ligne, False = en ligne
# idle : True = rien ne tourne actuellement
# mode : NORMAL (tout job) ou EXCLUSIVE (jobs spécifiques)


# === Désactiver / Activer un node ===

# Désactiver temporairement (pour maintenance)
server.disable_node(node_name, msg='Maintenance programmée')
print(f"[ROUGE] Node '{node_name}' désactivé")

# Réactiver
server.enable_node(node_name)
print(f"[VERT] Node '{node_name}' activé")


# === Vérifier la santé des nodes ===

def check_nodes_health(server):
    """
    Vérifier que tous les nodes sont OK
    """
    nodes = server.get_nodes()
    
    stats = {
        'total': len(nodes),
        'online': 0,
        'offline': 0,
        'idle': 0,
        'busy': 0
    }
    
    problems = []
    
    for node in nodes:
        info = server.get_node_info(node['name'])
        
        if info['offline']:
            stats['offline'] += 1
            problems.append({
                'node': node['name'],
                'issue': 'Offline',
                'reason': info.get('offlineCauseReason', 'Unknown')
            })
        else:
            stats['online'] += 1
            
            if info.get('idle'):
                stats['idle'] += 1
            else:
                stats['busy'] += 1
    
    print("[ECRAN] État des Nodes:")
    print(f"  Total: {stats['total']}")
    print(f"  [VERT] Online: {stats['online']}")
    print(f"  [ROUGE] Offline: {stats['offline']}")
    print(f"  [ATTENTE] Idle: {stats['idle']}")
    print(f"  [HOT] Busy: {stats['busy']}")
    
    if problems:
        print(f"\n[ATTENTION] {len(problems)} problèmes détectés:")
        for prob in problems:
            print(f"  - {prob['node']}: {prob['issue']} ({prob['reason']})")
    
    return stats, problems

# Utilisation
stats, problems = check_nodes_health(server)


[OK] EXEMPLES PRATIQUES COMPLETS

# === EXEMPLE 1: Script de déploiement automatisé ===

"""
Cas d'usage: Déployer automatiquement quand on push sur main
"""

def automated_deployment_on_push():
    """
    1. Créer un job qui se déclenche sur Git push
    2. Lance les tests
    3. Si OK, déploie en staging
    4. Envoie une notification
    """
    
    # Étape 1: Créer le job de déploiement
    deploy_job_config = '''<?xml version='1.0' encoding='UTF-8'?>
<project>
  <description>Déploiement automatique</description>
  
  <!-- Configuration Git -->
  <scm class="hudson.plugins.git.GitSCM">
    <userRemoteConfigs>
      <hudson.plugins.git.UserRemoteConfig>
        <url>https://github.com/user/mon-projet.git</url>
      </hudson.plugins.git.UserRemoteConfig>
    </userRemoteConfigs>
    <branches>
      <hudson.plugins.git.BranchSpec>
        <n>*/main</n>
      </hudson.plugins.git.BranchSpec>
    </branches>
  </scm>
  
  <!-- Déclencheur: webhook GitHub -->
  <triggers>
    <com.cloudbees.jenkins.GitHubPushTrigger>
      <spec></spec>
    </com.cloudbees.jenkins.GitHubPushTrigger>
  </triggers>
  
  <!-- Commandes à exécuter -->
  <builders>
    <hudson.tasks.Shell>
      <command>
#!/bin/bash
set -e  # Arrêter si erreur

echo "[RAPIDE] Démarrage du déploiement..."

# Environnement virtuel
python -m venv venv
source venv/bin/activate

# Installation
echo "[PACKAGE] Installation dépendances..."
pip install -r requirements.txt

# Tests
echo "[TEST] Exécution des tests..."
pytest tests/ -v

# Build
echo "[OUTIL] Build de l'application..."
python setup.py build

# Déploiement
echo "[TRANSPORT] Déploiement en staging..."
./deploy.sh staging

echo "[OK] Déploiement terminé!"
      </command>
    </hudson.tasks.Shell>
  </builders>
  
  <!-- Notifications -->
  <publishers>
    <hudson.tasks.Mailer>
      <recipients>team@example.com</recipients>
      <sendToIndividuals>false</sendToIndividuals>
    </hudson.tasks.Mailer>
  </publishers>
</project>'''
    
    # Créer le job
    server.create_job('auto-deploy-staging', deploy_job_config)
    print("[OK] Job de déploiement créé")
    
    # Étape 2: Configurer le webhook GitHub
    print("\n[NOTE] Configuration GitHub Webhook:")
    print("1. Aller dans votre repo GitHub")
    print("2. Settings > Webhooks > Add webhook")
    print("3. Payload URL: http://your-jenkins.com/github-webhook/")
    print("4. Content type: application/json")
    print("5. Events: Just the push event")
    
    return True

# Exécuter
automated_deployment_on_push()


# === EXEMPLE 2: Tests multi-versions Python ===

"""
Cas d'usage: Tester votre code sur Python 3.9, 3.10, 3.11, 3.12
"""

def create_multi_version_test():
    """
    Créer des jobs pour tester sur plusieurs versions Python
    """
    python_versions = ['3.9', '3.10', '3.11', '3.12']
    
    for version in python_versions:
        job_name = f'test-python-{version}'
        
        # Pipeline pour cette version
        pipeline = f'''
pipeline {{
    agent {{
        docker {{
            image 'python:{version}'
        }}
    }}
    
    stages {{
        stage('Info') {{
            steps {{
                sh 'python --version'
                sh 'pip --version'
            }}
        }}
        
        stage('Install') {{
            steps {{
                sh 'pip install -r requirements.txt'
                sh 'pip install pytest pytest-cov'
            }}
        }}
        
        stage('Test') {{
            steps {{
                sh 'pytest tests/ -v --cov=src'
            }}
        }}
    }}
    
    post {{
        success {{
            echo '[OK] Tests OK sur Python {version}'
        }}
        failure {{
            echo '[X] Tests KO sur Python {version}'
        }}
    }}
}}
'''
        
        config = f'''<?xml version='1.0' encoding='UTF-8'?>
<flow-definition plugin="workflow-job">
  <description>Tests Python {version}</description>
  <definition class="org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition">
    <script>{pipeline}</script>
    <sandbox>true</sandbox>
  </definition>
</flow-definition>'''
        
        server.create_job(job_name, config)
        print(f"[OK] Job créé: {job_name}")
    
    # Créer un job "orchestrateur" qui lance tous les tests
    orchestrator = '''
pipeline {
    agent any
    
    stages {
        stage('Test All Versions') {
            parallel {
                stage('Python 3.9') {
                    steps {
                        build job: 'test-python-3.9'
                    }
                }
                stage('Python 3.10') {
                    steps {
                        build job: 'test-python-3.10'
                    }
                }
                stage('Python 3.11') {
                    steps {
                        build job: 'test-python-3.11'
                    }
                }
                stage('Python 3.12') {
                    steps {
                        build job: 'test-python-3.12'
                    }
                }
            }
        }
    }
}
'''
    
    config = f'''<?xml version='1.0' encoding='UTF-8'?>
<flow-definition plugin="workflow-job">
  <description>Tester toutes les versions Python</description>
  <definition class="org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition">
    <script>{orchestrator}</script>
    <sandbox>true</sandbox>
  </definition>
</flow-definition>'''
    
    server.create_job('test-all-python-versions', config)
    print("[OK] Job orchestrateur créé")

# Exécuter
create_multi_version_test()

# Utilisation:
# Maintenant vous pouvez lancer 'test-all-python-versions'
# qui testera votre code sur les 4 versions EN PARALLÈLE


[OK] MONITORING & ALERTES (SURVEILLANCE)

# === Pourquoi surveiller Jenkins ? ===

# Jenkins est critique pour votre workflow de développement
# Si Jenkins tombe, votre équipe ne peut plus déployer !
# 
# Choses à surveiller:
# 1. Jenkins est-il accessible ?
# 2. Y a-t-il des builds qui échouent ?
# 3. La queue est-elle trop longue ?
# 4. Les nodes sont-ils online ?
# 5. L'espace disque est-il suffisant ?


# === Check 1: Jenkins est accessible ? ===

def is_jenkins_alive(server, timeout=10):
    """
    Vérifier que Jenkins répond
    """
    import time
    
    try:
        start = time.time()
        version = server.get_version()
        response_time = time.time() - start
        
        print(f"[OK] Jenkins OK (v{version})")
        print(f"   Temps de réponse: {response_time:.2f}s")
        
        if response_time > 5:
            print("[ATTENTION] Réponse lente (> 5s)")
        
        return True
        
    except Exception as e:
        print(f"[X] Jenkins inaccessible: {e}")
        return False

# Utilisation
if not is_jenkins_alive(server):
    # Envoyer alerte critique
    print("[ALERTE] ALERTE CRITIQUE: Jenkins DOWN!")


# === Check 2: Santé globale du système ===

def health_check(server):
    """
    Vérification complète de la santé de Jenkins
    """
    print("[HOPITAL] Health Check Jenkins\n")
    
    issues = []
    
    # 1. Version Jenkins
    try:
        version = server.get_version()
        print(f"[OK] Version: {version}")
    except Exception as e:
        issues.append(f"Cannot get version: {e}")
    
    # 2. Nodes
    try:
        nodes = server.get_nodes()
        offline_nodes = [n for n in nodes if n.get('offline')]
        
        print(f"[OK] Nodes: {len(nodes)} total, {len(offline_nodes)} offline")
        
        if offline_nodes:
            issues.append(f"{len(offline_nodes)} nodes offline")
            for node in offline_nodes:
                print(f"   [ATTENTION] {node['name']} is OFFLINE")
    except Exception as e:
        issues.append(f"Cannot check nodes: {e}")
    
    # 3. Queue
    try:
        queue = server.get_queue_info()
        stuck_items = [q for q in queue if q.get('stuck')]
        
        print(f"[OK] Queue: {len(queue)} items")
        
        if len(queue) > 20:
            issues.append(f"Queue très longue: {len(queue)} items")
        
        if stuck_items:
            issues.append(f"{len(stuck_items)} items bloqués dans la queue")
    except Exception as e:
        issues.append(f"Cannot check queue: {e}")
    
    # 4. Jobs en échec
    try:
        jobs = server.get_jobs()
        failed_jobs = []
        
        for job in jobs:
            info = server.get_job_info(job['name'])
            if info.get('color') == 'red':
                failed_jobs.append(job['name'])
        
        print(f"[OK] Jobs: {len(jobs)} total, {len(failed_jobs)} failed")
        
        if len(failed_jobs) > 5:
            issues.append(f"{len(failed_jobs)} jobs en échec")
    except Exception as e:
        issues.append(f"Cannot check jobs: {e}")
    
    # 5. Builds qui tournent depuis longtemps
    try:
        long_builds = []
        for job in jobs:
            info = server.get_job_info(job['name'])
            last_build = info.get('lastBuild')
            
            if last_build:
                build_info = server.get_build_info(job['name'], last_build['number'])
                
                if build_info['building']:
                    # Build en cours depuis plus de 1h
                    from datetime import datetime
                    start_time = datetime.fromtimestamp(build_info['timestamp'] / 1000)
                    duration = (datetime.now() - start_time).total_seconds()
                    
                    if duration > 3600:  # 1 heure
                        long_builds.append({
                            'job': job['name'],
                            'duration': duration / 60  # en minutes
                        })
        
        if long_builds:
            issues.append(f"{len(long_builds)} builds qui tournent depuis >1h")
            for build in long_builds:
                print(f"   [ATTENTION] {build['job']}: {build['duration']:.0f} minutes")
    except Exception as e:
        issues.append(f"Cannot check running builds: {e}")
    
    # Résumé
    print(f"\n{'='*50}")
    if issues:
        print(f"[ATTENTION] {len(issues)} problèmes détectés:\n")
        for issue in issues:
            print(f"  [X] {issue}")
        return False
    else:
        print("[OK] Tout est OK!")
        return True

# Utilisation
health_check(server)


# === Surveillance en continu ===

def continuous_monitoring(server, check_interval=300):
    """
    Surveillance continue de Jenkins
    Vérifie toutes les 5 minutes par défaut
    """
    import time
    
    print(f"[EYES] Surveillance continue activée")
    print(f"   Intervalle: {check_interval}s ({check_interval/60:.0f} minutes)")
    print("   Appuyez Ctrl+C pour arrêter\n")
    
    consecutive_failures = 0
    
    while True:
        try:
            print(f"\n{'='*50}")
            print(f"[HEURE] {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
            print(f"{'='*50}")
            
            # Vérifier santé
            is_healthy = health_check(server)
            
            if not is_healthy:
                consecutive_failures += 1
                
                print(f"\n[ATTENTION] Échecs consécutifs: {consecutive_failures}")
                
                # Alerte si 3 échecs d'affilée
                if consecutive_failures >= 3:
                    print("\n[ALERTE] ALERTE CRITIQUE!")
                    print("   Jenkins a des problèmes depuis 3 vérifications")
                    # Envoyer alerte critique
                    send_critical_alert()
            else:
                consecutive_failures = 0
            
            # Attendre avant prochaine vérification
            print(f"\n[HOURGLASS_WITH_FLOWING_SAND] Prochaine vérification dans {check_interval}s...")
            time.sleep(check_interval)
            
        except KeyboardInterrupt:
            print("\n\n[STOP] Surveillance arrêtée")
            break
        except Exception as e:
            print(f"\n[X] Erreur: {e}")
            time.sleep(check_interval)


def send_critical_alert():
    """
    Envoyer une alerte critique
    """
    message = """
[ALERTE] ALERTE JENKINS CRITIQUE [ALERTE]

Jenkins a des problèmes persistants.
Intervention humaine requise.

Vérifiez:
- http://jenkins.example.com
- Logs serveur
- Espace disque
- Nodes offline
"""
    
    print(message)
    # send_slack(message)
    # send_email(message)
    # send_sms(message)  # Pour les vraies alertes critiques


# === Statistiques et métriques ===

def get_jenkins_metrics(server):
    """
    Collecter des métriques sur Jenkins
    """
    from datetime import datetime, timedelta
    
    metrics = {
        'timestamp': datetime.now().isoformat(),
        'jenkins_version': server.get_version(),
        'total_jobs': 0,
        'active_jobs': 0,
        'disabled_jobs': 0,
        'failed_jobs': 0,
        'success_jobs': 0,
        'building_jobs': 0,
        'queue_size': 0,
        'nodes_total': 0,
        'nodes_online': 0,
        'nodes_offline': 0,
        'builds_last_hour': 0,
        'builds_last_day': 0
    }
    
    # Métriques des jobs
    jobs = server.get_jobs()
    metrics['total_jobs'] = len(jobs)
    
    cutoff_hour = datetime.now() - timedelta(hours=1)
    cutoff_day = datetime.now() - timedelta(days=1)
    
    for job in jobs:
        try:
            info = server.get_job_info(job['name'])
            
            if info.get('disabled'):
                metrics['disabled_jobs'] += 1
            else:
                metrics['active_jobs'] += 1
            
            last_build = info.get('lastBuild')
            if last_build:
                build_info = server.get_build_info(job['name'], last_build['number'])
                
                # Statut
                if build_info['building']:
                    metrics['building_jobs'] += 1
                elif build_info.get('result') == 'SUCCESS':
                    metrics['success_jobs'] += 1
                elif build_info.get('result') == 'FAILURE':
                    metrics['failed_jobs'] += 1
                
                # Compteur dernière heure/jour
                build_time = datetime.fromtimestamp(build_info['timestamp'] / 1000)
                if build_time > cutoff_hour:
                    metrics['builds_last_hour'] += 1
                if build_time > cutoff_day:
                    metrics['builds_last_day'] += 1
        except:
            pass
    
    # Métriques des nodes
    nodes = server.get_nodes()
    metrics['nodes_total'] = len(nodes)
    
    for node in nodes:
        if node.get('offline'):
            metrics['nodes_offline'] += 1
        else:
            metrics['nodes_online'] += 1
    
    # Métriques de la queue
    queue = server.get_queue_info()
    metrics['queue_size'] = len(queue)
    
    return metrics


def log_metrics(server, log_file='jenkins_metrics.log'):
    """
    Logger les métriques dans un fichier
    """
    import json
    
    metrics = get_jenkins_metrics(server)
    
    # Afficher
    print("[GRAPHIQUE] Métriques Jenkins:")
    print(f"  Jobs: {metrics['total_jobs']} total, {metrics['active_jobs']} actifs")
    print(f"  Builds: {metrics['builds_last_hour']} dernière heure, {metrics['builds_last_day']} aujourd'hui")
    print(f"  Succès: {metrics['success_jobs']} | Échecs: {metrics['failed_jobs']}")
    print(f"  Nodes: {metrics['nodes_online']}/{metrics['nodes_total']} online")
    print(f"  Queue: {metrics['queue_size']} items")
    
    # Logger dans fichier
    with open(log_file, 'a') as f:
        f.write(json.dumps(metrics) + '\n')
    
    return metrics


# === Génération de rapports ===

def generate_daily_report(server):
    """
    Générer un rapport quotidien
    """
    from datetime import datetime, timedelta
    
    print("[HAUSSE] Rapport Quotidien Jenkins")
    print(f"Date: {datetime.now().strftime('%Y-%m-%d')}\n")
    
    # Période: dernières 24h
    cutoff = datetime.now() - timedelta(days=1)
    
    # Statistiques
    jobs = server.get_jobs()
    
    stats = {
        'total_builds': 0,
        'successful_builds': 0,
        'failed_builds': 0,
        'total_duration': 0,
        'jobs_with_failures': []
    }
    
    for job in jobs:
        try:
            info = server.get_job_info(job['name'])
            job_failures = 0
            
            for build in info['builds']:
                build_info = server.get_build_info(job['name'], build['number'])
                build_time = datetime.fromtimestamp(build_info['timestamp'] / 1000)
                
                # Seulement dernières 24h
                if build_time < cutoff:
                    break
                
                stats['total_builds'] += 1
                stats['total_duration'] += build_info['duration']
                
                result = build_info.get('result')
                if result == 'SUCCESS':
                    stats['successful_builds'] += 1
                elif result == 'FAILURE':
                    stats['failed_builds'] += 1
                    job_failures += 1
            
            if job_failures > 0:
                stats['jobs_with_failures'].append({
                    'job': job['name'],
                    'failures': job_failures
                })
        except:
            pass
    
    # Afficher rapport
    print(f"{'='*50}")
    print(f"[GRAPHIQUE] STATISTIQUES")
    print(f"{'='*50}")
    print(f"Total builds: {stats['total_builds']}")
    print(f"[OK] Succès: {stats['successful_builds']}")
    print(f"[X] Échecs: {stats['failed_builds']}")
    
    if stats['total_builds'] > 0:
        success_rate = (stats['successful_builds'] / stats['total_builds']) * 100
        avg_duration = stats['total_duration'] / stats['total_builds'] / 1000 / 60
        
        print(f"[HAUSSE] Taux de réussite: {success_rate:.1f}%")
        print(f"[TEMPS] Durée moyenne: {avg_duration:.1f} minutes")
    
    if stats['jobs_with_failures']:
        print(f"\n{'='*50}")
        print(f"[ATTENTION] JOBS AVEC ÉCHECS")
        print(f"{'='*50}")
        
        # Trier par nombre d'échecs
        sorted_failures = sorted(
            stats['jobs_with_failures'],
            key=lambda x: x['failures'],
            reverse=True
        )
        
        for item in sorted_failures[:10]:  # Top 10
            print(f"  {item['job']}: {item['failures']} échecs")
    
    return stats


# === Alertes par Slack ===

def send_slack_alert(webhook_url, message, color='warning'):
    """
    Envoyer alerte sur Slack
    """
    import requests
    
    colors = {
        'good': '#36a64f',      # Vert
        'warning': '#ff9900',   # Orange
        'danger': '#ff0000'     # Rouge
    }
    
    payload = {
        'attachments': [{
            'color': colors.get(color, '#808080'),
            'text': message,
            'footer': 'Jenkins Monitoring',
            'footer_icon': 'https://www.jenkins.io/images/logos/jenkins/jenkins.png'
        }]
    }
    
    try:
        response = requests.post(webhook_url, json=payload)
        if response.status_code == 200:
            print("[OK] Alerte Slack envoyée")
        else:
            print(f"[X] Erreur Slack: {response.status_code}")
    except Exception as e:
        print(f"[X] Erreur Slack: {e}")


# === Exemple complet de monitoring ===

class JenkinsMonitor:
    """
    Système complet de monitoring Jenkins
    """
    
    def __init__(self, server, slack_webhook=None):
        self.server = server
        self.slack_webhook = slack_webhook
        self.last_check = None
        self.issues_history = []
    
    def run(self, interval=300):
        """
        Lancer la surveillance
        """
        import time
        
        print("[RAPIDE] Jenkins Monitor démarré")
        print(f"   Intervalle: {interval}s\n")
        
        while True:
            try:
                self.check()
                time.sleep(interval)
            except KeyboardInterrupt:
                print("\n[STOP] Monitoring arrêté")
                break
            except Exception as e:
                print(f"[X] Erreur: {e}")
                time.sleep(interval)
    
    def check(self):
        """
        Effectuer une vérification
        """
        from datetime import datetime
        
        print(f"\n{'='*60}")
        print(f"[HEURE] {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
        print(f"{'='*60}\n")
        
        # Vérifications
        checks = [
            self.check_accessibility(),
            self.check_queue(),
            self.check_failed_jobs(),
            self.check_nodes(),
            self.check_slow_builds()
        ]
        
        # Si tous OK
        if all(checks):
            print("\n[OK] Tous les checks sont OK")
        else:
            print("\n[ATTENTION] Des problèmes ont été détectés")
            
            # Envoyer alerte si configuré
            if self.slack_webhook:
                self.send_alert()
        
        self.last_check = datetime.now()
    
    def check_accessibility(self):
        """Check 1: Jenkins répond ?"""
        try:
            version = self.server.get_version()
            print(f"[OK] Jenkins accessible (v{version})")
            return True
        except Exception as e:
            print(f"[X] Jenkins inaccessible: {e}")
            self.issues_history.append({
                'type': 'accessibility',
                'message': f"Jenkins down: {e}"
            })
            return False
    
    def check_queue(self):
        """Check 2: Queue trop longue ?"""
        queue = self.server.get_queue_info()
        queue_size = len(queue)
        
        if queue_size > 10:
            print(f"[ATTENTION] Queue longue: {queue_size} items")
            self.issues_history.append({
                'type': 'queue',
                'message': f"Queue: {queue_size} items"
            })
            return False
        else:
            print(f"[OK] Queue OK: {queue_size} items")
            return True
    
    def check_failed_jobs(self):
        """Check 3: Jobs en échec ?"""
        jobs = self.server.get_jobs()
        failed = [j for j in jobs if j.get('color') == 'red']
        
        if len(failed) > 3:
            print(f"[ATTENTION] {len(failed)} jobs en échec")
            for job in failed[:5]:
                print(f"   - {job['name']}")
            self.issues_history.append({
                'type': 'failed_jobs',
                'message': f"{len(failed)} jobs failed"
            })
            return False
        else:
            print(f"[OK] Jobs OK ({len(failed)} échecs)")
            return True
    
    def check_nodes(self):
        """Check 4: Nodes online ?"""
        nodes = self.server.get_nodes()
        offline = [n for n in nodes if n.get('offline')]
        
        if offline:
            print(f"[ATTENTION] {len(offline)} nodes offline")
            for node in offline:
                print(f"   - {node['name']}")
            self.issues_history.append({
                'type': 'nodes',
                'message': f"{len(offline)} nodes offline"
            })
            return False
        else:
            print(f"[OK] Nodes OK ({len(nodes)} online)")
            return True
    
    def check_slow_builds(self):
        """Check 5: Builds qui traînent ?"""
        # À implémenter selon vos besoins
        print("[OK] Pas de builds lents détectés")
        return True
    
    def send_alert(self):
        """Envoyer alerte groupée"""
        if not self.issues_history:
            return
        
        message = "[ALERTE] *Problèmes Jenkins détectés*\n\n"
        
        for issue in self.issues_history[-5:]:  # 5 derniers
            message += f"• {issue['type']}: {issue['message']}\n"
        
        send_slack_alert(self.slack_webhook, message, color='danger')
        
        # Limiter l'historique
        if len(self.issues_history) > 100:
            self.issues_history = self.issues_history[-50:]

# Utilisation
monitor = JenkinsMonitor(
    server,
    slack_webhook='https://hooks.slack.com/services/YOUR/WEBHOOK'
)

# Lancer (bloquant)
# monitor.run(interval=300)  # Toutes les 5 minutes

"""
Cas d'usage: Dashboard web pour surveiller Jenkins en temps réel
"""

from flask import Flask, render_template_string, jsonify
import threading

app = Flask(__name__)

# Template HTML du dashboard
DASHBOARD_HTML = '''
<!DOCTYPE html>
<html>
<head>
    <title>Jenkins Dashboard</title>
    <meta charset="UTF-8">
    <meta http-equiv="refresh" content="30">
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 20px;
            background: #f5f5f5;
        }
        .container {
            max-width: 1200px;
            margin: 0 auto;
            background: white;
            padding: 20px;
            border-radius: 8px;
        }
        h1 {
            color: #333;
            border-bottom: 3px solid #4CAF50;
            padding-bottom: 10px;
        }
        .stats {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 20px;
            margin: 30px 0;
        }
        .stat-card {
            padding: 20px;
            border-radius: 8px;
            color: white;
            text-align: center;
        }
        .stat-card.success { background: linear-gradient(135deg, #4CAF50, #45a049); }
        .stat-card.warning { background: linear-gradient(135deg, #ff9800, #f57c00); }
        .stat-card.error { background: linear-gradient(135deg, #f44336, #d32f2f); }
        .stat-card.info { background: linear-gradient(135deg, #2196F3, #1976D2); }
        .stat-number { font-size: 48px; font-weight: bold; }
        .stat-label { font-size: 14px; opacity: 0.9; }
        .job-list {
            margin-top: 30px;
        }
        .job-item {
            display: flex;
            justify-content: space-between;
            padding: 15px;
            margin: 10px 0;
            background: #f9f9f9;
            border-radius: 5px;
            border-left: 4px solid #ddd;
        }
        .job-item.success { border-left-color: #4CAF50; }
        .job-item.failure { border-left-color: #f44336; }
        .job-name { font-weight: bold; }
        .job-status { padding: 5px 10px; border-radius: 3px; color: white; }
        .status-success { background: #4CAF50; }
        .status-failure { background: #f44336; }
        .status-building { background: #2196F3; }
    </style>
</head>
<body>
    <div class="container">
        <h1>[GRAPHIQUE] Jenkins Dashboard</h1>
        <p>Mise à jour automatique toutes les 30 secondes</p>
        
        <div class="stats">
            <div class="stat-card info">
                <div class="stat-label">TOTAL JOBS</div>
                <div class="stat-number">{{ stats.total_jobs }}</div>
            </div>
            <div class="stat-card success">
                <div class="stat-label">SUCCÈS</div>
                <div class="stat-number">{{ stats.success }}</div>
            </div>
            <div class="stat-card error">
                <div class="stat-label">ÉCHECS</div>
                <div class="stat-number">{{ stats.failures }}</div>
            </div>
            <div class="stat-card warning">
                <div class="stat-label">EN COURS</div>
                <div class="stat-number">{{ stats.building }}</div>
            </div>
        </div>
        
        <div class="job-list">
            <h2>[LISTE] Derniers Jobs</h2>
            {% for job in jobs %}
            <div class="job-item {{ job.status }}">
                <div>
                    <div class="job-name">{{ job.name }}</div>
                    <div>Build #{{ job.build_number }}</div>
                </div>
                <div>
                    <span class="job-status status-{{ job.status }}">
                        {{ job.result }}
                    </span>
                </div>
            </div>
            {% endfor %}
        </div>
    </div>
</body>
</html>
'''

@app.route('/')
def dashboard():
    """Page principale du dashboard"""
    # Récupérer stats
    jobs = server.get_jobs()
    
    stats = {
        'total_jobs': len(jobs),
        'success': 0,
        'failures': 0,
        'building': 0
    }
    
    jobs_data = []
    
    for job in jobs[:20]:  # Limiter à 20
        try:
            info = server.get_job_info(job['name'])
            last_build = info.get('lastBuild')
            
            if last_build:
                build_info = server.get_build_info(job['name'], last_build['number'])
                
                if build_info['building']:
                    stats['building'] += 1
                    result = 'BUILDING'
                    status = 'building'
                else:
                    result = build_info.get('result', 'UNKNOWN')
                    
                    if result == 'SUCCESS':
                        stats['success'] += 1
                        status = 'success'
                    elif result == 'FAILURE':
                        stats['failures'] += 1
                        status = 'failure'
                    else:
                        status = 'warning'
                
                jobs_data.append({
                    'name': job['name'],
                    'build_number': last_build['number'],
                    'result': result,
                    'status': status
                })
        except:
            pass
    
    return render_template_string(
        DASHBOARD_HTML, 
        stats=stats, 
        jobs=jobs_data
    )

@app.route('/api/stats')
def api_stats():
    """API JSON pour récupérer les stats"""
    jobs = server.get_jobs()
    queue = server.get_queue_info()
    
    stats = {
        'total_jobs': len(jobs),
        'queue_size': len(queue),
        'failed_jobs': 0,
        'success_jobs': 0,
        'building_jobs': 0
    }
    
    for job in jobs:
        try:
            info = server.get_job_info(job['name'])
            last_build = info.get('lastBuild')
            
            if last_build:
                build_info = server.get_build_info(job['name'], last_build['number'])
                if build_info['building']:
                    stats['building_jobs'] += 1
                elif build_info.get('result') == 'SUCCESS':
                    stats['success_jobs'] += 1
                elif build_info.get('result') == 'FAILURE':
                    stats['failed_jobs'] += 1
        except:
            pass
    
    return jsonify(stats)

def run_dashboard():
    """Lancer le serveur Flask"""
    print("[WEB] Dashboard démarré sur http://localhost:5000")
    app.run(host='0.0.0.0', port=5000, debug=False)

# Lancer dans un thread séparé
dashboard_thread = threading.Thread(target=run_dashboard, daemon=True)
dashboard_thread.start()

print("[OK] Dashboard accessible sur http://localhost:5000")


# === EXEMPLE 5: Notifications intelligentes ===

"""
Cas d'usage: Recevoir des notifications seulement quand c'est important
"""

class SmartNotifier:
    """
    Système de notification intelligent
    N'envoie des alertes que si nécessaire
    """
    
    def __init__(self, server, slack_webhook=None):
        self.server = server
        self.slack_webhook = slack_webhook
        self.failure_history = {}  # Historique des échecs
    
    def should_notify(self, job_name, build_info):
        """
        Décider si on doit envoyer une notification
        Retourne (True/False, raison)
        """
        result = build_info['result']
        
        # 1. PREMIER ÉCHEC -> Notifier
        if result == 'FAILURE':
            if job_name not in self.failure_history:
                self.failure_history[job_name] = 1
                return True, "first_failure"
            
            # 2. ÉCHECS RÉPÉTÉS (tous les 3) -> Notifier
            self.failure_history[job_name] += 1
            if self.failure_history[job_name] % 3 == 0:
                return True, "repeated_failure"
            
            # Sinon, ne pas notifier (on sait déjà qu'il y a un problème)
            return False, None
        
        # 3. RETOUR À LA NORMALE -> Notifier
        elif result == 'SUCCESS' and job_name in self.failure_history:
            del self.failure_history[job_name]
            return True, "back_to_normal"
        
        # 4. BUILD ANORMALEMENT LONG -> Notifier
        avg_duration = self.get_average_duration(job_name)
        if build_info['duration'] > avg_duration * 2:  # 2x plus long
            return True, "slow_build"
        
        # Cas normal, pas de notification
        return False, None
    
    def get_average_duration(self, job_name, last_n=10):
        """Calculer durée moyenne des derniers builds"""
        try:
            job_info = self.server.get_job_info(job_name)
            builds = job_info['builds'][:last_n]
            
            durations = []
            for build in builds:
                build_info = self.server.get_build_info(job_name, build['number'])
                if build_info.get('result') == 'SUCCESS':
                    durations.append(build_info['duration'])
            
            return sum(durations) / len(durations) if durations else 0
        except:
            return 0
    
    def send_notification(self, job_name, build_info, reason):
        """Envoyer la notification"""
        messages = {
            'first_failure': f"[ROUGE] **Premier échec détecté**\nJob: {job_name} #{build_info['number']}",
            'repeated_failure': f"[ATTENTION] **Échecs répétés** ({self.failure_history[job_name]}x)\nJob: {job_name}",
            'back_to_normal': f"[OK] **Retour à la normale**\nJob: {job_name} #{build_info['number']}",
            'slow_build': f"[LENT] **Build anormalement lent**\nJob: {job_name} ({build_info['duration']/1000:.0f}s)"
        }
        
        message = messages.get(reason, f"ℹ Build: {job_name}")
        message += f"\n[LIEN] {build_info['url']}"
        
        print(f"\n[ANNONCE] NOTIFICATION: {message}\n")
        
        # Envoyer sur Slack si configuré
        if self.slack_webhook:
            self.send_slack(message)
        
        # Envoyer email
        self.send_email(message)
    
    def send_slack(self, message):
        """Envoyer notification Slack"""
        import requests
        
        payload = {
            'text': message,
            'username': 'Jenkins Bot',
            'icon_emoji': ':robot_face:'
        }
        
        try:
            requests.post(self.slack_webhook, json=payload)
        except Exception as e:
            print(f"Erreur Slack: {e}")
    
    def send_email(self, message):
        """Envoyer notification par email"""
        import smtplib
        from email.mime.text import MIMEText
        
        # Configuration email (à adapter)
        smtp_server = "smtp.gmail.com"
        smtp_port = 587
        sender = "jenkins@example.com"
        password = "your_password"
        recipients = ["team@example.com"]
        
        msg = MIMEText(message)
        msg['Subject'] = 'Jenkins Alert'
        msg['From'] = sender
        msg['To'] = ', '.join(recipients)
        
        try:
            with smtplib.SMTP(smtp_server, smtp_port) as smtp:
                smtp.starttls()
                smtp.login(sender, password)
                smtp.send_message(msg)
        except Exception as e:
            print(f"Erreur email: {e}")
    
    def monitor_builds(self, interval=60):
        """
        Surveiller les builds en continu
        """
        import time
        
        print("[EYES] Surveillance des builds démarrée...")
        print(f"   Vérification toutes les {interval}s")
        
        while True:
            try:
                jobs = self.server.get_jobs()
                
                for job in jobs:
                    info = self.server.get_job_info(job['name'])
                    last_build = info.get('lastBuild')
                    
                    if last_build:
                        build_info = self.server.get_build_info(
                            job['name'], 
                            last_build['number']
                        )
                        
                        # Vérifier si notification nécessaire
                        should_notify, reason = self.should_notify(
                            job['name'], 
                            build_info
                        )
                        
                        if should_notify:
                            self.send_notification(
                                job['name'], 
                                build_info, 
                                reason
                            )
                
                time.sleep(interval)
                
            except KeyboardInterrupt:
                print("\n[STOP] Surveillance arrêtée")
                break
            except Exception as e:
                print(f"[X] Erreur: {e}")
                time.sleep(interval)

# Utilisation
notifier = SmartNotifier(
    server,
    slack_webhook='https://hooks.slack.com/services/YOUR/WEBHOOK/URL'
)

# Surveiller en continu (dans un script séparé)
# notifier.monitor_builds(interval=60)  # Vérifier toutes les minutes


# === EXEMPLE 6: Créer des jobs depuis un fichier YAML ===

"""
Cas d'usage: Définir tous vos jobs dans un fichier YAML
"""

import yaml

# Fichier de configuration: jobs.yaml
yaml_config = '''
jobs:
  - name: api-tests
    type: pipeline
    description: "Tests de l'API"
    git:
      url: https://github.com/user/api.git
      branch: main
    triggers:
      - cron: "H/15 * * * *"  # Toutes les 15 minutes
    stages:
      - name: Setup
        steps:
          - python -m venv venv
          - source venv/bin/activate
          - pip install -r requirements.txt
      
      - name: Test
        steps:
          - source venv/bin/activate
          - pytest tests/api/ -v
  
  - name: frontend-build
    type: pipeline
    description: "Build du frontend"
    git:
      url: https://github.com/user/frontend.git
      branch: develop
    stages:
      - name: Install
        steps:
          - npm install
      
      - name: Build
        steps:
          - npm run build
      
      - name: Test
        steps:
          - npm test
  
  - name: deploy-prod
    type: pipeline
    description: "Déploiement production"
    parameters:
      - name: VERSION
        type: string
        default: "latest"
      - name: CONFIRM
        type: boolean
        default: false
    stages:
      - name: Deploy
        steps:
          - ./deploy.sh production $VERSION
'''

def create_jobs_from_yaml(server, yaml_content):
    """
    Créer tous les jobs définis dans le YAML
    """
    config = yaml.safe_load(yaml_content)
    
    for job_config in config['jobs']:
        job_name = job_config['name']
        job_type = job_config['type']
        
        print(f"\n[OUTIL] Création de '{job_name}'...")
        
        if job_type == 'pipeline':
            create_pipeline_from_yaml(server, job_config)
        else:
            print(f"[ATTENTION] Type '{job_type}' non supporté")
            continue
        
        print(f"[OK] Job '{job_name}' créé")

def create_pipeline_from_yaml(server, config):
    """
    Créer un pipeline depuis la config YAML
    """
    # Construire les stages
    stages = []
    for stage in config.get('stages', []):
        steps = '\n                    '.join(stage['steps'])
        stages.append(f'''
        stage('{stage['name']}') {{
            steps {{
                sh """
                    {steps}
                """
            }}
        }}''')
    
    # Pipeline Groovy
    pipeline_script = f'''
pipeline {{
    agent any
    
    stages {{
{''.join(stages)}
    }}
    
    post {{
        success {{
            echo '[OK] Pipeline réussi!'
        }}
        failure {{
            echo '[X] Pipeline échoué!'
        }}
    }}
}}
'''
    
    # Configuration XML
    xml_config = f'''<?xml version='1.0' encoding='UTF-8'?>
<flow-definition plugin="workflow-job">
  <description>{config.get('description', '')}</description>
  <definition class="org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition">
    <script>{pipeline_script}</script>
    <sandbox>true</sandbox>
  </definition>
</flow-definition>'''
    
    server.create_job(config['name'], xml_config)

# Utilisation
create_jobs_from_yaml(server, yaml_config)

# Ou depuis un fichier
with open('jobs.yaml', 'r') as f:
    yaml_content = f.read()
    create_jobs_from_yaml(server, yaml_content)

"""
Cas d'usage: Sauvegarder Jenkins tous les jours
"""

import os
import shutil
from datetime import datetime

def backup_all_jenkins(server, backup_dir='./jenkins_backups'):
    """
    Sauvegarder TOUTE la configuration Jenkins
    """
    # Créer dossier de backup avec timestamp
    timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
    backup_path = os.path.join(backup_dir, f'backup_{timestamp}')
    os.makedirs(backup_path, exist_ok=True)
    
    print(f"[PACKAGE] Backup Jenkins vers: {backup_path}")
    
    # 1. Sauvegarder tous les jobs
    print("[SAUVEGARDE] Sauvegarde des jobs...")
    jobs = server.get_jobs()
    jobs_dir = os.path.join(backup_path, 'jobs')
    os.makedirs(jobs_dir, exist_ok=True)
    
    for job in jobs:
        try:
            config = server.get_job_config(job['name'])
            filename = os.path.join(jobs_dir, f"{job['name']}.xml")
            
            with open(filename, 'w', encoding='utf-8') as f:
                f.write(config)
            
            print(f"  [OK] {job['name']}")
        except Exception as e:
            print(f"  [X] {job['name']}: {e}")
    
    # 2. Sauvegarder les vues
    print("\n[SAUVEGARDE] Sauvegarde des vues...")
    views = server.get_views()
    views_dir = os.path.join(backup_path, 'views')
    os.makedirs(views_dir, exist_ok=True)
    
    for view in views:
        try:
            config = server.get_view_config(view['name'])
            filename = os.path.join(views_dir, f"{view['name']}.xml")
            
            with open(filename, 'w', encoding='utf-8') as f:
                f.write(config)
            
            print(f"  [OK] {view['name']}")
        except Exception as e:
            print(f"  [X] {view['name']}: {e}")
    
    # 3. Sauvegarder les nodes
    print("\n[SAUVEGARDE] Sauvegarde des nodes...")
    nodes = server.get_nodes()
    nodes_dir = os.path.join(backup_path, 'nodes')
    os.makedirs(nodes_dir, exist_ok=True)
    
    for node in nodes:
        if node['name'] == 'master':
            continue  # Skip master
        
        try:
            config = server.get_node_config(node['name'])
            filename = os.path.join(nodes_dir, f"{node['name']}.xml")
            
            with open(filename, 'w', encoding='utf-8') as f:
                f.write(config)
            
            print(f"  [OK] {node['name']}")
        except Exception as e:
            print(f"  [X] {node['name']}: {e}")
    
    # 4. Créer manifest (résumé du backup)
    import json
    manifest = {
        'timestamp': timestamp,
        'jenkins_version': server.get_version(),
        'jobs_count': len(jobs),
        'views_count': len(views),
        'nodes_count': len(nodes) - 1  # -1 pour master
    }
    
    with open(os.path.join(backup_path, 'manifest.json'), 'w') as f:
        json.dump(manifest, f, indent=2)
    
    # 5. Compresser le backup
    print("\n[PACKAGE] Compression...")
    shutil.make_archive(backup_path, 'zip', backup_path)
    
    # Supprimer le dossier non compressé
    shutil.rmtree(backup_path)
    
    backup_file = f"{backup_path}.zip"
    file_size = os.path.getsize(backup_file) / (1024 * 1024)  # MB
    
    print(f"\n[OK] Backup terminé!")
    print(f"[DOSSIER] Fichier: {backup_file}")
    print(f"[SAUVEGARDE] Taille: {file_size:.2f} MB")
    
    return backup_file


def schedule_daily_backup():
    """
    Planifier un backup quotidien à 2h du matin
    """
    import schedule
    import time
    
    def backup_job():
        try:
            backup_file = backup_all_jenkins(server)
            print(f"[OK] Backup quotidien OK: {backup_file}")
            
            # Nettoyer vieux backups (garder 7 derniers)
            cleanup_old_backups('./jenkins_backups', keep=7)
            
        except Exception as e:
            print(f"[X] Erreur backup: {e}")
    
    # Planifier tous les jours à 2h
    schedule.every().day.at("02:00").do(backup_job)
    
    print("[ALARM_CLOCK] Backup quotidien planifié à 2h00")
    print("   (Appuyez Ctrl+C pour arrêter)")
    
    while True:
        schedule.run_pending()
        time.sleep(60)  # Vérifier chaque minute


def cleanup_old_backups(backup_dir, keep=7):
    """
    Supprimer les vieux backups, garder seulement les N derniers
    """
    backups = sorted([
        f for f in os.listdir(backup_dir) 
        if f.endswith('.zip') and f.startswith('backup_')
    ])
    
    if len(backups) > keep:
        to_delete = backups[:-keep]
        
        print(f"\n[SUPPRIMER] Nettoyage: suppression de {len(to_delete)} vieux backups")
        
        for backup in to_delete:
            filepath = os.path.join(backup_dir, backup)
            os.remove(filepath)
            print(f"  [OK] Supprimé: {backup}")

# Utilisation:
# 1. Backup manuel
backup_all_jenkins(server)

# 2. Backup planifié (dans un script séparé)
# schedule_daily_backup()  # Tourne en boucle infinie

# === Lister les nodes ===

nodes = server.get_nodes()
for node in nodes:
    print(f"Node: {node['name']}")
    print(f"Offline: {node['offline']}")
    print()

# === Info d'un node ===

node_name = 'agent-1'
node_info = server.get_node_info(node_name)
print(f"Description: {node_info['description']}")
print(f"Executors: {node_info['numExecutors']}")
print(f"Mode: {node_info['mode']}")
print(f"Offline: {node_info['offline']}")
print(f"Temporairement offline: {node_info['temporarilyOffline']}")

# Raison offline
if node_info['offline']:
    print(f"Raison: {node_info.get('offlineCauseReason', 'N/A')}")

# === Configuration d'un node ===

node_config = server.get_node_config(node_name)
print(node_config)

# === Créer un node ===

node_config = {
    'name': 'new-agent',
    'nodeDescription': 'Agent Python',
    'numExecutors': 2,
    'remoteFS': '/home/jenkins',
    'labels': 'python docker',
    'mode': 'NORMAL',  # NORMAL ou EXCLUSIVE
    'launcher': {
        'class': 'hudson.slaves.JNLPLauncher'
    }
}

server.create_node(
    node_config['name'],
    numExecutors=node_config['numExecutors'],
    nodeDescription=node_config['nodeDescription'],
    remoteFS=node_config['remoteFS'],
    labels=node_config['labels'],
    launcher=node_config['launcher']
)

# === Activer/Désactiver un node ===

# Désactiver temporairement
server.disable_node(node_name, msg='Maintenance')

# Activer
server.enable_node(node_name)

# === Mettre offline/online ===

# Offline
server.node_offline(node_name)

# Online
server.node_online(node_name)

# === Supprimer un node ===

server.delete_node(node_name)

# === Vérifier si node existe ===

def node_exists(server, node_name):
    try:
        server.get_node_info(node_name)
        return True
    except jenkins.NotFoundException:
        return False


[OK] GESTION DES VUES

# === Lister les vues ===

views = server.get_views()
for view in views:
    print(f"Vue: {view['name']}")
    print(f"URL: {view['url']}")

# === Info d'une vue ===

view_name = 'All'
view_info = server.get_view_info(view_name)
print(f"Description: {view_info['description']}")
print(f"Jobs: {len(view_info['jobs'])}")

for job in view_info['jobs']:
    print(f"  - {job['name']}")

# === Configuration d'une vue ===

view_config = server.get_view_config(view_name)
print(view_config)

# === Créer une vue ===

# Vue liste
list_view_config = '''<?xml version='1.0' encoding='UTF-8'?>
<hudson.model.ListView>
  <name>Python Projects</name>
  <description>Tous les projets Python</description>
  <filterExecutors>false</filterExecutors>
  <filterQueue>false</filterQueue>
  <properties class="hudson.model.View$PropertyList"/>
  <jobNames>
    <comparator class="hudson.util.CaseInsensitiveComparator"/>
  </jobNames>
  <jobFilters/>
  <columns>
    <hudson.views.StatusColumn/>
    <hudson.views.WeatherColumn/>
    <hudson.views.JobColumn/>
    <hudson.views.LastSuccessColumn/>
    <hudson.views.LastFailureColumn/>
    <hudson.views.LastDurationColumn/>
    <hudson.views.BuildButtonColumn/>
  </columns>
  <includeRegex>.*python.*</includeRegex>
  <recurse>false</recurse>
</hudson.model.ListView>'''

server.create_view('Python Projects', list_view_config)

# === Ajouter job à une vue ===

server.add_job_to_view(view_name, 'my-python-job')

# === Retirer job d'une vue ===

server.remove_job_from_view(view_name, 'my-python-job')

# === Supprimer une vue ===

server.delete_view(view_name)


[OK] GESTION DES PLUGINS

# === Lister les plugins ===

plugins = server.get_plugins()
for plugin in plugins:
    print(f"Plugin: {plugin['shortName']}")
    print(f"Version: {plugin['version']}")
    print(f"Actif: {plugin['active']}")
    print(f"Activé: {plugin['enabled']}")
    print()

# Plugins avec dépendances
plugins_info = server.get_plugins_info()
for plugin in plugins_info:
    if 'dependencies' in plugin:
        print(f"{plugin['shortName']} dépend de:")
        for dep in plugin['dependencies']:
            print(f"  - {dep['shortName']}")

# === Vérifier si plugin installé ===

def plugin_installed(server, plugin_name):
    plugins = server.get_plugins()
    return any(p['shortName'] == plugin_name for p in plugins)

if plugin_installed(server, 'git'):
    print('Plugin Git installé')

# === Installer un plugin ===

# Note: nécessite privilèges admin et redémarrage Jenkins
server.install_plugin('docker-plugin')

# Attendre installation
def wait_for_plugin_installation(server, plugin_name, timeout=300):
    start = time.time()
    while time.time() - start < timeout:
        if plugin_installed(server, plugin_name):
            return True
        time.sleep(5)
    return False


[OK] GESTION DES CREDENTIALS

# === Lister credentials (nécessite plugin) ===

# Avec credentials plugin
# pip install python-jenkins-credentials

from jenkins_credentials import Credentials

creds = Credentials(server)
all_creds = creds.list()

for cred in all_creds:
    print(f"ID: {cred['id']}")
    print(f"Description: {cred['description']}")
    print(f"Type: {cred['typeName']}")

# === Créer credential ===

# Username/Password
cred_config = {
    'id': 'github-credentials',
    'username': 'myuser',
    'password': 'mypassword',
    'description': 'GitHub credentials'
}

# Secret text
secret_config = {
    'id': 'api-token',
    'secret': 'my-secret-token',
    'description': 'API Token'
}

# SSH Key
ssh_config = {
    'id': 'ssh-key',
    'username': 'deployuser',
    'privateKey': open('~/.ssh/id_rsa').read(),
    'passphrase': 'key-passphrase',
    'description': 'Deploy SSH Key'
}


[OK] PIPELINES (JENKINSFILE)

# === Créer un Pipeline job ===

pipeline_config = '''<?xml version='1.0' encoding='UTF-8'?>
<flow-definition plugin="workflow-job">
  <description>Pipeline Python</description>
  <definition class="org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition">
    <script>
pipeline {
    agent any
    
    stages {
        stage('Checkout') {
            steps {
                git 'https://github.com/user/repo.git'
            }
        }
        
        stage('Build') {
            steps {
                sh '''
                    python -m venv venv
                    . venv/bin/activate
                    pip install -r requirements.txt
                '''
            }
        }
        
        stage('Test') {
            steps {
                sh '''
                    . venv/bin/activate
                    pytest tests/ --junitxml=test-results.xml
                '''
            }
        }
        
        stage('Deploy') {
            when {
                branch 'main'
            }
            steps {
                sh './deploy.sh'
            }
        }
    }
    
    post {
        always {
            junit 'test-results.xml'
        }
        success {
            echo 'Pipeline réussi!'
        }
        failure {
            echo 'Pipeline échoué!'
        }
    }
}
    </script>
    <sandbox>true</sandbox>
  </definition>
</flow-definition>'''

server.create_job('python-pipeline', pipeline_config)

# === Pipeline depuis SCM ===

pipeline_scm_config = '''<?xml version='1.0' encoding='UTF-8'?>
<flow-definition plugin="workflow-job">
  <description>Pipeline depuis Git</description>
  <definition class="org.jenkinsci.plugins.workflow.cps.CpsScmFlowDefinition">
    <scm class="hudson.plugins.git.GitSCM">
      <userRemoteConfigs>
        <hudson.plugins.git.UserRemoteConfig>
          <url>https://github.com/user/repo.git</url>
        </hudson.plugins.git.UserRemoteConfig>
      </userRemoteConfigs>
      <branches>
        <hudson.plugins.git.BranchSpec>
          <name>*/main</name>
        </hudson.plugins.git.BranchSpec>
      </branches>
    </scm>
    <scriptPath>Jenkinsfile</scriptPath>
  </definition>
</flow-definition>'''

server.create_job('pipeline-from-scm', pipeline_scm_config)

# === Jenkinsfile exemples ===

# Jenkinsfile Python complet
jenkinsfile_python = '''
pipeline {
    agent {
        docker {
            image 'python:3.11'
            args '-v /tmp:/tmp'
        }
    }
    
    environment {
        VENV_PATH = "${WORKSPACE}/venv"
        PYTHONPATH = "${WORKSPACE}/src"
    }
    
    parameters {
        choice(name: 'ENVIRONMENT', choices: ['dev', 'staging', 'prod'], description: 'Environnement de déploiement')
        string(name: 'VERSION', defaultValue: 'latest', description: 'Version à déployer')
        booleanParam(name: 'RUN_TESTS', defaultValue: true, description: 'Exécuter les tests')
    }
    
    stages {
        stage('Checkout') {
            steps {
                checkout scm
            }
        }
        
        stage('Setup') {
            steps {
                sh '''
                    python -m venv ${VENV_PATH}
                    . ${VENV_PATH}/bin/activate
                    pip install --upgrade pip setuptools wheel
                    pip install -r requirements.txt
                    pip install -r requirements-dev.txt
                '''
            }
        }
        
        stage('Lint') {
            steps {
                sh '''
                    . ${VENV_PATH}/bin/activate
                    flake8 src/ tests/
                    black --check src/ tests/
                    mypy src/
                '''
            }
        }
        
        stage('Test') {
            when {
                expression { params.RUN_TESTS == true }
            }
            steps {
                sh '''
                    . ${VENV_PATH}/bin/activate
                    pytest tests/ \
                        --junitxml=test-results.xml \
                        --cov=src \
                        --cov-report=xml \
                        --cov-report=html
                '''
            }
            post {
                always {
                    junit 'test-results.xml'
                    publishHTML([
                        reportDir: 'htmlcov',
                        reportFiles: 'index.html',
                        reportName: 'Coverage Report'
                    ])
                }
            }
        }
        
        stage('Build') {
            steps {
                sh '''
                    . ${VENV_PATH}/bin/activate
                    python setup.py sdist bdist_wheel
                '''
            }
        }
        
        stage('Security Scan') {
            steps {
                sh '''
                    . ${VENV_PATH}/bin/activate
                    safety check
                    bandit -r src/
                '''
            }
        }
        
        stage('Deploy') {
            when {
                branch 'main'
            }
            steps {
                script {
                    def version = params.VERSION
                    def env = params.ENVIRONMENT
                    
                    sh """
                        . ${VENV_PATH}/bin/activate
                        python deploy.py --env ${env} --version ${version}
                    """
                }
            }
        }
    }
    
    post {
        always {
            cleanWs()
        }
        success {
            emailext(
                subject: "[OK] Build réussi: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
                body: """
                    Build: ${env.BUILD_URL}
                    Environnement: ${params.ENVIRONMENT}
                    Version: ${params.VERSION}
                """,
                to: 'team@example.com'
            )
        }
        failure {
            emailext(
                subject: "[X] Build échoué: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
                body: """
                    Build: ${env.BUILD_URL}
                    Console: ${env.BUILD_URL}console
                """,
                to: 'team@example.com'
            )
        }
    }
}
'''

# Jenkinsfile avec matrix (tests multi-versions)
jenkinsfile_matrix = '''
pipeline {
    agent none
    
    stages {
        stage('Test Matrix') {
            matrix {
                axes {
                    axis {
                        name 'PYTHON_VERSION'
                        values '3.9', '3.10', '3.11', '3.12'
                    }
                    axis {
                        name 'OS'
                        values 'ubuntu-latest', 'windows-latest', 'macos-latest'
                    }
                }
                agent {
                    docker {
                        image "python:${PYTHON_VERSION}"
                    }
                }
                stages {
                    stage('Test') {
                        steps {
                            sh '''
                                python -m venv venv
                                . venv/bin/activate
                                pip install -r requirements.txt
                                pytest tests/
                            '''
                        }
                    }
                }
            }
        }
    }
}
'''


[OK] SCRIPTING AVANCÉ

# === Classe wrapper Jenkins ===

class JenkinsManager:
    """Gestionnaire Jenkins avec méthodes utiles"""
    
    def __init__(self, url, username, password):
        self.server = jenkins.Jenkins(url, username=username, password=password)
        self.url = url
    
    def get_all_jobs(self, include_disabled=False):
        """Récupérer tous les jobs avec filtres"""
        jobs = self.server.get_jobs(folder_depth=None)
        if not include_disabled:
            jobs = [j for j in jobs if not self.is_job_disabled(j['name'])]
        return jobs
    
    def is_job_disabled(self, job_name):
        """Vérifier si job est désactivé"""
        try:
            info = self.server.get_job_info(job_name)
            return info.get('disabled', False)
        except:
            return None
    
    def get_job_status(self, job_name):
        """Obtenir status d'un job"""
        info = self.server.get_job_info(job_name)
        last_build = info.get('lastBuild')
        
        if not last_build:
            return 'NEVER_BUILT'
        
        build_info = self.server.get_build_info(job_name, last_build['number'])
        return build_info.get('result', 'BUILDING')
    
    def trigger_and_wait(self, job_name, parameters=None, timeout=600):
        """Déclencher build et attendre résultat"""
        queue_id = self.server.build_job(job_name, parameters=parameters)
        
        # Attendre que build démarre
        build_number = None
        start_time = time.time()
        
        while build_number is None:
            if time.time() - start_time > 60:
                raise TimeoutError("Build n'a pas démarré")
            
            try:
                queue_item = self.server.get_queue_item(queue_id)
                if 'executable' in queue_item:
                    build_number = queue_item['executable']['number']
            except:
                pass
            
            time.sleep(2)
        
        # Attendre fin du build
        while True:
            if time.time() - start_time > timeout:
                raise TimeoutError(f"Build timeout après {timeout}s")
            
            build_info = self.server.get_build_info(job_name, build_number)
            if not build_info['building']:
                return build_info
            
            time.sleep(5)
    
    def get_failed_jobs(self):
        """Lister jobs échoués"""
        jobs = self.get_all_jobs()
        failed = []
        
        for job in jobs:
            status = self.get_job_status(job['name'])
            if status in ['FAILURE', 'UNSTABLE']:
                failed.append({
                    'name': job['name'],
                    'status': status,
                    'url': job['url']
                })
        
        return failed
    
    def get_jobs_by_label(self, label):
        """Jobs exécutés sur un label spécifique"""
        # Nécessite parsing du XML de config
        jobs = self.get_all_jobs()
        matching_jobs = []
        
        for job in jobs:
            try:
                config = self.server.get_job_config(job['name'])
                if f'<label>{label}</label>' in config:
                    matching_jobs.append(job)
            except:
                pass
        
        return matching_jobs
    
    def cleanup_old_builds(self, job_name, keep_last=10):
        """Supprimer vieux builds"""
        job_info = self.server.get_job_info(job_name)
        builds = job_info['builds']
        
        if len(builds) <= keep_last:
            return 0
        
        deleted = 0
        for build in builds[keep_last:]:
            try:
                self.server.delete_build(job_name, build['number'])
                deleted += 1
            except:
                pass
        
        return deleted
    
    def backup_job(self, job_name, backup_dir='./jenkins_backup'):
        """Sauvegarder configuration d'un job"""
        import os
        os.makedirs(backup_dir, exist_ok=True)
        
        config = self.server.get_job_config(job_name)
        filename = os.path.join(backup_dir, f'{job_name}.xml')
        
        with open(filename, 'w') as f:
            f.write(config)
        
        return filename
    
    def restore_job(self, job_name, backup_file):
        """Restaurer job depuis sauvegarde"""
        with open(backup_file, 'r') as f:
            config = f.read()
        
        try:
            self.server.get_job_info(job_name)
            self.server.reconfig_job(job_name, config)
        except jenkins.NotFoundException:
            self.server.create_job(job_name, config)

# Utilisation
manager = JenkinsManager(
    'http://localhost:8080',
    'admin',
    'token'
)

# Lister jobs échoués
failed = manager.get_failed_jobs()
for job in failed:
    print(f"[X] {job['name']}: {job['status']}")

# Déclencher et attendre
result = manager.trigger_and_wait('my-job', timeout=300)
print(f"Build terminé: {result['result']}")

# Nettoyer vieux builds
deleted = manager.cleanup_old_builds('my-job', keep_last=20)
print(f"{deleted} builds supprimés")


# === Monitoring et alertes ===

class JenkinsMonitor:
    """Surveillance Jenkins"""
    
    def __init__(self, server):
        self.server = server
    
    def get_system_info(self):
        """Info système Jenkins"""
        return {
            'version': self.server.get_version(),
            'user': self.server.get_whoami(),
            'jobs_count': len(self.server.get_jobs()),
            'queue_size': len(self.server.get_queue_info())
        }
    
    def get_node_health(self):
        """Santé des nodes"""
        nodes = self.server.get_nodes()
        health = []
        
        for node in nodes:
            info = self.server.get_node_info(node['name'])
            health.append({
                'name': node['name'],
                'offline': info['offline'],
                'idle': info['idle'],
                'executors': info['numExecutors'],
                'temp_offline': info['temporarilyOffline']
            })
        
        return health
    
    def get_build_statistics(self, job_name, days=7):
        """Statistiques des builds"""
        from datetime import datetime, timedelta
        
        cutoff = datetime.now() - timedelta(days=days)
        job_info = self.server.get_job_info(job_name)
        
        stats = {
            'total': 0,
            'success': 0,
            'failure': 0,
            'aborted': 0,
            'unstable': 0,
            'avg_duration': 0
        }
        
        durations = []
        
        for build in job_info['builds']:
            build_info = self.server.get_build_info(job_name, build['number'])
            build_time = datetime.fromtimestamp(build_info['timestamp'] / 1000)
            
            if build_time < cutoff:
                continue
            
            stats['total'] += 1
            result = build_info.get('result', 'UNKNOWN')
            
            if result == 'SUCCESS':
                stats['success'] += 1
            elif result == 'FAILURE':
                stats['failure'] += 1
            elif result == 'ABORTED':
                stats['aborted'] += 1
            elif result == 'UNSTABLE':
                stats['unstable'] += 1
            
            durations.append(build_info['duration'])
        
        if durations:
            stats['avg_duration'] = sum(durations) / len(durations) / 1000  # secondes
        
        return stats
    
    def check_stale_jobs(self, days=30):
        """Jobs non exécutés depuis X jours"""
        from datetime import datetime, timedelta
        
        cutoff = datetime.now() - timedelta(days=days)
        stale_jobs = []
        
        jobs = self.server.get_jobs()
        for job in jobs:
            info = self.server.get_job_info(job['name'])
            last_build = info.get('lastBuild')
            
            if not last_build:
                stale_jobs.append({
                    'name': job['name'],
                    'last_build': 'NEVER'
                })
                continue
            
            build_info = self.server.get_build_info(job['name'], last_build['number'])
            build_time = datetime.fromtimestamp(build_info['timestamp'] / 1000)
            
            if build_time < cutoff:
                stale_jobs.append({
                    'name': job['name'],
                    'last_build': build_time.strftime('%Y-%m-%d')
                })
        
        return stale_jobs

# Utilisation
monitor = JenkinsMonitor(server)

# Info système
sys_info = monitor.get_system_info()
print(f"Jenkins {sys_info['version']}")
print(f"{sys_info['jobs_count']} jobs")

# Santé nodes
for node in monitor.get_node_health():
    status = '[ROUGE] OFFLINE' if node['offline'] else '[VERT] ONLINE'
    print(f"{node['name']}: {status}")

# Statistiques
stats = monitor.get_build_statistics('my-job', days=7)
print(f"Succès: {stats['success']}/{stats['total']}")
print(f"Durée moyenne: {stats['avg_duration']:.1f}s")

# Jobs obsolètes
stale = monitor.check_stale_jobs(days=30)
for job in stale:
    print(f"[ATTENTION] {job['name']} - dernier build: {job['last_build']}")


# === Génération automatique de jobs ===

class JobGenerator:
    """Générateur de jobs Jenkins"""
    
    def __init__(self, server):
        self.server = server
    
    def create_python_test_job(self, name, repo_url, branch='main'):
        """Créer job de test Python"""
        config = f'''<?xml version='1.0' encoding='UTF-8'?>
<project>
  <description>Tests automatiques pour {name}</description>
  <scm class="hudson.plugins.git.GitSCM">
    <userRemoteConfigs>
      <hudson.plugins.git.UserRemoteConfig>
        <url>{repo_url}</url>
      </hudson.plugins.git.UserRemoteConfig>
    </userRemoteConfigs>
    <branches>
      <hudson.plugins.git.BranchSpec>
        <name>*/{branch}</name>
      </hudson.plugins.git.BranchSpec>
    </branches>
  </scm>
  <triggers>
    <hudson.triggers.SCMTrigger>
      <spec>H/5 * * * *</spec>
    </hudson.triggers.SCMTrigger>
  </triggers>
  <builders>
    <hudson.tasks.Shell>
      <command>#!/bin/bash
set -e

# Setup environnement
python -m venv venv
source venv/bin/activate

# Installation
pip install --upgrade pip
pip install -r requirements.txt
pip install -r requirements-dev.txt

# Linting
echo "=== Linting ==="
flake8 src/ tests/ || true
black --check src/ tests/ || true

# Tests
echo "=== Tests ==="
pytest tests/ \\
    --junitxml=test-results.xml \\
    --cov=src \\
    --cov-report=xml \\
    --cov-report=html \\
    -v

# Coverage
echo "=== Coverage ==="
coverage report
      </command>
    </hudson.tasks.Shell>
  </builders>
  <publishers>
    <hudson.tasks.junit.JUnitResultArchiver>
      <testResults>test-results.xml</testResults>
    </hudson.tasks.junit.JUnitResultArchiver>
    <hudson.tasks.ArtifactArchiver>
      <artifacts>htmlcov/**/*</artifacts>
    </hudson.tasks.ArtifactArchiver>
  </publishers>
</project>'''
        
        self.server.create_job(name, config)
        return name
    
    def create_docker_build_job(self, name, repo_url, dockerfile='Dockerfile'):
        """Créer job de build Docker"""
        config = f'''<?xml version='1.0' encoding='UTF-8'?>
<project>
  <description>Build Docker pour {name}</description>
  <scm class="hudson.plugins.git.GitSCM">
    <userRemoteConfigs>
      <hudson.plugins.git.UserRemoteConfig>
        <url>{repo_url}</url>
      </hudson.plugins.git.UserRemoteConfig>
    </userRemoteConfigs>
    <branches>
      <hudson.plugins.git.BranchSpec>
        <name>*/main</name>
      </hudson.plugins.git.BranchSpec>
    </branches>
  </scm>
  <builders>
    <hudson.tasks.Shell>
      <command>#!/bin/bash
set -e

IMAGE_NAME="{name}"
VERSION=$(git describe --tags --always)

echo "Building $IMAGE_NAME:$VERSION"

docker build -t $IMAGE_NAME:$VERSION -f {dockerfile} .
docker tag $IMAGE_NAME:$VERSION $IMAGE_NAME:latest

echo "Build complete: $IMAGE_NAME:$VERSION"
      </command>
    </hudson.tasks.Shell>
  </builders>
</project>'''
        
        self.server.create_job(name, config)
        return name
    
    def create_microservices_pipeline(self, services):
        """Créer pipeline pour microservices"""
        stages = []
        
        for service in services:
            stages.append(f'''
        stage('{service["name"]} - Test') {{
            steps {{
                dir('{service["path"]}') {{
                    sh """
                        python -m venv venv
                        . venv/bin/activate
                        pip install -r requirements.txt
                        pytest tests/
                    """
                }}
            }}
        }}
        
        stage('{service["name"]} - Build') {{
            steps {{
                dir('{service["path"]}') {{
                    sh """
                        docker build -t {service["name"]}:${{BUILD_NUMBER}} .
                    """
                }}
            }}
        }}
''')
        
        pipeline = f'''
pipeline {{
    agent any
    
    stages {{
        stage('Checkout') {{
            steps {{
                checkout scm
            }}
        }}
        
{''.join(stages)}
        
        stage('Deploy') {{
            when {{
                branch 'main'
            }}
            steps {{
                sh 'docker-compose up -d'
            }}
        }}
    }}
}}
'''
        
        return pipeline

# Utilisation
generator = JobGenerator(server)

# Créer job de test
generator.create_python_test_job(
    'myapp-tests',
    'https://github.com/user/myapp.git',
    branch='develop'
)

# Créer jobs pour microservices
services = [
    {'name': 'auth-service', 'path': 'services/auth'},
    {'name': 'api-service', 'path': 'services/api'},
    {'name': 'worker-service', 'path': 'services/worker'}
]

pipeline = generator.create_microservices_pipeline(services)
print(pipeline)


# === Batch operations ===

def bulk_enable_jobs(server, job_pattern):
    """Activer plusieurs jobs par pattern"""
    jobs = server.get_jobs()
    enabled = []
    
    for job in jobs:
        if job_pattern in job['name']:
            try:
                server.enable_job(job['name'])
                enabled.append(job['name'])
            except Exception as e:
                print(f"Erreur pour {job['name']}: {e}")
    
    return enabled

def bulk_trigger_jobs(server, job_names, parameters=None):
    """Déclencher plusieurs jobs"""
    results = {}
    
    for job_name in job_names:
        try:
            queue_id = server.build_job(job_name, parameters=parameters)
            results[job_name] = {'status': 'queued', 'queue_id': queue_id}
        except Exception as e:
            results[job_name] = {'status': 'error', 'error': str(e)}
    
    return results

def migrate_jobs_to_folder(server, job_names, folder_name):
    """Déplacer jobs vers un dossier"""
    # Créer dossier si nécessaire
    # Copier et supprimer jobs
    for job_name in job_names:
        config = server.get_job_config(job_name)
        new_name = f'{folder_name}/{job_name}'
        server.create_job(new_name, config)
        server.delete_job(job_name)

# Utilisation
enabled = bulk_enable_jobs(server, 'python-')
print(f"{len(enabled)} jobs activés")

jobs_to_trigger = ['job1', 'job2', 'job3']
results = bulk_trigger_jobs(server, jobs_to_trigger, {'ENV': 'prod'})


[OK] INTÉGRATION AVEC D'AUTRES OUTILS

# === Intégration Slack ===

def send_slack_notification(webhook_url, message):
    """Envoyer notification Slack"""
    import requests
    
    payload = {
        'text': message,
        'username': 'Jenkins Bot',
        'icon_emoji': ':robot_face:'
    }
    
    response = requests.post(webhook_url, json=payload)
    return response.status_code == 200

def notify_build_result(server, job_name, build_number, slack_webhook):
    """Notifier résultat de build sur Slack"""
    build_info = server.get_build_info(job_name, build_number)
    result = build_info['result']
    
    emoji = {
        'SUCCESS': '[OK]',
        'FAILURE': '[X]',
        'UNSTABLE': '[ATTENTION]',
        'ABORTED': '[STOP]'
    }.get(result, '[?]')
    
    message = f"{emoji} Job: {job_name} #{build_number} - {result}\n{build_info['url']}"
    send_slack_notification(slack_webhook, message)

# === Intégration GitHub ===

def create_github_webhook_job(server, repo_name, github_url):
    """Créer job déclenché par webhook GitHub"""
    config = f'''<?xml version='1.0' encoding='UTF-8'?>
<project>
  <description>Build déclenché par GitHub webhook</description>
  <properties>
    <com.coravy.hudson.plugins.github.GithubProjectProperty>
      <projectUrl>{github_url}</projectUrl>
    </com.coravy.hudson.plugins.github.GithubProjectProperty>
  </properties>
  <scm class="hudson.plugins.git.GitSCM">
    <userRemoteConfigs>
      <hudson.plugins.git.UserRemoteConfig>
        <url>{github_url}</url>
      </hudson.plugins.git.UserRemoteConfig>
    </userRemoteConfigs>
    <branches>
      <hudson.plugins.git.BranchSpec>
        <name>*/main</name>
      </hudson.plugins.git.BranchSpec>
    </branches>
  </scm>
  <triggers>
    <com.cloudbees.jenkins.GitHubPushTrigger>
      <spec></spec>
    </com.cloudbees.jenkins.GitHubPushTrigger>
  </triggers>
  <builders>
    <hudson.tasks.Shell>
      <command>python -m pytest tests/</command>
    </hudson.tasks.Shell>
  </builders>
</project>'''
    
    server.create_job(repo_name, config)

# === Intégration Jira ===

def update_jira_from_build(server, job_name, build_number, jira_client):
    """Mettre à jour Jira depuis build Jenkins"""
    # Récupérer changements du build
    build_info = server.get_build_info(job_name, build_number)
    
    # Parser commit messages pour issues Jira
    import re
    jira_pattern = r'([A-Z]+-\d+)'
    
    for change_set in build_info.get('changeSets', []):
        for item in change_set.get('items', []):
            msg = item.get('msg', '')
            issues = re.findall(jira_pattern, msg)
            
            for issue_key in issues:
                # Ajouter commentaire dans Jira
                comment = f"Build {job_name} #{build_number}: {build_info['result']}\n{build_info['url']}"
                jira_client.add_comment(issue_key, comment)

# === Intégration Docker Registry ===

def push_to_registry(image_name, tag, registry_url, username, password):
    """Push image Docker vers registry"""
    import subprocess
    
    # Login
    subprocess.run([
        'docker', 'login',
        '-u', username,
        '-p', password,
        registry_url
    ])
    
    # Tag
    full_image = f"{registry_url}/{image_name}:{tag}"
    subprocess.run(['docker', 'tag', f"{image_name}:{tag}", full_image])
    
    # Push
    subprocess.run(['docker', 'push', full_image])

# === Intégration AWS ===

def deploy_to_aws(server, job_name, build_number):
    """Déployer sur AWS depuis Jenkins"""
    import boto3
    
    # Récupérer artefacts
    build_info = server.get_build_info(job_name, build_number)
    
    # Upload vers S3
    s3 = boto3.client('s3')
    s3.upload_file(
        'dist/myapp.tar.gz',
        'my-bucket',
        f'releases/{build_number}/myapp.tar.gz'
    )
    
    # Déclencher déploiement ECS
    ecs = boto3.client('ecs')
    ecs.update_service(
        cluster='my-cluster',
        service='my-service',
        forceNewDeployment=True
    )


[OK] TESTS & QUALITÉ DE CODE

# === Configuration pytest dans Jenkins ===

pytest_job_config = '''<?xml version='1.0' encoding='UTF-8'?>
<project>
  <builders>
    <hudson.tasks.Shell>
      <command>#!/bin/bash
set -e

# Environnement virtuel
python -m venv venv
source venv/bin/activate

# Installation
pip install -r requirements.txt
pip install pytest pytest-cov pytest-html pytest-xdist

# Tests avec coverage
pytest tests/ \\
    --junitxml=test-results/junit.xml \\
    --html=test-results/report.html \\
    --self-contained-html \\
    --cov=src \\
    --cov-report=xml:coverage.xml \\
    --cov-report=html:htmlcov \\
    --cov-report=term \\
    -n auto \\
    -v
      </command>
    </hudson.tasks.Shell>
  </builders>
  <publishers>
    <hudson.tasks.junit.JUnitResultArchiver>
      <testResults>test-results/junit.xml</testResults>
      <keepLongStdio>true</keepLongStdio>
    </hudson.tasks.junit.JUnitResultArchiver>
    <htmlpublisher.HtmlPublisher>
      <reportTargets>
        <htmlpublisher.HtmlPublisherTarget>
          <reportName>Test Report</reportName>
          <reportDir>test-results</reportDir>
          <reportFiles>report.html</reportFiles>
        </htmlpublisher.HtmlPublisherTarget>
        <htmlpublisher.HtmlPublisherTarget>
          <reportName>Coverage Report</reportName>
          <reportDir>htmlcov</reportDir>
          <reportFiles>index.html</reportFiles>
        </htmlpublisher.HtmlPublisherTarget>
      </reportTargets>
    </htmlpublisher.HtmlPublisher>
  </publishers>
</project>'''

# === Code quality checks ===

quality_checks_script = '''#!/bin/bash
set -e

source venv/bin/activate

echo "=== Flake8 ==="
flake8 src/ tests/ --max-line-length=100 --statistics

echo "=== Black ==="
black --check src/ tests/

echo "=== isort ==="
isort --check-only src/ tests/

echo "=== mypy ==="
mypy src/ --ignore-missing-imports

echo "=== pylint ==="
pylint src/ --rcfile=.pylintrc

echo "=== bandit (security) ==="
bandit -r src/ -f json -o bandit-report.json

echo "=== safety (dependencies) ==="
safety check --json > safety-report.json

echo "=== radon (complexity) ==="
radon cc src/ -a -nb
radon mi src/ -nb
'''


[OK] SÉCURITÉ

# === Scan de sécurité ===

security_scan_job = '''pipeline {
    agent any
    
    stages {
        stage('Security Scan') {
            parallel {
                stage('Dependencies') {
                    steps {
                        sh """
                            . venv/bin/activate
                            
                            # Safety check
                            safety check --full-report
                            
                            # pip-audit
                            pip-audit
                        """
                    }
                }
                
                stage('Code Analysis') {
                    steps {
                        sh """
                            . venv/bin/activate
                            
                            # Bandit
                            bandit -r src/ -f json -o bandit.json
                            
                            # Semgrep
                            semgrep --config=auto src/
                        """
                    }
                }
                
                stage('Secrets Detection') {
                    steps {
                        sh """
                            # Truffhog
                            trufflehog filesystem . --json > secrets.json
                            
                            # Gitleaks
                            gitleaks detect --source . --report-path gitleaks.json
                        """
                    }
                }
            }
        }
    }
}'''

# === Gestion sécurisée des secrets ===

def get_credentials_safely(server, credentials_id):
    """Récupérer credentials de manière sécurisée"""
    # Utiliser credentials binding dans Groovy
    script = f"""
import jenkins.model.Jenkins
import com.cloudbees.plugins.credentials.CredentialsProvider

def creds = CredentialsProvider.lookupCredentials(
    com.cloudbees.plugins.credentials.Credentials.class,
    Jenkins.instance,
    null,
    null
).find {{ it.id == '{credentials_id}' }}

return creds
"""
    
    return server.run_script(script)


[OK] PERFORMANCE & OPTIMISATION

# === Build distribué ===

distributed_pipeline = '''pipeline {
    agent none
    
    stages {
        stage('Parallel Tests') {
            parallel {
                stage('Unit Tests - Node 1') {
                    agent {{ label 'python-node-1' }}
                    steps {{
                        sh 'pytest tests/unit/'
                    }}
                }}
                
                stage('Integration Tests - Node 2') {
                    agent {{ label 'python-node-2' }}
                    steps {{
                        sh 'pytest tests/integration/'
                    }}
                }}
                
                stage('E2E Tests - Node 3') {
                    agent {{ label 'python-node-3' }}
                    steps {{
                        sh 'pytest tests/e2e/'
                    }}
                }}
            }}
        }}
    }}
}}'''

# === Cache des dépendances ===

cached_build_script = '''#!/bin/bash
set -e

CACHE_DIR="/var/jenkins_home/cache/pip"
mkdir -p $CACHE_DIR

# Utiliser cache pip
export PIP_CACHE_DIR=$CACHE_DIR

python -m venv venv
source venv/bin/activate

# Installer avec cache
pip install --cache-dir=$CACHE_DIR -r requirements.txt

# Tests
pytest tests/
'''

# === Monitoring des performances ===

def analyze_build_performance(server, job_name, num_builds=50):
    """Analyser performances des builds"""
    job_info = server.get_job_info(job_name)
    builds = job_info['builds'][:num_builds]
    
    durations = []
    stages_time = {}
    
    for build in builds:
        build_info = server.get_build_info(job_name, build['number'])
        
        if build_info['result'] != 'SUCCESS':
            continue
        
        durations.append(build_info['duration'] / 1000)  # secondes
    
    if not durations:
        return None
    
    return {
        'avg_duration': sum(durations) / len(durations),
        'min_duration': min(durations),
        'max_duration': max(durations),
        'total_builds': len(durations)
    }

# Utilisation
perf = analyze_build_performance(server, 'my-job', num_builds=100)
print(f"Durée moyenne: {perf['avg_duration']:.1f}s")
print(f"Min: {perf['min_duration']:.1f}s, Max: {perf['max_duration']:.1f}s")


[OK] BACKUP & RESTORE

# === Backup complet Jenkins ===

import os
import shutil
from datetime import datetime

def backup_jenkins(server, backup_dir='./jenkins_backups'):
    """Sauvegarder configuration Jenkins complète"""
    timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
    backup_path = os.path.join(backup_dir, f'jenkins_backup_{timestamp}')
    os.makedirs(backup_path, exist_ok=True)
    
    # Sauvegarder tous les jobs
    jobs = server.get_jobs()
    jobs_dir = os.path.join(backup_path, 'jobs')
    os.makedirs(jobs_dir, exist_ok=True)
    
    for job in jobs:
        try:
            config = server.get_job_config(job['name'])
            filename = os.path.join(jobs_dir, f"{job['name']}.xml")
            
            with open(filename, 'w') as f:
                f.write(config)
        except Exception as e:
            print(f"Erreur backup {job['name']}: {e}")
    
    # Sauvegarder vues
    views = server.get_views()
    views_dir = os.path.join(backup_path, 'views')
    os.makedirs(views_dir, exist_ok=True)
    
    for view in views:
        try:
            config = server.get_view_config(view['name'])
            filename = os.path.join(views_dir, f"{view['name']}.xml")
            
            with open(filename, 'w') as f:
                f.write(config)
        except Exception as e:
            print(f"Erreur backup view {view['name']}: {e}")
    
    # Sauvegarder nodes
    nodes = server.get_nodes()
    nodes_dir = os.path.join(backup_path, 'nodes')
    os.makedirs(nodes_dir, exist_ok=True)
    
    for node in nodes:
        try:
            config = server.get_node_config(node['name'])
            filename = os.path.join(nodes_dir, f"{node['name']}.xml")
            
            with open(filename, 'w') as f:
                f.write(config)
        except Exception as e:
            print(f"Erreur backup node {node['name']}: {e}")
    
    # Créer manifest
    manifest = {
        'timestamp': timestamp,
        'jenkins_version': server.get_version(),
        'jobs_count': len(jobs),
        'views_count': len(views),
        'nodes_count': len(nodes)
    }
    
    import json
    with open(os.path.join(backup_path, 'manifest.json'), 'w') as f:
        json.dump(manifest, f, indent=2)
    
    # Compresser
    shutil.make_archive(backup_path, 'zip', backup_path)
    shutil.rmtree(backup_path)
    
    return f"{backup_path}.zip"

# Utilisation
backup_file = backup_jenkins(server)
print(f"Backup créé: {backup_file}")


def restore_jenkins(server, backup_file):
    """Restaurer configuration Jenkins depuis backup"""
    import zipfile
    import tempfile
    
    # Extraire backup
    with tempfile.TemporaryDirectory() as temp_dir:
        with zipfile.ZipFile(backup_file, 'r') as zip_ref:
            zip_ref.extractall(temp_dir)
        
        # Lire manifest
        with open(os.path.join(temp_dir, 'manifest.json'), 'r') as f:
            manifest = json.load(f)
        
        print(f"Restoration depuis backup {manifest['timestamp']}")
        
        # Restaurer jobs
        jobs_dir = os.path.join(temp_dir, 'jobs')
        if os.path.exists(jobs_dir):
            for filename in os.listdir(jobs_dir):
                job_name = filename.replace('.xml', '')
                
                with open(os.path.join(jobs_dir, filename), 'r') as f:
                    config = f.read()
                
                try:
                    server.get_job_info(job_name)
                    server.reconfig_job(job_name, config)
                    print(f"[OK] Job mis à jour: {job_name}")
                except jenkins.NotFoundException:
                    server.create_job(job_name, config)
                    print(f"[OK] Job créé: {job_name}")
        
        # Restaurer vues
        views_dir = os.path.join(temp_dir, 'views')
        if os.path.exists(views_dir):
            for filename in os.listdir(views_dir):
                view_name = filename.replace('.xml', '')
                
                with open(os.path.join(views_dir, filename), 'r') as f:
                    config = f.read()
                
                try:
                    server.create_view(view_name, config)
                    print(f"[OK] Vue créée: {view_name}")
                except:
                    print(f"[X] Erreur vue: {view_name}")

# Utilisation
restore_jenkins(server, 'jenkins_backup_20241111_120000.zip')


# === Backup automatique planifié ===

def schedule_backup(server, backup_dir, interval_hours=24):
    """Planifier backups automatiques"""
    import schedule
    import time
    
    def job():
        try:
            backup_file = backup_jenkins(server, backup_dir)
            print(f"[OK] Backup automatique créé: {backup_file}")
            
            # Nettoyer vieux backups (garder 7 derniers)
            cleanup_old_backups(backup_dir, keep=7)
        except Exception as e:
            print(f"[X] Erreur backup automatique: {e}")
    
    schedule.every(interval_hours).hours.do(job)
    
    print(f"Backup planifié toutes les {interval_hours}h")
    
    while True:
        schedule.run_pending()
        time.sleep(60)

def cleanup_old_backups(backup_dir, keep=7):
    """Supprimer vieux backups"""
    backups = sorted([
        f for f in os.listdir(backup_dir) 
        if f.endswith('.zip')
    ])
    
    if len(backups) > keep:
        for backup in backups[:-keep]:
            os.remove(os.path.join(backup_dir, backup))
            print(f"Supprimé: {backup}")


[OK] REPORTING & DASHBOARD

# === Générer rapport HTML ===

def generate_jenkins_report(server, output_file='jenkins_report.html'):
    """Générer rapport HTML complet"""
    from datetime import datetime
    
    # Collecter données
    jobs = server.get_jobs()
    nodes = server.get_nodes()
    queue = server.get_queue_info()
    
    # Statistiques jobs
    total_jobs = len(jobs)
    disabled_jobs = 0
    failed_jobs = 0
    success_jobs = 0
    
    for job in jobs:
        info = server.get_job_info(job['name'])
        if info.get('disabled'):
            disabled_jobs += 1
        
        last_build = info.get('lastBuild')
        if last_build:
            build_info = server.get_build_info(job['name'], last_build['number'])
            result = build_info.get('result')
            if result == 'SUCCESS':
                success_jobs += 1
            elif result == 'FAILURE':
                failed_jobs += 1
    
    # Statistiques nodes
    total_nodes = len(nodes)
    offline_nodes = sum(1 for n in nodes if n.get('offline', False))
    
    # Générer HTML
    html = f'''<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Jenkins Report - {datetime.now().strftime('%Y-%m-%d')}</title>
    <style>
        body {{ 
            font-family: Arial, sans-serif; 
            margin: 20px;
            background: #f5f5f5;
        }}
        .container {{ 
            max-width: 1200px; 
            margin: 0 auto;
            background: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }}
        h1 {{ 
            color: #335; 
            border-bottom: 3px solid #4CAF50;
            padding-bottom: 10px;
        }}
        .stats {{
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 20px;
            margin: 30px 0;
        }}
        .stat-card {{
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            padding: 20px;
            border-radius: 8px;
            text-align: center;
        }}
        .stat-card.success {{ 
            background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%);
        }}
        .stat-card.warning {{ 
            background: linear-gradient(135deg, #ff9800 0%, #f57c00 100%);
        }}
        .stat-card.error {{ 
            background: linear-gradient(135deg, #f44336 0%, #d32f2f 100%);
        }}
        .stat-number {{ 
            font-size: 48px; 
            font-weight: bold; 
            margin: 10px 0;
        }}
        .stat-label {{ 
            font-size: 14px; 
            opacity: 0.9;
            text-transform: uppercase;
        }}
        table {{ 
            width: 100%; 
            border-collapse: collapse; 
            margin: 20px 0;
        }}
        th {{ 
            background: #335; 
            color: white; 
            padding: 12px;
            text-align: left;
        }}
        td {{ 
            padding: 12px; 
            border-bottom: 1px solid #ddd;
        }}
        tr:hover {{ background: #f5f5f5; }}
        .status-success {{ color: #4CAF50; font-weight: bold; }}
        .status-failure {{ color: #f44336; font-weight: bold; }}
        .status-building {{ color: #2196F3; font-weight: bold; }}
        .footer {{ 
            margin-top: 40px; 
            padding-top: 20px;
            border-top: 1px solid #ddd;
            text-align: center; 
            color: #666;
            font-size: 12px;
        }}
    </style>
</head>
<body>
    <div class="container">
        <h1>[GRAPHIQUE] Jenkins Dashboard Report</h1>
        <p>Généré le {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>
        
        <div class="stats">
            <div class="stat-card">
                <div class="stat-label">Total Jobs</div>
                <div class="stat-number">{total_jobs}</div>
            </div>
            <div class="stat-card success">
                <div class="stat-label">Succès</div>
                <div class="stat-number">{success_jobs}</div>
            </div>
            <div class="stat-card error">
                <div class="stat-label">Échecs</div>
                <div class="stat-number">{failed_jobs}</div>
            </div>
            <div class="stat-card warning">
                <div class="stat-label">Désactivés</div>
                <div class="stat-number">{disabled_jobs}</div>
            </div>
        </div>
        
        <h2>[ECRAN] Nodes Status</h2>
        <p>Total: {total_nodes} | Offline: {offline_nodes}</p>
        
        <h2>[LISTE] Jobs Overview</h2>
        <table>
            <thead>
                <tr>
                    <th>Job Name</th>
                    <th>Last Build</th>
                    <th>Status</th>
                    <th>Duration</th>
                </tr>
            </thead>
            <tbody>
'''
    
    for job in jobs[:50]:  # Limiter à 50
        info = server.get_job_info(job['name'])
        last_build = info.get('lastBuild')
        
        if last_build:
            build_info = server.get_build_info(job['name'], last_build['number'])
            result = build_info.get('result', 'BUILDING')
            duration = build_info['duration'] / 1000  # secondes
            
            status_class = 'status-success' if result == 'SUCCESS' else 'status-failure'
            if result == 'BUILDING':
                status_class = 'status-building'
            
            html += f'''
                <tr>
                    <td>{job['name']}</td>
                    <td>#{last_build['number']}</td>
                    <td class="{status_class}">{result}</td>
                    <td>{duration:.1f}s</td>
                </tr>
'''
        else:
            html += f'''
                <tr>
                    <td>{job['name']}</td>
                    <td>-</td>
                    <td>Never built</td>
                    <td>-</td>
                </tr>
'''
    
    html += '''
            </tbody>
        </table>
        
        <div class="footer">
            <p>Jenkins Report Generator | Python jenkins library</p>
        </div>
    </div>
</body>
</html>
'''
    
    with open(output_file, 'w') as f:
        f.write(html)
    
    return output_file

# Utilisation
report_file = generate_jenkins_report(server)
print(f"Rapport généré: {report_file}")


# === Dashboard temps réel avec Flask ===

from flask import Flask, render_template, jsonify
import threading

app = Flask(__name__)

@app.route('/')
def dashboard():
    return render_template('dashboard.html')

@app.route('/api/stats')
def get_stats():
    """API pour statistiques temps réel"""
    jobs = server.get_jobs()
    queue = server.get_queue_info()
    
    stats = {
        'total_jobs': len(jobs),
        'queue_size': len(queue),
        'failed_jobs': 0,
        'success_jobs': 0,
        'building_jobs': 0
    }
    
    for job in jobs:
        info = server.get_job_info(job['name'])
        last_build = info.get('lastBuild')
        
        if last_build:
            build_info = server.get_build_info(job['name'], last_build['number'])
            if build_info['building']:
                stats['building_jobs'] += 1
            elif build_info.get('result') == 'SUCCESS':
                stats['success_jobs'] += 1
            elif build_info.get('result') == 'FAILURE':
                stats['failed_jobs'] += 1
    
    return jsonify(stats)

@app.route('/api/jobs')
def get_jobs():
    """API pour liste des jobs"""
    jobs = server.get_jobs()
    jobs_data = []
    
    for job in jobs[:20]:
        info = server.get_job_info(job['name'])
        last_build = info.get('lastBuild')
        
        job_data = {
            'name': job['name'],
            'url': job['url']
        }
        
        if last_build:
            build_info = server.get_build_info(job['name'], last_build['number'])
            job_data.update({
                'last_build': last_build['number'],
                'status': build_info.get('result', 'BUILDING'),
                'duration': build_info['duration'] / 1000
            })
        
        jobs_data.append(job_data)
    
    return jsonify(jobs_data)

def run_dashboard():
    app.run(host='0.0.0.0', port=5000)

# Lancer dashboard dans un thread
dashboard_thread = threading.Thread(target=run_dashboard)
dashboard_thread.daemon = True
dashboard_thread.start()


[OK] CLI TOOLS

# === CLI interactif pour Jenkins ===

import click

@click.group()
@click.option('--url', envvar='JENKINS_URL', required=True)
@click.option('--user', envvar='JENKINS_USER', required=True)
@click.option('--token', envvar='JENKINS_TOKEN', required=True)
@click.pass_context
def cli(ctx, url, user, token):
    """Jenkins CLI Tool"""
    ctx.obj = jenkins.Jenkins(url, username=user, password=token)

@cli.command()
@click.pass_context
def list_jobs(ctx):
    """Lister tous les jobs"""
    server = ctx.obj
    jobs = server.get_jobs()
    
    for job in jobs:
        info = server.get_job_info(job['name'])
        status = '[OK]' if info.get('color') == 'blue' else '[X]'
        click.echo(f"{status} {job['name']}")

@cli.command()
@click.argument('job_name')
@click.option('--params', '-p', multiple=True, help='Paramètres (KEY=VALUE)')
@click.pass_context
def build(ctx, job_name, params):
    """Déclencher un build"""
    server = ctx.obj
    
    # Parser paramètres
    parameters = {}
    for param in params:
        key, value = param.split('=')
        parameters[key] = value
    
    click.echo(f"Déclenchement build: {job_name}")
    queue_id = server.build_job(job_name, parameters=parameters)
    click.echo(f"[OK] Build en queue: {queue_id}")

@cli.command()
@click.argument('job_name')
@click.argument('build_number', type=int)
@click.option('--follow', '-f', is_flag=True, help='Suivre logs en temps réel')
@click.pass_context
def logs(ctx, job_name, build_number, follow):
    """Afficher logs d'un build"""
    server = ctx.obj
    
    if follow:
        start = 0
        while True:
            output = server.get_build_console_output(job_name, build_number)
            new_output = output[start:]
            
            if new_output:
                click.echo(new_output, nl=False)
                start = len(output)
            
            build_info = server.get_build_info(job_name, build_number)
            if not build_info['building']:
                break
            
            time.sleep(2)
    else:
        output = server.get_build_console_output(job_name, build_number)
        click.echo(output)

@cli.command()
@click.argument('job_name')
@click.pass_context
def status(ctx, job_name):
    """Status d'un job"""
    server = ctx.obj
    info = server.get_job_info(job_name)
    
    click.echo(f"Job: {job_name}")
    click.echo(f"URL: {info['url']}")
    click.echo(f"Buildable: {info['buildable']}")
    click.echo(f"Color: {info['color']}")
    
    last_build = info.get('lastBuild')
    if last_build:
        build_info = server.get_build_info(job_name, last_build['number'])
        click.echo(f"\nDernier build: #{last_build['number']}")
        click.echo(f"Résultat: {build_info.get('result', 'BUILDING')}")
        click.echo(f"Durée: {build_info['duration'] / 1000:.1f}s")

@cli.command()
@click.option('--output', '-o', default='jenkins_backup.zip')
@click.pass_context
def backup(ctx, output):
    """Créer backup Jenkins"""
    server = ctx.obj
    
    with click.progressbar(length=100, label='Backup en cours') as bar:
        backup_file = backup_jenkins(server, os.path.dirname(output))
        bar.update(100)
    
    click.echo(f"[OK] Backup créé: {backup_file}")

if __name__ == '__main__':
    cli()

# Utilisation:
# python jenkins_cli.py --url http://localhost:8080 --user admin --token xxx list-jobs
# python jenkins_cli.py build my-job -p ENV=prod -p VERSION=1.0
# python jenkins_cli.py logs my-job 42 --follow
# python jenkins_cli.py status my-job
# python jenkins_cli.py backup -o ./backup.zip


[OK] WEBHOOKS & API

# === Serveur webhook pour Jenkins ===

from flask import Flask, request, jsonify
import hmac
import hashlib

app = Flask(__name__)

# Configuration
WEBHOOK_SECRET = 'your-secret-key'
JENKINS_SERVER = jenkins.Jenkins('http://localhost:8080', 'admin', 'token')

@app.route('/webhook/github', methods=['POST'])
def github_webhook():
    """Webhook GitHub"""
    # Vérifier signature
    signature = request.headers.get('X-Hub-Signature-256')
    if signature:
        expected = 'sha256=' + hmac.new(
            WEBHOOK_SECRET.encode(),
            request.data,
            hashlib.sha256
        ).hexdigest()
        
        if not hmac.compare_digest(signature, expected):
            return jsonify({'error': 'Invalid signature'}), 403
    
    # Parser payload
    payload = request.json
    event = request.headers.get('X-GitHub-Event')
    
    if event == 'push':
        repo = payload['repository']['name']
        branch = payload['ref'].split('/')[-1]
        
        # Déclencher build Jenkins
        job_name = f"{repo}-{branch}"
        
        try:
            JENKINS_SERVER.build_job(job_name, parameters={
                'GIT_COMMIT': payload['after'],
                'GIT_BRANCH': branch,
                'PUSHER': payload['pusher']['name']
            })
            
            return jsonify({
                'status': 'success',
                'job': job_name,
                'message': 'Build triggered'
            })
        except Exception as e:
            return jsonify({'error': str(e)}), 500
    
    return jsonify({'status': 'ignored'})

@app.route('/webhook/gitlab', methods=['POST'])
def gitlab_webhook():
    """Webhook GitLab"""
    token = request.headers.get('X-Gitlab-Token')
    
    if token != WEBHOOK_SECRET:
        return jsonify({'error': 'Invalid token'}), 403
    
    payload = request.json
    event = request.headers.get('X-Gitlab-Event')
    
    if event == 'Push Hook':
        project = payload['project']['name']
        branch = payload['ref'].split('/')[-1]
        
        job_name = f"{project}-{branch}"
        
        try:
            JENKINS_SERVER.build_job(job_name)
            return jsonify({'status': 'success', 'job': job_name})
        except Exception as e:
            return jsonify({'error': str(e)}), 500
    
    return jsonify({'status': 'ignored'})

@app.route('/webhook/custom', methods=['POST'])
def custom_webhook():
    """Webhook personnalisé"""
    data = request.json
    
    job_name = data.get('job')
    parameters = data.get('parameters', {})
    
    if not job_name:
        return jsonify({'error': 'Missing job name'}), 400
    
    try:
        JENKINS_SERVER.build_job(job_name, parameters=parameters)
        return jsonify({
            'status': 'success',
            'job': job_name,
            'parameters': parameters
        })
    except Exception as e:
        return jsonify({'error': str(e)}), 500

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8000)


[OK] EXEMPLES PRATIQUES

# === Exemple 1: CI/CD Pipeline complet ===

def create_cicd_pipeline(server, project_name, git_url):
    """Créer pipeline CI/CD complet"""
    
    pipeline_script = f'''
pipeline {{
    agent any
    
    environment {{
        PROJECT_NAME = '{project_name}'
        DOCKER_REGISTRY = 'registry.example.com'
        KUBECONFIG = credentials('kubernetes-config')
    }}
    
    stages {{
        stage('Checkout') {{
            steps {{
                git branch: 'main', url: '{git_url}'
            }}
        }}
        
        stage('Setup') {{
            steps {{
                sh """
                    python -m venv venv
                    . venv/bin/activate
                    pip install -r requirements.txt
                    pip install -r requirements-dev.txt
                """
            }}
        }}
        
        stage('Lint & Format') {{
            parallel {{
                stage('Flake8') {{
                    steps {{
                        sh """
                            . venv/bin/activate
                            flake8 src/ tests/ --max-line-length=100
                        """
                    }}
                }}
                stage('Black') {{
                    steps {{
                        sh """
                            . venv/bin/activate
                            black --check src/ tests/
                        """
                    }}
                }}
                stage('MyPy') {{
                    steps {{
                        sh """
                            . venv/bin/activate
                            mypy src/
                        """
                    }}
                }}
            }}
        }}
        
        stage('Tests') {{
            parallel {{
                stage('Unit Tests') {{
                    steps {{
                        sh """
                            . venv/bin/activate
                            pytest tests/unit/ -v --junitxml=unit-results.xml
                        """
                    }}
                }}
                stage('Integration Tests') {{
                    steps {{
                        sh """
                            . venv/bin/activate
                            pytest tests/integration/ -v --junitxml=integration-results.xml
                        """
                    }}
                }}
            }}
            post {{
                always {{
                    junit '*-results.xml'
                }}
            }}
        }}
        
        stage('Security Scan') {{
            steps {{
                sh """
                    . venv/bin/activate
                    safety check
                    bandit -r src/ -f json -o bandit-report.json
                """
            }}
        }}
        
        stage('Build Docker Image') {{
            steps {{
                script {{
                    def version = sh(returnStdout: true, script: 'git describe --tags --always').trim()
                    sh """
                        docker build -t ${{DOCKER_REGISTRY}}/${{PROJECT_NAME}}:${{version}} .
                        docker tag ${{DOCKER_REGISTRY}}/${{PROJECT_NAME}}:${{version}} ${{DOCKER_REGISTRY}}/${{PROJECT_NAME}}:latest
                    """
                }}
            }}
        }}
        
        stage('Push to Registry') {{
            when {{
                branch 'main'
            }}
            steps {{
                sh """
                    docker push ${{DOCKER_REGISTRY}}/${{PROJECT_NAME}}:${{version}}
                    docker push ${{DOCKER_REGISTRY}}/${{PROJECT_NAME}}:latest
                """
            }}
        }}
        
        stage('Deploy to Staging') {{
            when {{
                branch 'main'
            }}
            steps {{
                sh """
                    kubectl set image deployment/${{PROJECT_NAME}} ${{PROJECT_NAME}}=${{DOCKER_REGISTRY}}/${{PROJECT_NAME}}:${{version}} -n staging
                    kubectl rollout status deployment/${{PROJECT_NAME}} -n staging
                """
            }}
        }}
        
        stage('Smoke Tests') {{
            when {{
                branch 'main'
            }}
            steps {{
                sh """
                    . venv/bin/activate
                    pytest tests/smoke/ --base-url=https://staging.example.com
                """
            }}
        }}
        
        stage('Deploy to Production') {{
            when {{
                branch 'main'
            }}
            input {{
                message "Deploy to production?"
                ok "Deploy"
            }}
            steps {{
                sh """
                    kubectl set image deployment/${{PROJECT_NAME}} ${{PROJECT_NAME}}=${{DOCKER_REGISTRY}}/${{PROJECT_NAME}}:${{version}} -n production
                    kubectl rollout status deployment/${{PROJECT_NAME}} -n production
                """
            }}
        }}
    }}
    
    post {{
        always {{
            cleanWs()
        }}
        success {{
            slackSend color: 'good', message: "[OK] Pipeline réussi: ${{env.JOB_NAME}} #${{env.BUILD_NUMBER}}"
        }}
        failure {{
            slackSend color: 'danger', message: "[X] Pipeline échoué: ${{env.JOB_NAME}} #${{env.BUILD_NUMBER}}"
        }}
    }}
}}
'''
    
    config = f'''<?xml version='1.0' encoding='UTF-8'?>
<flow-definition plugin="workflow-job">
  <description>CI/CD Pipeline pour {project_name}</description>
  <definition class="org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition">
    <script>{pipeline_script}</script>
    <sandbox>true</sandbox>
  </definition>
</flow-definition>'''
    
    server.create_job(f'{project_name}-pipeline', config)
    print(f"[OK] Pipeline créé: {project_name}-pipeline")


# === Exemple 2: Tests matrix multi-versions ===

def create_matrix_test_job(server, project_name, python_versions, os_list):
    """Créer job de test matrix"""
    
    versions_str = "', '".join(python_versions)
    os_str = "', '".join(os_list)
    
    pipeline = f'''
pipeline {{
    agent none
    
    stages {{
        stage('Matrix Tests') {{
            matrix {{
                axes {{
                    axis {{
                        name 'PYTHON_VERSION'
                        values '{versions_str}'
                    }}
                    axis {{
                        name 'OS'
                        values '{os_str}'
                    }}
                }}
                agent {{
                    docker {{
                        image "python:${{PYTHON_VERSION}}"
                        label "${{OS}}"
                    }}
                }}
                stages {{
                    stage('Test') {{
                        steps {{
                            sh """
                                python -m venv venv
                                . venv/bin/activate
                                pip install -r requirements.txt
                                pytest tests/ -v
                            """
                        }}
                    }}
                }}
            }}
        }}
    }}
}}
'''
    
    config = f'''<?xml version='1.0' encoding='UTF-8'?>
<flow-definition plugin="workflow-job">
  <description>Tests matrix pour {project_name}</description>
  <definition class="org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition">
    <script>{pipeline}</script>
    <sandbox>true</sandbox>
  </definition>
</flow-definition>'''
    
    server.create_job(f'{project_name}-matrix-tests', config)
    print(f"[OK] Job matrix créé: {project_name}-matrix-tests")


# === Exemple 3: Auto-scaling des builds ===

class BuildScaler:
    """Gestion automatique des agents selon charge"""
    
    def __init__(self, server, min_agents=1, max_agents=5):
        self.server = server
        self.min_agents = min_agents
        self.max_agents = max_agents
    
    def get_queue_length(self):
        """Longueur de la queue"""
        return len(self.server.get_queue_info())
    
    def get_active_executors(self):
        """Nombre d'executors actifs"""
        nodes = self.server.get_nodes()
        total = 0
        busy = 0
        
        for node in nodes:
            info = self.server.get_node_info(node['name'])
            if not info['offline']:
                total += info['numExecutors']
                busy += info['numExecutors'] - len(info.get('idle', []))
        
        return total, busy
    
    def scale_up(self):
        """Ajouter un agent"""
        nodes = self.server.get_nodes()
        current = len([n for n in nodes if not n.get('offline')])
        
        if current < self.max_agents:
            # Créer nouvel agent dynamique
            # (nécessite plugin Docker ou cloud provider)
            print(f"Scaling up: {current} -> {current + 1}")
            return True
        return False
    
    def scale_down(self):
        """Retirer un agent"""
        nodes = self.server.get_nodes()
        online_nodes = [n for n in nodes if not n.get('offline')]
        
        if len(online_nodes) > self.min_agents:
            # Désactiver agent idle
            for node in online_nodes:
                info = self.server.get_node_info(node['name'])
                if info.get('idle'):
                    self.server.disable_node(node['name'])
                    print(f"Scaling down: removed {node['name']}")
                    return True
        return False
    
    def auto_scale(self):
        """Auto-scaling basé sur la charge"""
        queue_len = self.get_queue_length()
        total_exec, busy_exec = self.get_active_executors()
        
        utilization = busy_exec / total_exec if total_exec > 0 else 0
        
        print(f"Queue: {queue_len} | Executors: {busy_exec}/{total_exec} ({utilization:.0%})")
        
        # Scale up si queue longue ou haute utilisation
        if queue_len > 5 or utilization > 0.8:
            self.scale_up()
        
        # Scale down si faible utilisation
        elif utilization < 0.2 and queue_len == 0:
            self.scale_down()

# Utilisation
scaler = BuildScaler(server, min_agents=2, max_agents=10)

import schedule
schedule.every(1).minutes.do(scaler.auto_scale)

while True:
    schedule.run_pending()
    time.sleep(30)


# === Exemple 4: Notification intelligente ===

class SmartNotifier:
    """Notifications intelligentes selon contexte"""
    
    def __init__(self, server):
        self.server = server
        self.failure_history = {}
    
    def should_notify(self, job_name, build_info):
        """Décider si notification nécessaire"""
        result = build_info['result']
        
        # Toujours notifier premier échec
        if result == 'FAILURE':
            if job_name not in self.failure_history:
                self.failure_history[job_name] = 1
                return True, "first_failure"
            
            # Notifier tous les 3 échecs consécutifs
            self.failure_history[job_name] += 1
            if self.failure_history[job_name] % 3 == 0:
                return True, "repeated_failure"
        
        # Notifier retour au succès après échec
        elif result == 'SUCCESS' and job_name in self.failure_history:
            del self.failure_history[job_name]
            return True, "back_to_normal"
        
        # Notifier build long (> moyenne + 50%)
        avg_duration = self.get_average_duration(job_name)
        if build_info['duration'] > avg_duration * 1.5:
            return True, "slow_build"
        
        return False, None
    
    def get_average_duration(self, job_name, last_n=10):
        """Durée moyenne des derniers builds"""
        job_info = self.server.get_job_info(job_name)
        builds = job_info['builds'][:last_n]
        
        durations = []
        for build in builds:
            build_info = self.server.get_build_info(job_name, build['number'])
            if build_info.get('result') == 'SUCCESS':
                durations.append(build_info['duration'])
        
        return sum(durations) / len(durations) if durations else 0
    
    def send_notification(self, job_name, build_info, reason):
        """Envoyer notification contextuelle"""
        messages = {
            'first_failure': f"[ROUGE] Premier échec: {job_name} #{build_info['number']}",
            'repeated_failure': f"[ATTENTION] Échecs répétés ({self.failure_history[job_name]}x): {job_name}",
            'back_to_normal': f"[OK] Retour à la normale: {job_name} #{build_info['number']}",
            'slow_build': f"[LENT] Build lent: {job_name} ({build_info['duration']/1000:.0f}s)"
        }
        
        message = messages.get(reason, f"Build: {job_name}")
        print(message)
        # send_slack_notification(webhook, message)
        # send_email(message)

# Utilisation
notifier = SmartNotifier(server)

def check_builds():
    jobs = server.get_jobs()
    for job in jobs:
        info = server.get_job_info(job['name'])
        last_build = info.get('lastBuild')
        
        if last_build:
            build_info = server.get_build_info(job['name'], last_build['number'])
            should_notify, reason = notifier.should_notify(job['name'], build_info)
            
            if should_notify:
                notifier.send_notification(job['name'], build_info, reason)


# === Exemple 5: Job generator depuis YAML ===

import yaml

def create_jobs_from_yaml(server, yaml_file):
    """Créer jobs depuis configuration YAML"""
    
    with open(yaml_file, 'r') as f:
        config = yaml.safe_load(f)
    
    for job_config in config['jobs']:
        job_name = job_config['name']
        job_type = job_config['type']
        
        if job_type == 'freestyle':
            create_freestyle_from_config(server, job_config)
        elif job_type == 'pipeline':
            create_pipeline_from_config(server, job_config)
        
        print(f"[OK] Job créé: {job_name}")

def create_freestyle_from_config(server, config):
    """Créer freestyle job depuis config"""
    
    scm_config = ''
    if 'git' in config:
        scm_config = f'''
  <scm class="hudson.plugins.git.GitSCM">
    <userRemoteConfigs>
      <hudson.plugins.git.UserRemoteConfig>
        <url>{config['git']['url']}</url>
      </hudson.plugins.git.UserRemoteConfig>
    </userRemoteConfigs>
    <branches>
      <hudson.plugins.git.BranchSpec>
        <n>*/{config['git'].get('branch', 'main')}</n>
      </hudson.plugins.git.BranchSpec>
    </branches>
  </scm>'''
    
    triggers_config = ''
    if 'triggers' in config:
        if 'cron' in config['triggers']:
            triggers_config = f'''
  <triggers>
    <hudson.triggers.TimerTrigger>
      <spec>{config['triggers']['cron']}</spec>
    </hudson.triggers.TimerTrigger>
  </triggers>'''
    
    builders_config = ''
    if 'steps' in config:
        steps = '\n'.join(config['steps'])
        builders_config = f'''
  <builders>
    <hudson.tasks.Shell>
      <command>{steps}</command>
    </hudson.tasks.Shell>
  </builders>'''
    
    xml_config = f'''<?xml version='1.0' encoding='UTF-8'?>
<project>
  <description>{config.get('description', '')}</description>
  {scm_config}
  {triggers_config}
  {builders_config}
  <publishers/>
</project>'''
    
    server.create_job(config['name'], xml_config)

def create_pipeline_from_config(server, config):
    """Créer pipeline depuis config"""
    
    stages = []
    for stage in config.get('stages', []):
        stage_steps = '\n                    '.join(stage['steps'])
        stages.append(f'''
        stage('{stage['name']}') {{
            steps {{
                sh """
                    {stage_steps}
                """
            }}
        }}''')
    
    pipeline_script = f'''
pipeline {{
    agent any
    
    stages {{
{''.join(stages)}
    }}
}}
'''
    
    xml_config = f'''<?xml version='1.0' encoding='UTF-8'?>
<flow-definition plugin="workflow-job">
  <description>{config.get('description', '')}</description>
  <definition class="org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition">
    <script>{pipeline_script}</script>
    <sandbox>true</sandbox>
  </definition>
</flow-definition>'''
    
    server.create_job(config['name'], xml_config)

# Exemple de fichier YAML: jobs.yaml
example_yaml = '''
jobs:
  - name: python-test-job
    type: freestyle
    description: "Tests Python automatiques"
    git:
      url: https://github.com/user/repo.git
      branch: main
    triggers:
      cron: "H */4 * * *"
    steps:
      - python -m venv venv
      - source venv/bin/activate
      - pip install -r requirements.txt
      - pytest tests/
  
  - name: deploy-pipeline
    type: pipeline
    description: "Pipeline de déploiement"
    stages:
      - name: Build
        steps:
          - docker build -t myapp:latest .
      - name: Test
        steps:
          - docker run myapp:latest pytest
      - name: Deploy
        steps:
          - kubectl apply -f k8s/
'''

# Utilisation
create_jobs_from_yaml(server, 'jobs.yaml')


[OK] TROUBLESHOOTING & DEBUGGING

# === Debugging helpers ===

def diagnose_job(server, job_name):
    """Diagnostic complet d'un job"""
    print(f"=== Diagnostic: {job_name} ===\n")
    
    try:
        # Info job
        info = server.get_job_info(job_name)
        print(f"[OK] Job existe")
        print(f"  Buildable: {info['buildable']}")
        print(f"  Disabled: {info.get('disabled', False)}")
        print(f"  In Queue: {info.get('inQueue', False)}")
        
        # Dernier build
        last_build = info.get('lastBuild')
        if last_build:
            build_info = server.get_build_info(job_name, last_build['number'])
            print(f"\n[OK] Dernier build: #{last_build['number']}")
            print(f"  Résultat: {build_info.get('result', 'BUILDING')}")
            print(f"  Durée: {build_info['duration'] / 1000:.1f}s")
            print(f"  Building: {build_info['building']}")
            
            # Erreurs dans les logs
            if build_info.get('result') == 'FAILURE':
                console = server.get_build_console_output(job_name, last_build['number'])
                errors = [line for line in console.split('\n') if 'error' in line.lower()]
                
                if errors:
                    print(f"\n[ATTENTION] Erreurs trouvées dans les logs:")
                    for error in errors[:5]:  # Limiter à 5
                        print(f"  - {error[:100]}")
        else:
            print(f"\n[ATTENTION] Aucun build exécuté")
        
        # Configuration
        config = server.get_job_config(job_name)
        
        # Vérifier SCM
        if '<scm class="hudson.scm.NullSCM"' in config:
            print(f"\n[ATTENTION] Aucun SCM configuré")
        elif 'GitSCM' in config:
            print(f"\n[OK] Git configuré")
        
        # Vérifier triggers
        if '<triggers/>' in config or '<triggers>' not in config:
            print(f"[ATTENTION] Aucun trigger configuré")
        else:
            print(f"[OK] Triggers configurés")
        
        # Vérifier builders
        if '<builders/>' in config:
            print(f"[ATTENTION] Aucun builder configuré")
        else:
            print(f"[OK] Builders configurés")
        
    except jenkins.NotFoundException:
        print(f"[X] Job '{job_name}' introuvable")
    except Exception as e:
        print(f"[X] Erreur: {e}")

# Utilisation
diagnose_job(server, 'my-problematic-job')


def check_jenkins_health(server):
    """Vérification santé complète Jenkins"""
    print("=== Health Check Jenkins ===\n")
    
    issues = []
    
    # Version
    try:
        version = server.get_version()
        print(f"[OK] Jenkins version: {version}")
    except Exception as e:
        issues.append(f"Cannot get version: {e}")
    
    # Nodes
    try:
        nodes = server.get_nodes()
        offline = [n for n in nodes if n.get('offline')]
        
        print(f"[OK] Nodes: {len(nodes)} total, {len(offline)} offline")
        
        if offline:
            issues.append(f"{len(offline)} nodes offline")
            for node in offline:
                print(f"  [ATTENTION] {node['name']} is offline")
    except Exception as e:
        issues.append(f"Cannot check nodes: {e}")
    
    # Queue
    try:
        queue = server.get_queue_info()
        stuck = [q for q in queue if q.get('stuck')]
        
        print(f"[OK] Queue: {len(queue)} items, {len(stuck)} stuck")
        
        if len(queue) > 20:
            issues.append(f"Large queue: {len(queue)} items")
        
        if stuck:
            issues.append(f"{len(stuck)} stuck items in queue")
    except Exception as e:
        issues.append(f"Cannot check queue: {e}")
    
    # Jobs problématiques
    try:
        jobs = server.get_jobs()
        failed_jobs = []
        
        for job in jobs[:50]:  # Limiter
            info = server.get_job_info(job['name'])
            last_build = info.get('lastBuild')
            
            if last_build:
                build_info = server.get_build_info(job['name'], last_build['number'])
                if build_info.get('result') == 'FAILURE':
                    failed_jobs.append(job['name'])
        
        print(f"[OK] Jobs: {len(jobs)} total, {len(failed_jobs)} failed")
        
        if failed_jobs:
            issues.append(f"{len(failed_jobs)} jobs in failed state")
    except Exception as e:
        issues.append(f"Cannot check jobs: {e}")
    
    # Résumé
    print(f"\n{'='*50}")
    if issues:
        print(f"[ATTENTION] {len(issues)} issues trouvés:")
        for issue in issues:
            print(f"  - {issue}")
    else:
        print(f"[OK] Tout est OK!")
    
    return issues

# Utilisation
issues = check_jenkins_health(server)


def retry_failed_builds(server, max_retries=3):
    """Relancer automatiquement les builds échoués"""
    jobs = server.get_jobs()
    retried = []
    
    for job in jobs:
        info = server.get_job_info(job['name'])
        last_build = info.get('lastBuild')
        
        if not last_build:
            continue
        
        build_info = server.get_build_info(job['name'], last_build['number'])
        
        # Vérifier si échec récent
        if build_info.get('result') == 'FAILURE':
            # Compter nombre d'échecs consécutifs
            consecutive_failures = 0
            for build in info['builds'][:max_retries]:
                b_info = server.get_build_info(job['name'], build['number'])
                if b_info.get('result') == 'FAILURE':
                    consecutive_failures += 1
                else:
                    break
            
            # Relancer si pas trop d'échecs
            if consecutive_failures < max_retries:
                print(f"Relancement: {job['name']} (échec #{consecutive_failures})")
                server.build_job(job['name'])
                retried.append(job['name'])
    
    return retried


[OK] BONNES PRATIQUES

# === Configuration recommandée ===

# 1. Variables d'environnement (.env)
"""
JENKINS_URL=http://localhost:8080
JENKINS_USER=admin
JENKINS_TOKEN=your_api_token_here
JENKINS_TIMEOUT=30
"""

# 2. Structure projet recommandée
"""
jenkins-automation/
├── .env
├── .gitignore
├── requirements.txt
├── config/
│   ├── jobs.yaml
│   └── pipelines/
├── scripts/
│   ├── backup.py
│   ├── deploy.py
│   └── monitor.py
├── templates/
│   ├── freestyle_job.xml
│   └── pipeline_job.xml
└── tests/
    └── test_jenkins.py
"""

# 3. Logging approprié
import logging

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('jenkins_automation.log'),
        logging.StreamHandler()
    ]
)

logger = logging.getLogger(__name__)

def safe_build_job(server, job_name, parameters=None):
    """Build job avec logging"""
    try:
        logger.info(f"Déclenchement build: {job_name}")
        queue_id = server.build_job(job_name, parameters=parameters)
        logger.info(f"Build en queue: {queue_id}")
        return queue_id
    except jenkins.JenkinsException as e:
        logger.error(f"Erreur Jenkins: {e}")
        raise
    except Exception as e:
        logger.exception(f"Erreur inattendue: {e}")
        raise

# 4. Retry logic
from tenacity import retry, stop_after_attempt, wait_exponential

@retry(
    stop=stop_after_attempt(3),
    wait=wait_exponential(multiplier=1, min=4, max=10)
)
def reliable_api_call(server, method, *args, **kwargs):
    """Appel API avec retry automatique"""
    return getattr(server, method)(*args, **kwargs)

# Utilisation
job_info = reliable_api_call(server, 'get_job_info', 'my-job')

# 5. Context manager pour connexion
from contextlib import contextmanager

@contextmanager
def jenkins_connection(url, username, password):
    """Context manager pour connexion Jenkins"""
    server = jenkins.Jenkins(url, username=username, password=password)
    try:
        # Vérifier connexion
        server.get_whoami()
        yield server
    except Exception as e:
        logger.error(f"Erreur connexion Jenkins: {e}")
        raise
    finally:
        # Cleanup si nécessaire
        pass

# Utilisation
with jenkins_connection(jenkins_url, user, token) as server:
    jobs = server.get_jobs()
    for job in jobs:
        print(job['name'])

# 6. Cache pour optimisation
from functools import lru_cache
import time

class CachedJenkinsServer:
    """Wrapper avec cache pour réduire appels API"""
    
    def __init__(self, server, cache_ttl=60):
        self.server = server
        self.cache_ttl = cache_ttl
        self._cache = {}
    
    def _is_cache_valid(self, key):
        if key not in self._cache:
            return False
        timestamp, _ = self._cache[key]
        return time.time() - timestamp < self.cache_ttl
    
    def get_jobs(self, use_cache=True):
        key = 'jobs'
        
        if use_cache and self._is_cache_valid(key):
            _, data = self._cache[key]
            return data
        
        data = self.server.get_jobs()
        self._cache[key] = (time.time(), data)
        return data
    
    def get_job_info(self, job_name, use_cache=True):
        key = f'job_info_{job_name}'
        
        if use_cache and self._is_cache_valid(key):
            _, data = self._cache[key]
            return data
        
        data = self.server.get_job_info(job_name)
        self._cache[key] = (time.time(), data)
        return data
    
    def clear_cache(self):
        self._cache.clear()


[OK] TESTS UNITAIRES

# === Tests avec pytest ===

import pytest
from unittest.mock import Mock, patch

@pytest.fixture
def mock_jenkins():
    """Mock Jenkins server"""
    mock = Mock()
    mock.get_version.return_value = "2.387.1"
    mock.get_whoami.return_value = {"fullName": "Admin User"}
    return mock

def test_get_jobs(mock_jenkins):
    """Test récupération jobs"""
    mock_jenkins.get_jobs.return_value = [
        {"name": "job1", "url": "http://jenkins/job/job1"},
        {"name": "job2", "url": "http://jenkins/job/job2"}
    ]
    
    jobs = mock_jenkins.get_jobs()
    assert len(jobs) == 2
    assert jobs[0]['name'] == 'job1'

def test_build_job(mock_jenkins):
    """Test déclenchement build"""
    mock_jenkins.build_job.return_value = 123
    
    queue_id = mock_jenkins.build_job('test-job', parameters={'ENV': 'prod'})
    assert queue_id == 123
    
    mock_jenkins.build_job.assert_called_once_with(
        'test-job',
        parameters={'ENV': 'prod'}
    )

def test_job_not_found(mock_jenkins):
    """Test job inexistant"""
    mock_jenkins.get_job_info.side_effect = jenkins.NotFoundException("Job not found")
    
    with pytest.raises(jenkins.NotFoundException):
        mock_jenkins.get_job_info('non-existent-job')

@patch('jenkins.Jenkins')
def test_jenkins_manager(mock_jenkins_class):
    """Test JenkinsManager class"""
    mock_server = Mock()
    mock_jenkins_class.return_value = mock_server
    
    manager = JenkinsManager('http://localhost:8080', 'admin', 'token')
    
    mock_server.get_jobs.return_value = [
        {"name": "job1"},
        {"name": "job2"}
    ]
    
    jobs = manager.get_all_jobs()
    assert len(jobs) == 2


[OK] RESSOURCES & DOCUMENTATION

# Documentation officielle:
# Python Jenkins: https://python-jenkins.readthedocs.io/
# Jenkins API: https://www.jenkins.io/doc/book/using/remote-access-api/
# Jenkins Pipeline: https://www.jenkins.io/doc/book/pipeline/

# Plugins utiles:
# - Blue Ocean: UI moderne
# - Docker Pipeline: Intégration Docker
# - Kubernetes: Agents dynamiques K8s
# - GitHub Integration: Webhooks GitHub
# - Slack Notification: Notifications Slack
# - Pipeline: Pipeline as Code
# - Credentials Binding: Gestion secrets

# Alternatives et outils complémentaires:
# - GitLab CI: Alternative à Jenkins
# - GitHub Actions: CI/CD GitHub
# - CircleCI: CI/CD cloud
# - Travis CI: CI/CD cloud
# - Jenkins X: Jenkins pour Kubernetes

# Communauté:
# - GitHub: https://github.com/pycontribs/python-jenkins
# - Stack Overflow: tag [jenkins] [python]
# - Jenkins User Mailing List

# Exemples avancés:
# https://github.com/jenkinsci/python-jenkins/tree/master/examples


[OK] TROUBLESHOOTING (RÉSOLUTION DE PROBLÈMES)

# === PROBLÈME 1: "Connection refused" ===

# [X] ERREUR:
# requests.exceptions.ConnectionError: [Errno 111] Connection refused

# [RECHERCHE] CAUSES POSSIBLES:
# 1. Jenkins n'est pas démarré
# 2. Mauvais port (8080 par défaut)
# 3. Mauvaise URL (http vs https)
# 4. Firewall bloque la connexion

# [OK] SOLUTIONS:

# Solution 1: Vérifier que Jenkins tourne
# Dans votre navigateur: http://localhost:8080
# Si ça ne marche pas, Jenkins n'est pas démarré

# Démarrer Jenkins (Docker):
docker ps  # Voir si conteneur tourne
docker start jenkins  # Démarrer si arrêté

# Démarrer Jenkins (Ubuntu):
sudo systemctl status jenkins
sudo systemctl start jenkins

# Solution 2: Vérifier l'URL et le port
server = jenkins.Jenkins('http://localhost:8080')  # Pas https !
# Vérifier dans Jenkins: Manage Jenkins > Configure System > Jenkins URL

# Solution 3: Tester la connexion
import requests
try:
    response = requests.get('http://localhost:8080')
    print(f"Status: {response.status_code}")  # Doit être 200 ou 403
except Exception as e:
    print(f"Erreur: {e}")

# Solution 4: Vérifier le firewall
# Linux:
sudo ufw status
sudo ufw allow 8080

# Windows: Vérifier pare-feu Windows


# === PROBLÈME 2: "Unauthorized" (401) ===

# [X] ERREUR:
# jenkins.JenkinsException: Error in request. Possibly authentication failed [401]

# [RECHERCHE] CAUSES:
# 1. Mauvais username
# 2. Mauvais token/password
# 3. Token expiré
# 4. Pas de credentials du tout

# [OK] SOLUTIONS:

# Solution 1: Vérifier les credentials
print(f"URL: {os.getenv('JENKINS_URL')}")
print(f"User: {os.getenv('JENKINS_USER')}")
print(f"Token: {os.getenv('JENKINS_TOKEN')[:10]}...")  # Premiers caractères

# Solution 2: Régénérer le token API
# 1. Jenkins > User (votre nom en haut à droite)
# 2. Configure
# 3. API Token > Add new Token
# 4. Copier le nouveau token
# 5. Mettre à jour .env

# Solution 3: Tester avec curl
# Linux/Mac:
curl -u username:token http://localhost:8080/api/json

# Si ça marche avec curl mais pas Python, problème dans votre code

# Solution 4: Vérifier que la sécurité est activée
# Jenkins > Manage Jenkins > Configure Global Security
# "Enable security" doit être coché


# === PROBLÈME 3: "Forbidden" (403) ===

# [X] ERREUR:
# jenkins.JenkinsException: Forbidden

# [RECHERCHE] CAUSES:
# L'utilisateur n'a pas les permissions nécessaires

# [OK] SOLUTIONS:

# Solution 1: Vérifier les permissions
# Jenkins > Manage Jenkins > Security > Authorization
# Si "Matrix-based security", vérifier que votre user a les droits

# Solution 2: Utiliser un compte admin
# Le plus simple: utiliser le compte admin Jenkins

# Solution 3: Donner les permissions nécessaires
# Permissions minimales requises:
# - Overall/Read
# - Job/Read
# - Job/Build (pour déclencher builds)
# - Job/Create (pour créer jobs)
# - Job/Configure (pour modifier jobs)

# Solution 4: Désactiver temporairement la sécurité (DEV uniquement!)
# [ATTENTION] ATTENTION: Ne JAMAIS faire ça en production !
# Jenkins > Configure Global Security > Disable


# === PROBLÈME 4: Jobs ne se créent pas ===

# [X] SYMPTÔME:
# Le script s'exécute sans erreur mais le job n'apparaît pas dans Jenkins

# [RECHERCHE] CAUSES:
# 1. XML invalide
# 2. Plugins manquants
# 3. Job créé dans un dossier invisible

# [OK] SOLUTIONS:

# Solution 1: Vérifier que le job existe vraiment
jobs = server.get_jobs()
print([j['name'] for j in jobs])

# Solution 2: Valider le XML avant de créer
import xml.etree.ElementTree as ET

def validate_xml(xml_string):
    """Vérifier que le XML est valide"""
    try:
        ET.fromstring(xml_string)
        print("[OK] XML valide")
        return True
    except ET.ParseError as e:
        print(f"[X] XML invalide: {e}")
        return False

# Utiliser avant create_job:
if validate_xml(job_config):
    server.create_job('my-job', job_config)

# Solution 3: Tester dans l'interface web d'abord
# 1. Créer le job manuellement dans Jenkins
# 2. Récupérer sa config: server.get_job_config('job-name')
# 3. Utiliser cette config comme base


# === PROBLÈME 5: Builds ne démarrent pas ===

# [X] SYMPTÔME:
# server.build_job() ne retourne pas d'erreur mais le build ne démarre pas

# [RECHERCHE] CAUSES:
# 1. Job désactivé
# 2. Pas d'executor libre
# 3. Node offline
# 4. Job déjà en cours (si concurrent = false)

# [OK] SOLUTIONS:

# Solution 1: Vérifier que le job est activé
job_info = server.get_job_info('my-job')
if job_info.get('disabled'):
    print("[X] Job désactivé !")
    server.enable_job('my-job')

# Solution 2: Vérifier les executors
queue = server.get_queue_info()
print(f"Items en queue: {len(queue)}")

# Si queue longue, ajouter des executors:
# Jenkins > Manage Jenkins > Nodes > Configure > # of executors

# Solution 3: Vérifier les nodes
nodes = server.get_nodes()
for node in nodes:
    info = server.get_node_info(node['name'])
    if info['offline']:
        print(f"[X] Node {node['name']} est offline !")

# Solution 4: Attendre et vérifier
queue_item = server.build_job('my-job')
print(f"Build en queue: {queue_item}")

import time
time.sleep(5)

try:
    item_info = server.get_queue_item(queue_item)
    print(f"Status: {item_info}")
except Exception as e:
    print(f"Erreur: {e}")


# === PROBLÈME 6: "Unable to find build" ===

# [X] ERREUR:
# jenkins.NotFoundException: Unable to find build

# [RECHERCHE] CAUSES:
# 1. Build a été supprimé
# 2. Mauvais numéro de build
# 3. Build pas encore démarré

# [OK] SOLUTIONS:

# Solution 1: Vérifier que le build existe
job_info = server.get_job_info('my-job')
build_numbers = [b['number'] for b in job_info['builds']]
print(f"Builds disponibles: {build_numbers}")

# Solution 2: Utiliser le dernier build
last_build = job_info['lastBuild']['number']
build_info = server.get_build_info('my-job', last_build)

# Solution 3: Fonction safe
def get_build_safe(server, job_name, build_number):
    """Récupérer build avec gestion d'erreur"""
    try:
        return server.get_build_info(job_name, build_number)
    except jenkins.NotFoundException:
        print(f"Build #{build_number} introuvable")
        return None

# Utilisation
build_info = get_build_safe(server, 'my-job', 42)
if build_info:
    print(f"Build trouvé: {build_info['result']}")


# === PROBLÈME 7: Logs de builds tronqués ===

# [X] SYMPTÔME:
# get_build_console_output() ne retourne qu'une partie des logs

# [RECHERCHE] CAUSE:
# Par défaut, Jenkins peut limiter la taille des logs

# [OK] SOLUTIONS:

# Solution 1: Récupérer les logs en plusieurs fois
def get_full_console_output(server, job_name, build_number):
    """Récupérer TOUS les logs même si très long"""
    try:
        output = server.get_build_console_output(job_name, build_number)
        return output
    except Exception as e:
        print(f"Erreur: {e}")
        return None

# Solution 2: Si vraiment trop long, sauvegarder dans un fichier
console = server.get_build_console_output('my-job', 42)
with open('build_42_logs.txt', 'w') as f:
    f.write(console)


# === PROBLÈME 8: Timeout lors des requêtes ===

# [X] ERREUR:
# requests.exceptions.ReadTimeout: HTTPConnectionPool

# [RECHERCHE] CAUSES:
# 1. Jenkins est très lent
# 2. Timeout trop court
# 3. Gros volumes de données

# [OK] SOLUTIONS:

# Solution 1: Augmenter le timeout
server = jenkins.Jenkins(
    'http://localhost:8080',
    username='admin',
    password='token',
    timeout=60  # 60 secondes au lieu de 30
)

# Solution 2: Retry automatique
from tenacity import retry, stop_after_attempt, wait_fixed

@retry(stop=stop_after_attempt(3), wait=wait_fixed(5))
def reliable_get_jobs(server):
    """Récupérer jobs avec retry"""
    return server.get_jobs()

# Utilisation
try:
    jobs = reliable_get_jobs(server)
except Exception as e:
    print(f"Échec après 3 tentatives: {e}")

# Solution 3: Vérifier la charge de Jenkins
# Peut-être que Jenkins rame à cause de:
# - Trop de builds en cours
# - Pas assez de mémoire
# - Disque plein


# === PROBLÈME 9: Caractères spéciaux dans les noms ===

# [X] ERREUR:
# Problèmes avec jobs contenant des espaces ou caractères spéciaux

# [OK] SOLUTIONS:

# Solution 1: Encoder les caractères spéciaux
from urllib.parse import quote

job_name = "My Job With Spaces"
encoded_name = quote(job_name)
print(f"Encodé: {encoded_name}")  # My%20Job%20With%20Spaces

# python-jenkins gère ça automatiquement normalement
job_info = server.get_job_info("My Job With Spaces")

# Solution 2: Éviter les espaces et caractères spéciaux
# Bonne pratique: utiliser des tirets
# [OK] "my-python-job"
# [X] "My Python Job!"


# === PROBLÈME 10: "Module jenkins not found" ===

# [X] ERREUR:
# ModuleNotFoundError: No module named 'jenkins'

# [RECHERCHE] CAUSE:
# python-jenkins n'est pas installé ou mauvais environnement

# [OK] SOLUTIONS:

# Solution 1: Installer python-jenkins
pip install python-jenkins

# Solution 2: Vérifier l'environnement virtuel
which python  # Linux/Mac
where python  # Windows

# Vous devriez voir le chemin de votre venv
# Si ce n'est pas le cas, activer le venv:
source venv/bin/activate  # Linux/Mac
venv\Scripts\activate     # Windows

# Solution 3: Vérifier que c'est bien installé
pip list | grep jenkins
python -c "import jenkins; print(jenkins.__version__)"


# === PROBLÈME 11: SSL Certificate Error ===

# [X] ERREUR:
# SSLError: [SSL: CERTIFICATE_VERIFY_FAILED]

# [RECHERCHE] CAUSE:
# Certificat SSL invalide ou auto-signé

# [OK] SOLUTIONS:

# Solution 1: Désactiver vérification SSL (DEV uniquement!)
import urllib3
urllib3.disable_warnings()

import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry

session = requests.Session()
session.verify = False  # [ATTENTION] Dangereux en production

server = jenkins.Jenkins(
    'https://jenkins.example.com',
    username='admin',
    password='token',
    requester=session
)

# Solution 2: Utiliser le certificat
session = requests.Session()
session.verify = '/path/to/certificate.pem'

server = jenkins.Jenkins(
    'https://jenkins.example.com',
    username='admin',
    password='token',
    requester=session
)


# === PROBLÈME 12: Job créé mais ne fonctionne pas ===

# [X] SYMPTÔME:
# Le job est créé mais échoue immédiatement

# [RECHERCHE] CAUSES:
# 1. Erreur dans le XML
# 2. Chemin invalide
# 3. Commande inexistante
# 4. Plugin manquant

# [OK] SOLUTIONS:

# Solution 1: Tester le job manuellement
# 1. Ouvrir Jenkins web
# 2. Trouver le job
# 3. Cliquer "Build Now"
# 4. Regarder les logs

# Solution 2: Simplifier le job au maximum
simple_test = '''<?xml version='1.0' encoding='UTF-8'?>
<project>
  <builders>
    <hudson.tasks.Shell>
      <command>echo "Test"</command>
    </hudson.tasks.Shell>
  </builders>
</project>'''

server.create_job('simple-test', simple_test)
# Si ce job marche, le problème vient de votre config

# Solution 3: Comparer avec un job qui marche
working_job_config = server.get_job_config('job-qui-marche')
print(working_job_config)
# Comparer avec votre config


# === OUTILS DE DIAGNOSTIC ===

def diagnose_jenkins_connection():
    """
    Diagnostiquer les problèmes de connexion
    """
    print("[RECHERCHE] Diagnostic de connexion Jenkins\n")
    
    # 1. Variables d'environnement
    print("1⃣ Variables d'environnement:")
    url = os.getenv('JENKINS_URL')
    user = os.getenv('JENKINS_USER')
    token = os.getenv('JENKINS_TOKEN')
    
    if url:
        print(f"   [OK] JENKINS_URL: {url}")
    else:
        print(f"   [X] JENKINS_URL non défini")
    
    if user:
        print(f"   [OK] JENKINS_USER: {user}")
    else:
        print(f"   [X] JENKINS_USER non défini")
    
    if token:
        print(f"   [OK] JENKINS_TOKEN: {token[:10]}...")
    else:
        print(f"   [X] JENKINS_TOKEN non défini")
    
    if not all([url, user, token]):
        print("\n[X] Variables manquantes dans .env")
        return False
    
    # 2. Accessibilité réseau
    print("\n2⃣ Test réseau:")
    try:
        import requests
        response = requests.get(url, timeout=10)
        print(f"   [OK] Jenkins accessible (status: {response.status_code})")
    except requests.exceptions.ConnectionError:
        print(f"   [X] Impossible de contacter {url}")
        print(f"   -> Vérifier que Jenkins est démarré")
        return False
    except Exception as e:
        print(f"   [X] Erreur: {e}")
        return False
    
    # 3. Authentification
    print("\n3⃣ Test authentification:")
    try:
        server = jenkins.Jenkins(url, username=user, password=token)
        who = server.get_whoami()
        print(f"   [OK] Authentification OK")
        print(f"   [UTILISATEUR] Connecté en tant que: {who['fullName']}")
    except jenkins.JenkinsException as e:
        print(f"   [X] Échec authentification: {e}")
        print(f"   -> Vérifier username et token")
        return False
    
    # 4. Permissions
    print("\n4⃣ Test permissions:")
    try:
        jobs = server.get_jobs()
        print(f"   [OK] Lecture jobs OK ({len(jobs)} jobs)")
    except Exception as e:
        print(f"   [X] Impossible de lire les jobs: {e}")
        print(f"   -> Vérifier les permissions de l'utilisateur")
        return False
    
    print("\n[OK] Tous les tests passés!")
    return True

# Exécuter le diagnostic
diagnose_jenkins_connection()


def debug_job_creation(server, job_name, config):
    """
    Déboguer la création d'un job
    """
    print(f"[RECHERCHE] Debug création job '{job_name}'\n")
    
    # 1. Vérifier XML
    print("1⃣ Validation XML:")
    try:
        import xml.etree.ElementTree as ET
        ET.fromstring(config)
        print("   [OK] XML valide")
    except ET.ParseError as e:
        print(f"   [X] XML invalide: {e}")
        return False
    
    # 2. Vérifier si job existe déjà
    print("\n2⃣ Vérification existence:")
    try:
        server.get_job_info(job_name)
        print(f"   [ATTENTION] Job '{job_name}' existe déjà")
        print(f"   -> Utiliser reconfig_job() au lieu de create_job()")
        return False
    except jenkins.NotFoundException:
        print(f"   [OK] Job '{job_name}' n'existe pas encore")
    
    # 3. Tenter la création
    print("\n3⃣ Création:")
    try:
        server.create_job(job_name, config)
        print(f"   [OK] Job créé")
    except Exception as e:
        print(f"   [X] Erreur: {e}")
        return False
    
    # 4. Vérifier que c'est bien créé
    print("\n4⃣ Vérification:")
    try:
        info = server.get_job_info(job_name)
        print(f"   [OK] Job confirmé")
        print(f"   [NOTE] URL: {info['url']}")
    except Exception as e:
        print(f"   [X] Job pas trouvé: {e}")
        return False
    
    print("\n[OK] Job créé avec succès!")
    return True


# === LOGS ET DEBUGGING ===

# Activer les logs détaillés
import logging

logging.basicConfig(
    level=logging.DEBUG,  # Niveau DEBUG pour tout voir
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)

# Les logs python-jenkins apparaîtront maintenant
server = jenkins.Jenkins('http://localhost:8080', 'admin', 'token')
jobs = server.get_jobs()  # Vous verrez toutes les requêtes HTTP


# === CHECKLIST DE DÉPANNAGE ===

"""
[LISTE] CHECKLIST COMPLÈTE DE DÉPANNAGE

[OK] 1. JENKINS EST DÉMARRÉ ?
   - Ouvrir http://localhost:8080 dans navigateur
   - Si erreur: démarrer Jenkins (docker start / systemctl start)

[OK] 2. BONNES CREDENTIALS ?
   - Vérifier .env: JENKINS_URL, JENKINS_USER, JENKINS_TOKEN
   - Tester avec curl: curl -u user:token http://localhost:8080/api/json

[OK] 3. PERMISSIONS OK ?
   - Jenkins > Manage Jenkins > Security
   - Utilisateur doit avoir les droits nécessaires

[OK] 4. ENVIRONNEMENT VIRTUEL ACTIVÉ ?
   - Vérifier: which python
   - Activer: source venv/bin/activate

[OK] 5. PYTHON-JENKINS INSTALLÉ ?
   - Vérifier: pip list | grep jenkins
   - Installer: pip install python-jenkins

[OK] 6. RÉSEAU OK ?
   - Pas de firewall bloquant ?
   - Bonne URL (http pas https) ?

[OK] 7. XML VALIDE ?
   - Tester avec xml.etree.ElementTree
   - Comparer avec job existant

[OK] 8. LOGS JENKINS ?
   - Regarder logs Jenkins: /var/log/jenkins/jenkins.log
   - Ou dans Docker: docker logs jenkins

[OK] 9. VERSION COMPATIBLE ?
   - python-jenkins >= 1.8.0
   - Jenkins >= 2.0

[OK] 10. ESSAYER EN MODE DEBUG
    - logging.basicConfig(level=logging.DEBUG)
    - Voir toutes les requêtes HTTP
"""


[OK] BONNES PRATIQUES & CONSEILS

# === 1. TOUJOURS utiliser un environnement virtuel ===

# [OK] BON:
python -m venv jenkins_env
source jenkins_env/bin/activate
pip install python-jenkins

# [X] MAUVAIS:
pip install python-jenkins  # Installation globale


# === 2. NE JAMAIS mettre les credentials dans le code ===

# [X] MAUVAIS:
server = jenkins.Jenkins(
    'http://localhost:8080',
    username='admin',
    password='mon_password_secret'  # [ATTENTION] DANGER !
)

# [OK] BON:
from dotenv import load_dotenv
load_dotenv()

server = jenkins.Jenkins(
    os.getenv('JENKINS_URL'),
    username=os.getenv('JENKINS_USER'),
    password=os.getenv('JENKINS_TOKEN')
)


# === 3. TOUJOURS gérer les erreurs ===

# [X] MAUVAIS:
jobs = server.get_jobs()  # Peut planter

# [OK] BON:
try:
    jobs = server.get_jobs()
except jenkins.JenkinsException as e:
    print(f"Erreur Jenkins: {e}")
except Exception as e:
    print(f"Erreur inattendue: {e}")


# === 4. Vérifier AVANT de créer/modifier ===

# [OK] BON:
def safe_create_job(server, name, config):
    """Créer job avec vérifications"""
    # Vérifier XML
    try:
        import xml.etree.ElementTree as ET
        ET.fromstring(config)
    except ET.ParseError as e:
        print(f"XML invalide: {e}")
        return False
    
    # Vérifier si existe
    try:
        server.get_job_info(name)
        print(f"Job '{name}' existe déjà")
        return False
    except jenkins.NotFoundException:
        pass
    
    # Créer
    try:
        server.create_job(name, config)
        print(f"[OK] Job '{name}' créé")
        return True
    except Exception as e:
        print(f"[X] Erreur: {e}")
        return False


# === 5. Logger les opérations importantes ===

import logging

logger = logging.getLogger(__name__)

def build_and_log(server, job_name, params=None):
    """Déclencher build avec logging"""
    logger.info(f"Déclenchement build: {job_name}")
    if params:
        logger.info(f"Paramètres: {params}")
    
    try:
        queue_id = server.build_job(job_name, parameters=params)
        logger.info(f"Build en queue: {queue_id}")
        return queue_id
    except Exception as e:
        logger.error(f"Échec build: {e}")
        raise


# === 6. Sauvegarder AVANT de modifier ===

# [OK] BON:
def safe_modify_job(server, job_name, modifications):
    """Modifier job avec backup automatique"""
    # Backup
    original_config = server.get_job_config(job_name)
    
    try:
        # Modifier
        new_config = apply_modifications(original_config, modifications)
        server.reconfig_job(job_name, new_config)
        print("[OK] Job modifié")
        return True
    except Exception as e:
        print(f"[X] Erreur, restauration: {e}")
        server.reconfig_job(job_name, original_config)
        return False


# === 7. Tester d'abord dans l'interface web ===

# Workflow recommandé:
# 1. Créer job manuellement dans Jenkins web
# 2. Tester qu'il marche
# 3. Récupérer sa config: server.get_job_config('job')
# 4. Utiliser cette config comme base pour Python
# 5. Modifier progressivement


# === 8. Utiliser des timeouts ===

# [OK] BON:
server = jenkins.Jenkins(
    url,
    username=user,
    password=token,
    timeout=30  # 30 secondes max
)

# Évite que votre script se bloque indéfiniment


# === 9. Limiter les requêtes ===

# [X] MAUVAIS (trop de requêtes):
for job in server.get_jobs():
    for build in server.get_job_info(job['name'])['builds']:
        build_info = server.get_build_info(job['name'], build['number'])
        # 1000+ requêtes !

# [OK] BON (regrouper):
jobs = server.get_jobs()
for job in jobs[:10]:  # Limiter
    info = server.get_job_info(job['name'])
    # Traiter info qui contient déjà les builds


# === 10. Documenter vos scripts ===

# [OK] BON:
"""
create_deployment_jobs.py

Ce script crée les jobs de déploiement pour tous les environnements.

Usage:
    python create_deployment_jobs.py

Requirements:
    - Jenkins accessible sur localhost:8080
    - Credentials dans .env
    - Plugins: Git, Docker

Author: Votre Nom
Date: 2024-01-15
"""


[OK] RESSOURCES & DOCUMENTATION

# === Documentation officielle ===

# Python Jenkins:
# https://python-jenkins.readthedocs.io/

# Jenkins API:
# https://www.jenkins.io/doc/book/using/remote-access-api/

# Jenkins Pipeline:
# https://www.jenkins.io/doc/book/pipeline/

# Groovy (pour Jenkinsfiles):
# https://groovy-lang.org/documentation.html


# === Plugins Jenkins utiles ===

# Pour Python:
# - ShiningPanda Plugin : Gestion environnements Python
# - Cobertura Plugin : Rapports de coverage
# - Warnings Next Generation : Analyse statique

# Pour CI/CD:
# - Git Plugin : Intégration Git
# - Docker Pipeline : Build/run dans Docker
# - Kubernetes Plugin : Déploiement K8s
# - Blue Ocean : Interface moderne

# Pour notifications:
# - Slack Notification : Notifs Slack
# - Email Extension : Emails avancés
# - Telegram Notifications : Notifs Telegram


# === Communauté & Support ===

# GitHub python-jenkins:
# https://github.com/pycontribs/python-jenkins

# Stack Overflow:
# Tags: [jenkins] [python] [python-jenkins]

# Jenkins Community:
# https://community.jenkins.io/

# IRC/Discord:
# #jenkins sur Libera.Chat


# === Livres & Tutoriels ===

# Jenkins: The Definitive Guide (O'Reilly)
# Real Python - Jenkins CI/CD
# DigitalOcean - How To Set Up Jenkins


# === Exemples de code ===

# Repo GitHub avec exemples:
# https://github.com/pycontribs/python-jenkins/tree/master/examples


[OK] CONCLUSION

# [OBJECTIF] Ce que vous avez appris:

# 1. [OK] Installer et configurer Jenkins
# 2. [OK] Se connecter avec python-jenkins
# 3. [OK] Créer et gérer des jobs
# 4. [OK] Déclencher et surveiller des builds
# 5. [OK] Créer des pipelines modernes
# 6. [OK] Automatiser avec des exemples pratiques
# 7. [OK] Monitorer et recevoir des alertes
# 8. [OK] Résoudre les problèmes courants

# [RAPIDE] Prochaines étapes:

# 1. Installer Jenkins localement
# 2. Créer votre premier job simple
# 3. Le déclencher avec Python
# 4. Créer un pipeline
# 5. Automatiser votre workflow

# [IDEE] Conseil final:

# Jenkins + Python = Automatisation puissante !
# 
# - Jenkins automatise vos tâches répétitives
# - Python vous donne le contrôle programmatique
# - Ensemble: CI/CD complet et flexible
# 
# Commencez petit, testez beaucoup, automatisez tout !

# [COURS] N'oubliez pas:

# - TOUJOURS utiliser un environnement virtuel
# - NE JAMAIS commiter les credentials
# - TESTER dans l'interface web d'abord
# - SAUVEGARDER avant de modifier
# - LOGGER les opérations importantes


[OK] SCRIPTS COMPLETS PRÊTS À L'EMPLOI

# === Script 1: Configuration initiale complète ===

"""
setup_jenkins.py
Script pour configurer votre environnement Jenkins depuis zéro
"""

import os
import jenkins
from dotenv import load_dotenv

def setup_jenkins_environment():
    """
    Configuration complète de l'environnement Jenkins
    """
    print("[RAPIDE] Configuration Jenkins\n")
    
    # 1. Vérifier .env
    print("1⃣ Vérification .env...")
    if not os.path.exists('.env'):
        print("   [ATTENTION] Fichier .env introuvable")
        create_env_file()
    else:
        print("   [OK] Fichier .env trouvé")
    
    load_dotenv()
    
    # 2. Tester connexion
    print("\n2⃣ Test de connexion...")
    server = connect_to_jenkins()
    if not server:
        print("   [X] Impossible de se connecter")
        return None
    
    # 3. Créer structure de base
    print("\n3⃣ Création structure...")
    create_base_structure(server)
    
    print("\n[OK] Configuration terminée!")
    return server

def create_env_file():
    """Créer fichier .env interactif"""
    print("\n[NOTE] Création du fichier .env")
    
    url = input("   URL Jenkins (ex: http://localhost:8080): ").strip()
    user = input("   Username: ").strip()
    token = input("   API Token: ").strip()
    
    with open('.env', 'w') as f:
        f.write(f"JENKINS_URL={url}\n")
        f.write(f"JENKINS_USER={user}\n")
        f.write(f"JENKINS_TOKEN={token}\n")
    
    print("   [OK] Fichier .env créé")
    
    # Ajouter au .gitignore
    if not os.path.exists('.gitignore'):
        with open('.gitignore', 'w') as f:
            f.write('.env\n')
    else:
        with open('.gitignore', 'a') as f:
            f.write('\n.env\n')
    
    print("   [OK] .env ajouté au .gitignore")

def connect_to_jenkins():
    """Se connecter à Jenkins avec gestion d'erreurs"""
    try:
        server = jenkins.Jenkins(
            os.getenv('JENKINS_URL'),
            username=os.getenv('JENKINS_USER'),
            password=os.getenv('JENKINS_TOKEN')
        )
        
        user = server.get_whoami()
        version = server.get_version()
        
        print(f"   [OK] Connecté en tant que: {user['fullName']}")
        print(f"   [PACKAGE] Jenkins version: {version}")
        
        return server
        
    except Exception as e:
        print(f"   [X] Erreur: {e}")
        return None

def create_base_structure(server):
    """Créer vues et dossiers de base"""
    # Créer vue "Python Projects"
    view_config = '''<?xml version='1.0' encoding='UTF-8'?>
<hudson.model.ListView>
  <name>Python Projects</name>
  <description>Tous les projets Python</description>
  <filterExecutors>false</filterExecutors>
  <filterQueue>false</filterQueue>
  <properties class="hudson.model.View$PropertyList"/>
  <jobNames>
    <comparator class="hudson.util.CaseInsensitiveComparator"/>
  </jobNames>
  <jobFilters/>
  <columns>
    <hudson.views.StatusColumn/>
    <hudson.views.WeatherColumn/>
    <hudson.views.JobColumn/>
    <hudson.views.LastSuccessColumn/>
    <hudson.views.LastFailureColumn/>
    <hudson.views.LastDurationColumn/>
    <hudson.views.BuildButtonColumn/>
  </columns>
  <includeRegex>.*python.*</includeRegex>
  <recurse>false</recurse>
</hudson.model.ListView>'''
    
    try:
        server.create_view('Python Projects', view_config)
        print("   [OK] Vue 'Python Projects' créée")
    except:
        print("   [ATTENTION] Vue 'Python Projects' existe déjà")

# Exécuter
if __name__ == '__main__':
    server = setup_jenkins_environment()


# === Script 2: Gestionnaire de jobs en CLI ===

"""
jenkins_manager.py
Interface en ligne de commande pour gérer Jenkins
"""

import click
import jenkins
import os
from dotenv import load_dotenv
from tabulate import tabulate

load_dotenv()

@click.group()
@click.pass_context
def cli(ctx):
    """Gestionnaire Jenkins en ligne de commande"""
    ctx.obj = jenkins.Jenkins(
        os.getenv('JENKINS_URL'),
        username=os.getenv('JENKINS_USER'),
        password=os.getenv('JENKINS_TOKEN')
    )

@cli.command()
@click.pass_context
@click.option('--filter', '-f', help='Filtrer par nom')
def list(ctx, filter):
    """Lister tous les jobs"""
    server = ctx.obj
    jobs = server.get_jobs()
    
    if filter:
        jobs = [j for j in jobs if filter.lower() in j['name'].lower()]
    
    # Préparer les données pour le tableau
    data = []
    for job in jobs:
        info = server.get_job_info(job['name'])
        
        # Status emoji
        if info.get('color') == 'blue':
            status = '[OK]'
        elif info.get('color') == 'red':
            status = '[X]'
        elif info.get('color') == 'disabled':
            status = '[ROUGE]'
        else:
            status = '[BLANC]'
        
        # Dernier build
        last_build = info.get('lastBuild')
        if last_build:
            last_num = f"#{last_build['number']}"
        else:
            last_num = 'N/A'
        
        data.append([status, job['name'], last_num])
    
    # Afficher le tableau
    headers = ['Status', 'Job Name', 'Last Build']
    print(tabulate(data, headers=headers, tablefmt='grid'))
    print(f"\n[GRAPHIQUE] Total: {len(data)} jobs")

@cli.command()
@click.argument('job_name')
@click.pass_context
def info(ctx, job_name):
    """Afficher les infos d'un job"""
    server = ctx.obj
    
    try:
        info = server.get_job_info(job_name)
        
        print(f"\n[LISTE] Job: {job_name}")
        print(f"{'='*50}")
        print(f"Description: {info.get('description', 'N/A')}")
        print(f"URL: {info['url']}")
        print(f"Actif: {'Non' if info.get('disabled') else 'Oui'}")
        print(f"Buildable: {'Oui' if info['buildable'] else 'Non'}")
        
        if info.get('lastBuild'):
            build = info['lastBuild']
            build_info = server.get_build_info(job_name, build['number'])
            
            print(f"\n[GRAPHIQUE] Dernier build:")
            print(f"  Numéro: #{build['number']}")
            print(f"  Résultat: {build_info.get('result', 'BUILDING')}")
            print(f"  Durée: {build_info['duration']/1000:.1f}s")
        
    except jenkins.NotFoundException:
        print(f"[X] Job '{job_name}' introuvable")

@cli.command()
@click.argument('job_name')
@click.option('--param', '-p', multiple=True, help='Paramètres KEY=VALUE')
@click.pass_context
def build(ctx, job_name, param):
    """Déclencher un build"""
    server = ctx.obj
    
    # Parser paramètres
    parameters = {}
    for p in param:
        key, value = p.split('=', 1)
        parameters[key] = value
    
    try:
        print(f"[RAPIDE] Déclenchement build: {job_name}")
        if parameters:
            print(f"   Paramètres: {parameters}")
        
        queue_id = server.build_job(job_name, parameters=parameters)
        print(f"[OK] Build en queue: {queue_id}")
        
    except Exception as e:
        print(f"[X] Erreur: {e}")

@cli.command()
@click.argument('job_name')
@click.argument('build_number', type=int)
@click.option('--follow', '-f', is_flag=True, help='Suivre les logs')
@click.pass_context
def logs(ctx, job_name, build_number, follow):
    """Afficher les logs d'un build"""
    server = ctx.obj
    
    if follow:
        # Suivre en temps réel
        import time
        start = 0
        
        print(f"[FICHIER] Logs du build #{build_number} (temps réel)\n")
        
        while True:
            try:
                output = server.get_build_console_output(job_name, build_number)
                new_output = output[start:]
                
                if new_output:
                    print(new_output, end='')
                    start = len(output)
                
                build_info = server.get_build_info(job_name, build_number)
                if not build_info['building']:
                    print(f"\n\n[OK] Build terminé: {build_info['result']}")
                    break
                
                time.sleep(2)
            except KeyboardInterrupt:
                print("\n\n[STOP] Arrêté")
                break
    else:
        # Logs complets
        try:
            output = server.get_build_console_output(job_name, build_number)
            print(output)
        except Exception as e:
            print(f"[X] Erreur: {e}")

@cli.command()
@click.pass_context
def health(ctx):
    """Vérifier la santé de Jenkins"""
    server = ctx.obj
    
    print("[HOPITAL] Health Check Jenkins\n")
    
    # Version
    version = server.get_version()
    print(f"[OK] Version: {version}")
    
    # Jobs
    jobs = server.get_jobs()
    failed = [j for j in jobs if j.get('color') == 'red']
    print(f"[OK] Jobs: {len(jobs)} total, {len(failed)} en échec")
    
    # Queue
    queue = server.get_queue_info()
    print(f"[OK] Queue: {len(queue)} items")
    
    # Nodes
    nodes = server.get_nodes()
    offline = [n for n in nodes if n.get('offline')]
    print(f"[OK] Nodes: {len(nodes)} total, {len(offline)} offline")
    
    if offline or len(failed) > 5 or len(queue) > 10:
        print("\n[ATTENTION] Des problèmes ont été détectés")
    else:
        print("\n[OK] Tout est OK!")

@cli.command()
@click.argument('output_file', default='jenkins_backup.zip')
@click.pass_context
def backup(ctx, output_file):
    """Créer un backup de Jenkins"""
    server = ctx.obj
    
    from datetime import datetime
    import shutil
    
    print("[PACKAGE] Backup Jenkins...")
    
    backup_dir = f'jenkins_backup_{datetime.now().strftime("%Y%m%d_%H%M%S")}'
    os.makedirs(backup_dir, exist_ok=True)
    
    # Sauvegarder jobs
    jobs = server.get_jobs()
    jobs_dir = os.path.join(backup_dir, 'jobs')
    os.makedirs(jobs_dir, exist_ok=True)
    
    for job in jobs:
        try:
            config = server.get_job_config(job['name'])
            with open(os.path.join(jobs_dir, f"{job['name']}.xml"), 'w') as f:
                f.write(config)
            print(f"  [OK] {job['name']}")
        except:
            print(f"  [X] {job['name']}")
    
    # Compresser
    shutil.make_archive(output_file.replace('.zip', ''), 'zip', backup_dir)
    shutil.rmtree(backup_dir)
    
    print(f"\n[OK] Backup créé: {output_file}")

if __name__ == '__main__':
    cli()

# Utilisation:
# python jenkins_manager.py list
# python jenkins_manager.py list --filter python
# python jenkins_manager.py info my-job
# python jenkins_manager.py build my-job -p ENV=prod -p VERSION=1.0
# python jenkins_manager.py logs my-job 42 --follow
# python jenkins_manager.py health
# python jenkins_manager.py backup


# === Script 3: Création de jobs en masse ===

"""
bulk_create_jobs.py
Créer plusieurs jobs d'un coup depuis un fichier YAML
"""

import yaml
import jenkins
import os
from dotenv import load_dotenv

load_dotenv()

def bulk_create_jobs(yaml_file):
    """
    Créer des jobs en masse depuis YAML
    """
    print(f"[LISTE] Lecture de {yaml_file}...")
    
    with open(yaml_file, 'r') as f:
        config = yaml.safe_load(f)
    
    server = jenkins.Jenkins(
        os.getenv('JENKINS_URL'),
        username=os.getenv('JENKINS_USER'),
        password=os.getenv('JENKINS_TOKEN')
    )
    
    print(f"[OUTIL] Création de {len(config['jobs'])} jobs...\n")
    
    created = 0
    failed = 0
    skipped = 0
    
    for job_def in config['jobs']:
        job_name = job_def['name']
        
        # Vérifier si existe
        try:
            server.get_job_info(job_name)
            print(f"[ATTENTION] {job_name} - existe déjà (skipped)")
            skipped += 1
            continue
        except jenkins.NotFoundException:
            pass
        
        # Créer le job
        try:
            job_config = generate_job_config(job_def)
            server.create_job(job_name, job_config)
            print(f"[OK] {job_name} - créé")
            created += 1
        except Exception as e:
            print(f"[X] {job_name} - erreur: {e}")
            failed += 1
    
    print(f"\n{'='*50}")
    print(f"[OK] Créés: {created}")
    print(f"[ATTENTION] Skipped: {skipped}")
    print(f"[X] Échecs: {failed}")

def generate_job_config(job_def):
    """Générer config XML depuis définition YAML"""
    
    if job_def['type'] == 'pipeline':
        # Construire stages
        stages = []
        for stage in job_def.get('stages', []):
            steps = '\n                    '.join(stage['steps'])
            stages.append(f'''
        stage('{stage['name']}') {{
            steps {{
                sh """
                    {steps}
                """
            }}
        }}''')
        
        pipeline_script = f'''
pipeline {{
    agent any
    
    stages {{
{''.join(stages)}
    }}
}}
'''
        
        return f'''<?xml version='1.0' encoding='UTF-8'?>
<flow-definition plugin="workflow-job">
  <description>{job_def.get('description', '')}</description>
  <definition class="org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition">
    <script>{pipeline_script}</script>
    <sandbox>true</sandbox>
  </definition>
</flow-definition>'''
    
    else:  # freestyle
        return f'''<?xml version='1.0' encoding='UTF-8'?>
<project>
  <description>{job_def.get('description', '')}</description>
  <builders>
    <hudson.tasks.Shell>
      <command>{job_def.get('script', 'echo "No script"')}</command>
    </hudson.tasks.Shell>
  </builders>
</project>'''

# Exemple de fichier YAML: jobs.yaml
example_yaml = """
jobs:
  - name: api-tests
    type: pipeline
    description: Tests de l'API
    stages:
      - name: Setup
        steps:
          - python -m venv venv
          - source venv/bin/activate
          - pip install -r requirements.txt
      - name: Test
        steps:
          - source venv/bin/activate
          - pytest tests/ -v
  
  - name: deploy-staging
    type: pipeline
    description: Déploiement staging
    stages:
      - name: Deploy
        steps:
          - ./deploy.sh staging
"""

# Utilisation
if __name__ == '__main__':
    # Créer exemple YAML si n'existe pas
    if not os.path.exists('jobs.yaml'):
        with open('jobs.yaml', 'w') as f:
            f.write(example_yaml)
        print("[NOTE] Fichier jobs.yaml créé")
    
    bulk_create_jobs('jobs.yaml')


# === Script 4: Moniteur en temps réel ===

"""
jenkins_monitor.py
Dashboard en temps réel dans le terminal
"""

import jenkins
import os
import time
from datetime import datetime
from dotenv import load_dotenv

load_dotenv()

def clear_screen():
    """Effacer l'écran"""
    os.system('clear' if os.name != 'nt' else 'cls')

def get_emoji(color):
    """Emoji selon status"""
    return {
        'blue': '[OK]',
        'red': '[X]',
        'yellow': '[ATTENTION]',
        'grey': '[BLANC]',
        'disabled': '[ROUGE]',
        'blue_anime': '[BLEU]',
        'red_anime': '[ROUGE]'
    }.get(color, '[?]')

def monitor_dashboard():
    """
    Afficher un dashboard en temps réel
    """
    server = jenkins.Jenkins(
        os.getenv('JENKINS_URL'),
        username=os.getenv('JENKINS_USER'),
        password=os.getenv('JENKINS_TOKEN')
    )
    
    print("[RAPIDE] Démarrage du dashboard...")
    print("   Appuyez Ctrl+C pour quitter\n")
    time.sleep(2)
    
    while True:
        try:
            clear_screen()
            
            # Header
            print("=" * 80)
            print(f"  JENKINS DASHBOARD - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
            print("=" * 80)
            
            # Stats globales
            jobs = server.get_jobs()
            queue = server.get_queue_info()
            nodes = server.get_nodes()
            
            stats = {
                'total': len(jobs),
                'success': 0,
                'failed': 0,
                'building': 0
            }
            
            for job in jobs:
                color = job.get('color', '')
                if 'blue' in color:
                    stats['success'] += 1
                elif 'red' in color:
                    stats['failed'] += 1
                if 'anime' in color:
                    stats['building'] += 1
            
            print(f"\n[GRAPHIQUE] STATISTIQUES")
            print(f"  Total Jobs: {stats['total']}")
            print(f"  [OK] Succès: {stats['success']}")
            print(f"  [X] Échecs: {stats['failed']}")
            print(f"  [BLEU] En cours: {stats['building']}")
            print(f"  [LISTE] Queue: {len(queue)}")
            print(f"  [ECRAN]  Nodes: {len(nodes)}")
            
            # Jobs récents
            print(f"\n[LISTE] JOBS RÉCENTS")
            print("-" * 80)
            
            for job in jobs[:15]:  # Top 15
                emoji = get_emoji(job.get('color', ''))
                name = job['name'][:40]  # Tronquer si trop long
                
                # Info dernier build
                try:
                    info = server.get_job_info(job['name'])
                    last_build = info.get('lastBuild')
                    
                    if last_build:
                        build_info = server.get_build_info(job['name'], last_build['number'])
                        build_num = f"#{last_build['number']}"
                        
                        if build_info['building']:
                            status = "BUILDING"
                        else:
                            status = build_info.get('result', 'N/A')
                        
                        duration = f"{build_info['duration']/1000:.0f}s"
                    else:
                        build_num = "N/A"
                        status = "NEVER"
                        duration = "N/A"
                    
                    print(f"  {emoji} {name:<40} {build_num:<8} {status:<12} {duration}")
                    
                except:
                    print(f"  {emoji} {name:<40} ERROR")
            
            # Queue si non vide
            if queue:
                print(f"\n[HOURGLASS_WITH_FLOWING_SAND] QUEUE ({len(queue)} items)")
                print("-" * 80)
                for item in queue[:5]:
                    job_name = item['task']['name'][:50]
                    why = item.get('why', 'En attente')[:25]
                    print(f"  • {job_name} - {why}")
            
            # Footer
            print("\n" + "=" * 80)
            print("  Rafraîchissement dans 5s... (Ctrl+C pour quitter)")
            
            time.sleep(5)
            
        except KeyboardInterrupt:
            clear_screen()
            print("\n[STOP] Dashboard arrêté\n")
            break
        except Exception as e:
            print(f"\n[X] Erreur: {e}")
            time.sleep(5)

# Lancer le dashboard
if __name__ == '__main__':
    monitor_dashboard()


# === Script 5: Générateur de rapports ===

"""
generate_report.py
Générer un rapport HTML complet
"""

import jenkins
import os
from datetime import datetime, timedelta
from dotenv import load_dotenv

load_dotenv()

def generate_html_report(output_file='jenkins_report.html'):
    """
    Générer rapport HTML complet
    """
    server = jenkins.Jenkins(
        os.getenv('JENKINS_URL'),
        username=os.getenv('JENKINS_USER'),
        password=os.getenv('JENKINS_TOKEN')
    )
    
    print("[GRAPHIQUE] Génération du rapport...")
    
    # Collecter données
    jobs = server.get_jobs()
    cutoff = datetime.now() - timedelta(days=7)
    
    stats = {
        'total_jobs': len(jobs),
        'total_builds': 0,
        'successful_builds': 0,
        'failed_builds': 0,
        'avg_duration': 0,
        'most_active_jobs': [],
        'problematic_jobs': []
    }
    
    job_details = []
    durations = []
    
    for job in jobs:
        try:
            info = server.get_job_info(job['name'])
            job_builds = 0
            job_failures = 0
            
            for build in info['builds'][:20]:  # 20 derniers
                build_info = server.get_build_info(job['name'], build['number'])
                build_time = datetime.fromtimestamp(build_info['timestamp'] / 1000)
                
                if build_time < cutoff:
                    break
                
                stats['total_builds'] += 1
                job_builds += 1
                
                result = build_info.get('result')
                if result == 'SUCCESS':
                    stats['successful_builds'] += 1
                elif result == 'FAILURE':
                    stats['failed_builds'] += 1
                    job_failures += 1
                
                if build_info['duration'] > 0:
                    durations.append(build_info['duration'])
            
            job_details.append({
                'name': job['name'],
                'builds': job_builds,
                'failures': job_failures,
                'color': job.get('color', 'grey')
            })
        except:
            pass
    
    # Calculs
    if durations:
        stats['avg_duration'] = sum(durations) / len(durations) / 1000 / 60  # minutes
    
    # Top jobs actifs
    stats['most_active_jobs'] = sorted(
        job_details,
        key=lambda x: x['builds'],
        reverse=True
    )[:10]
    
    # Jobs problématiques
    stats['problematic_jobs'] = sorted(
        [j for j in job_details if j['failures'] > 3],
        key=lambda x: x['failures'],
        reverse=True
    )[:10]
    
    # Générer HTML
    html = generate_html_template(stats)
    
    with open(output_file, 'w') as f:
        f.write(html)
    
    print(f"[OK] Rapport généré: {output_file}")
    
    # Ouvrir dans le navigateur
    import webbrowser
    webbrowser.open(f'file://{os.path.abspath(output_file)}')

def generate_html_template(stats):
    """Template HTML du rapport"""
    return f'''<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Jenkins Report</title>
    <style>
        body {{
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            margin: 0;
            padding: 20px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
        }}
        .container {{
            max-width: 1200px;
            margin: 0 auto;
            background: white;
            padding: 40px;
            border-radius: 15px;
            box-shadow: 0 10px 40px rgba(0,0,0,0.2);
        }}
        h1 {{
            color: #333;
            text-align: center;
            font-size: 36px;
            margin-bottom: 10px;
        }}
        .subtitle {{
            text-align: center;
            color: #666;
            margin-bottom: 40px;
        }}
        .stats-grid {{
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
            gap: 20px;
            margin: 40px 0;
        }}
        .stat-card {{
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            padding: 30px;
            border-radius: 10px;
            text-align: center;
            box-shadow: 0 4px 15px rgba(0,0,0,0.1);
        }}
        .stat-card.green {{ background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%); }}
        .stat-card.red {{ background: linear-gradient(135deg, #f44336 0%, #d32f2f 100%); }}
        .stat-card.orange {{ background: linear-gradient(135deg, #ff9800 0%, #f57c00 100%); }}
        .stat-number {{
            font-size: 48px;
            font-weight: bold;
            margin: 15px 0;
        }}
        .stat-label {{
            font-size: 14px;
            opacity: 0.9;
            text-transform: uppercase;
            letter-spacing: 1px;
        }}
        table {{
            width: 100%;
            border-collapse: collapse;
            margin: 30px 0;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }}
        th {{
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            padding: 15px;
            text-align: left;
            font-weight: 600;
        }}
        td {{
            padding: 12px 15px;
            border-bottom: 1px solid #eee;
        }}
        tr:hover {{
            background: #f5f5f5;
        }}
        .section {{
            margin: 50px 0;
        }}
        .section-title {{
            font-size: 24px;
            color: #333;
            margin-bottom: 20px;
            padding-bottom: 10px;
            border-bottom: 3px solid #667eea;
        }}
    </style>
</head>
<body>
    <div class="container">
        <h1>[GRAPHIQUE] Jenkins Report</h1>
        <p class="subtitle">Généré le {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>
        
        <div class="stats-grid">
            <div class="stat-card">
                <div class="stat-label">Total Jobs</div>
                <div class="stat-number">{stats['total_jobs']}</div>
            </div>
            <div class="stat-card green">
                <div class="stat-label">Builds Réussis</div>
                <div class="stat-number">{stats['successful_builds']}</div>
            </div>
            <div class="stat-card red">
                <div class="stat-label">Builds Échoués</div>
                <div class="stat-number">{stats['failed_builds']}</div>
            </div>
            <div class="stat-card orange">
                <div class="stat-label">Durée Moyenne</div>
                <div class="stat-number">{stats['avg_duration']:.1f}m</div>
            </div>
        </div>
        
        <div class="section">
            <h2 class="section-title">[HOT] Jobs les Plus Actifs</h2>
            <table>
                <thead>
                    <tr>
                        <th>Job</th>
                        <th>Builds (7j)</th>
                        <th>Échecs</th>
                    </tr>
                </thead>
                <tbody>
                    {''.join([f'<tr><td>{j["name"]}</td><td>{j["builds"]}</td><td>{j["failures"]}</td></tr>' for j in stats['most_active_jobs']])}
                </tbody>
            </table>
        </div>
        
        <div class="section">
            <h2 class="section-title">[ATTENTION] Jobs Problématiques</h2>
            <table>
                <thead>
                    <tr>
                        <th>Job</th>
                        <th>Échecs (7j)</th>
                    </tr>
                </thead>
                <tbody>
                    {''.join([f'<tr><td>{j["name"]}</td><td>{j["failures"]}</td></tr>' for j in stats['problematic_jobs']]) if stats['problematic_jobs'] else '<tr><td colspan="2">Aucun problème détecté [OK]</td></tr>'}
                </tbody>
            </table>
        </div>
    </div>
</body>
</html>'''

# Utilisation
if __name__ == '__main__':
    generate_html_report()


[OK] INTÉGRATIONS AVANCÉES

# === Intégration avec GitHub Actions ===

"""
Déclencher Jenkins depuis GitHub Actions
"""

# Fichier: .github/workflows/trigger-jenkins.yml
github_actions_workflow = '''
name: Trigger Jenkins

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  trigger-jenkins:
    runs-on: ubuntu-latest
    
    steps:
    - name: Trigger Jenkins Job
      run: |
        curl -X POST \\
          -u ${{ secrets.JENKINS_USER }}:${{ secrets.JENKINS_TOKEN }} \\
          "${{ secrets.JENKINS_URL }}/job/my-project/build"
    
    - name: Wait for Build
      run: |
        python trigger_jenkins.py
      env:
        JENKINS_URL: ${{ secrets.JENKINS_URL }}
        JENKINS_USER: ${{ secrets.JENKINS_USER }}
        JENKINS_TOKEN: ${{ secrets.JENKINS_TOKEN }}
'''

# Script Python appelé par GitHub Actions
"""trigger_jenkins.py"""
import jenkins
import os
import time

def trigger_and_wait():
    server = jenkins.Jenkins(
        os.getenv('JENKINS_URL'),
        username=os.getenv('JENKINS_USER'),
        password=os.getenv('JENKINS_TOKEN')
    )
    
    # Déclencher
    queue_id = server.build_job('my-project', parameters={
        'GIT_COMMIT': os.getenv('GITHUB_SHA'),
        'GIT_BRANCH': os.getenv('GITHUB_REF')
    })
    
    print(f"Build triggered: {queue_id}")
    
    # Attendre démarrage
    time.sleep(5)
    build_number = None
    
    for _ in range(30):  # 30 tentatives max
        try:
            item = server.get_queue_item(queue_id)
            if 'executable' in item:
                build_number = item['executable']['number']
                break
        except:
            pass
        time.sleep(2)
    
    if not build_number:
        print("Build didn't start")
        exit(1)
    
    # Attendre fin
    print(f"Build #{build_number} started")
    
    while True:
        build_info = server.get_build_info('my-project', build_number)
        
        if not build_info['building']:
            result = build_info['result']
            print(f"Build finished: {result}")
            
            if result == 'SUCCESS':
                exit(0)
            else:
                exit(1)
        
        time.sleep(10)

if __name__ == '__main__':
    trigger_and_wait()


# === Intégration avec Slack (avancé) ===

"""
Bot Slack pour contrôler Jenkins
"""

from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler
import jenkins
import os

# Initialiser Slack
app = App(token=os.getenv("SLACK_BOT_TOKEN"))

# Connexion Jenkins
server = jenkins.Jenkins(
    os.getenv('JENKINS_URL'),
    username=os.getenv('JENKINS_USER'),
    password=os.getenv('JENKINS_TOKEN')
)

@app.command("/jenkins-status")
def jenkins_status(ack, say, command):
    """Commande: /jenkins-status"""
    ack()
    
    jobs = server.get_jobs()
    failed = [j for j in jobs if j.get('color') == 'red']
    queue = server.get_queue_info()
    
    message = f"""
[GRAPHIQUE] *Jenkins Status*

• Total Jobs: {len(jobs)}
• [X] Failed: {len(failed)}
• [LISTE] Queue: {len(queue)}
"""
    
    say(message)

@app.command("/jenkins-build")
def jenkins_build(ack, say, command):
    """Commande: /jenkins-build job-name"""
    ack()
    
    job_name = command['text'].strip()
    
    if not job_name:
        say("Usage: /jenkins-build <job-name>")
        return
    
    try:
        queue_id = server.build_job(job_name)
        say(f"[OK] Build triggered for *{job_name}* (queue: {queue_id})")
    except Exception as e:
        say(f"[X] Error: {e}")

@app.command("/jenkins-logs")
def jenkins_logs(ack, say, command):
    """Commande: /jenkins-logs job-name build-number"""
    ack()
    
    parts = command['text'].strip().split()
    
    if len(parts) != 2:
        say("Usage: /jenkins-logs <job-name> <build-number>")
        return
    
    job_name, build_number = parts[0], int(parts[1])
    
    try:
        output = server.get_build_console_output(job_name, build_number)
        
        # Slack limite à 3000 caractères
        if len(output) > 3000:
            output = output[-3000:]
            output = "...\n" + output
        
        say(f"```{output}```")
    except Exception as e:
        say(f"[X] Error: {e}")

# Lancer le bot
if __name__ == "__main__":
    handler = SocketModeHandler(app, os.getenv("SLACK_APP_TOKEN"))
    handler.start()


# === Intégration avec Telegram ===

"""
Bot Telegram pour Jenkins
"""

from telegram import Update
from telegram.ext import ApplicationBuilder, CommandHandler, ContextTypes
import jenkins
import os

server = jenkins.Jenkins(
    os.getenv('JENKINS_URL'),
    username=os.getenv('JENKINS_USER'),
    password=os.getenv('JENKINS_TOKEN')
)

async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Commande /start"""
    await update.message.reply_text(
        "[BOT] Jenkins Bot\n\n"
        "Commandes:\n"
        "/status - État de Jenkins\n"
        "/jobs - Liste des jobs\n"
        "/build <job> - Déclencher un build\n"
    )

async def status(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Commande /status"""
    jobs = server.get_jobs()
    failed = [j for j in jobs if j.get('color') == 'red']
    queue = server.get_queue_info()
    
    message = (
        f"[GRAPHIQUE] Jenkins Status\n\n"
        f"Total Jobs: {len(jobs)}\n"
        f"[X] Failed: {len(failed)}\n"
        f"[LISTE] Queue: {len(queue)}\n"
    )
    
    await update.message.reply_text(message)

async def jobs_list(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Commande /jobs"""
    jobs = server.get_jobs()[:10]  # Top 10
    
    message = "[LISTE] Jobs:\n\n"
    for job in jobs:
        emoji = '[OK]' if job.get('color') == 'blue' else '[X]'
        message += f"{emoji} {job['name']}\n"
    
    await update.message.reply_text(message)

async def build(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Commande /build job-name"""
    if not context.args:
        await update.message.reply_text("Usage: /build <job-name>")
        return
    
    job_name = context.args[0]
    
    try:
        queue_id = server.build_job(job_name)
        await update.message.reply_text(
            f"[OK] Build triggered: {job_name}\n"
            f"Queue ID: {queue_id}"
        )
    except Exception as e:
        await update.message.reply_text(f"[X] Error: {e}")

# Créer l'application
app = ApplicationBuilder().token(os.getenv("TELEGRAM_BOT_TOKEN")).build()

# Ajouter les handlers
app.add_handler(CommandHandler("start", start))
app.add_handler(CommandHandler("status", status))
app.add_handler(CommandHandler("jobs", jobs_list))
app.add_handler(CommandHandler("build", build))

# Lancer le bot
if __name__ == '__main__':
    print("[BOT] Bot Telegram démarré")
    app.run_polling()


# === Intégration avec AWS ===

"""
Déclencher Jenkins depuis Lambda AWS
"""

import json
import jenkins
import os

def lambda_handler(event, context):
    """
    Handler Lambda AWS
    Déclenché par EventBridge, SNS, API Gateway, etc.
    """
    
    # Connexion Jenkins
    server = jenkins.Jenkins(
        os.environ['JENKINS_URL'],
        username=os.environ['JENKINS_USER'],
        password=os.environ['JENKINS_TOKEN']
    )
    
    # Parser l'événement
    job_name = event.get('job_name', 'default-job')
    parameters = event.get('parameters', {})
    
    try:
        # Déclencher build
        queue_id = server.build_job(job_name, parameters=parameters)
        
        return {
            'statusCode': 200,
            'body': json.dumps({
                'message': 'Build triggered',
                'job': job_name,
                'queue_id': queue_id
            })
        }
    
    except Exception as e:
        return {
            'statusCode': 500,
            'body': json.dumps({
                'error': str(e)
            })
        }

# Déployer avec AWS SAM ou Terraform
# Variables d'environnement à définir:
# - JENKINS_URL
# - JENKINS_USER
# - JENKINS_TOKEN


# === Intégration avec Kubernetes ===

"""
CronJob Kubernetes qui surveille Jenkins
"""

# Fichier: k8s-jenkins-monitor.yaml
kubernetes_cronjob = '''
apiVersion: batch/v1
kind: CronJob
metadata:
  name: jenkins-monitor
spec:
  schedule: "*/5 * * * *"  # Toutes les 5 minutes
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: monitor
            image: python:3.11-slim
            command:
            - /bin/sh
            - -c
            - |
              pip install python-jenkins requests
              python /scripts/monitor.py
            env:
            - name: JENKINS_URL
              valueFrom:
                secretKeyRef:
                  name: jenkins-credentials
                  key: url
            - name: JENKINS_USER
              valueFrom:
                secretKeyRef:
                  name: jenkins-credentials
                  key: username
            - name: JENKINS_TOKEN
              valueFrom:
                secretKeyRef:
                  name: jenkins-credentials
                  key: token
            volumeMounts:
            - name: scripts
              mountPath: /scripts
          volumes:
          - name: scripts
            configMap:
              name: jenkins-monitor-script
          restartPolicy: OnFailure
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: jenkins-monitor-script
data:
  monitor.py: |
    import jenkins
    import os
    
    server = jenkins.Jenkins(
        os.getenv('JENKINS_URL'),
        username=os.getenv('JENKINS_USER'),
        password=os.getenv('JENKINS_TOKEN')
    )
    
    # Vérifier santé
    jobs = server.get_jobs()
    failed = [j for j in jobs if j.get('color') == 'red']
    
    if len(failed) > 5:
        print(f"WARNING: {len(failed)} jobs failed")
        # Envoyer alerte
    else:
        print(f"OK: {len(failed)} jobs failed")
---
apiVersion: v1
kind: Secret
metadata:
  name: jenkins-credentials
type: Opaque
stringData:
  url: http://jenkins.default.svc.cluster.local:8080
  username: admin
  token: your-token-here
'''


[OK] PATTERNS & ARCHITECTURES

# === Pattern 1: Job Factory ===

"""
Factory pattern pour créer différents types de jobs
"""

class JobFactory:
    """Factory pour créer des jobs standardisés"""
    
    def __init__(self, server):
        self.server = server
    
    def create_python_test_job(self, name, repo_url, branch='main'):
        """Job de test Python standard"""
        pipeline = f'''
pipeline {{
    agent any
    
    stages {{
        stage('Checkout') {{
            steps {{
                git branch: '{branch}', url: '{repo_url}'
            }}
        }}
        
        stage('Setup') {{
            steps {{
                sh """
                    python -m venv venv
                    . venv/bin/activate
                    pip install -r requirements.txt
                    pip install -r requirements-dev.txt
                """
            }}
        }}
        
        stage('Lint') {{
            steps {{
                sh """
                    . venv/bin/activate
                    flake8 src/ tests/
                    black --check src/ tests/
                """
            }}
        }}
        
        stage('Test') {{
            steps {{
                sh """
                    . venv/bin/activate
                    pytest tests/ -v --cov=src --cov-report=xml
                """
            }}
        }}
    }}
    
    post {{
        always {{
            junit 'test-results.xml'
        }}
    }}
}}
'''
        
        config = self._create_pipeline_config(pipeline, f"Tests pour {name}")
        self.server.create_job(name, config)
        return name
    
    def create_docker_build_job(self, name, repo_url, dockerfile='Dockerfile'):
        """Job de build Docker standard"""
        pipeline = f'''
pipeline {{
    agent any
    
    environment {{
        IMAGE_NAME = '{name}'
        REGISTRY = 'registry.example.com'
    }}
    
    stages {{
        stage('Checkout') {{
            steps {{
                git '{repo_url}'
            }}
        }}
        
        stage('Build') {{
            steps {{
                sh """
                    docker build -t $REGISTRY/$IMAGE_NAME:$BUILD_NUMBER -f {dockerfile} .
                    docker tag $REGISTRY/$IMAGE_NAME:$BUILD_NUMBER $REGISTRY/$IMAGE_NAME:latest
                """
            }}
        }}
        
        stage('Push') {{
            steps {{
                sh """
                    docker push $REGISTRY/$IMAGE_NAME:$BUILD_NUMBER
                    docker push $REGISTRY/$IMAGE_NAME:latest
                """
            }}
        }}
    }}
}}
'''
        
        config = self._create_pipeline_config(pipeline, f"Build Docker pour {name}")
        self.server.create_job(name, config)
        return name
    
    def create_deploy_job(self, name, environment):
        """Job de déploiement standard"""
        pipeline = f'''
pipeline {{
    agent any
    
    parameters {{
        string(name: 'VERSION', defaultValue: 'latest', description: 'Version à déployer')
    }}
    
    stages {{
        stage('Deploy') {{
            steps {{
                sh """
                    ./deploy.sh {environment} $VERSION
                """
            }}
        }}
        
        stage('Health Check') {{
            steps {{
                sh """
                    ./health-check.sh {environment}
                """
            }}
        }}
    }}
}}
'''
        
        config = self._create_pipeline_config(pipeline, f"Déploiement {environment}")
        self.server.create_job(name, config)
        return name
    
    def _create_pipeline_config(self, pipeline_script, description):
        """Créer config XML pipeline"""
        return f'''<?xml version='1.0' encoding='UTF-8'?>
<flow-definition plugin="workflow-job">
  <description>{description}</description>
  <definition class="org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition">
    <script>{pipeline_script}</script>
    <sandbox>true</sandbox>
  </definition>
</flow-definition>'''

# Utilisation
factory = JobFactory(server)

# Créer jobs pour un nouveau projet
project = 'mon-api'
factory.create_python_test_job(f'{project}-tests', 'https://github.com/user/api.git')
factory.create_docker_build_job(f'{project}-build', 'https://github.com/user/api.git')
factory.create_deploy_job(f'{project}-deploy-staging', 'staging')
factory.create_deploy_job(f'{project}-deploy-prod', 'production')


# === Pattern 2: Repository Pattern ===

"""
Repository pattern pour abstraire les opérations Jenkins
"""

class JenkinsRepository:
    """Repository pour opérations Jenkins"""
    
    def __init__(self, server):
        self.server = server
        self._cache = {}
    
    def get_job(self, name):
        """Récupérer un job avec cache"""
        if name not in self._cache:
            self._cache[name] = self.server.get_job_info(name)
        return self._cache[name]
    
    def get_all_jobs(self, refresh=False):
        """Récupérer tous les jobs"""
        if 'all_jobs' not in self._cache or refresh:
            self._cache['all_jobs'] = self.server.get_jobs()
        return self._cache['all_jobs']
    
    def get_failed_jobs(self):
        """Récupérer jobs en échec"""
        jobs = self.get_all_jobs()
        return [j for j in jobs if j.get('color') == 'red']
    
    def get_building_jobs(self):
        """Récupérer jobs en cours"""
        jobs = self.get_all_jobs()
        return [j for j in jobs if 'anime' in j.get('color', '')]
    
    def clear_cache(self):
        """Vider le cache"""
        self._cache.clear()

# Utilisation
repo = JenkinsRepository(server)

failed = repo.get_failed_jobs()
building = repo.get_building_jobs()

print(f"Failed: {len(failed)}, Building: {len(building)}")


# === Pattern 3: Observer Pattern pour événements ===

"""
Observer pattern pour surveiller les événements Jenkins
"""

class JenkinsObserver:
    """Base class pour observateurs"""
    
    def on_build_started(self, job_name, build_number):
        pass
    
    def on_build_completed(self, job_name, build_number, result):
        pass
    
    def on_build_failed(self, job_name, build_number):
        pass

class SlackObserver(JenkinsObserver):
    """Observateur qui notifie sur Slack"""
    
    def __init__(self, webhook_url):
        self.webhook_url = webhook_url
    
    def on_build_failed(self, job_name, build_number):
        message = f"[X] Build failed: {job_name} #{build_number}"
        self._send_slack(message)
    
    def _send_slack(self, message):
        import requests
        requests.post(self.webhook_url, json={'text': message})

class EmailObserver(JenkinsObserver):
    """Observateur qui envoie des emails"""
    
    def on_build_failed(self, job_name, build_number):
        self._send_email(
            to='team@example.com',
            subject=f'Build failed: {job_name}',
            body=f'Build #{build_number} has failed'
        )
    
    def _send_email(self, to, subject, body):
        # Envoyer email
        pass

class JenkinsMonitorWithObservers:
    """Moniteur Jenkins avec pattern Observer"""
    
    def __init__(self, server):
        self.server = server
        self.observers = []
        self.last_builds = {}
    
    def add_observer(self, observer):
        """Ajouter un observateur"""
        self.observers.append(observer)
    
    def check(self):
        """Vérifier les builds et notifier"""
        jobs = self.server.get_jobs()
        
        for job in jobs:
            info = self.server.get_job_info(job['name'])
            last_build = info.get('lastBuild')
            
            if not last_build:
                continue
            
            build_number = last_build['number']
            build_info = self.server.get_build_info(job['name'], build_number)
            
            # Nouveau build?
            if job['name'] not in self.last_builds:
                self.last_builds[job['name']] = build_number
                continue
            
            if build_number > self.last_builds[job['name']]:
                # Build terminé?
                if not build_info['building']:
                    result = build_info['result']
                    
                    # Notifier observateurs
                    for observer in self.observers:
                        observer.on_build_completed(job['name'], build_number, result)
                        
                        if result == 'FAILURE':
                            observer.on_build_failed(job['name'], build_number)
                    
                    self.last_builds[job['name']] = build_number

# Utilisation
monitor = JenkinsMonitorWithObservers(server)

# Ajouter observateurs
monitor.add_observer(SlackObserver('https://hooks.slack.com/...'))
monitor.add_observer(EmailObserver())

# Vérifier périodiquement
import time
while True:
    monitor.check()
    time.sleep(30)


[OK] TESTS UNITAIRES POUR VOS SCRIPTS

"""
test_jenkins_scripts.py
Tests unitaires avec pytest
"""

import pytest
from unittest.mock import Mock, patch
import jenkins

@pytest.fixture
def mock_server():
    """Fixture: serveur Jenkins mocké"""
    server = Mock(spec=jenkins.Jenkins)
    server.get_version.return_value = "2.387.1"
    server.get_whoami.return_value = {"fullName": "Admin"}
    return server

def test_get_jobs(mock_server):
    """Test: récupérer jobs"""
    mock_server.get_jobs.return_value = [
        {"name": "job1", "color": "blue"},
        {"name": "job2", "color": "red"}
    ]
    
    jobs = mock_server.get_jobs()
    
    assert len(jobs) == 2
    assert jobs[0]['name'] == 'job1'
    mock_server.get_jobs.assert_called_once()

def test_build_job(mock_server):
    """Test: déclencher build"""
    mock_server.build_job.return_value = 123
    
    queue_id = mock_server.build_job('test-job', parameters={'ENV': 'prod'})
    
    assert queue_id == 123
    mock_server.build_job.assert_called_with('test-job', parameters={'ENV': 'prod'})

def test_job_not_found(mock_server):
    """Test: job inexistant"""
    mock_server.get_job_info.side_effect = jenkins.NotFoundException("Not found")
    
    with pytest.raises(jenkins.NotFoundException):
        mock_server.get_job_info('nonexistent')

@patch('jenkins.Jenkins')
def test_connection(mock_jenkins_class):
    """Test: connexion Jenkins"""
    mock_instance = Mock()
    mock_jenkins_class.return_value = mock_instance
    
    server = jenkins.Jenkins('http://localhost:8080', 'admin', 'token')
    
    mock_jenkins_class.assert_called_with('http://localhost:8080', 'admin', 'token')

# Lancer les tests:
# pytest test_jenkins_scripts.py -v


[OK] AIDE-MÉMOIRE RAPIDE

"""
╔══════════════════════════════════════════════════════════════╗
║              JENKINS + PYTHON - AIDE-MÉMOIRE                 ║
╚══════════════════════════════════════════════════════════════╝

[PACKAGE] INSTALLATION
  pip install python-jenkins python-dotenv

[SECURISE] CONNEXION
  from dotenv import load_dotenv
  load_dotenv()
  server = jenkins.Jenkins(
      os.getenv('JENKINS_URL'),
      username=os.getenv('JENKINS_USER'),
      password=os.getenv('JENKINS_TOKEN')
  )

[LISTE] JOBS
  jobs = server.get_jobs()                    # Lister
  info = server.get_job_info('job')           # Info
  server.create_job('name', xml_config)       # Créer
  server.copy_job('src', 'dst')               # Copier
  server.delete_job('name')                   # Supprimer

[OUTIL] BUILDS
  server.build_job('job')                     # Déclencher
  server.build_job('job', parameters={...})   # Avec params
  info = server.get_build_info('job', 42)     # Info
  logs = server.get_build_console_output(...)  # Logs
  server.stop_build('job', 42)                # Arrêter

[GRAPHIQUE] MONITORING
  queue = server.get_queue_info()             # Queue
  nodes = server.get_nodes()                  # Nodes
  version = server.get_version()              # Version

[ATTENTION]  ERREURS FRÉQUENTES
  Connection refused -> Jenkins pas démarré
  Unauthorized -> Mauvais credentials
  Forbidden -> Pas les permissions
  Not found -> Job/build n'existe pas

[OK] BONNES PRATIQUES
  [OK] Utiliser venv
  [OK] Credentials dans .env
  [OK] Gérer les erreurs (try/except)
  [OK] Tester dans l'interface web d'abord
  [OK] Sauvegarder avant modifications

[LIEN] RESSOURCES
  Docs: https://python-jenkins.readthedocs.io/
  GitHub: https://github.com/pycontribs/python-jenkins
  Jenkins API: https://www.jenkins.io/doc/book/using/remote-access-api/
"""


# === FIN DU CHEATSHEET COMPLET ===

"""
[BRAVO] FÉLICITATIONS !

Vous avez maintenant toutes les connaissances pour:
[OK] Installer et configurer Jenkins
[OK] Contrôler Jenkins avec Python
[OK] Créer des jobs et pipelines
[OK] Automatiser vos workflows CI/CD
[OK] Monitorer et maintenir Jenkins
[OK] Résoudre les problèmes courants
[OK] Intégrer avec d'autres outils

[RAPIDE] PROCHAINES ÉTAPES:

1. Installez Jenkins localement (Docker recommandé)
2. Testez les exemples de ce cheatsheet
3. Créez vos premiers jobs
4. Automatisez votre workflow
5. Partagez avec votre équipe!

[IDEE] N'oubliez pas: Commencez simple, testez beaucoup, automatisez progressivement

[DOCS] Ce cheatsheet contient:
   - 2000+ lignes de code
   - 50+ exemples pratiques
   - 10+ scripts complets
   - Tous les patterns utiles
   - Résolution de problèmes
   - Intégrations avancées

Bonne automatisation ! [BOT]
"""