mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-03 05:51:15 +02:00
Merge pull request #2868 from yueguobin/feat/template-delete-usage-guard
feat: forbid deleting templates and images still used by projects
This commit is contained in:
8 files changed
+478
-14
No files matched your search
@@ -197,11 +197,16 @@ async def prune_images(
|
||||
"""
|
||||
Prune images not attached to any template.
|
||||
|
||||
Images referenced by a node in any project (opened or closed) are kept.
|
||||
|
||||
Required privilege: Image.Allocate
|
||||
"""
|
||||
|
||||
skip_images = get_builtin_disks()
|
||||
await images_repo.prune_images(skip_images)
|
||||
# a single pass over all projects' node properties protects every
|
||||
# referenced file name at once
|
||||
referenced_filenames = Controller.instance().collect_referenced_image_filenames()
|
||||
await images_repo.prune_images(list(skip_images) + list(referenced_filenames))
|
||||
|
||||
|
||||
@router.post(
|
||||
@@ -309,6 +314,10 @@ async def delete_image(
|
||||
template_names = ", ".join([template.name for template in templates])
|
||||
raise ControllerError(f"Image '{image_path}' is used by one or more templates: {template_names}")
|
||||
|
||||
project_names = Controller.instance().find_projects_using_image(image.filename)
|
||||
if project_names:
|
||||
raise ControllerError(f"Image '{image_path}' is used by one or more projects: {', '.join(project_names)}")
|
||||
|
||||
try:
|
||||
os.remove(image.path)
|
||||
except OSError:
|
||||
|
||||
@@ -35,6 +35,7 @@ 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 gns3server.controller import Controller
|
||||
from gns3server.controller.controller_error import ControllerError, ControllerBadRequestError
|
||||
from gns3server.utils.images import get_builtin_disks
|
||||
|
||||
@@ -135,28 +136,49 @@ async def delete_template(
|
||||
Required privilege: Template.Allocate
|
||||
"""
|
||||
|
||||
controller = Controller.instance()
|
||||
images = await templates_repo.get_template_images(template_id)
|
||||
await TemplatesService(templates_repo).delete_template(template_id)
|
||||
await rbac_repo.delete_all_ace_starting_with_path(f"/templates/{template_id}")
|
||||
|
||||
# Run every usage check before mutating anything, so a refused deletion
|
||||
# cannot leave the template gone while its images survive.
|
||||
images_to_prune = []
|
||||
if prune_images and images:
|
||||
skip_images = get_builtin_disks()
|
||||
# collected lazily: a single pass over all projects' node properties
|
||||
referenced_filenames = None
|
||||
for image in images:
|
||||
if image.filename in skip_images:
|
||||
continue
|
||||
templates = await images_repo.get_image_templates(image.image_id)
|
||||
if templates:
|
||||
template_names = ", ".join([template.name for template in templates])
|
||||
# the template being deleted is still in the database at this
|
||||
# point, exclude it from the other-templates check
|
||||
other_templates = [
|
||||
template for template in await images_repo.get_image_templates(image.image_id)
|
||||
if str(template.template_id) != str(template_id)
|
||||
]
|
||||
if other_templates:
|
||||
template_names = ", ".join([template.name for template in other_templates])
|
||||
raise ControllerError(f"Image '{image.path}' is used by one or more templates: {template_names}")
|
||||
|
||||
try:
|
||||
os.remove(image.path)
|
||||
except OSError:
|
||||
log.warning(f"Could not delete image file {image.path}")
|
||||
if referenced_filenames is None:
|
||||
referenced_filenames = controller.collect_referenced_image_filenames()
|
||||
if image.filename in referenced_filenames:
|
||||
project_names = controller.find_projects_using_image(image.filename)
|
||||
raise ControllerError(f"Image '{image.path}' is used by one or more projects: {', '.join(project_names)}")
|
||||
images_to_prune.append(image)
|
||||
|
||||
print(f"Deleting image '{image.path}'")
|
||||
success = await images_repo.delete_image(image.path)
|
||||
if not success:
|
||||
raise ControllerError(f"Image '{image.path}' could not removed from the database")
|
||||
await TemplatesService(templates_repo).delete_template(template_id)
|
||||
await rbac_repo.delete_all_ace_starting_with_path(f"/templates/{template_id}")
|
||||
|
||||
for image in images_to_prune:
|
||||
try:
|
||||
os.remove(image.path)
|
||||
except OSError:
|
||||
log.warning(f"Could not delete image file {image.path}")
|
||||
|
||||
log.info(f"Deleting image '{image.path}'")
|
||||
success = await images_repo.delete_image(image.path)
|
||||
if not success:
|
||||
raise ControllerError(f"Image '{image.path}' could not removed from the database")
|
||||
|
||||
|
||||
@router.get(
|
||||
|
||||
Reference in new issue
Block a user