Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release 4.3.3 #215

Merged
merged 14 commits into from
May 16, 2024
2 changes: 2 additions & 0 deletions .github/workflows/entrypoint_nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ jobs:
run: python -m build --sdist --outdir dist/ .
- name: Publish distribution 📦 to Test PyPI
uses: pypa/gh-action-pypi-publish@release/v1
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
with:
repository-url: https://test.pypi.org/legacy/
skip-existing: true
verbose: true
2 changes: 1 addition & 1 deletion .github/workflows/entrypoint_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: Release
on:
push:
tags:
- '*'
- '[0-9]+.[0-9]+.[0-9]+'

jobs:
test:
Expand Down
5 changes: 3 additions & 2 deletions exegol/config/ConstantConfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
class ConstantConfig:
"""Constant parameters information"""
# Exegol Version
version: str = "4.3.2"
version: str = "4.3.3"

# Exegol documentation link
documentation: str = "https://exegol.rtfd.io/"
Expand All @@ -23,7 +23,8 @@ class ConstantConfig:
exegol_config_path: Path = Path().home() / ".exegol"
# Docker Desktop for mac config file
docker_desktop_mac_config_path = Path().home() / "Library/Group Containers/group.com.docker/settings.json"
docker_desktop_windows_config_path = Path().home() / "AppData/Roaming/Docker/settings.json"
docker_desktop_windows_config_short_path = "AppData/Roaming/Docker/settings.json"
docker_desktop_windows_config_path = Path().home() / docker_desktop_windows_config_short_path
# Install mode, check if Exegol has been git cloned or installed using pip package
git_source_installation: bool = (src_root_path_obj / '.git').is_dir()
pip_installed: bool = src_root_path_obj.name == "site-packages"
Expand Down
39 changes: 29 additions & 10 deletions exegol/config/EnvInfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import platform
from enum import Enum
from pathlib import Path
from typing import Optional, Any, List
from typing import Optional, List, Dict

from exegol.config.ConstantConfig import ConstantConfig
from exegol.utils.ExeLog import logger
Expand Down Expand Up @@ -149,6 +149,11 @@ def isMacHost(cls) -> bool:
"""Return true if macOS is detected on the host"""
return cls.getHostOs() == cls.HostOs.MAC

@classmethod
def isLinuxHost(cls) -> bool:
"""Return true if Linux is detected on the host"""
return cls.getHostOs() == cls.HostOs.LINUX

@classmethod
def isWaylandAvailable(cls) -> bool:
"""Return true if wayland is detected on the host"""
Expand Down Expand Up @@ -185,7 +190,7 @@ def getShellType(cls):
return "Unknown"

@classmethod
def getDockerDesktopSettings(cls) -> Optional[Any]:
def getDockerDesktopSettings(cls) -> Dict:
"""Applicable only for docker desktop on macos"""
if cls.isDockerDesktop():
if cls.__docker_desktop_resource_config is None:
Expand All @@ -194,20 +199,34 @@ def getDockerDesktopSettings(cls) -> Optional[Any]:
elif cls.is_windows_shell:
path = ConstantConfig.docker_desktop_windows_config_path
else:
return None
# TODO support from WSL shell
# Find docker desktop config
config_file = list(Path("/mnt/c/Users").glob(f"*/{ConstantConfig.docker_desktop_windows_config_short_path}"))
if len(config_file) == 0:
return {}
else:
path = config_file[0]
logger.debug(f"Docker desktop config found at {path}")
try:
with open(path, 'r') as docker_desktop_config:
cls.__docker_desktop_resource_config = json.load(docker_desktop_config)
except FileNotFoundError:
logger.warning(f"Docker Desktop configuration file not found: '{path}'")
return None
return {}
return cls.__docker_desktop_resource_config
return None
return {}

@classmethod
def getDockerDesktopResources(cls) -> List[str]:
config = cls.getDockerDesktopSettings()
if config:
return config.get('filesharingDirectories', [])
return []
return cls.getDockerDesktopSettings().get('filesharingDirectories', [])

@classmethod
def isHostNetworkAvailable(cls) -> bool:
if cls.isLinuxHost():
return True
elif cls.isOrbstack():
return True
elif cls.isDockerDesktop():
res = cls.getDockerDesktopSettings().get('hostNetworkingEnabled', False)
return res if res is not None else False
logger.warning("Unknown or not supported environment for host network mode.")
return False
2 changes: 1 addition & 1 deletion exegol/console/cli/ExegolCompleter.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

