diff --git a/exegol/model/ExegolImage.py b/exegol/model/ExegolImage.py index b065bc69..74008dfc 100644 --- a/exegol/model/ExegolImage.py +++ b/exegol/model/ExegolImage.py @@ -166,9 +166,9 @@ def setDockerObject(self, docker_image: Union[DockerImage, PodmanImage]): self.__build_date = self.__image.labels.get('org.exegol.build_date', '[bright_black]N/A[/bright_black]') # Check if local image is sync with remote digest id (check up-to-date status) if self.__profile_digest: - self.__is_update = self.__profile_digest == self.__parseDigest(docker_image) + self.__is_update = self.__profile_digest in self.__parseDigest(docker_image) # unlike docker, podman associates multiple digests to an image else: - self.__is_update = self.__digest == self.__parseDigest(docker_image) + self.__is_update = self.__digest in self.__parseDigest(docker_image) # unlike docker, podman associates multiple digests to an image # If this image is remote, set digest ID self.__is_remote = not (len(self.__image.attrs["RepoDigests"]) == 0 and self.__checkLocalLabel()) if self.__is_remote: @@ -204,7 +204,7 @@ def setMetaImage(self, meta: MetaImages, status: Status): if meta.meta_id: self.__setLatestRemoteId(meta.meta_id) # Check if local image is sync with remote digest id (check up-to-date status) - self.__is_update = self.__digest == self.__profile_digest + self.__is_update = self.__profile_digest in self.__digest # unlike docker, podman associates multiple digests to an image if not self.__digest and meta.is_latest and meta.meta_id: # If the digest is lost (multiple same image installed locally) fallback to meta id (only if latest) self.__setDigest(meta.meta_id) @@ -295,7 +295,7 @@ def autoLoad(self, from_cache: bool = True) -> 'ExegolImage': self.__setLatestRemoteId(remote_digest) if self.__digest: # Compare current and remote latest digest for up-to-date status - self.__is_update = self.__digest == self.__profile_digest + self.__is_update = self.__profile_digest in self.__digest # unlike docker, podman associates multiple digests to an image if version is not None: # Set latest remote version self.__setLatestVersion(version) @@ -481,7 +481,7 @@ def __eq__(self, other): """Operation == overloading for ExegolImage object""" # How to compare two ExegolImage if type(other) is ExegolImage: - return self.__name == other.__name and self.__digest == other.__digest and self.__arch == other.__arch + return self.__name == other.__name and other.__digest in self.__digest and self.__arch == other.__arch # How to compare ExegolImage with str elif type(other) is str: return self.__name == other @@ -533,19 +533,26 @@ def getType(self) -> str: """Image type getter""" return "remote" if self.__is_remote else "local" - def __setDigest(self, digest: Optional[str]): + def __setDigest(self, digests: Optional[List[str]]): """Remote image digest setter""" - if digest is not None: - self.__digest = digest + if digests is not None and isinstance(digests, list): + self.__digest = digests # Store the entire list + elif isinstance(digests, str): # Handle backward compatibility + self.__digest = [digests] # Convert single digest to a list + else: + self.__digest = None # No digest @staticmethod - def __parseDigest(docker_image: Union[DockerImage, PodmanImage]) -> str: - """Parse the remote image digest ID. - Return digest id from the docker object.""" + def __parseDigest(docker_image: Union[DockerImage, PodmanImage]) -> List[str]: + """Parse the remote image digest IDs. + Return a list of digest IDs from the docker object. + Note that a list is returned because Podman allows + multiple digests to be associated with an image. """ + digests = [] for digest_id in docker_image.attrs["RepoDigests"]: - if ConstantConfig.IMAGE_NAME in digest_id: # Find digest id from the right repository - return digest_id.split('@')[1] - return "" + if ConstantConfig.IMAGE_NAME in digest_id: + digests.append(digest_id.split('@')[1]) + return digests def getRemoteId(self) -> str: """Remote digest getter""" diff --git a/exegol/utils/DockerUtils.py b/exegol/utils/DockerUtils.py index 327964b5..6be39a52 100644 --- a/exegol/utils/DockerUtils.py +++ b/exegol/utils/DockerUtils.py @@ -55,12 +55,10 @@ def __init__(self): if (client := self.__connect_to_docker()): self.__client = client except DockerException: - logger.warning("Docker not available, trying to connect to Podman...") + logger.debug("Docker not available, trying to connect to Podman...") if (client := self.__connect_to_podman()): self.__client = client - else: - raise RuntimeError("Neither Docker nor Podman is running.") # Check if the docker daemon is serving linux container self.__daemon_info = self.__client.info() @@ -84,22 +82,22 @@ def get_container_runtime(self): def __connect_to_docker(self): """Attempts to connect to Docker.""" self.container_runtime = "docker" - client = docker.from_env() # Removed try-except block to let exceptions propagate + client = docker.from_env() logger.info("Connected to Docker.") return client def __connect_to_podman(self): """Attempts to connect to Podman.""" self.container_runtime = "podman" - client = podman.from_env() # Removed try-except block to let exceptions propagate + client = podman.from_env() client.ping() # Check if the Podman service is reachable logger.info("Connected to Podman.") return client def __handle_connection_error(self, err): """Handles connection errors for both Docker and Podman.""" - if 'ConnectionRefusedError' in str(err) or 'APIError' in str(err): - logger.critical(f"Unable to connect to {self.get_container_runtime()}. Is it running on your machine? Exiting.{os.linesep}" + if 'ConnectionRefusedError' in str(err) or 'HEAD operation failed' in str(err) or 'APIError' in str(err): + logger.critical(f"Unable to connect to docker or podman. Is one of them 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.get_container_runtime()}. Is it installed on your machine? Exiting.{os.linesep}" @@ -108,7 +106,7 @@ def __handle_connection_error(self, err): logger.critical(f"{self.get_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.get_container_runtime()}. Is it operational and accessible? Exiting.") + logger.critical(f"Unable to connect to docker or podman. Is one of them operational and accessible? Exiting.") def clearCache(self): """Remove class's images and containers data cache