From 332fa47b5072a7890ab3139474cd60f3490ba54d Mon Sep 17 00:00:00 2001 From: grossmj Date: Mon, 23 Aug 2021 10:27:10 +0930 Subject: [PATCH] Option to prune images when deleting template. --- gns3server/api/routes/controller/templates.py | 9 +++-- gns3server/db/repositories/images.py | 3 +- gns3server/services/templates.py | 2 +- tests/api/routes/controller/test_templates.py | 34 +++++++++++++++++++ 4 files changed, 44 insertions(+), 4 deletions(-) diff --git a/gns3server/api/routes/controller/templates.py b/gns3server/api/routes/controller/templates.py index a4eb9aef..c5982212 100644 --- a/gns3server/api/routes/controller/templates.py +++ b/gns3server/api/routes/controller/templates.py @@ -25,14 +25,15 @@ import logging log = logging.getLogger(__name__) -from fastapi import APIRouter, Request, Response, HTTPException, Depends, Response, status -from typing import List +from fastapi import APIRouter, Request, HTTPException, Depends, Response, status +from typing import List, Optional from uuid import UUID from gns3server import schemas from gns3server.db.repositories.templates import TemplatesRepository from gns3server.services.templates import TemplatesService from gns3server.db.repositories.rbac import RbacRepository +from gns3server.db.repositories.images import ImagesRepository from .dependencies.authentication import get_current_active_user from .dependencies.database import get_repository @@ -97,7 +98,9 @@ async def update_template( @router.delete("/{template_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_template( template_id: UUID, + prune_images: Optional[bool] = False, templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)), + images_repo: RbacRepository = Depends(get_repository(ImagesRepository)), rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)) ) -> Response: """ @@ -106,6 +109,8 @@ async def delete_template( await TemplatesService(templates_repo).delete_template(template_id) await rbac_repo.delete_all_permissions_with_path(f"/templates/{template_id}") + if prune_images: + await images_repo.prune_images() return Response(status_code=status.HTTP_204_NO_CONTENT) diff --git a/gns3server/db/repositories/images.py b/gns3server/db/repositories/images.py index 02b77a5c..365d558d 100644 --- a/gns3server/db/repositories/images.py +++ b/gns3server/db/repositories/images.py @@ -117,10 +117,11 @@ class ImagesRepository(BaseRepository): images_deleted = 0 for image in images: try: + log.debug(f"Deleting image '{image.path}'") os.remove(image.path) except OSError: log.warning(f"Could not delete image file {image.path}") if await self.delete_image(image.filename): images_deleted += 1 - log.info(f"{images_deleted} image have been deleted") + log.info(f"{images_deleted} image(s) have been deleted") return images_deleted diff --git a/gns3server/services/templates.py b/gns3server/services/templates.py index 6683b39e..c796470b 100644 --- a/gns3server/services/templates.py +++ b/gns3server/services/templates.py @@ -159,7 +159,7 @@ class TemplatesService: image = await self._templates_repo.get_image(image_name) if not image or not os.path.exists(image.path): - raise ControllerNotFoundError(f"Image {image_name} could not be found") + raise ControllerNotFoundError(f"Image '{image_name}' could not be found") return image async def _find_images(self, template_type: str, settings: dict) -> List[models.Image]: diff --git a/tests/api/routes/controller/test_templates.py b/tests/api/routes/controller/test_templates.py index 5e174ebc..e9b59ae0 100644 --- a/tests/api/routes/controller/test_templates.py +++ b/tests/api/routes/controller/test_templates.py @@ -116,6 +116,40 @@ class TestTemplateRoutes: response = await client.delete(app.url_path_for("delete_template", template_id=template_id)) assert response.status_code == status.HTTP_204_NO_CONTENT + async def test_template_delete_with_prune_images( + self, + app: FastAPI, + client: AsyncClient, + db_session: AsyncSession, + tmpdir: str, + ) -> None: + + path = os.path.join(tmpdir, "test.qcow2") + with open(path, "wb+") as f: + f.write(b'\x42\x42\x42\x42') + images_repo = ImagesRepository(db_session) + await images_repo.add_image("test.qcow2", "qemu", 42, path, "e342eb86c1229b6c154367a5476969b5", "md5") + + template_id = str(uuid.uuid4()) + params = {"template_id": template_id, + "name": "QEMU_TEMPLATE", + "compute_id": "local", + "hda_disk_image": "test.qcow2", + "template_type": "qemu"} + + response = await client.post(app.url_path_for("create_template"), json=params) + assert response.status_code == status.HTTP_201_CREATED + + response = await client.delete( + app.url_path_for("delete_template", template_id=template_id), + params={"prune_images": True} + ) + assert response.status_code == status.HTTP_204_NO_CONTENT + + images_repo = ImagesRepository(db_session) + images = await images_repo.get_images() + assert len(images) == 0 + # async def test_create_node_from_template(self, controller_api, controller, project): # # id = str(uuid.uuid4())