Skip to content

Commit 008d6fc

Browse files
committed
Adding podman support
1 parent 040a170 commit 008d6fc

8 files changed

+165
-89
lines changed

exegol/model/ContainerConfig.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010
from pathlib import Path, PurePath
1111
from typing import Optional, List, Dict, Union, Tuple, cast
1212

13-
from docker.models.containers import Container
13+
from docker.models.containers import Container as DockerContainer
1414
from docker.types import Mount
15+
from podman.domain.containers import Container as PodmanContainer
1516
from rich.prompt import Prompt
1617

1718
from exegol.config.ConstantConfig import ConstantConfig
@@ -81,7 +82,7 @@ class ExegolEnv(Enum):
8182
ExegolMetadata.comment.value: ["setComment", "getComment"],
8283
ExegolMetadata.password.value: ["setPasswd", "getPasswd"]}
8384

84-
def __init__(self, container: Optional[Container] = None):
85+
def __init__(self, container: Optional[Union[DockerContainer, PodmanContainer]] = None):
8586
"""Container config default value"""
8687
self.hostname = ""
8788
self.__enable_gui: bool = False
@@ -132,7 +133,7 @@ def __init__(self, container: Optional[Container] = None):
132133

133134
# ===== Config parsing section =====
134135

135-
def __parseContainerConfig(self, container: Container):
136+
def __parseContainerConfig(self, container: Union[DockerContainer, PodmanContainer]):
136137
"""Parse Docker object to setup self configuration"""
137138
# Reset default attributes
138139
self.__passwd = None

exegol/model/ExegolContainer.py

+20-17
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66
from typing import Optional, Dict, Sequence, Tuple, Union
77

88
from docker.errors import NotFound, ImageNotFound, APIError
9-
from docker.models.containers import Container
9+
from docker.models.containers import Container as DockerContainer
10+
11+
from podman.errors import NotFound as PodmanNotFound, ImageNotFound as PodmanImageNotFound, APIError as PodmanAPIError
12+
from podman.domain.containers import Container as PodmanContainer
1013

1114
from exegol.config.EnvInfo import EnvInfo
1215
from exegol.console.ExegolPrompt import Confirm
@@ -24,36 +27,36 @@
2427
class ExegolContainer(ExegolContainerTemplate, SelectableInterface):
2528
"""Class of an exegol container already create in docker"""
2629

