diff --git a/gns3server/api/routes/controller/images.py b/gns3server/api/routes/controller/images.py index 031a9be2..e61444ce 100644 --- a/gns3server/api/routes/controller/images.py +++ b/gns3server/api/routes/controller/images.py @@ -69,7 +69,8 @@ async def upload_image( templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)), current_user: schemas.User = Depends(get_current_active_user), rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)), - install_appliances: Optional[bool] = False + install_appliances: Optional[bool] = False, + allow_raw_image: Optional[bool] = False ) -> schemas.Image: """ Upload an image. @@ -90,7 +91,7 @@ async def upload_image( raise ControllerBadRequestError(f"Image '{image_path}' already exists") try: - image = await write_image(image_path, full_path, request.stream(), images_repo) + image = await write_image(image_path, full_path, request.stream(), images_repo, allow_raw_image=allow_raw_image) except (OSError, InvalidImageError, ClientDisconnect) as e: raise ControllerError(f"Could not save image '{image_path}': {e}") diff --git a/gns3server/controller/appliance_manager.py b/gns3server/controller/appliance_manager.py index 17d8374a..e4bc72f1 100644 --- a/gns3server/controller/appliance_manager.py +++ b/gns3server/controller/appliance_manager.py @@ -123,7 +123,7 @@ class ApplianceManager: async with HTTPClient.get(image_url) as response: if response.status != 200: raise ControllerError(f"Could not download '{image_name}' due to HTTP error code {response.status}") - await write_image(image_name, image_path, response.content.iter_any(), images_repo) + await write_image(image_name, image_path, response.content.iter_any(), images_repo, allow_raw_image=True) except (OSError, InvalidImageError) as e: raise ControllerError(f"Could not save {image_type} image '{image_path}': {e}") except ClientError as e: @@ -162,7 +162,7 @@ class ApplianceManager: cache_to_md5file=False ) == image_checksum: async with aiofiles.open(image_path, "rb") as f: - await write_image(appliance_file, image_path, f, images_repo) + await write_image(appliance_file, image_path, f, images_repo, allow_raw_image=True) else: # download the image if there is a direct download URL direct_download_url = image.get("direct_download_url") diff --git a/gns3server/db/tasks.py b/gns3server/db/tasks.py index d879fbb3..a31210a9 100644 --- a/gns3server/db/tasks.py +++ b/gns3server/db/tasks.py @@ -91,8 +91,10 @@ async def get_computes(app: FastAPI) -> List[dict]: def image_filter(change: Change, path: str) -> bool: if change == Change.added and os.path.isfile(path): - if path.endswith(".tmp") or path.endswith(".md5sum") or path.startswith(".") or \ - "/lib/" in path or "/lib64/" in path: + if path.endswith(".tmp") or path.endswith(".md5sum") or path.startswith("."): + return False + if "/lib/" in path or "/lib64/" in path: + # ignore custom IOU libraries return False header_magic_len = 7 with open(path, "rb") as f: @@ -101,10 +103,10 @@ def image_filter(change: Change, path: str) -> bool: try: check_valid_image_header(image_header) except InvalidImageError as e: - log.debug(f"New image '{path}' added: {e}") + log.debug(f"New image '{path}': {e}") return False else: - log.debug(f"New image '{path}' added: size is too small to be valid") + log.debug(f"New image '{path}': size is too small to be valid") return False return True # FIXME: should we support image deletion? diff --git a/gns3server/utils/images.py b/gns3server/utils/images.py index ff0986e5..8e5a48a2 100644 --- a/gns3server/utils/images.py +++ b/gns3server/utils/images.py @@ -144,6 +144,9 @@ async def discover_images(image_type: str, skip_image_paths: list = None) -> Lis path = os.path.join(root, filename) if not os.path.isfile(path) or skip_image_paths and path in skip_image_paths or path in files: continue + if "/lib/" in path or "/lib64/" in path: + # ignore custom IOU libraries + continue files.add(path) try: @@ -285,7 +288,7 @@ class InvalidImageError(Exception): return self._message -def check_valid_image_header(data: bytes) -> str: +def check_valid_image_header(data: bytes, allow_raw_image: bool = False) -> str: if data[:7] == b'\x7fELF\x01\x02\x01': # for IOS images: file must start with the ELF magic number, be 32-bit, big endian and have an ELF version of 1 @@ -298,6 +301,8 @@ def check_valid_image_header(data: bytes) -> str: # for Qemy images: file must be QCOW2 or VMDK return "qemu" else: + if allow_raw_image is True: + return "qemu" raise InvalidImageError("Could not detect image type, please make sure it is a valid image") @@ -306,7 +311,8 @@ async def write_image( image_path: str, stream: AsyncGenerator[bytes, None], images_repo: ImagesRepository, - check_image_header=True + check_image_header=True, + allow_raw_image=False ) -> models.Image: image_dir, image_name = os.path.split(image_filename) @@ -322,7 +328,7 @@ async def write_image( async for chunk in stream: if check_image_header and len(chunk) >= header_magic_len: check_image_header = False - image_type = check_valid_image_header(chunk) + image_type = check_valid_image_header(chunk, allow_raw_image) await f.write(chunk) checksum.update(chunk)