Files
innotexBoard/backend/app/api/endpoints/docker.py
innotex c51592c7ea feat: Add Docker image update system (TrueNAS Scale inspired)
- Implement UpdateService for image version checking and atomic updates
- Add DockerComposeManager for centralized docker-compose management
- Create 12 docker-compose references in /home/innotex/Docker
- Add 13 new API endpoints (6 for images, 7 for compose management)
- Add comprehensive documentation and examples
2026-01-16 19:37:23 +01:00

274 lines
8.7 KiB
Python

from fastapi import APIRouter, Depends, HTTPException, status
from typing import List
from app.core.security import get_current_user, User
from app.services.docker_service import DockerService, ContainerInfo
from app.services.update_service import UpdateService, ImageInfo, ImageUpdate
router = APIRouter()
docker_service = DockerService()
update_service = UpdateService()
@router.get("/status")
async def get_docker_status(current_user: User = Depends(get_current_user)):
"""Vérifie le statut de la connexion Docker"""
return {
"connected": docker_service.is_connected(),
"message": "Docker est accessible" if docker_service.is_connected() else "Docker n'est pas accessible"
}
@router.get("/containers", response_model=List[ContainerInfo])
async def list_containers(
all: bool = True,
current_user: User = Depends(get_current_user)
):
"""Liste tous les conteneurs Docker"""
if not docker_service.is_connected():
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Docker n'est pas accessible"
)
return docker_service.get_containers(all=all)
@router.post("/containers/{container_id}/start")
async def start_container(
container_id: str,
current_user: User = Depends(get_current_user)
):
"""Démarre un conteneur"""
if not docker_service.is_connected():
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Docker n'est pas accessible"
)
success = docker_service.start_container(container_id)
if not success:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Impossible de démarrer le conteneur"
)
return {"status": "success", "message": f"Conteneur {container_id} démarré"}
@router.post("/containers/{container_id}/stop")
async def stop_container(
container_id: str,
current_user: User = Depends(get_current_user)
):
"""Arrête un conteneur"""
if not docker_service.is_connected():
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Docker n'est pas accessible"
)
success = docker_service.stop_container(container_id)
if not success:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Impossible d'arrêter le conteneur"
)
return {"status": "success", "message": f"Conteneur {container_id} arrêté"}
@router.post("/containers/{container_id}/restart")
async def restart_container(
container_id: str,
current_user: User = Depends(get_current_user)
):
"""Redémarre un conteneur"""
if not docker_service.is_connected():
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Docker n'est pas accessible"
)
success = docker_service.restart_container(container_id)
if not success:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Impossible de redémarrer le conteneur"
)
return {"status": "success", "message": f"Conteneur {container_id} redémarré"}
@router.delete("/containers/{container_id}")
async def delete_container(
container_id: str,
force: bool = False,
current_user: User = Depends(get_current_user)
):
"""Supprime un conteneur"""
if not docker_service.is_connected():
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Docker n'est pas accessible"
)
success = docker_service.remove_container(container_id, force=force)
if not success:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Impossible de supprimer le conteneur"
)
return {"status": "success", "message": f"Conteneur {container_id} supprimé"}
# ===== ENDPOINTS DE MISE À JOUR =====
@router.get("/images", response_model=List[ImageInfo])
async def list_images(current_user: User = Depends(get_current_user)):
"""Liste toutes les images Docker locales"""
if not update_service.is_connected():
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Docker n'est pas accessible"
)
return update_service.get_all_images_info()
@router.get("/images/check-update/{image_name}")
async def check_image_update(
image_name: str,
current_user: User = Depends(get_current_user)
):
"""Vérifie si une image a une mise à jour disponible"""
if not update_service.is_connected():
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Docker n'est pas accessible"
)
update_info = update_service.check_image_updates(image_name)
if not update_info:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Impossible de vérifier les mises à jour pour {image_name}"
)
return update_info
@router.get("/images/check-all-updates")
async def check_all_updates(current_user: User = Depends(get_current_user)):
"""Vérifie les mises à jour de toutes les images en cours d'utilisation"""
if not update_service.is_connected():
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Docker n'est pas accessible"
)
try:
from app.services.docker_service import DockerService
ds = DockerService()
containers = ds.get_containers(all=False) # Conteneurs actifs uniquement
updates = []
for container in containers:
image_name = container.image
update_info = update_service.check_image_updates(image_name)
if update_info:
updates.append({
"container": container.name,
"update": update_info
})
return {
"total_containers": len(containers),
"containers_with_updates": len(updates),
"updates": updates
}
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Erreur lors de la vérification des mises à jour: {str(e)}"
)
@router.post("/images/pull")
async def pull_image(
image: str,
tag: str = "latest",
current_user: User = Depends(get_current_user)
):
"""Télécharge une image Docker"""
if not update_service.is_connected():
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Docker n'est pas accessible"
)
success = update_service.pull_image(image, tag)
if not success:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Impossible de télécharger l'image {image}:{tag}"
)
return {
"status": "success",
"message": f"Image {image}:{tag} téléchargée avec succès"
}
@router.post("/containers/{container_id}/update-image")
async def update_container_image(
container_id: str,
new_image: str,
new_tag: str = "latest",
current_user: User = Depends(get_current_user)
):
"""Met à jour l'image d'un conteneur avec la dernière version
Processus inspiré de TrueNAS Scale:
1. Arrête le conteneur
2. Télécharge la nouvelle image
3. Redémarre le conteneur
"""
if not update_service.is_connected():
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Docker n'est pas accessible"
)
result = update_service.update_container_image(container_id, new_image, new_tag)
if not result.get("success"):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=result.get("message", "Erreur lors de la mise à jour")
)
return result
@router.post("/images/prune")
async def prune_images(
dangling_only: bool = True,
current_user: User = Depends(get_current_user)
):
"""Nettoie les images inutilisées (similaire à TrueNAS Scale)"""
if not update_service.is_connected():
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Docker n'est pas accessible"
)
result = update_service.prune_unused_images(dangling_only=dangling_only)
if not result.get("success"):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=result.get("message", "Erreur lors du nettoyage")
)
return result