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
This commit is contained in:
@@ -2,9 +2,11 @@ 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")
|
||||
@@ -117,3 +119,155 @@ async def delete_container(
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user