146 lines
5.0 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."""
playbook_path = ansible_service.playbooks_dir / filename
if not filename.endswith(('.yml', '.yaml')):
raise HTTPException(status_code=400, detail="Extension de fichier invalide. Utilisez .yml ou .yaml")
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)}")