Skip to content

Commit

Permalink
podman parse multiple digests support
Browse files Browse the repository at this point in the history
  • Loading branch information
D3vil0p3r committed Dec 20, 2024
1 parent a287b22 commit c3b6486
Show file tree
Hide file tree
Showing 2 changed files with 27 additions and 22 deletions.
35 changes: 21 additions & 14 deletions exegol/model/ExegolImage.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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"""
Expand Down
14 changes: 6 additions & 8 deletions exegol/utils/DockerUtils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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}"
Expand All @@ -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
Expand Down

0 comments on commit c3b6486

Please sign in to comment.