Skip to content

Commit

Permalink
Adding podman support
Browse files Browse the repository at this point in the history
  • Loading branch information
D3vil0p3r committed Dec 19, 2024
1 parent 040a170 commit 0d7c491
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 82 deletions.
9 changes: 6 additions & 3 deletions exegol/model/ContainerConfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
37 changes: 20 additions & 17 deletions exegol/model/ExegolContainer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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).")

Expand Down Expand Up @@ -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...")

Expand Down
33 changes: 21 additions & 12 deletions exegol/model/ExegolImage.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -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 = ""
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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", "<none>") + "-" + image.labels.get("org.exegol.version", "v?")

Expand All @@ -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."""
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 ""

Expand All @@ -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"""
Expand Down
9 changes: 5 additions & 4 deletions exegol/model/MetaImages.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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"
Expand Down
6 changes: 4 additions & 2 deletions exegol/utils/ContainerLogStream.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit 0d7c491

Please sign in to comment.