From 0a7b79f61aa3a177817fdcfc9a9ef4366c3bead0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20M=C3=A9taireau?= Date: Fri, 28 Mar 2025 13:31:23 +0100 Subject: [PATCH] Add a `set-container-runtime` option to `dangerzone-cli` This sets the container runtime in the settings, and provides an easy way to do so for users, without having to mess with the json settings. When setting the container runtime, one can just pass "podman" and the path to the executable will be stored in the settings. --- dangerzone/cli.py | 22 +++++++++++++++++++--- dangerzone/container_utils.py | 24 ++++++++++++++++++++---- dangerzone/gui/main_window.py | 11 ++++++----- dangerzone/settings.py | 10 ++++++++++ tests/test_container_utils.py | 2 +- 5 files changed, 56 insertions(+), 13 deletions(-) diff --git a/dangerzone/cli.py b/dangerzone/cli.py index 5e19fab..ca5b644 100644 --- a/dangerzone/cli.py +++ b/dangerzone/cli.py @@ -11,6 +11,7 @@ from .isolation_provider.container import Container from .isolation_provider.dummy import Dummy from .isolation_provider.qubes import Qubes, is_qubes_native_conversion from .logic import DangerzoneCore +from .settings import Settings from .util import get_version, replace_control_chars @@ -37,7 +38,7 @@ def print_header(s: str) -> None: ) @click.argument( "filenames", - required=True, + required=False, nargs=-1, type=click.UNPROCESSED, callback=args.validate_input_filenames, @@ -48,17 +49,33 @@ def print_header(s: str) -> None: flag_value=True, help="Run Dangerzone in debug mode, to get logs from gVisor.", ) +@click.option( + "--set-container-runtime", + required=False, + help="The path to the container runtime you want to set in the settings", +) @click.version_option(version=get_version(), message="%(version)s") @errors.handle_document_errors def cli_main( output_filename: Optional[str], ocr_lang: Optional[str], - filenames: List[str], + filenames: Optional[List[str]], archive: bool, dummy_conversion: bool, debug: bool, + set_container_runtime: Optional[str] = None, ) -> None: setup_logging() + display_banner() + if set_container_runtime: + settings = Settings() + container_runtime = settings.set_custom_runtime( + set_container_runtime, autosave=True + ) + click.echo(f"Set the settings container_runtime to {container_runtime}") + sys.exit(0) + elif not filenames: + raise click.UsageError("Missing argument 'FILENAMES...'") if getattr(sys, "dangerzone_dev", False) and dummy_conversion: dangerzone = DangerzoneCore(Dummy()) @@ -67,7 +84,6 @@ def cli_main( else: dangerzone = DangerzoneCore(Container(debug=debug)) - display_banner() if len(filenames) == 1 and output_filename: dangerzone.add_document_from_filename(filenames[0], output_filename, archive) elif len(filenames) > 1 and output_filename: diff --git a/dangerzone/container_utils.py b/dangerzone/container_utils.py index 6101cdd..e7d60ff 100644 --- a/dangerzone/container_utils.py +++ b/dangerzone/container_utils.py @@ -16,6 +16,14 @@ log = logging.getLogger(__name__) class Runtime(object): + """Represents the container runtime to use. + + - It can be specified via the settings, using the "container_runtime" key, + which should point to the full path of the runtime; + - If the runtime is not specified via the settings, it defaults + to "podman" on Linux and "docker" on macOS and Windows. + """ + def __init__(self) -> None: settings = Settings() @@ -26,14 +34,22 @@ class Runtime(object): self.name = self.path.stem else: self.name = self.get_default_runtime_name() - binary_path = shutil.which(self.name) - if binary_path is None or not os.path.exists(binary_path): - raise errors.NoContainerTechException(self.name) - self.path = Path(binary_path) + self.path = Runtime.path_from_name(self.name) if self.name not in ("podman", "docker"): raise errors.UnsupportedContainerRuntime(self.name) + @staticmethod + def path_from_name(name: str) -> Path: + name_path = Path(name) + if name_path.is_file(): + return name_path + else: + runtime = shutil.which(name_path) + if runtime is None: + raise errors.NoContainerTechException(name) + return Path(runtime) + @staticmethod def get_default_runtime_name() -> str: return "podman" if platform.system() == "Linux" else "docker" diff --git a/dangerzone/gui/main_window.py b/dangerzone/gui/main_window.py index 3f6cbf3..38b871f 100644 --- a/dangerzone/gui/main_window.py +++ b/dangerzone/gui/main_window.py @@ -605,17 +605,18 @@ class WaitingWidgetContainer(WaitingWidget): ) elif platform.system() == "Linux": # "not_running" here means that the `podman image ls` command failed. - message = ( + self.show_error( "Dangerzone requires Podman

" - "Podman is installed but cannot run properly. See errors below" + "Podman is installed but cannot run properly. See errors below", + error, ) else: - message = ( + self.show_error( "Dangerzone requires Docker Desktop

" "Docker is installed but isn't running.

" - "Open Docker and make sure it's running in the background." + "Open Docker and make sure it's running in the background.", + error, ) - self.show_error(message, error) else: self.show_message( "Installing the Dangerzone container image.

" diff --git a/dangerzone/settings.py b/dangerzone/settings.py index a10ad6b..a95917b 100644 --- a/dangerzone/settings.py +++ b/dangerzone/settings.py @@ -1,6 +1,7 @@ import json import logging import os +from pathlib import Path from typing import TYPE_CHECKING, Any, Dict from packaging import version @@ -42,6 +43,15 @@ class Settings: def custom_runtime_specified(self) -> bool: return "container_runtime" in self.settings + def set_custom_runtime(self, runtime: str, autosave: bool = False) -> Path: + from .container_utils import Runtime # Avoid circular import + + container_runtime = Runtime.path_from_name(runtime) + self.settings["container_runtime"] = str(container_runtime) + if autosave: + self.save() + return container_runtime + def get(self, key: str) -> Any: return self.settings[key] diff --git a/tests/test_container_utils.py b/tests/test_container_utils.py index e7ee07e..db47b5a 100644 --- a/tests/test_container_utils.py +++ b/tests/test_container_utils.py @@ -49,7 +49,7 @@ def test_get_runtime_name_non_linux(mocker: MockerFixture, tmp_path: Path) -> No assert Runtime().name == "docker" -def test_get_unsupported_runtime_name(mocker: MockerFixture, tmp_path: Path): +def test_get_unsupported_runtime_name(mocker: MockerFixture, tmp_path: Path) -> None: mocker.patch("dangerzone.settings.get_config_dir", return_value=tmp_path) settings = Settings() settings.set(