""" Routes API pour la gestion des playbooks. """ import re from datetime import datetime, timezone import yaml from fastapi import APIRouter, Depends, HTTPException from app.core.dependencies import verify_api_key from app.schemas.ansible import PlaybookContentRequest from app.schemas.common import LogEntry from app.services import ansible_service, db router = APIRouter() @router.get("/{filename}/content") async def get_playbook_content( filename: str, api_key_valid: bool = Depends(verify_api_key) ): """Récupère le contenu d'un playbook (normal ou builtin).""" if not filename.endswith(('.yml', '.yaml')): raise HTTPException(status_code=400, detail="Extension de fichier invalide. Utilisez .yml ou .yaml") # Vérifier si c'est un builtin playbook (commence par _builtin_) if filename.startswith("_builtin_"): playbook_path = ansible_service.playbooks_dir / "builtin" / filename else: playbook_path = ansible_service.playbooks_dir / filename if not playbook_path.exists(): raise HTTPException(status_code=404, detail=f"Playbook non trouvé: {filename}") # Vérifier que le fichier est bien dans le répertoire playbooks (sécurité) try: playbook_path.resolve().relative_to(ansible_service.playbooks_dir.resolve()) except ValueError: raise HTTPException(status_code=403, detail="Accès non autorisé") try: content = playbook_path.read_text(encoding='utf-8') stat = playbook_path.stat() return { "filename": filename, "content": content, "size": stat.st_size, "modified": datetime.fromtimestamp(stat.st_mtime, tz=timezone.utc).isoformat() } except Exception as e: raise HTTPException(status_code=500, detail=f"Erreur lecture fichier: {str(e)}") @router.put("/{filename}/content") async def save_playbook_content( filename: str, request: PlaybookContentRequest, api_key_valid: bool = Depends(verify_api_key) ): """Sauvegarde le contenu d'un playbook.""" if not filename.endswith(('.yml', '.yaml')): raise HTTPException(status_code=400, detail="Extension de fichier invalide. Utilisez .yml ou .yaml") # Valider le nom de fichier (sécurité) if not re.match(r'^[a-zA-Z0-9_-]+\.(yml|yaml)$', filename): raise HTTPException(status_code=400, detail="Nom de fichier invalide") playbook_path = ansible_service.playbooks_dir / filename # S'assurer que le répertoire existe ansible_service.playbooks_dir.mkdir(parents=True, exist_ok=True) # Valider le contenu YAML try: parsed = yaml.safe_load(request.content) if parsed is None: raise HTTPException(status_code=400, detail="Contenu YAML vide ou invalide") except yaml.YAMLError as e: raise HTTPException(status_code=400, detail=f"Erreur de syntaxe YAML: {str(e)}") is_new = not playbook_path.exists() try: playbook_path.write_text(request.content, encoding='utf-8') stat = playbook_path.stat() # Log l'action action = "créé" if is_new else "modifié" log_entry = LogEntry( id=db.get_next_id("logs"), timestamp=datetime.now(timezone.utc), level="INFO", message=f"Playbook {filename} {action}", source="playbook_editor" ) db.logs.insert(0, log_entry) return { "success": True, "message": f"Playbook {filename} {'créé' if is_new else 'sauvegardé'} avec succès", "filename": filename, "size": stat.st_size, "modified": datetime.fromtimestamp(stat.st_mtime, tz=timezone.utc).isoformat(), "is_new": is_new } except Exception as e: raise HTTPException(status_code=500, detail=f"Erreur sauvegarde fichier: {str(e)}") @router.delete("/{filename}") async def delete_playbook( filename: str, api_key_valid: bool = Depends(verify_api_key) ): """Supprime un playbook.""" if not filename.endswith(('.yml', '.yaml')): raise HTTPException(status_code=400, detail="Extension de fichier invalide") playbook_path = ansible_service.playbooks_dir / filename if not playbook_path.exists(): raise HTTPException(status_code=404, detail=f"Playbook non trouvé: {filename}") # Vérifier que le fichier est bien dans le répertoire playbooks (sécurité) try: playbook_path.resolve().relative_to(ansible_service.playbooks_dir.resolve()) except ValueError: raise HTTPException(status_code=403, detail="Accès non autorisé") try: playbook_path.unlink() log_entry = LogEntry( id=db.get_next_id("logs"), timestamp=datetime.now(timezone.utc), level="WARN", message=f"Playbook {filename} supprimé", source="playbook_editor" ) db.logs.insert(0, log_entry) return { "success": True, "message": f"Playbook {filename} supprimé avec succès" } except Exception as e: raise HTTPException(status_code=500, detail=f"Erreur suppression fichier: {str(e)}")