def ContainerCompleter(prefix: str, parsed_args: Namespace, **kwargs) -> Tuple[str, ...]:
"""Function to dynamically load a container list for CLI autocompletion purpose"""
data = [c.name for c in DockerUtils.listContainers()]
data = [c.name for c in DockerUtils().listContainers()]
for obj in data:
# filter data if needed
if prefix and not obj.lower().startswith(prefix.lower()):
Expand Down
5 changes: 5 additions & 0 deletions exegol/manager/ExegolController.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
import git

from exegol.utils.ExeLog import logger, ExeLog, console
from exegol.utils.DockerUtils import DockerUtils
from exegol.console.cli.ParametersManager import ParametersManager
from exegol.console.cli.actions.ExegolParameters import Command
from exegol.manager.ExegolManager import ExegolManager
except ModuleNotFoundError as e:
print("Mandatory dependencies are missing:", e)
print("Please install them with python3 -m pip install --upgrade -r requirements.txt")
Expand All @@ -32,6 +34,9 @@ class ExegolController:
def call_action(cls):
"""Dynamically retrieve the main function corresponding to the action selected by the user
and execute it on the main thread"""
ExegolManager.print_version()
DockerUtils() # Init dockerutils
ExegolManager.print_debug_banner()
# Check for missing parameters
missing_params = cls.__action.check_parameters()
if len(missing_params) == 0:
Expand Down
43 changes: 19 additions & 24 deletions exegol/manager/ExegolManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ class ExegolManager:
@classmethod
def info(cls):
"""Print a list of available images and containers on the current host"""
ExegolManager.print_version()
if logger.isEnabledFor(ExeLog.VERBOSE):
logger.verbose("Listing user configurations")
ExegolTUI.printTable(UserConfig().get_configs(), title="[not italic]:brain: [/not italic][gold3][g]User configurations[/g][/gold3]")
Expand All @@ -53,8 +52,8 @@ def info(cls):
else:
# Without any parameter, show all images and containers info
# Fetch data
images = DockerUtils.listImages(include_version_tag=False)
containers = DockerUtils.listContainers()
images = DockerUtils().listImages(include_version_tag=False)
containers = DockerUtils().listContainers()
# List and print images
color = ConsoleFormat.getArchColor(ParametersManager().arch)
logger.verbose(f"Listing local and remote Exegol images (filtering for architecture [{color}]{ParametersManager().arch}[/{color}])")
Expand All @@ -68,7 +67,6 @@ def info(cls):
@classmethod
def start(cls):
"""Create and/or start an exegol container to finally spawn an interactive shell"""
ExegolManager.print_version()
logger.info("Starting exegol")
# Check if the first positional parameter have been supplied
cls.__interactive_mode = not bool(ParametersManager().containertag)
Expand All @@ -86,7 +84,6 @@ def start(cls):
def exec(cls):
"""Create and/or start an exegol container to execute a specific command.
The execution can be seen in console output or be relayed in the background as a daemon."""
ExegolManager.print_version()
logger.info("Starting exegol")
if ParametersManager().tmp:
container = cls.__createTmpContainer(ParametersManager().selector)
Expand All @@ -103,7 +100,6 @@ def exec(cls):
@classmethod
def stop(cls):
"""Stop an exegol container"""
ExegolManager.print_version()
logger.info("Stopping exegol")
container = cls.__loadOrCreateContainer(multiple=True, must_exist=True)
assert container is not None and type(container) is list
Expand All @@ -113,7 +109,6 @@ def stop(cls):
@classmethod
def restart(cls):
"""Stop and start an exegol container"""
ExegolManager.print_version()
container = cast(ExegolContainer, cls.__loadOrCreateContainer(must_exist=True))
if container:
container.stop(timeout=5)
Expand All @@ -124,7 +119,6 @@ def restart(cls):
@classmethod
def install(cls):
"""Pull or build a docker exegol image"""
ExegolManager.print_version()
try:
if not ExegolModules().isExegolResourcesReady():
raise CancelOperation
Expand All @@ -136,7 +130,6 @@ def install(cls):
@classmethod
def update(cls):
"""Update python wrapper (git installation required) and Pull a docker exegol image"""
ExegolManager.print_version()
if ParametersManager().offline_mode:
logger.critical("It's not possible to update Exegol in offline mode. Please retry later with an internet connection.")
if not ParametersManager().skip_git:
Expand All @@ -149,7 +142,6 @@ def update(cls):
@classmethod
def uninstall(cls):
"""Remove an exegol image"""
ExegolManager.print_version()
logger.info("Uninstalling an exegol image")
# Set log level to verbose in order to show every image installed including the outdated.
if not logger.isEnabledFor(ExeLog.VERBOSE):
Expand All @@ -165,12 +157,11 @@ def uninstall(cls):
logger.error("Aborting operation.")
return
for img in images:
DockerUtils.removeImage(img)
DockerUtils().removeImage(img)

