From 0d7c4918910dc20ab7de92392c94970e85fd9370 Mon Sep 17 00:00:00 2001 From: Antonio Date: Thu, 24 Oct 2024 19:32:57 +0200 Subject: [PATCH] Adding podman support --- exegol/model/ContainerConfig.py | 9 +- exegol/model/ExegolContainer.py | 37 ++++---- exegol/model/ExegolImage.py | 33 ++++--- exegol/model/MetaImages.py | 9 +- exegol/utils/ContainerLogStream.py | 6 +- exegol/utils/DockerUtils.py | 133 +++++++++++++++++++---------- requirements.txt | 1 + setup.py | 1 + 8 files changed, 147 insertions(+), 82 deletions(-) diff --git a/exegol/model/ContainerConfig.py b/exegol/model/ContainerConfig.py index 03bdd87a..3934e7c4 100644 --- a/exegol/model/ContainerConfig.py +++ b/exegol/model/ContainerConfig.py @@ -10,8 +10,11 @@ from pathlib import Path, PurePath from typing import Optional, List, Dict, Union, Tuple, cast -from docker.models.containers import Container +from docker.models.containers import Container as DockerContainer from docker.types import Mount + +from podman.domain.containers import Container as PodmanContainer + from rich.prompt import Prompt from exegol.config.ConstantConfig import ConstantConfig @@ -81,7 +84,7 @@ class ExegolEnv(Enum): ExegolMetadata.comment.value: ["setComment", "getComment"], ExegolMetadata.password.value: ["setPasswd", "getPasswd"]} - def __init__(self, container: Optional[Container] = None): + def __init__(self, container: Optional[Union[DockerContainer, PodmanContainer]] = None): """Container config default value""" self.hostname = "" self.__enable_gui: bool = False @@ -132,7 +135,7 @@ def __init__(self, container: Optional[Container] = None): # ===== Config parsing section ===== - def __parseContainerConfig(self, container: Container): + def __parseContainerConfig(self, container: Union[DockerContainer, PodmanContainer]): """Parse Docker object to setup self configuration""" # Reset default attributes self.__passwd = None diff --git a/exegol/model/ExegolContainer.py b/exegol/model/ExegolContainer.py index e5d699d5..6c50158b 100644 --- a/exegol/model/ExegolContainer.py +++ b/exegol/model/ExegolContainer.py @@ -6,7 +6,10 @@ from typing import Optional, Dict, Sequence, Tuple, Union from docker.errors import NotFound, ImageNotFound, APIError -from docker.models.containers import Container +from docker.models.containers import Container as DockerContainer + +from podman.errors import NotFound as PodmanNotFound, ImageNotFound as PodmanImageNotFound, APIError as PodmanAPIError +from podman.domain.containers import Container as PodmanContainer from exegol.config.EnvInfo import EnvInfo from exegol.console.ExegolPrompt import Confirm @@ -24,36 +27,36 @@ class ExegolContainer(ExegolContainerTemplate, SelectableInterface): """Class of an exegol container already create in docker""" - def __init__(self, docker_container: Container, model: Optional[ExegolContainerTemplate] = None): - logger.debug(f"Loading container: {docker_container.name}") - self.__container: Container = docker_container - self.__id: str = docker_container.id + def __init__(self, container_obj: Union[DockerContainer, PodmanContainer], model: Optional[ExegolContainerTemplate] = None): + logger.debug(f"Loading container: {container_obj.name}") + self.__container = container_obj + self.__id: str = container_obj.id self.__xhost_applied = False if model is None: image_name = "" try: # Try to find the attached docker image - docker_image = docker_container.image - except ImageNotFound: + docker_image = container_obj.image + except (ImageNotFound, PodmanImageNotFound): # If it is not found, the user has probably forcibly deleted it manually logger.warning(f"Some images were forcibly removed by docker when they were used by existing containers!") - logger.error(f"The '{docker_container.name}' containers might not work properly anymore and should also be deleted and recreated with a new image.") + logger.error(f"The '{container_obj.name}' containers might not work properly anymore and should also be deleted and recreated with a new image.") docker_image = None image_name = "[red bold]BROKEN[/red bold]" # Create Exegol container from an existing docker container - super().__init__(docker_container.name, - config=ContainerConfig(docker_container), + super().__init__(container_obj.name, + config=ContainerConfig(container_obj), image=ExegolImage(name=image_name, docker_image=docker_image), - hostname=docker_container.attrs.get('Config', {}).get('Hostname'), + hostname=container_obj.attrs.get('Config', {}).get('Hostname'), new_container=False) - self.image.syncContainerData(docker_container) + self.image.syncContainerData(container_obj) # At this stage, the container image object has an unknown status because no synchronization with a registry has been done. # This could be done afterwards (with container.image.autoLoad()) if necessary because it takes time. self.__new_container = False else: # Create Exegol container from a newly created docker container with its object template. - super().__init__(docker_container.name, - config=ContainerConfig(docker_container), + super().__init__(container_obj.name, + config=ContainerConfig(container_obj), # Rebuild config from docker object to update workspace path image=model.image, hostname=model.config.hostname, @@ -121,7 +124,7 @@ def __start_container(self): start_date = datetime.now() try: self.__container.start() - except APIError as e: + except (APIError, PodmanAPIError) as e: logger.debug(e) logger.critical(f"Docker raise a critical error when starting the container [green]{self.name}[/green], error message is: {e.explanation}") if not self.config.legacy_entrypoint: # TODO improve startup compatibility check @@ -230,7 +233,7 @@ def remove(self): try: self.__container.remove() logger.success(f"Container {self.name} successfully removed.") - except NotFound: + except (NotFound, PodmanNotFound): logger.error( f"The container {self.name} has already been removed (probably created as a temporary container).") @@ -319,7 +322,7 @@ def postCreateSetup(self, is_temporary: bool = False): self.__start_container() try: self.__updatePasswd() - except APIError as e: + except (APIError, PodmanAPIError) as e: if "is not running" in e.explanation: logger.critical("An unexpected error occurred. Exegol cannot start the container after its creation...") diff --git a/exegol/model/ExegolImage.py b/exegol/model/ExegolImage.py index 596b88d3..caff3cb0 100644 --- a/exegol/model/ExegolImage.py +++ b/exegol/model/ExegolImage.py @@ -1,8 +1,12 @@ from datetime import datetime from typing import Optional, List, Dict, Any, Union -from docker.models.containers import Container -from docker.models.images import Image +from docker.models.containers import Container as DockerContainer +from docker.models.images import Image as DockerImage + +from podman.domain.containers import Container as PodmanContainer +from podman.domain.images import Image as PodmanImage + from rich.status import Status from exegol.config.DataCache import DataCache @@ -24,7 +28,7 @@ def __init__(self, dockerhub_data: Optional[Dict[str, Any]] = None, meta_img: Optional[MetaImages] = None, image_id: Optional[str] = None, - docker_image: Optional[Image] = None, + docker_image: Optional[Union[DockerImage, PodmanImage]] = None, isUpToDate: bool = False): """Docker image default value""" # Prepare parameters @@ -36,7 +40,7 @@ def __init__(self, version_parsed = MetaImages.tagNameParsing(name) self.__version_specific = bool(version_parsed) # Init attributes - self.__image: Optional[Image] = docker_image + self.__image: Optional[Union[DockerImage, PodmanImage]] = docker_image self.__name: str = name self.__alt_name: str = '' self.__arch = "" @@ -150,7 +154,7 @@ def resetDockerImage(self): self.__build_date = "[bright_black]N/A[/bright_black]" self.__disk_size = "[bright_black]N/A[/bright_black]" - def setDockerObject(self, docker_image: Image): + def setDockerObject(self, docker_image: Union[DockerImage, PodmanImage]): """Docker object setter. Parse object to set up self configuration.""" self.__image = docker_image # When a docker image exist, image is locally installed @@ -230,7 +234,7 @@ def __labelVersionParsing(self): self.__profile_version = self.__image_version @classmethod - def parseAliasTagName(cls, image: Image) -> str: + def parseAliasTagName(cls, image: Union[DockerImage, PodmanImage]) -> str: """Create a tag name alias from labels when image's tag is lost""" return image.labels.get("org.exegol.tag", "") + "-" + image.labels.get("org.exegol.version", "v?") @@ -247,7 +251,7 @@ def syncStatus(self): else: self.__custom_status = "" - def syncContainerData(self, container: Container): + def syncContainerData(self, container: Union[DockerContainer, PodmanContainer]): """Synchronization between the container and the image. If the image has been updated, the tag is lost, but it is saved in the properties of the container that still uses it.""" @@ -352,7 +356,7 @@ def __mergeMetaImages(cls, images: List[MetaImages]): pass @classmethod - def mergeImages(cls, remote_images: List[MetaImages], local_images: List[Image], status: Status) -> List['ExegolImage']: + def mergeImages(cls, remote_images: List[MetaImages], local_images: List[Union[DockerImage, PodmanImage]]) -> List['ExegolImage']: """Compare and merge local images and remote images. Use case to process : - up-to-date : "Version specific" image can use exact digest_id matching. Latest image must match corresponding tag @@ -537,11 +541,11 @@ def __setDigest(self, digest: Optional[str]): self.__digest = digest @staticmethod - def __parseDigest(docker_image: Image) -> str: + def __parseDigest(docker_image: Union[DockerImage, PodmanImage]) -> str: """Parse the remote image digest ID. Return digest id from the docker object.""" for digest_id in docker_image.attrs["RepoDigests"]: - if digest_id.startswith(ConstantConfig.IMAGE_NAME): # Find digest id from the right repository + if ConstantConfig.IMAGE_NAME in digest_id: # Find digest id from the right repository return digest_id.split('@')[1] return "" @@ -558,9 +562,14 @@ def getLatestRemoteId(self) -> str: return self.__profile_digest def __setImageId(self, image_id: Optional[str]): - """Local image id setter""" + """Local image id setter for both Docker and Podman""" if image_id is not None: - self.__image_id = image_id.split(":")[1][:12] + # Check if the image_id contains a colon (as in Docker's format) + if ":" in image_id: + self.__image_id = image_id.split(":")[1][:12] + else: + # For Podman, where image_id does not contain the 'sha256:' prefix + self.__image_id = image_id[:12] def getLocalId(self) -> str: """Local id getter""" diff --git a/exegol/model/MetaImages.py b/exegol/model/MetaImages.py index 779fac5d..fc73ce74 100644 --- a/exegol/model/MetaImages.py +++ b/exegol/model/MetaImages.py @@ -1,6 +1,7 @@ from typing import Optional, Set, Union -from docker.models.images import Image +from docker.models.images import Image as DockerImage +from podman.domain.images import Image as PodmanImage from exegol.utils.ExeLog import logger from exegol.utils.WebUtils import WebUtils @@ -58,13 +59,13 @@ def tagNameParsing(tag_name: str) -> str: return version @staticmethod - def parseArch(docker_image: Union[dict, Image]) -> str: + def parseArch(docker_image: Union[dict, DockerImage, PodmanImage]) -> str: """Parse and format arch in dockerhub style from registry dict struct. Return arch in format 'arch/variant'.""" arch_key = "architecture" variant_key = "variant" - # Support Docker image struct with specific dict key - if type(docker_image) is Image: + # Support Docker and Podman image struct with specific dict key + if isinstance(docker_image, (DockerImage, PodmanImage)): docker_image = docker_image.attrs arch_key = "Architecture" variant_key = "Variant" diff --git a/exegol/utils/ContainerLogStream.py b/exegol/utils/ContainerLogStream.py index f5dc6b30..33de2af8 100644 --- a/exegol/utils/ContainerLogStream.py +++ b/exegol/utils/ContainerLogStream.py @@ -2,14 +2,16 @@ from datetime import datetime, timedelta from typing import Optional -from docker.models.containers import Container +from docker.models.containers import Container as DockerContainer + +from podman.domain.containers import Container as PodmanContainer from exegol.utils.ExeLog import logger class ContainerLogStream: - def __init__(self, container: Container, start_date: Optional[datetime] = None, timeout: int = 5): + def __init__(self, container: Union[DockerContainer, PodmanContainer], start_date: Optional[datetime] = None, timeout: int = 5): # Container to extract logs from self.__container = container # Fetch more logs from this datetime diff --git a/exegol/utils/DockerUtils.py b/exegol/utils/DockerUtils.py index 11000c56..3b93416b 100644 --- a/exegol/utils/DockerUtils.py +++ b/exegol/utils/DockerUtils.py @@ -7,8 +7,15 @@ import requests.exceptions from docker import DockerClient from docker.errors import APIError, DockerException, NotFound, ImageNotFound -from docker.models.images import Image -from docker.models.volumes import Volume +from docker.models.images import Image as DockerImage +from docker.models.volumes import Volume as DockerVolume + +import podman +from podman import PodmanClient +from podman.errors import APIError as PodmanAPIError, DockerException as PodmanException, NotFound as PodmanNotFound, ImageNotFound as PodmanImageNotFound +from podman.domain.images import Image as PodmanImage +from podman.domain.volumes import Volume as PodmanVolume + from requests import ReadTimeout from rich.status import Status @@ -34,37 +41,75 @@ class DockerUtils(metaclass=MetaSingleton): def __init__(self): - """Utility class between exegol and the Docker SDK""" + """Utility class to manage interactions between exegol and Docker or Podman.""" + self.__client = None + self.__daemon_info = None + self.container_runtime = None # Will be set to either 'docker' or 'podman' + + # List of exceptions that could be raised by both Docker and Podman + connection_exceptions = (DockerException, PodmanException) + try: - # Connect Docker SDK to the local docker instance. - # Docker connection setting is loaded from the user environment variables. - self.__client: DockerClient = docker.from_env() + # Attempt to connect to Docker + self.__client = self.__connect_to_docker() + + if not self.__client: + # If Docker fails, attempt to connect to Podman + self.__client = self.__connect_to_podman() + + if not self.__client: + raise RuntimeError("Failed to connect to both Docker and Podman.") + # Check if the docker daemon is serving linux container self.__daemon_info = self.__client.info() if self.__daemon_info.get("OSType", "linux").lower() != "linux": logger.critical( f"Docker daemon is not serving linux container ! Docker OS Type is: {self.__daemon_info.get('OSType', 'linux')}") EnvInfo.initData(self.__daemon_info) - except DockerException as err: - if 'ConnectionRefusedError' in str(err): - logger.critical(f"Unable to connect to docker (from env config). Is docker running on your machine? Exiting.{os.linesep}" - f" Check documentation for help: https://exegol.readthedocs.io/en/latest/getting-started/faq.html#unable-to-connect-to-docker") - elif 'FileNotFoundError' in str(err): - logger.critical(f"Unable to connect to docker. Is docker installed on your machine? Exiting.{os.linesep}" - f" Check documentation for help: https://exegol.readthedocs.io/en/latest/getting-started/faq.html#unable-to-connect-to-docker") - elif 'PermissionError' in str(err): - logger.critical(f"Docker is installed on your host but you don't have the permission to interact with it. Exiting.{os.linesep}" - f" Check documentation for help: https://exegol.readthedocs.io/en/latest/getting-started/install.html#optional-run-exegol-with-appropriate-privileges") - else: - logger.error(err) - logger.critical( - "Unable to connect to docker (from env config). Is docker operational and accessible? on your machine? " - "Exiting.") - except (ReadTimeout, requests.exceptions.ConnectionError): - logger.critical("Docker daemon seems busy, Exegol receives timeout response. Try again later.") + except connection_exceptions as err: + self.__handle_connection_error(err) + except Exception as err: + logger.error(f"Unexpected error: {err}") + self.__images: Optional[List[ExegolImage]] = None self.__containers: Optional[List[ExegolContainer]] = None + def __connect_to_docker(self): + """Attempts to connect to Docker.""" + try: + client = docker.from_env() + self.container_runtime = "docker" + logger.info("Connected to Docker.") + return client + except DockerException as err: + logger.warning(f"Unable to connect to Docker: {err}") + return None + + def __connect_to_podman(self): + """Attempts to connect to Podman.""" + try: + client = podman.from_env() + self.container_runtime = "podman" + logger.info("Connected to Podman.") + return client + except PodmanException as err: + logger.warning(f"Unable to connect to Podman: {err}") + return None + + def __handle_connection_error(self, err): + """Handles connection errors for both Docker and Podman.""" + if 'ConnectionRefusedError' in str(err): + logger.critical(f"Unable to connect to {self.container_runtime}. Is it running on your machine? Exiting.{os.linesep}" + f" Check documentation for help: https://exegol.readthedocs.io/en/latest/getting-started/faq.html#unable-to-connect-to-docker") + elif 'FileNotFoundError' in str(err): + logger.critical(f"Unable to connect to {self.container_runtime}. Is it installed on your machine? Exiting.{os.linesep}" + f" Check documentation for help: https://exegol.readthedocs.io/en/latest/getting-started/faq.html#unable-to-connect-to-docker") + elif 'PermissionError' in str(err): + logger.critical(f"{self.container_runtime.capitalize()} is installed on your host but you don't have permission to interact with it. Exiting.{os.linesep}" + f" Check documentation for help: https://exegol.readthedocs.io/en/latest/getting-started/install.html#optional-run-exegol-with-appropriate-privileges") + else: + logger.critical(f"Unable to connect to {self.container_runtime}. Is it operational and accessible? Exiting.") + def clearCache(self): """Remove class's images and containers data cache Only needed if the list has to be updated in the same runtime at a later moment""" @@ -84,7 +129,7 @@ def listContainers(self) -> List[ExegolContainer]: self.__containers = [] try: docker_containers = self.__client.containers.list(all=True, filters={"name": "exegol-"}) - except APIError as err: + except (APIError, PodmanAPIError) as err: logger.debug(err) logger.critical(err.explanation) # Not reachable, critical logging will exit @@ -141,7 +186,7 @@ def createContainer(self, model: ExegolContainerTemplate, temporary: bool = Fals docker_args["auto_remove"] = temporary try: container = docker_create_function(**docker_args) - except APIError as err: + except (APIError, PodmanAPIError) as err: message = err.explanation.decode('utf-8').replace('[', '\\[') if type(err.explanation) is bytes else err.explanation if message is not None: message = message.replace('[', '\\[') @@ -173,7 +218,7 @@ def getContainer(self, tag: str) -> ExegolContainer: try: # Fetch potential container match from DockerSDK container = self.__client.containers.list(all=True, filters={"name": f"exegol-{tag}"}) - except APIError as err: + except (APIError, PodmanAPIError) as err: logger.debug(err) logger.critical(err.explanation) # Not reachable, critical logging will exit @@ -200,7 +245,7 @@ def getContainer(self, tag: str) -> ExegolContainer: # # # Volumes Section # # # - def __loadDockerVolume(self, volume_path: str, volume_name: str) -> Volume: + def __loadDockerVolume(self, volume_path: str, volume_name: str) -> Union[DockerVolume, PodmanVolume]: """Load or create a docker volume for exegol containers (must be created before the container, SDK limitation) Return the docker volume object""" @@ -217,7 +262,7 @@ def __loadDockerVolume(self, volume_path: str, volume_name: str) -> Volume: try: self.__client.api.remove_volume(name=volume_name) raise NotFound('Volume must be reloaded') - except APIError as e: + except (APIError, PodmanAPIError) as e: if e.status_code == 409: logger.warning("The path of the volume specified by the user is not the same as in the existing docker volume. " "The user path will be [red]ignored[/red] as long as the docker volume already exists.") @@ -228,7 +273,7 @@ def __loadDockerVolume(self, volume_path: str, volume_name: str) -> Volume: except ReadTimeout: logger.error(f"Received a timeout error, Docker is busy... Volume {volume_name} cannot be automatically removed. Please, retry later the following command:{os.linesep}" f" [orange3]docker volume rm {volume_name}[/orange3]") - except NotFound: + except (NotFound, PodmanNotFound): try: # Creating a docker volume bind to a host path # Docker volume are more easily shared by container @@ -237,7 +282,7 @@ def __loadDockerVolume(self, volume_path: str, volume_name: str) -> Volume: driver_opts={'o': 'bind', 'device': volume_path, 'type': 'none'}) - except APIError as err: + except (APIError, PodmanAPIError) as err: logger.error(f"Error while creating docker volume '{volume_name}'.") logger.debug(err) logger.critical(err.explanation) @@ -245,7 +290,7 @@ def __loadDockerVolume(self, volume_path: str, volume_name: str) -> Volume: except ReadTimeout: logger.critical(f"Received a timeout error, Docker is busy... Volume {volume_name} cannot be created.") return # type: ignore - except APIError as err: + except (APIError, PodmanAPIError) as err: logger.critical(f"Unexpected error by Docker SDK : {err}") return None # type: ignore except ReadTimeout: @@ -313,7 +358,7 @@ def getInstalledImage(self, tag: str) -> ExegolImage: try: docker_local_image = self.__client.images.get(f"{ConstantConfig.IMAGE_NAME}:{tag}") # DockerSDK image get is an exact matching, no need to add more check - except APIError as err: + except (APIError, PodmanAPIError) as err: if err.status_code == 404: # try to find it in recovery mode logger.verbose("Unable to find your image. Trying to find in recovery mode.") @@ -345,14 +390,14 @@ def getInstalledImage(self, tag: str) -> ExegolImage: logger.critical(f"The desired image is not installed or do not exist ({ConstantConfig.IMAGE_NAME}:{tag}). Exiting.") return # type: ignore - def __listLocalImages(self, tag: Optional[str] = None) -> List[Image]: + def __listLocalImages(self, tag: Optional[str] = None) -> List[Union[DockerImage, PodmanImage]]: """List local docker images already installed. Return a list of docker images objects""" logger.debug("Fetching local image tags, digests (and other attributes)") try: image_name = ConstantConfig.IMAGE_NAME + ("" if tag is None else f":{tag}") - images = self.__client.images.list(image_name, filters={"dangling": False}) - except APIError as err: + images = self.__client.images.list(name=image_name, filters={"dangling": False}) + except (APIError, PodmanAPIError) as err: logger.debug(err) logger.critical(err.explanation) # Not reachable, critical logging will exit @@ -366,7 +411,7 @@ def __listLocalImages(self, tag: Optional[str] = None) -> List[Image]: for img in images: # len tags = 0 handle exegol images (nightly image lost their tag after update) if len(img.attrs.get('RepoTags', [])) == 0 or \ - ConstantConfig.IMAGE_NAME in [repo_tag.split(':')[0] for repo_tag in img.attrs.get("RepoTags", [])]: + any(ConstantConfig.IMAGE_NAME in repo_tag.split(':')[0] for repo_tag in img.attrs.get("RepoTags", [])): result.append(img) ids.add(img.id) @@ -383,7 +428,7 @@ def __listLocalImages(self, tag: Optional[str] = None) -> List[Image]: ids.add(img.id) return result - def __findLocalRecoveryImages(self, include_untag: bool = False) -> List[Image]: + def __findLocalRecoveryImages(self, include_untag: bool = False) -> List[Union[DockerImage, PodmanImage]]: """This method try to recovery untagged docker images. Set include_untag option to recover images with a valid RepoDigest (no not dangling) but without tag.""" try: @@ -391,7 +436,7 @@ def __findLocalRecoveryImages(self, include_untag: bool = False) -> List[Image]: recovery_images = self.__client.images.list(filters={"dangling": True}) if include_untag: recovery_images += self.__client.images.list(ConstantConfig.IMAGE_NAME, filters={"dangling": False}) - except APIError as err: + except (APIError, PodmanAPIError) as err: logger.debug(f"Error occurred in recovery mode: {err}") return [] except ReadTimeout: @@ -454,7 +499,7 @@ def __findImageMatch(self, remote_image: ExegolImage): remote_id = remote_image.getRemoteId() try: docker_image = self.__client.images.get(f"{ConstantConfig.IMAGE_NAME}@{remote_id}") - except ImageNotFound: + except (ImageNotFound, PodmanImageNotFound): raise ObjectNotFound except ReadTimeout: logger.critical("Received a timeout error, Docker is busy... Unable to find a specific image, retry later.") @@ -487,7 +532,7 @@ def downloadImage(self, image: ExegolImage, install_mode: bool = False) -> bool: if not install_mode and image.isInstall() and UserConfig().auto_remove_images: self.removeImage(image, upgrade_mode=not install_mode) return True - except APIError as err: + except (APIError, PodmanAPIError) as err: if err.status_code == 500: logger.error(f"Error: {err.explanation}") logger.error(f"Error while contacting docker registry. Aborting.") @@ -510,7 +555,7 @@ def downloadVersionTag(self, image: ExegolImage) -> Union[ExegolImage, str]: tag=image.getLatestVersionName(), platform="linux/" + image.getArch()) return ExegolImage(docker_image=image, isUpToDate=True) - except APIError as err: + except (APIError, PodmanAPIError) as err: if err.status_code == 500: return f"error while contacting docker registry: {err.explanation}" elif err.status_code == 404: @@ -540,7 +585,7 @@ def removeImage(self, image: ExegolImage, upgrade_mode: bool = False) -> bool: logger.verbose(f"Removing {'previous ' if upgrade_mode else ''}image [green]{image.getName()}[/green]...") logger.success(f"{'Previous d' if upgrade_mode else 'D'}ocker image successfully removed.") return True - except APIError as err: + except (APIError, PodmanAPIError) as err: # Handle docker API error code logger.verbose(err.explanation) if err.status_code == 409: @@ -573,7 +618,7 @@ def __remove_image(self, image_name: str) -> bool: try: _ = self.__client.images.get(image_name) # DockerSDK image getter is an exact matching, no need to add more check - except APIError as err: + except (APIError, PodmanAPIError) as err: if err.status_code == 404: return True else: @@ -616,7 +661,7 @@ def buildImage(self, tag: str, build_profile: Optional[str] = None, build_docker pull=True, decode=True)) logger.success(f"Exegol image successfully built") - except APIError as err: + except (APIError, PodmanAPIError) as err: logger.debug(f"Error: {err}") if err.status_code == 500: logger.error(f"Error: {err.explanation}") diff --git a/requirements.txt b/requirements.txt index cf72eb36..084cb7e4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ docker~=7.1.0 +podman~=5.2.0 requests~=2.32.3 rich~=13.7.1 GitPython~=3.1.43 diff --git a/setup.py b/setup.py index 317fcf33..7039c14e 100644 --- a/setup.py +++ b/setup.py @@ -56,6 +56,7 @@ ], install_requires=[ 'docker~=7.1.0', + 'podman~=5.2.0', 'requests~=2.32.3', 'rich~=13.7.1', 'GitPython~=3.1.43',