diff --git a/dangerzone/container_utils.py b/dangerzone/container_utils.py index 5794764..bbdb75a 100644 --- a/dangerzone/container_utils.py +++ b/dangerzone/container_utils.py @@ -5,6 +5,7 @@ import subprocess from typing import List, Tuple from . import errors +from .settings import Settings from .util import get_resource_path, get_subprocess_startupinfo CONTAINER_NAME = "dangerzone.rocks/dangerzone" @@ -13,14 +14,22 @@ log = logging.getLogger(__name__) def get_runtime_name() -> str: - if platform.system() == "Linux": - runtime_name = "podman" - else: - # Windows, Darwin, and unknown use docker for now, dangerzone-vm eventually - runtime_name = "docker" + settings = Settings() + try: + runtime_name = settings.get("container_runtime") + except KeyError: + return "podman" if platform.system() == "Linux" else "docker" 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]: """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. """ # Get the Docker/Podman version, using a Go template. - runtime = get_runtime_name() - if runtime == "podman": + runtime_name = get_runtime_name() + + if runtime_name == "podman": query = "{{.Client.Version}}" else: query = "{{.Server.Version}}" - cmd = [runtime, "version", "-f", query] + cmd = [get_runtime(), "version", "-f", query] try: version = subprocess.run( cmd, @@ -46,7 +56,7 @@ def get_runtime_version() -> Tuple[int, int]: check=True, ).stdout.decode() 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 # 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)) except Exception as e: 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}" ) 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]: """Get the tags of all loaded Dangerzone images. diff --git a/dangerzone/gui/main_window.py b/dangerzone/gui/main_window.py index 9e4e23b..d162fcf 100644 --- a/dangerzone/gui/main_window.py +++ b/dangerzone/gui/main_window.py @@ -574,8 +574,15 @@ class WaitingWidgetContainer(WaitingWidget): self.finished.emit() def state_change(self, state: str, error: Optional[str] = None) -> None: + custom_runtime = self.dangerzone.settings.custom_runtime_specified() + if state == "not_installed": - if platform.system() == "Linux": + if custom_runtime: + self.show_error( + "We could not find the container runtime defined in your settings

" + "Please check your settings, install it if needed, and retry." + ) + elif platform.system() == "Linux": self.show_error( "Dangerzone requires Podman

" "Install it and retry." @@ -588,7 +595,12 @@ class WaitingWidgetContainer(WaitingWidget): ) elif state == "not_running": - if platform.system() == "Linux": + if custom_runtime: + self.show_error( + "We were unable to start the container runtime defined in your settings

" + "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. message = ( "Dangerzone requires Podman

" diff --git a/dangerzone/settings.py b/dangerzone/settings.py index 4c8b939..a10ad6b 100644 --- a/dangerzone/settings.py +++ b/dangerzone/settings.py @@ -39,6 +39,9 @@ class Settings: "updater_errors": 0, } + def custom_runtime_specified(self) -> bool: + return "container_runtime" in self.settings + def get(self, key: str) -> Any: return self.settings[key] diff --git a/tests/isolation_provider/test_container.py b/tests/isolation_provider/test_container.py index 7f39664..797986e 100644 --- a/tests/isolation_provider/test_container.py +++ b/tests/isolation_provider/test_container.py @@ -87,7 +87,7 @@ class TestContainer(IsolationProviderTest): ) -> None: """When an image keep being not installed, it should return False""" fp.register_subprocess( - ["podman", "version", "-f", "{{.Client.Version}}"], + [container_utils.get_runtime(), "version", "-f", "{{.Client.Version}}"], stdout="4.0.0", ) diff --git a/tests/test_container_utils.py b/tests/test_container_utils.py new file mode 100644 index 0000000..e921507 --- /dev/null +++ b/tests/test_container_utils.py @@ -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"