27-
def __init__(self, docker_container: Container, model: Optional[ExegolContainerTemplate] = None):
28-
logger.debug(f"Loading container: {docker_container.name}")
29-
self.__container: Container = docker_container
30-
self.__id: str = docker_container.id
30+
def __init__(self, container_obj: Union[DockerContainer, PodmanContainer], model: Optional[ExegolContainerTemplate] = None):
31+
logger.debug(f"Loading container: {container_obj.name}")
32+
self.__container: Container = container_obj
33+
self.__id: str = container_obj.id
3134
self.__xhost_applied = False
3235
if model is None:
3336
image_name = ""
3437
try:
3538
# Try to find the attached docker image
36-
docker_image = docker_container.image
37-
except ImageNotFound:
39+
docker_image = container_obj.image
40+
except (ImageNotFound, PodmanImageNotFound):
3841
# If it is not found, the user has probably forcibly deleted it manually
3942
logger.warning(f"Some images were forcibly removed by docker when they were used by existing containers!")
40-
logger.error(f"The '{docker_container.name}' containers might not work properly anymore and should also be deleted and recreated with a new image.")
43+
logger.error(f"The '{container_obj.name}' containers might not work properly anymore and should also be deleted and recreated with a new image.")
4144
docker_image = None
4245
image_name = "[red bold]BROKEN[/red bold]"
4346
# Create Exegol container from an existing docker container
44-
super().__init__(docker_container.name,
45-
config=ContainerConfig(docker_container),
47+
super().__init__(container_obj.name,
48+
config=ContainerConfig(container_obj),
4649
image=ExegolImage(name=image_name, docker_image=docker_image),
47-
hostname=docker_container.attrs.get('Config', {}).get('Hostname'),
50+
hostname=container_obj.attrs.get('Config', {}).get('Hostname'),
4851
new_container=False)
49-
self.image.syncContainerData(docker_container)
52+
self.image.syncContainerData(container_obj)
5053
# At this stage, the container image object has an unknown status because no synchronization with a registry has been done.
5154
# This could be done afterwards (with container.image.autoLoad()) if necessary because it takes time.
5255
self.__new_container = False
5356
else:
5457
# Create Exegol container from a newly created docker container with its object template.
55-
super().__init__(docker_container.name,
56-
config=ContainerConfig(docker_container),
58+
super().__init__(container_obj.name,
59+
config=ContainerConfig(container_obj),
5760
# Rebuild config from docker object to update workspace path
5861
image=model.image,
5962
hostname=model.config.hostname,
@@ -121,7 +124,7 @@ def __start_container(self):
121124
start_date = datetime.now()
122125
try:
123126
self.__container.start()
124-
except APIError as e:
127+
except (APIError, PodmanAPIError) as e:
125128
logger.debug(e)
126129
logger.critical(f"Docker raise a critical error when starting the container [green]{self.name}[/green], error message is: {e.explanation}")
127130
if not self.config.legacy_entrypoint: # TODO improve startup compatibility check
@@ -230,7 +233,7 @@ def remove(self):
230233
try:
231234
self.__container.remove()
232235
logger.success(f"Container {self.name} successfully removed.")
233-
except NotFound:
236+
except (NotFound, PodmanNotFound):
234237
logger.error(
235238
f"The container {self.name} has already been removed (probably created as a temporary container).")
236239

@@ -319,7 +322,7 @@ def postCreateSetup(self, is_temporary: bool = False):
319322
self.__start_container()
320323
try:
321324
self.__updatePasswd()
322-
except APIError as e:
325+
except (APIError, PodmanAPIError) as e:
323326
if "is not running" in e.explanation:
324327
logger.critical("An unexpected error occurred. Exegol cannot start the container after its creation...")
325328

exegol/model/ExegolImage.py

+19-12
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
from datetime import datetime
22
from typing import Optional, List, Dict, Any, Union
33

4-
from docker.models.containers import Container
5-
from docker.models.images import Image
4+
from docker.models.containers import Container as DockerContainer
5+
from docker.models.images import Image as DockerImage
6+
from podman.domain.containers import Container as PodmanContainer
7+
from podman.domain.images import Image as PodmanImage
68
from rich.status import Status
79

810
from exegol.config.DataCache import DataCache
@@ -24,7 +26,7 @@ def __init__(self,
2426
dockerhub_data: Optional[Dict[str, Any]] = None,
2527
meta_img: Optional[MetaImages] = None,
2628
image_id: Optional[str] = None,
27-
docker_image: Optional[Image] = None,
29+
docker_image: Optional[Union[DockerImage, PodmanImage]] = None,
2830
isUpToDate: bool = False):
2931
"""Docker image default value"""
3032
# Prepare parameters
@@ -36,7 +38,7 @@ def __init__(self,
3638
version_parsed = MetaImages.tagNameParsing(name)
3739
self.__version_specific = bool(version_parsed)
3840
# Init attributes
39-
self.__image: Optional[Image] = docker_image
41+
self.__image: Optional[Union[DockerImage, PodmanImage]] = docker_image
4042
self.__name: str = name
4143
self.__alt_name: str = ''
4244
self.__arch = ""
@@ -150,7 +152,7 @@ def resetDockerImage(self):
150152
self.__build_date = "[bright_black]N/A[/bright_black]"
151153
self.__disk_size = "[bright_black]N/A[/bright_black]"
152154

153-
def setDockerObject(self, docker_image: Image):
155+
def setDockerObject(self, docker_image: Union[DockerImage, PodmanImage]):
154156
"""Docker object setter. Parse object to set up self configuration."""
155157
self.__image = docker_image
156158
# When a docker image exist, image is locally installed
@@ -230,7 +232,7 @@ def __labelVersionParsing(self):
230232
self.__profile_version = self.__image_version
231233

232234
@classmethod
233-
def parseAliasTagName(cls, image: Image) -> str:
235+
def parseAliasTagName(cls, image: Union[DockerImage, PodmanImage]) -> str:
234236
"""Create a tag name alias from labels when image's tag is lost"""
235237
return image.labels.get("org.exegol.tag", "<none>") + "-" + image.labels.get("org.exegol.version", "v?")
236238

@@ -247,7 +249,7 @@ def syncStatus(self):
247249
else:
248250
self.__custom_status = ""
249251

250-
def syncContainerData(self, container: Container):
252+
def syncContainerData(self, container: Union[DockerImage, PodmanImage]):
251253
"""Synchronization between the container and the image.
252254
If the image has been updated, the tag is lost,
253255
but it is saved in the properties of the container that still uses it."""
@@ -352,7 +354,7 @@ def __mergeMetaImages(cls, images: List[MetaImages]):
352354
pass
353355

354356
@classmethod
355-
def mergeImages(cls, remote_images: List[MetaImages], local_images: List[Image], status: Status) -> List['ExegolImage']:
357+
def mergeImages(cls, remote_images: List[MetaImages], local_images: List[Union[DockerImage, PodmanImage]], status: Status) -> List['ExegolImage']:
356358
"""Compare and merge local images and remote images.
357359
Use case to process :
358360
- up-to-date : "Version specific" image can use exact digest_id matching. Latest image must match corresponding tag
@@ -537,11 +539,11 @@ def __setDigest(self, digest: Optional[str]):
537539
self.__digest = digest
538540

539541
@staticmethod
540-
def __parseDigest(docker_image: Image) -> str:
542+
def __parseDigest(docker_image: Union[DockerImage, PodmanImage]) -> str:
541543
"""Parse the remote image digest ID.
542544
Return digest id from the docker object."""
543545
for digest_id in docker_image.attrs["RepoDigests"]:
544-
if digest_id.startswith(ConstantConfig.IMAGE_NAME): # Find digest id from the right repository
546+
if ConstantConfig.IMAGE_NAME in digest_id: # Find digest id from the right repository
545547
return digest_id.split('@')[1]
546548
return ""
547549

@@ -558,9 +560,14 @@ def getLatestRemoteId(self) -> str:
558560
return self.__profile_digest
559561

560562
def __setImageId(self, image_id: Optional[str]):
561-
"""Local image id setter"""
563+
"""Local image id setter for both Docker and Podman"""
562564
if image_id is not None:
563-
self.__image_id = image_id.split(":")[1][:12]
565+
# Check if the image_id contains a colon (as in Docker's format)
566+
if ":" in image_id:
567+
self.__image_id = image_id.split(":")[1][:12]
568+
else:
569+
# For Podman, where image_id does not contain the 'sha256:' prefix
570+
self.__image_id = image_id[:12]
564571

565572
def getLocalId(self) -> str:
566573
"""Local id getter"""

exegol/model/MetaImages.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from typing import Optional, Set, Union
22

3-
from docker.models.images import Image
3+
from docker.models.images import Image as DockerImage
4+
from podman.domain.images import Image as PodmanImage
45

56
from exegol.utils.ExeLog import logger
67
from exegol.utils.WebUtils import WebUtils
@@ -58,13 +59,13 @@ def tagNameParsing(tag_name: str) -> str:
5859
return version
5960

6061
@staticmethod
61-
def parseArch(docker_image: Union[dict, Image]) -> str:
62+
def parseArch(docker_image: Union[dict, DockerImage, PodmanImage]) -> str:
6263
"""Parse and format arch in dockerhub style from registry dict struct.
6364
Return arch in format 'arch/variant'."""
6465
arch_key = "architecture"
6566
variant_key = "variant"
66-
# Support Docker image struct with specific dict key
67-
if type(docker_image) is Image:
67+
# Support Docker and Podman image struct with specific dict key
68+
if isinstance(docker_image, (DockerImage, PodmanImage)):
6869
docker_image = docker_image.attrs
6970
arch_key = "Architecture"
7071
variant_key = "Variant"

exegol/utils/ContainerLogStream.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import time
22
from datetime import datetime, timedelta
3-
from typing import Optional
3+
from typing import Optional, Union
44

5-
from docker.models.containers import Container
5+
from docker.models.containers import Container as DockerContainer
6+
from podman.domain.containers import Container as PodmanContainer
67

78
from exegol.utils.ExeLog import logger
89

910

1011
class ContainerLogStream:
1112

12-
def __init__(self, container: Container, start_date: Optional[datetime] = None, timeout: int = 5):
13+
def __init__(self, container: Union[DockerContainer, PodmanContainer], start_date: Optional[datetime] = None, timeout: int = 5):
1314
# Container to extract logs from
1415
self.__container = container
1516
# Fetch more logs from this datetime

0 commit comments

Comments
 (0)