From c5ab46dc308b4a962384a2815398c8a40e8ab85c Mon Sep 17 00:00:00 2001 From: Dramelac Date: Thu, 20 Jul 2023 13:45:04 +0200 Subject: [PATCH 01/15] Fix latest version formatting --- exegol/config/ConstantConfig.py | 2 +- exegol/manager/UpdateManager.py | 5 ++++- tests/test_exegol.py | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/exegol/config/ConstantConfig.py b/exegol/config/ConstantConfig.py index 0c7b194a..1b3c7618 100644 --- a/exegol/config/ConstantConfig.py +++ b/exegol/config/ConstantConfig.py @@ -5,7 +5,7 @@ class ConstantConfig: """Constant parameters information""" # Exegol Version - version: str = "4.2.4" + version: str = "4.2.5b1" # Exegol documentation link documentation: str = "https://exegol.rtfd.io/" diff --git a/exegol/manager/UpdateManager.py b/exegol/manager/UpdateManager.py index f802be36..1a9c4c60 100644 --- a/exegol/manager/UpdateManager.py +++ b/exegol/manager/UpdateManager.py @@ -289,7 +289,10 @@ def isUpdateTag(cls) -> bool: @classmethod def display_latest_version(cls) -> str: - return f"[blue]v{DataCache().get_wrapper_data().last_version}[/blue]" + last_version = DataCache().get_wrapper_data().last_version + if len(last_version) == 8 and '.' not in last_version: + return f"[bright_black]\[{last_version}][/bright_black]" + return f"[blue]v{last_version}[/blue]" @classmethod def __untagUpdateAvailable(cls, current_version: Optional[str] = None): diff --git a/tests/test_exegol.py b/tests/test_exegol.py index b98740f9..1687cd35 100644 --- a/tests/test_exegol.py +++ b/tests/test_exegol.py @@ -2,4 +2,4 @@ def test_version(): - assert __version__ == '4.2.4' + assert __version__ == '4.2.5' From e2cfedf08b37c19dd3712e37636e6a47e8ed7865 Mon Sep 17 00:00:00 2001 From: Dramelac Date: Thu, 20 Jul 2023 14:38:40 +0200 Subject: [PATCH 02/15] Fix error message containing rich char '[' + handling rollback action --- exegol/model/ContainerConfig.py | 13 +++++++++++-- exegol/model/ExegolContainerTemplate.py | 4 ++++ exegol/utils/DockerUtils.py | 5 ++++- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/exegol/model/ContainerConfig.py b/exegol/model/ContainerConfig.py index 2357ab50..1164a584 100644 --- a/exegol/model/ContainerConfig.py +++ b/exegol/model/ContainerConfig.py @@ -588,10 +588,19 @@ def prepareShare(self, share_name: str): # Skip default volume workspace if disabled return else: - # Add shared-data-volumes private workspace bind volume + # Add dedicated private workspace bind volume volume_path = str(UserConfig().private_volume_path.joinpath(share_name)) self.addVolume(volume_path, '/workspace', enable_sticky_group=True) + def rollback_preparation(self, share_name: str): + """Undo preparation in case of container creation failure""" + if self.__workspace_custom_path is None and not self.__disable_workspace: + # Remove dedicated workspace volume + logger.info("Rollback: removing dedicated workspace directory") + directory_path = UserConfig().private_volume_path.joinpath(share_name) + if directory_path.is_dir(): + directory_path.rmdir() + def setNetworkMode(self, host_mode: Optional[bool]): """Set container's network mode, true for host, false for bridge""" if host_mode is None: @@ -852,7 +861,7 @@ def removeVolume(self, host_path: Optional[str] = None, container_path: Optional """Remove a volume from the container configuration (Only before container creation)""" if host_path is None and container_path is None: # This is a dev problem - raise ReferenceError('At least one parameter must be set') + raise ValueError('At least one parameter must be set') for i in range(len(self.__mounts)): # For each Mount object compare the host_path if supplied or the container_path si supplied if host_path is not None and self.__mounts[i].get("Source") == host_path: diff --git a/exegol/model/ExegolContainerTemplate.py b/exegol/model/ExegolContainerTemplate.py index e39260c1..cb7ca608 100644 --- a/exegol/model/ExegolContainerTemplate.py +++ b/exegol/model/ExegolContainerTemplate.py @@ -31,6 +31,10 @@ def prepare(self): """Prepare the model before creating the docker container""" self.config.prepareShare(self.name) + def rollback(self): + """Rollback change in case of container creation fail.""" + self.config.rollback_preparation(self.name) + def getDisplayName(self) -> str: """Getter of the container's name for TUI purpose""" if self.container_name != self.hostname: diff --git a/exegol/utils/DockerUtils.py b/exegol/utils/DockerUtils.py index a65f3fec..2417f3ca 100644 --- a/exegol/utils/DockerUtils.py +++ b/exegol/utils/DockerUtils.py @@ -125,8 +125,11 @@ def createContainer(cls, model: ExegolContainerTemplate, temporary: bool = False auto_remove=temporary, working_dir=model.config.getWorkingDir()) except APIError as err: - logger.error(err.explanation.decode('utf-8') if type(err.explanation) is bytes else err.explanation) + message = err.explanation.decode('utf-8').replace('[', '\\[') if type(err.explanation) is bytes else err.explanation + message = message.replace('[', '\\[') + logger.error(message) logger.debug(err) + model.rollback() logger.critical("Error while creating exegol container. Exiting.") # Not reachable, critical logging will exit return # type: ignore From 2b449bbe2ebf1f42b49220441583aa981368e448 Mon Sep 17 00:00:00 2001 From: Dramelac Date: Wed, 26 Jul 2023 19:37:09 +0200 Subject: [PATCH 03/15] Force container in lowercase for case-insensitive OS --- exegol/model/ExegolContainerTemplate.py | 4 ++++ exegol/utils/DockerUtils.py | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/exegol/model/ExegolContainerTemplate.py b/exegol/model/ExegolContainerTemplate.py index cb7ca608..cd950d91 100644 --- a/exegol/model/ExegolContainerTemplate.py +++ b/exegol/model/ExegolContainerTemplate.py @@ -3,6 +3,7 @@ from rich.prompt import Prompt +from exegol.config.EnvInfo import EnvInfo from exegol.model.ContainerConfig import ContainerConfig from exegol.model.ExegolImage import ExegolImage @@ -14,6 +15,9 @@ def __init__(self, name: Optional[str], config: ContainerConfig, image: ExegolIm if name is None: name = Prompt.ask("[bold blue][?][/bold blue] Enter the name of your new exegol container", default="default") assert name is not None + if EnvInfo.isWindowsHost() or EnvInfo.isMacHost(): + # Force container as lowercase because the filesystem of windows / mac are case-insensitive => https://github.com/ThePorgs/Exegol/issues/167 + name = name.lower() self.container_name: str = name if name.startswith("exegol-") else f'exegol-{name}' self.name: str = name.replace('exegol-', '') if hostname: diff --git a/exegol/utils/DockerUtils.py b/exegol/utils/DockerUtils.py index 2417f3ca..72660c14 100644 --- a/exegol/utils/DockerUtils.py +++ b/exegol/utils/DockerUtils.py @@ -154,6 +154,13 @@ def getContainer(cls, tag: str) -> ExegolContainer: return # type: ignore # Check if there is at least 1 result. If no container was found, raise ObjectNotFound. if container is None or len(container) == 0: + # Handle case-insensitive OS + if EnvInfo.isWindowsHost() or EnvInfo.isMacHost(): + # First try to fetch the container as-is (for retroactive support with old container with uppercase characters) + # If the user's input didn't match any container, try to force the name in lowercase if not already tried + lowered_tag = tag.lower() + if lowered_tag != tag: + return cls.getContainer(lowered_tag) raise ObjectNotFound # Filter results with exact name matching for c in container: From b99ac55131dcc9cfe8f3e68bbf4159c272cb092b Mon Sep 17 00:00:00 2001 From: Dramelac Date: Wed, 26 Jul 2023 22:05:02 +0200 Subject: [PATCH 04/15] Don't change existing container name --- exegol/model/ExegolContainerTemplate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exegol/model/ExegolContainerTemplate.py b/exegol/model/ExegolContainerTemplate.py index cd950d91..213f4ae3 100644 --- a/exegol/model/ExegolContainerTemplate.py +++ b/exegol/model/ExegolContainerTemplate.py @@ -15,7 +15,7 @@ def __init__(self, name: Optional[str], config: ContainerConfig, image: ExegolIm if name is None: name = Prompt.ask("[bold blue][?][/bold blue] Enter the name of your new exegol container", default="default") assert name is not None - if EnvInfo.isWindowsHost() or EnvInfo.isMacHost(): + if (EnvInfo.isWindowsHost() or EnvInfo.isMacHost()) and not name.startswith("exegol-"): # Force container as lowercase because the filesystem of windows / mac are case-insensitive => https://github.com/ThePorgs/Exegol/issues/167 name = name.lower() self.container_name: str = name if name.startswith("exegol-") else f'exegol-{name}' From 758e6efa730ce431ac12ffc2771aad3832ad95a3 Mon Sep 17 00:00:00 2001 From: Dramelac Date: Wed, 26 Jul 2023 22:19:45 +0200 Subject: [PATCH 05/15] Add XQuartz warning if running as root --- exegol/utils/GuiUtils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/exegol/utils/GuiUtils.py b/exegol/utils/GuiUtils.py index c385fc1a..b3482c0e 100644 --- a/exegol/utils/GuiUtils.py +++ b/exegol/utils/GuiUtils.py @@ -92,6 +92,8 @@ def __macGuiChecks(cls) -> bool: # Notify user to change configuration logger.error("XQuartz does not allow network connections. " "You need to manually change the configuration to 'Allow connections from network clients'") + if os.getuid() == 0: + logger.warning("You are running exegol as [red]root[/red]! The root user cannot check in the user context whether XQuartz is properly configured or not.") return False # Check if XQuartz is started, check is dir exist and if there is at least one socket From c86d7252a23f13a2c45ff935153eb4b454dd12d3 Mon Sep 17 00:00:00 2001 From: Dramelac Date: Sun, 30 Jul 2023 14:44:36 +0200 Subject: [PATCH 06/15] Catch permission error on DataFileUtils --- exegol/config/DataCache.py | 2 +- exegol/utils/DataFileUtils.py | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/exegol/config/DataCache.py b/exegol/config/DataCache.py index 87c60474..cee1309f 100644 --- a/exegol/config/DataCache.py +++ b/exegol/config/DataCache.py @@ -8,7 +8,7 @@ class DataCache(DataFileUtils, metaclass=MetaSingleton): """This class allows loading cached information defined configurations - Exemple of data: + Example of data: { wrapper: { update: { diff --git a/exegol/utils/DataFileUtils.py b/exegol/utils/DataFileUtils.py index 4c3a541f..cfedc5d4 100644 --- a/exegol/utils/DataFileUtils.py +++ b/exegol/utils/DataFileUtils.py @@ -62,16 +62,18 @@ def _build_file_content(self) -> str: This fonction build the default file content. Called when the file doesn't exist yet or have been upgrade and need to be updated. :return: """ - raise NotImplementedError( - f"The '_build_default_file' method hasn't been implemented in the '{self.__class__}' class.") + raise NotImplementedError(f"The '_build_default_file' method hasn't been implemented in the '{self.__class__}' class.") def _create_config_file(self): """ Create or overwrite the file content to the default / current value depending on the '_build_default_file' that must be redefined in child class. :return: """ - with open(self._file_path, 'w') as file: - file.write(self._build_file_content()) + try: + with open(self._file_path, 'w') as file: + file.write(self._build_file_content()) + except PermissionError as e: + logger.critical(f"Unable to open the file '{self._file_path}' ({e}). Please fix your file permissions or run exegol with the correct rights.") def _parse_config(self): data: Dict = {} From df479e8b846cf402d90b62670890c7a5a9c1a20e Mon Sep 17 00:00:00 2001 From: Dramelac Date: Thu, 3 Aug 2023 13:43:49 +0200 Subject: [PATCH 07/15] Remove container after a failed creation --- exegol/model/ExegolContainer.py | 3 +++ exegol/utils/DockerUtils.py | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/exegol/model/ExegolContainer.py b/exegol/model/ExegolContainer.py index d2cfccf6..05563be1 100644 --- a/exegol/model/ExegolContainer.py +++ b/exegol/model/ExegolContainer.py @@ -212,6 +212,9 @@ def __removeVolume(self): list_files = [] else: return + except FileNotFoundError: + logger.debug("This workspace has already been removed.") + return try: if len(list_files) > 0: # Directory is not empty diff --git a/exegol/utils/DockerUtils.py b/exegol/utils/DockerUtils.py index 72660c14..ae2752c5 100644 --- a/exegol/utils/DockerUtils.py +++ b/exegol/utils/DockerUtils.py @@ -130,6 +130,15 @@ def createContainer(cls, model: ExegolContainerTemplate, temporary: bool = False logger.error(message) logger.debug(err) model.rollback() + try: + container = cls.__client.containers.list(all=True, filters={"name": model.container_name}) + if container is not None and len(container) > 0: + for c in container: + if c.name == model.container_name: # Search for exact match + container[0].remove() + logger.debug("Container removed") + except Exception: + pass logger.critical("Error while creating exegol container. Exiting.") # Not reachable, critical logging will exit return # type: ignore From e52c0e5e8967ae96d48e7c7ebb8f69b62334a7e6 Mon Sep 17 00:00:00 2001 From: Dramelac Date: Fri, 4 Aug 2023 11:34:08 +0200 Subject: [PATCH 08/15] Exclude windows env from getuid check --- exegol/utils/GuiUtils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/exegol/utils/GuiUtils.py b/exegol/utils/GuiUtils.py index b3482c0e..a597428e 100644 --- a/exegol/utils/GuiUtils.py +++ b/exegol/utils/GuiUtils.py @@ -2,6 +2,7 @@ import os import shutil import subprocess +import sys import time from pathlib import Path from typing import Optional @@ -92,7 +93,8 @@ def __macGuiChecks(cls) -> bool: # Notify user to change configuration logger.error("XQuartz does not allow network connections. " "You need to manually change the configuration to 'Allow connections from network clients'") - if os.getuid() == 0: + # Add sys.platform check to exclude windows env (fix for mypy static code analysis) + if sys.platform != "win32" and os.getuid() == 0: logger.warning("You are running exegol as [red]root[/red]! The root user cannot check in the user context whether XQuartz is properly configured or not.") return False From 43b428c67b0da92cc0de8267f5d6bfdea2fad50e Mon Sep 17 00:00:00 2001 From: Shutdown <40902872+ShutdownRepo@users.noreply.github.com> Date: Wed, 9 Aug 2023 19:49:55 -0700 Subject: [PATCH 09/15] Adding BHUS23 badge --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index d39af743..2a906778 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,9 @@ Black Hat Asia 2023 + + Black Hat USA 2023 +

