Allow to read the container runtime from the settings

Add a few tests for this along the way, and update the end-user messages
about Docker/Podman to account for this change.
This commit is contained in:
Alexis Métaireau 2025-03-24 16:39:39 +01:00
parent b551a4dec4
commit c0215062bc
No known key found for this signature in database
GPG key ID: C65C7A89A8FFC56E
5 changed files with 66 additions and 21 deletions

View file

@ -5,6 +5,7 @@ import subprocess
from typing import List, Tuple from typing import List, Tuple
from . import errors from . import errors
from .settings import Settings
from .util import get_resource_path, get_subprocess_startupinfo from .util import get_resource_path, get_subprocess_startupinfo
CONTAINER_NAME = "dangerzone.rocks/dangerzone" CONTAINER_NAME = "dangerzone.rocks/dangerzone"
@ -13,14 +14,22 @@ log = logging.getLogger(__name__)
def get_runtime_name() -> str: def get_runtime_name() -> str:
if platform.system() == "Linux": settings = Settings()
runtime_name = "podman" try:
else: runtime_name = settings.get("container_runtime")
# Windows, Darwin, and unknown use docker for now, dangerzone-vm eventually except KeyError:
runtime_name = "docker" return "podman" if platform.system() == "Linux" else "docker"
return runtime_name return runtime_name
def get_runtime() -> str:
container_tech = get_runtime_name()
runtime = shutil.which(container_tech)
if runtime is None:
raise errors.NoContainerTechException(container_tech)
return runtime
def get_runtime_version() -> Tuple[int, int]: def get_runtime_version() -> Tuple[int, int]:
"""Get the major/minor parts of the Docker/Podman version. """Get the major/minor parts of the Docker/Podman version.
@ -31,13 +40,14 @@ def get_runtime_version() -> Tuple[int, int]:
semver parser is an overkill. semver parser is an overkill.
""" """
# Get the Docker/Podman version, using a Go template. # Get the Docker/Podman version, using a Go template.
runtime = get_runtime_name() runtime_name = get_runtime_name()
if runtime == "podman":
if runtime_name == "podman":
query = "{{.Client.Version}}" query = "{{.Client.Version}}"
else: else:
query = "{{.Server.Version}}" query = "{{.Server.Version}}"
cmd = [runtime, "version", "-f", query] cmd = [get_runtime(), "version", "-f", query]
try: try:
version = subprocess.run( version = subprocess.run(
cmd, cmd,
@ -46,7 +56,7 @@ def get_runtime_version() -> Tuple[int, int]:
check=True, check=True,
).stdout.decode() ).stdout.decode()
except Exception as e: except Exception as e:
msg = f"Could not get the version of the {runtime.capitalize()} tool: {e}" msg = f"Could not get the version of the {runtime.name.capitalize()} tool: {e}"
raise RuntimeError(msg) from e raise RuntimeError(msg) from e
# Parse this version and return the major/minor parts, since we don't need the # Parse this version and return the major/minor parts, since we don't need the
@ -56,20 +66,12 @@ def get_runtime_version() -> Tuple[int, int]:
return (int(major), int(minor)) return (int(major), int(minor))
except Exception as e: except Exception as e:
msg = ( msg = (
f"Could not parse the version of the {runtime.capitalize()} tool" f"Could not parse the version of the {runtime_name.capitalize()} tool"
f" (found: '{version}') due to the following error: {e}" f" (found: '{version}') due to the following error: {e}"
) )
raise RuntimeError(msg) raise RuntimeError(msg)
def get_runtime() -> str:
container_tech = get_runtime_name()
runtime = shutil.which(container_tech)
if runtime is None:
raise errors.NoContainerTechException(container_tech)
return runtime
def list_image_tags() -> List[str]: def list_image_tags() -> List[str]:
"""Get the tags of all loaded Dangerzone images. """Get the tags of all loaded Dangerzone images.

View file

@ -574,8 +574,15 @@ class WaitingWidgetContainer(WaitingWidget):
self.finished.emit() self.finished.emit()
def state_change(self, state: str, error: Optional[str] = None) -> None: def state_change(self, state: str, error: Optional[str] = None) -> None:
custom_runtime = self.dangerzone.settings.custom_runtime_specified()
if state == "not_installed": if state == "not_installed":
if platform.system() == "Linux": if custom_runtime:
self.show_error(
"<strong>We could not find the container runtime defined in your settings</strong><br><br>"
"Please check your settings, install it if needed, and retry."
)
elif platform.system() == "Linux":
self.show_error( self.show_error(
"<strong>Dangerzone requires Podman</strong><br><br>" "<strong>Dangerzone requires Podman</strong><br><br>"
"Install it and retry." "Install it and retry."
@ -588,7 +595,12 @@ class WaitingWidgetContainer(WaitingWidget):
) )
elif state == "not_running": elif state == "not_running":
if platform.system() == "Linux": if custom_runtime:
self.show_error(
"<strong>We were unable to start the container runtime defined in your settings</strong><br><br>"
"Please check your settings, install it if needed, and retry."
)
elif platform.system() == "Linux":
# "not_running" here means that the `podman image ls` command failed. # "not_running" here means that the `podman image ls` command failed.
message = ( message = (
"<strong>Dangerzone requires Podman</strong><br><br>" "<strong>Dangerzone requires Podman</strong><br><br>"

View file

@ -39,6 +39,9 @@ class Settings:
"updater_errors": 0, "updater_errors": 0,
} }
def custom_runtime_specified(self) -> bool:
return "container_runtime" in self.settings
def get(self, key: str) -> Any: def get(self, key: str) -> Any:
return self.settings[key] return self.settings[key]

View file

@ -87,7 +87,7 @@ class TestContainer(IsolationProviderTest):
) -> None: ) -> None:
"""When an image keep being not installed, it should return False""" """When an image keep being not installed, it should return False"""
fp.register_subprocess( fp.register_subprocess(
["podman", "version", "-f", "{{.Client.Version}}"], [container_utils.get_runtime(), "version", "-f", "{{.Client.Version}}"],
stdout="4.0.0", stdout="4.0.0",
) )

View file

@ -0,0 +1,28 @@
from pathlib import Path
from pytest_mock import MockerFixture
from dangerzone.container_utils import get_runtime_name
from dangerzone.settings import Settings
def test_get_runtime_name_from_settings(mocker: MockerFixture, tmp_path: Path) -> None:
mocker.patch("dangerzone.settings.get_config_dir", return_value=tmp_path)
settings = Settings()
settings.set("container_runtime", "new-kid-on-the-block", autosave=True)
assert get_runtime_name() == "new-kid-on-the-block"
def test_get_runtime_name_linux(mocker: MockerFixture) -> None:
mocker.patch("platform.system", return_value="Linux")
assert get_runtime_name() == "podman"
def test_get_runtime_name_non_linux(mocker: MockerFixture) -> None:
mocker.patch("platform.system", return_value="Windows")
assert get_runtime_name() == "docker"
mocker.patch("platform.system", return_value="Something else")
assert get_runtime_name() == "docker"