Appliance management refactoring:

* Install an appliance based on selected version
* Each template have unique name and version
* Allow to download an appliance file
This commit is contained in:
grossmj committed 2021-10-18 18:04:30 +10:30
1 parent 8a643cf4a4
commit 04934691df
11 files changed
+326 -76

No files matched your search

+60 -3
View File
@@ -18,8 +18,25 @@
API routes for appliances.
"""
from fastapi import APIRouter
import os
import logging
from fastapi import APIRouter, Depends, Response, status
from fastapi.responses import FileResponse
from typing import Optional, List
from uuid import UUID
from gns3server import schemas
from gns3server.controller import Controller
from gns3server.controller.controller_error import ControllerNotFoundError
from gns3server.db.repositories.images import ImagesRepository
from gns3server.db.repositories.templates import TemplatesRepository
from gns3server.db.repositories.rbac import RbacRepository
from .dependencies.authentication import get_current_active_user
from .dependencies.database import get_repository
log = logging.getLogger(__name__)
router = APIRouter()
@@ -30,10 +47,50 @@ async def get_appliances(update: Optional[bool] = False, symbol_theme: Optional[
Return all appliances known by the controller.
"""
from gns3server.controller import Controller
controller = Controller.instance()
if update:
await controller.appliance_manager.download_appliances()
controller.appliance_manager.load_appliances(symbol_theme=symbol_theme)
return [c.asdict() for c in controller.appliance_manager.appliances.values()]
@router.get("/{appliance_id}/download")
def download_appliance(appliance_id: UUID) -> FileResponse:
"""
Download an appliance file.
"""
controller = Controller.instance()
appliance = controller.appliance_manager.appliances.get(str(appliance_id))
if not appliance:
raise ControllerNotFoundError(message=f"Could not find appliance '{appliance_id}'")
if not os.path.exists(appliance.path):
raise ControllerNotFoundError(message=f"Could not find appliance file '{appliance.path}'")
return FileResponse(appliance.path, media_type="application/json")
@router.post("/{appliance_id}/install", status_code=status.HTTP_204_NO_CONTENT)
async def install_appliance(
appliance_id: UUID,
version: Optional[str] = None,
images_repo: ImagesRepository = Depends(get_repository(ImagesRepository)),
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)),
current_user: schemas.User = Depends(get_current_active_user),
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
) -> Response:
"""
Install an appliance.
"""
controller = Controller.instance()
await controller.appliance_manager.install_appliance(
appliance_id,
version,
images_repo,
templates_repo,
rbac_repo,
current_user
)
return Response(status_code=status.HTTP_204_NO_CONTENT)
+14 -20
View File
@@ -24,14 +24,12 @@ import urllib.parse
from fastapi import APIRouter, Request, Response, Depends, status
from sqlalchemy.orm.exc import MultipleResultsFound
from typing import List
from typing import List, Optional
from gns3server import schemas
from pydantic import ValidationError
from gns3server.utils.images import InvalidImageError, default_images_directory, write_image
from gns3server.db.repositories.images import ImagesRepository
from gns3server.db.repositories.templates import TemplatesRepository
from gns3server.services.templates import TemplatesService
from gns3server.db.repositories.rbac import RbacRepository
from gns3server.controller import Controller
from gns3server.controller.controller_error import (
@@ -68,7 +66,8 @@ async def upload_image(
images_repo: ImagesRepository = Depends(get_repository(ImagesRepository)),
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)),
current_user: schemas.User = Depends(get_current_active_user),
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
install_appliances: Optional[bool] = True
) -> schemas.Image:
"""
Upload an image.
@@ -92,25 +91,18 @@ async def upload_image(
except (OSError, InvalidImageError) as e:
raise ControllerError(f"Could not save {image_type} image '{image_path}': {e}")
try:
# attempt to automatically create a template based on image checksum
template = await Controller.instance().appliance_manager.install_appliance_from_image(
if install_appliances:
# attempt to automatically create templates based on image checksum
await Controller.instance().appliance_manager.install_appliances_from_image(
image_path,
image.checksum,
images_repo,
templates_repo,
rbac_repo,
current_user,
directory
)
if template:
template_create = schemas.TemplateCreate(**template)
template = await TemplatesService(templates_repo).create_template(template_create)
template_id = template.get("template_id")
await rbac_repo.add_permission_to_user_with_path(current_user.user_id, f"/templates/{template_id}/*")
log.info(f"Template '{template.get('name')}' version {template.get('version')} "
f"has been created using image '{image_name}'")
except (ControllerError, ValidationError, InvalidImageError) as e:
log.warning(f"Could not automatically create template using image '{image_path}': {e}")
return image
@@ -150,8 +142,10 @@ async def delete_image(
if not image:
raise ControllerNotFoundError(f"Image '{image_path}' not found")
if await images_repo.get_image_templates(image.image_id):
raise ControllerError(f"Image '{image_path}' is used by one or more templates")
templates = await images_repo.get_image_templates(image.image_id)
if templates:
template_names = ", ".join([template.name for template in templates])
raise ControllerError(f"Image '{image_path}' is used by one or more templates: {template_names}")
try:
os.remove(image.path)