Bruno Charest 493668f746
Some checks failed
Tests / Backend Tests (Python) (3.10) (push) Has been cancelled
Tests / Backend Tests (Python) (3.11) (push) Has been cancelled
Tests / Backend Tests (Python) (3.12) (push) Has been cancelled
Tests / Frontend Tests (JS) (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / All Tests Passed (push) Has been cancelled
Add comprehensive SSH terminal drawer feature with embedded and popout modes, integrate playbook lint results API with local cache fallback, and enhance host management UI with terminal access buttons
2025-12-17 23:59:17 -05:00

150 lines
5.2 KiB
Python

"""
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)}")