@classmethod
def remove(cls):
"""Remove an exegol container"""
ExegolManager.print_version()
logger.info("Removing an exegol container")
containers = cls.__loadOrCreateContainer(multiple=True, must_exist=True)
assert type(containers) is list
Expand All @@ -187,7 +178,7 @@ def remove(cls):
c.remove()
# If the image used is deprecated, it must be deleted after the removal of its container
if c.image.isLocked() and UserConfig().auto_remove_images:
DockerUtils.removeImage(c.image, upgrade_mode=True)
DockerUtils().removeImage(c.image, upgrade_mode=True)

@classmethod
def print_version(cls):
Expand All @@ -210,6 +201,10 @@ def print_version(cls):
elif 'b' in ConstantConfig.version:
logger.empty_line()
logger.warning("You are currently using a [orange3]Beta[/orange3] version of Exegol, which may be unstable.")

@classmethod
def print_debug_banner(cls):
"""Print header debug info"""
logger.debug(f"Pip installation: {boolFormatter(ConstantConfig.pip_installed)}")
logger.debug(f"Git source installation: {boolFormatter(ConstantConfig.git_source_installation)}")
logger.debug(f"Host OS: {EnvInfo.getHostOs().value} [bright_black]({EnvInfo.getDockerEngine().value})[/bright_black]")
Expand Down Expand Up @@ -265,9 +260,9 @@ def __loadOrInstallImage(cls,
if multiple:
image_selection = []
for image_tag in image_tags:
image_selection.append(DockerUtils.getInstalledImage(image_tag))
image_selection.append(DockerUtils().getInstalledImage(image_tag))
else:
image_selection = DockerUtils.getInstalledImage(image_tag)
image_selection = DockerUtils().getInstalledImage(image_tag)
except ObjectNotFound:
# ObjectNotFound is raised when the image_tag provided by the user does not match any existing image.
if image_tag is not None:
Expand Down Expand Up @@ -342,9 +337,9 @@ def __checkImageInstallationStatus(cls,
# Check if the selected image is installed and install it
logger.warning("The selected image is not installed.")
# Download remote image
if DockerUtils.downloadImage(check_img[i], install_mode=True):
if DockerUtils().downloadImage(check_img[i], install_mode=True):
# Select installed image
check_img[i] = DockerUtils.getInstalledImage(check_img[i].getName())
check_img[i] = DockerUtils().getInstalledImage(check_img[i].getName())
else:
logger.error("This image cannot be installed.")
return False, None
Expand Down Expand Up @@ -381,7 +376,7 @@ def __loadOrCreateContainer(cls,
# test each user tag
for container_tag in container_tags:
try:
cls.__container.append(DockerUtils.getContainer(container_tag))
cls.__container.append(DockerUtils().getContainer(container_tag))
except ObjectNotFound:
# on multi select, an object not found is not critical
if must_exist:
Expand All @@ -393,7 +388,7 @@ def __loadOrCreateContainer(cls,
raise NotImplemented
else:
assert container_tag is not None
cls.__container = DockerUtils.getContainer(container_tag)
cls.__container = DockerUtils().getContainer(container_tag)
except (ObjectNotFound, IndexError):
# ObjectNotFound is raised when the container_tag provided by the user does not match any existing container.
# IndexError is raise when no container exist (raised from TUI interactive selection)
Expand All @@ -417,10 +412,10 @@ def __interactiveSelection(cls,
# Object listing depending on the type
if object_type is ExegolContainer:
# List all images available
object_list = DockerUtils.listContainers()
object_list = DockerUtils().listContainers()
elif object_type is ExegolImage:
# List all images available
object_list = DockerUtils.listInstalledImages() if must_exist else DockerUtils.listImages()
object_list = DockerUtils().listInstalledImages() if must_exist else DockerUtils().listImages()
else:
logger.critical("Unknown object type during interactive selection. Exiting.")
raise Exception
Expand Down Expand Up @@ -517,7 +512,7 @@ def __createContainer(cls, name: Optional[str]) -> ExegolContainer:
logger.info("To use exegol [orange3]without interaction[/orange3], "
"read CLI options with [green]exegol start -h[/green]")

container = DockerUtils.createContainer(model)
container = DockerUtils().createContainer(model)
container.postCreateSetup()
return container

Expand All @@ -541,9 +536,9 @@ def __createTmpContainer(cls, image_name: Optional[str] = None) -> ExegolContain
model = ExegolContainerTemplate(name, config, image, hostname=ParametersManager().hostname)

# Mount entrypoint as a volume (because in tmp mode the container is created with run instead of create method)
model.config.addVolume(str(ConstantConfig.entrypoint_context_path_obj), "/.exegol/entrypoint.sh", must_exist=True, read_only=True)
model.config.addVolume(ConstantConfig.entrypoint_context_path_obj, "/.exegol/entrypoint.sh", must_exist=True, read_only=True)

container = DockerUtils.createContainer(model, temporary=True)
container = DockerUtils().createContainer(model, temporary=True)
container.postCreateSetup(is_temporary=True)
return container

Expand Down
16 changes: 8 additions & 8 deletions exegol/manager/UpdateManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ def updateImage(cls, tag: Optional[str] = None, install_mode: bool = False) -> O
if tag is None:
# Filter for updatable images
if install_mode:
available_images = [i for i in DockerUtils.listImages() if not i.isLocked()]
available_images = [i for i in DockerUtils().listImages() if not i.isLocked()]
else:
available_images = [i for i in DockerUtils.listImages() if i.isInstall() and not i.isUpToDate() and not i.isLocked()]
available_images = [i for i in DockerUtils().listImages() if i.isInstall() and not i.isUpToDate() and not i.isLocked()]
if len(available_images) == 0:
logger.success("All images already installed are up to date!")
return None
Expand All @@ -55,7 +55,7 @@ def updateImage(cls, tag: Optional[str] = None, install_mode: bool = False) -> O
else:
try:
# Find image by name
selected_image = DockerUtils.getImage(tag)
selected_image = DockerUtils().getImage(tag)
except ObjectNotFound:
# If the image do not exist, ask to build it
if install_mode:
Expand All @@ -66,21 +66,21 @@ def updateImage(cls, tag: Optional[str] = None, install_mode: bool = False) -> O

if selected_image is not None and type(selected_image) is ExegolImage:
# Update existing ExegolImage
if DockerUtils.downloadImage(selected_image, install_mode):
if DockerUtils().downloadImage(selected_image, install_mode):
sync_result = None
# Name comparison allow detecting images without version tag
if not selected_image.isVersionSpecific() and selected_image.getName() != selected_image.getLatestVersionName():
with console.status(f"Synchronizing version tag information. Please wait.", spinner_style="blue"):
# Download associated version tag.
sync_result = DockerUtils.downloadVersionTag(selected_image)
sync_result = DockerUtils().downloadVersionTag(selected_image)
# Detect if an error have been triggered during the download
if type(sync_result) is str:
logger.error(f"Error while downloading version tag, {sync_result}")
sync_result = None
# if version tag have been successfully download, returning ExegolImage from docker response
if sync_result is not None and type(sync_result) is ExegolImage:
return sync_result
return DockerUtils.getInstalledImage(selected_image.getName())
return DockerUtils().getInstalledImage(selected_image.getName())
elif type(selected_image) is str:
# Build a new image using TUI selected name, confirmation has already been requested by TUI
return cls.buildAndLoad(selected_image)
Expand Down Expand Up @@ -368,14 +368,14 @@ def __buildSource(cls, build_name: Optional[str] = None) -> str:
title="[not italic]:dog: [/not italic][gold3]Profile[/gold3]"))
logger.debug(f"Using {build_profile} build profile ({build_dockerfile})")
# Docker Build
DockerUtils.buildImage(tag=build_name, build_profile=build_profile, build_dockerfile=build_dockerfile, dockerfile_path=build_path.as_posix())
DockerUtils().buildImage(tag=build_name, build_profile=build_profile, build_dockerfile=build_dockerfile, dockerfile_path=build_path.as_posix())
return build_name

@classmethod
def buildAndLoad(cls, tag: str):
"""Build an image and load it"""
build_name = cls.__buildSource(tag)
return DockerUtils.getInstalledImage(build_name)
return DockerUtils().getInstalledImage(build_name)

@classmethod
def listBuildProfiles(cls, profiles_path: Path = ConstantConfig.build_context_path_obj) -> Dict:
Expand Down
Loading