Add prune images endpoint.

Use many-to-many relationship between images and templates.
This commit is contained in:
grossmj committed 2021-08-20 15:58:41 +09:30
1 parent 078c42f185
commit 4d9e4e1059
5 files changed
+90 -13

No files matched your search

+13 -1
View File
@@ -22,7 +22,7 @@ import os
import logging
import urllib.parse
from fastapi import APIRouter, Request, Depends, status
from fastapi import APIRouter, Request, Response, Depends, status
from typing import List
from gns3server import schemas
@@ -125,3 +125,15 @@ async def delete_image(
success = await images_repo.delete_image(image_name)
if not success:
raise ControllerError(f"Image '{image_name}' could not be deleted")
@router.post("/prune", status_code=status.HTTP_204_NO_CONTENT)
async def prune_images(
images_repo: ImagesRepository = Depends(get_repository(ImagesRepository)),
) -> Response:
"""
Prune images not attached to any template.
"""
await images_repo.prune_images()
return Response(status_code=status.HTTP_204_NO_CONTENT)
+11 -3
View File
@@ -15,10 +15,18 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from sqlalchemy import Column, String, Integer, BigInteger
from sqlalchemy import Table, Column, String, Integer, ForeignKey, BigInteger
from sqlalchemy.orm import relationship
from .base import BaseTable
from .base import Base, BaseTable, GUID
image_template_link = Table(
"images_templates_link",
Base.metadata,
Column("image_id", Integer, ForeignKey("images.id", ondelete="CASCADE")),
Column("template_id", GUID, ForeignKey("templates.template_id", ondelete="CASCADE"))
)
class Image(BaseTable):
@@ -32,4 +40,4 @@ class Image(BaseTable):
path = Column(String)
checksum = Column(String)
checksum_algorithm = Column(String)
templates = relationship("Template")
templates = relationship("Template", secondary=image_template_link, back_populates="images")
+3 -2
View File
@@ -17,8 +17,10 @@
from sqlalchemy import Boolean, Column, String, Integer, ForeignKey, PickleType
from sqlalchemy.orm import relationship
from .base import BaseTable, generate_uuid, GUID
from .images import image_template_link
class Template(BaseTable):
@@ -34,8 +36,7 @@ class Template(BaseTable):
compute_id = Column(String)
usage = Column(String)
template_type = Column(String)
image_id = Column(Integer, ForeignKey('images.id', ondelete="CASCADE"))
images = relationship("Image", secondary=image_template_link, back_populates="templates")
__mapper_args__ = {
"polymorphic_identity": "templates",
+51 -7
View File
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
from typing import Optional, List
from sqlalchemy import select, delete
from sqlalchemy.ext.asyncio import AsyncSession
@@ -23,6 +25,10 @@ from .base import BaseRepository
import gns3server.db.models as models
import logging
log = logging.getLogger(__name__)
class ImagesRepository(BaseRepository):
@@ -31,33 +37,48 @@ class ImagesRepository(BaseRepository):
super().__init__(db_session)
async def get_image(self, image_name: str) -> Optional[models.Image]:
"""
Get an image by its name (filename).
"""
query = select(models.Image).where(models.Image.filename == image_name)
result = await self._db_session.execute(query)
return result.scalars().first()
async def get_image_by_checksum(self, checksum: str) -> Optional[models.Image]:
"""
Get an image by its checksum.
"""
query = select(models.Image).where(models.Image.checksum == checksum)
result = await self._db_session.execute(query)
return result.scalars().first()
async def get_images(self) -> List[models.Image]:
"""
Get all images.
"""
query = select(models.Image)
result = await self._db_session.execute(query)
return result.scalars().all()
async def get_image_templates(self, image_id: int) -> Optional[List[models.Template]]:
"""
Get all templates that an image belongs to.
"""
query = select(models.Template).\
join(models.Image.templates). \
join(models.Template.images).\
filter(models.Image.id == image_id)
result = await self._db_session.execute(query)
return result.scalars().all()
async def get_image_by_checksum(self, checksum: str) -> Optional[models.Image]:
query = select(models.Image).where(models.Image.checksum == checksum)
result = await self._db_session.execute(query)
return result.scalars().first()
async def add_image(self, image_name, image_type, image_size, path, checksum, checksum_algorithm) -> models.Image:
"""
Create a new image.
"""
db_image = models.Image(
id=None,
@@ -75,8 +96,31 @@ class ImagesRepository(BaseRepository):
return db_image
async def delete_image(self, image_name: str) -> bool:
"""
Delete an image.
"""
query = delete(models.Image).where(models.Image.filename == image_name)
result = await self._db_session.execute(query)
await self._db_session.commit()
return result.rowcount > 0
async def prune_images(self) -> int:
"""
Prune images not attached to any template.
"""
query = select(models.Image).\
filter(~models.Image.templates.any())
result = await self._db_session.execute(query)
images = result.scalars().all()
images_deleted = 0
for image in images:
try:
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")
return images_deleted