diff --git a/dangerzone/gui/main_window.py b/dangerzone/gui/main_window.py index 782da4b..aae0f59 100644 --- a/dangerzone/gui/main_window.py +++ b/dangerzone/gui/main_window.py @@ -235,13 +235,14 @@ class MainWindow(QtWidgets.QMainWindow): # This allows us to make QSS rules conditional on the OS color mode. self.setProperty("OSColorMode", self.dangerzone.app.os_color_mode.value) + if isinstance(self.dangerzone.isolation_provider, Container): + is_version_valid, version = ( + self.dangerzone.isolation_provider.check_docker_desktop_version() + ) + if not is_version_valid: + self.handle_docker_desktop_version_check(is_version_valid, version) + self.show() - print("Checking Docker Desktop version") - is_version_valid, version = ( - self.dangerzone.isolation_provider.check_docker_desktop_version() - ) - if not is_version_valid: - self.handle_docker_desktop_version_check(is_version_valid, version) def show_update_success(self) -> None: """Inform the user about a new Dangerzone release.""" @@ -299,8 +300,8 @@ class MainWindow(QtWidgets.QMainWindow): ) -> None: hamburger_menu = self.hamburger_button.menu() sep = hamburger_menu.insertSeparator(hamburger_menu.actions()[0]) - error_action = QAction("Docker Desktop should be upgraded", hamburger_menu) - error_action.setIcon( + upgrade_action = QAction("Docker Desktop should be upgraded", hamburger_menu) + upgrade_action.setIcon( QtGui.QIcon( load_svg_image( "hamburger_menu_update_dot_error.svg", width=64, height=64 @@ -308,23 +309,21 @@ class MainWindow(QtWidgets.QMainWindow): ) ) - def _show_alert() -> None: - message = """ -

Your Docker Desktop version is too old. Please upgrade to a more recent version.

-

Visit the Docker Desktop website to download the latest version.

- Updating Docker Desktop allows you to have more confidence that you can process your documents safely. - """ - widget = Alert( - self.dangerzone, - title="Your Docker Desktop version should be upgraded", - message=message, - ok_text="Ok", - has_cancel=False, - ) - widget.launch() + message = """ +

A new version of Docker Desktop is available. Please upgrade your system.

+

Visit the Docker Desktop website to download the latest version.