Join us on Discord

From e41d6771e8007176b23b71e9cb976d9ad612ea3b Mon Sep 17 00:00:00 2001 From: Dramelac Date: Tue, 8 Aug 2023 11:01:15 +0200 Subject: [PATCH 10/15] Fix utf-8 encoding --- exegol/utils/WebUtils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exegol/utils/WebUtils.py b/exegol/utils/WebUtils.py index 7aabee7a..66254855 100644 --- a/exegol/utils/WebUtils.py +++ b/exegol/utils/WebUtils.py @@ -92,7 +92,7 @@ def getRemoteVersion(cls, tag: str) -> Optional[str]: response = cls.__runRequest(url, service_name="Docker Registry", headers=manifest_headers, method="GET") version: Optional[str] = None if response is not None and response.status_code == 200: - data = json.loads(response.text) + data = json.loads(response.content.decode("utf-8")) # Parse metadata of the current image from v1 schema metadata = json.loads(data.get("history", [])[0]['v1Compatibility']) # Find version label and extract data From c9316cb15a650759a9fcb93c4a89a24e2c8bff0f Mon Sep 17 00:00:00 2001 From: Dramelac Date: Wed, 9 Aug 2023 21:28:48 -0700 Subject: [PATCH 11/15] Use utf-8 on webutils --- exegol/utils/WebUtils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exegol/utils/WebUtils.py b/exegol/utils/WebUtils.py index 66254855..e43a437c 100644 --- a/exegol/utils/WebUtils.py +++ b/exegol/utils/WebUtils.py @@ -106,7 +106,7 @@ def runJsonRequest(cls, url: str, service_name: str, headers: Optional[Dict] = N return None data = cls.__runRequest(url, service_name, headers, method, data, retry_count) if data is not None and data.status_code == 200: - data = json.loads(data.text) + data = json.loads(data.content.decode("utf-8")) elif data is not None: logger.error(f"Error during web request to {service_name} ({data.status_code}) on {url}") if data.status_code == 404 and service_name == "Dockerhub": @@ -127,7 +127,7 @@ def __runRequest(cls, url: str, service_name: str, headers: Optional[Dict] = Non response = requests.request(method=method, url=url, timeout=(5, 10), verify=ParametersManager().verify, headers=headers, data=data) return response except requests.exceptions.HTTPError as e: - logger.error(f"Response error: {e.response.text}") + logger.error(f"Response error: {e.response.content.decode('utf-8')}") except requests.exceptions.ConnectionError as err: logger.debug(f"Error: {err}") error_re = re.search(r"\[Errno [-\d]+]\s?([^']*)('\))+\)*", str(err)) From 4131c2c068cd0b65a74ce59d7445232b4e6b38ca Mon Sep 17 00:00:00 2001 From: Dramelac Date: Wed, 9 Aug 2023 21:33:09 -0700 Subject: [PATCH 12/15] Ready to release --- exegol-docker-build | 2 +- exegol/config/ConstantConfig.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/exegol-docker-build b/exegol-docker-build index 4b8aa946..ad28264b 160000 --- a/exegol-docker-build +++ b/exegol-docker-build @@ -1 +1 @@ -Subproject commit 4b8aa9464301674e20195876f31ccc90284d97cc +Subproject commit ad28264bd1698f45d522866d0033fb53942dbd98 diff --git a/exegol/config/ConstantConfig.py b/exegol/config/ConstantConfig.py index 1b3c7618..c5c00b3c 100644 --- a/exegol/config/ConstantConfig.py +++ b/exegol/config/ConstantConfig.py @@ -5,7 +5,7 @@ class ConstantConfig: """Constant parameters information""" # Exegol Version - version: str = "4.2.5b1" + version: str = "4.2.5" # Exegol documentation link documentation: str = "https://exegol.rtfd.io/" From 3a395607fabdfaedc26be2b12277453ca5e3b91a Mon Sep 17 00:00:00 2001 From: Dramelac Date: Wed, 9 Aug 2023 21:41:18 -0700 Subject: [PATCH 13/15] Handle arm/ arch type --- exegol/console/TUI.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exegol/console/TUI.py b/exegol/console/TUI.py index 1d70bb51..70b43b0b 100644 --- a/exegol/console/TUI.py +++ b/exegol/console/TUI.py @@ -422,7 +422,7 @@ def printContainerRecap(cls, container: ExegolContainerTemplate): container_info_header += f" - v.{container.image.getImageVersion()}" if "Unknown" not in container.image.getStatus(): container_info_header += f" ({container.image.getStatus(include_version=False)})" - if container.image.getArch() != EnvInfo.arch or logger.isEnabledFor(ExeLog.VERBOSE): + if container.image.getArch().split('/')[0] != EnvInfo.arch or logger.isEnabledFor(ExeLog.VERBOSE): color = ConsoleFormat.getArchColor(container.image.getArch()) container_info_header += f" [{color}]({container.image.getArch()})[/{color}]" recap.add_column(container_info_header) From e66f73462080e358ffaffe204ec52b684c4dbf1c Mon Sep 17 00:00:00 2001 From: Dramelac Date: Wed, 9 Aug 2023 21:45:10 -0700 Subject: [PATCH 14/15] Dont change branch during update without verbose mode --- exegol/manager/UpdateManager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exegol/manager/UpdateManager.py b/exegol/manager/UpdateManager.py index 1a9c4c60..aa6df1f2 100644 --- a/exegol/manager/UpdateManager.py +++ b/exegol/manager/UpdateManager.py @@ -143,7 +143,7 @@ def __updateGit(gitUtils: GitUtils) -> bool: if current_branch is None: logger.warning("HEAD is detached. Please checkout to an existing branch.") current_branch = "unknown" - if logger.isEnabledFor(ExeLog.VERBOSE) or current_branch not in ["master", "main"]: + if logger.isEnabledFor(ExeLog.VERBOSE): available_branches = gitUtils.listBranch() # Ask to checkout only if there is more than one branch available if len(available_branches) > 1: From 76aba4831e727527f2926b554f328fe397916d8d Mon Sep 17 00:00:00 2001 From: Dramelac Date: Wed, 9 Aug 2023 21:46:58 -0700 Subject: [PATCH 15/15] Handle arm64/extra for image table --- exegol/model/ExegolImage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exegol/model/ExegolImage.py b/exegol/model/ExegolImage.py index f5c46728..ffa67df0 100644 --- a/exegol/model/ExegolImage.py +++ b/exegol/model/ExegolImage.py @@ -616,7 +616,7 @@ def getName(self) -> str: def getDisplayName(self) -> str: """Image's display name getter""" result = self.__alt_name if self.__alt_name else self.__name - if self.getArch() != ParametersManager().arch or logger.isEnabledFor(ExeLog.VERBOSE): + if self.getArch().split('/')[0] != ParametersManager().arch or logger.isEnabledFor(ExeLog.VERBOSE): color = ConsoleFormat.getArchColor(self.getArch()) result += f" [{color}]({self.getArch()})[/{color}]" return result