+ Keeping Docker Desktop up to date allows you to have more confidence that your documents are processed safely. + """ + self.alert = Alert( + self.dangerzone, + title="Upgrade Docker Desktop", + message=message, + ok_text="Ok", + has_cancel=False, + ) - error_action.triggered.connect(_show_alert) - hamburger_menu.insertAction(sep, error_action) + upgrade_action.triggered.connect(lambda: widget.launch()) + hamburger_menu.insertAction(sep, upgrade_action) self.hamburger_button.setIcon( QtGui.QIcon( diff --git a/dangerzone/isolation_provider/container.py b/dangerzone/isolation_provider/container.py index 91a50e3..c86baf0 100644 --- a/dangerzone/isolation_provider/container.py +++ b/dangerzone/isolation_provider/container.py @@ -12,9 +12,9 @@ from ..util import get_resource_path, get_subprocess_startupinfo from .base import IsolationProvider, terminate_process_group TIMEOUT_KILL = 5 # Timeout in seconds until the kill command returns. -MINIMUM_DOCKER_VERSION = { - "Darwin": "4.35.1", - "Windows": "4.35.1", +MINIMUM_DOCKER_DESKTOP = { + "Darwin": "4.36.0", + "Windows": "4.36.0", } # Define startupinfo for subprocesses @@ -203,6 +203,7 @@ class Container(IsolationProvider): @staticmethod def check_docker_desktop_version() -> Tuple[bool, str]: # On windows and darwin, check that the minimum version is met + version = "" if platform.system() != "Linux": with subprocess.Popen( ["docker", "version", "--format", "{{.Server.Platform.Name}}"], @@ -212,12 +213,15 @@ class Container(IsolationProvider): ) as p: stdout, stderr = p.communicate() if p.returncode != 0: - raise NotAvailableContainerTechException("docker", stderr.decode()) + # In the case where there were an error, consider that + # the check went trough, as we're checking for installation + # compatibiliy somewhere else already + return True, version # The output is like "Docker Desktop 4.35.1 (173168)" version = stdout.decode().replace("Docker Desktop", "").split()[0] - if version < MINIMUM_DOCKER_VERSION[platform.system()]: + if version < MINIMUM_DOCKER_DESKTOP[platform.system()]: return False, version - return True, "" + return True, version @staticmethod def is_runtime_available() -> bool: diff --git a/tests/gui/test_main_window.py b/tests/gui/test_main_window.py index ff45075..0b5d803 100644 --- a/tests/gui/test_main_window.py +++ b/tests/gui/test_main_window.py @@ -7,7 +7,6 @@ from typing import List from pytest import MonkeyPatch, fixture from pytest_mock import MockerFixture -from pytest_subprocess import FakeProcess from pytestqt.qtbot import QtBot from dangerzone.document import Document @@ -590,3 +589,48 @@ def test_installation_failure_return_false(qtbot: QtBot, mocker: MockerFixture) assert "the following error occured" in widget.label.text() assert "The image cannot be found" in widget.traceback.toPlainText() + + +def test_up_to_date_docker_desktop_does_nothing( + qtbot: QtBot, mocker: MockerFixture +) -> None: + # Setup install to return False + mock_app = mocker.MagicMock() + dummy = mocker.MagicMock(spec=Container) + dummy.check_docker_desktop_version.return_value = (True, "1.0.0") + dz = DangerzoneGui(mock_app, dummy) + + window = MainWindow(dz) + qtbot.addWidget(window) + + menu_actions = window.hamburger_button.menu().actions() + assert "Docker Desktop should be upgraded" not in [ + a.toolTip() for a in menu_actions + ] + + +def test_outdated_docker_desktop_displays_warning( + qtbot: QtBot, mocker: MockerFixture +) -> None: + # Setup install to return False + mock_app = mocker.MagicMock() + dummy = mocker.MagicMock(spec=Container) + dummy.check_docker_desktop_version.return_value = (False, "1.0.0") + dz = DangerzoneGui(mock_app, dummy) + + load_svg_spy = mocker.spy(main_window_module, "load_svg_image") + + window = MainWindow(dz) + qtbot.addWidget(window) + + menu_actions = window.hamburger_button.menu().actions() + assert menu_actions[0].toolTip() == "Docker Desktop should be upgraded" + + # Check that the hamburger icon has changed with the expected SVG image. + assert load_svg_spy.call_count == 4 + assert ( + load_svg_spy.call_args_list[2].args[0] == "hamburger_menu_update_dot_error.svg" + ) + + # Clicking the menu item should open a warning message + menu_actions[0].trigger() diff --git a/tests/isolation_provider/test_container.py b/tests/isolation_provider/test_container.py index 3fb3243..1aea30e 100644 --- a/tests/isolation_provider/test_container.py +++ b/tests/isolation_provider/test_container.py @@ -13,6 +13,7 @@ from dangerzone.isolation_provider.container import ( from dangerzone.isolation_provider.qubes import is_qubes_native_conversion from .base import IsolationProviderTermination, IsolationProviderTest +import platform # Run the tests in this module only if we can spawn containers. if is_qubes_native_conversion(): @@ -116,6 +117,92 @@ class TestContainer(IsolationProviderTest): with pytest.raises(ImageNotPresentException): provider.install() + @pytest.mark.skipif( + platform.system() not in ("Windows", "Darwin"), + reason="macOS and Windows specific", + ) + def test_old_docker_desktop_version_is_detected( + self, mocker: MockerFixture, provider: Container, fp: FakeProcess + ): + fp.register_subprocess( + [ + "docker", + "version", + "--format", + "{{.Server.Platform.Name}}", + ], + stdout="Docker Desktop 1.0.0 (173100)", + ) + + mocker.patch( + "dangerzone.isolation_provider.container.MINIMUM_DOCKER_DESKTOP", + {"Darwin": "1.0.1", "Windows": "1.0.1"}, + ) + assert (False, "1.0.0") == provider.check_docker_desktop_version() + + @pytest.mark.skipif( + platform.system() not in ("Windows", "Darwin"), + reason="macOS and Windows specific", + ) + def test_up_to_date_docker_desktop_version_is_detected( + self, mocker: MockerFixture, provider: Container, fp: FakeProcess + ): + fp.register_subprocess( + [ + "docker", + "version", + "--format", + "{{.Server.Platform.Name}}", + ], + stdout="Docker Desktop 1.0.1 (173100)", + ) + + # Require version 1.0.1 + mocker.patch( + "dangerzone.isolation_provider.container.MINIMUM_DOCKER_DESKTOP", + {"Darwin": "1.0.1", "Windows": "1.0.1"}, + ) + assert (True, "1.0.1") == provider.check_docker_desktop_version() + + fp.register_subprocess( + [ + "docker", + "version", + "--format", + "{{.Server.Platform.Name}}", + ], + stdout="Docker Desktop 2.0.0 (173100)", + ) + assert (True, "2.0.0") == provider.check_docker_desktop_version() + + @pytest.mark.skipif( + platform.system() not in ("Windows", "Darwin"), + reason="macOS and Windows specific", + ) + def test_docker_desktop_version_failure_returns_true( + self, mocker: MockerFixture, provider: Container, fp: FakeProcess + ): + fp.register_subprocess( + [ + "docker", + "version", + "--format", + "{{.Server.Platform.Name}}", + ], + stderr="Oopsie", + returncode=1, + ) + assert provider.check_docker_desktop_version() == (True, "") + + @pytest.mark.skipif( + platform.system() != "Linux", + reason="Linux specific", + ) + def test_linux_skips_desktop_version_check_returns_true( + self, mocker: MockerFixture, provider: Container + ): + assert (True, "") == provider.check_docker_desktop_version() + class TestContainerTermination(IsolationProviderTermination): pass