mirror of
https://github.com/freedomofpress/dangerzone.git
synced 2025-05-18 19:20:35 +02:00
Compare commits
43 commits
785ace8e30
...
2b45c5cfa0
Author | SHA1 | Date | |
---|---|---|---|
![]() |
2b45c5cfa0 | ||
![]() |
cbb7ed902f | ||
![]() |
eacf1eb2fa | ||
![]() |
505db39ca0 | ||
![]() |
0f0fa49923 | ||
![]() |
8911b72529 | ||
![]() |
725ce3b9c7 | ||
![]() |
afc5e8e636 | ||
![]() |
5bb37ef48f | ||
![]() |
c70d1970dd | ||
![]() |
ec616be2c0 | ||
![]() |
acbc433717 | ||
![]() |
685cf431a3 | ||
![]() |
2b71e615a8 | ||
![]() |
02e63e5a49 | ||
![]() |
9daf30154b | ||
![]() |
92d8a4c556 | ||
![]() |
aa710e84c9 | ||
![]() |
c1f25484ff | ||
![]() |
6cf4c5cc46 | ||
![]() |
e77580f845 | ||
![]() |
b8755f26ee | ||
![]() |
f019ce05d6 | ||
![]() |
cbeb103067 | ||
![]() |
96ab442873 | ||
![]() |
10b8cd48af | ||
![]() |
b42bd67f6c | ||
![]() |
45f43964a5 | ||
![]() |
e02dbfdc79 | ||
![]() |
d53c4d06b5 | ||
![]() |
279322bf43 | ||
![]() |
7a59940493 | ||
![]() |
375efe5af4 | ||
![]() |
a8436bba98 | ||
![]() |
fccfd510b7 | ||
![]() |
1ca3ef9796 | ||
![]() |
460b7a178b | ||
![]() |
42646877d7 | ||
![]() |
5ff1d30278 | ||
![]() |
9c0c880cd3 | ||
![]() |
e554a573e5 | ||
![]() |
d2f483e970 | ||
![]() |
df3a60edc6 |
10 changed files with 19 additions and 363 deletions
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
@ -497,4 +497,4 @@ jobs:
|
||||||
|
|
||||||
- name: Reproduce the same container image
|
- name: Reproduce the same container image
|
||||||
run: |
|
run: |
|
||||||
./dev_scripts/reproduce-image.py
|
./dev_scripts/reproduce.py --source podman://dangerzone.rocks/dangerzone:$(cat share/image-id.txt)
|
||||||
|
|
|
@ -8,13 +8,12 @@ Here is a list of tasks that should be done before issuing the release:
|
||||||
|
|
||||||
- [ ] Create a new issue named **QA and Release for version \<VERSION\>**, to track the general progress.
|
- [ ] Create a new issue named **QA and Release for version \<VERSION\>**, to track the general progress.
|
||||||
You can generate its content with the the `poetry run ./dev_scripts/generate-release-tasks.py` command.
|
You can generate its content with the the `poetry run ./dev_scripts/generate-release-tasks.py` command.
|
||||||
- [ ] [Add new Linux platforms and remove obsolete ones](https://github.com/freedomofpress/dangerzone/blob/main/RELEASE.md#add-new-linux-platforms-and-remove-obsolete-ones)
|
- [ ] [Add new Linux platforms and remove obsolete ones](https://github.com/freedomofpress/dangerzone/blob/main/RELEASE.md#add-new-platforms-and-remove-obsolete-ones)
|
||||||
- [ ] Bump the Python dependencies using `poetry lock`
|
- [ ] Bump the Python dependencies using `poetry lock`
|
||||||
- [ ] Update `version` in `pyproject.toml`
|
- [ ] Update `version` in `pyproject.toml`
|
||||||
- [ ] Update `share/version.txt`
|
- [ ] Update `share/version.txt`
|
||||||
- [ ] Update the "Version" field in `install/linux/dangerzone.spec`
|
- [ ] Update the "Version" field in `install/linux/dangerzone.spec`
|
||||||
- [ ] Bump the Debian version by adding a new changelog entry in `debian/changelog`
|
- [ ] Bump the Debian version by adding a new changelog entry in `debian/changelog`
|
||||||
- [ ] [Bump the minimum Docker Desktop versions](https://github.com/freedomofpress/dangerzone/blob/main/RELEASE.md#bump-the-minimum-docker-desktop-version) in `isolation_provider/container.py`
|
|
||||||
- [ ] Bump the dates and versions in the `Dockerfile`
|
- [ ] Bump the dates and versions in the `Dockerfile`
|
||||||
- [ ] Update screenshot in `README.md`, if necessary
|
- [ ] Update screenshot in `README.md`, if necessary
|
||||||
- [ ] CHANGELOG.md should be updated to include a list of all major changes since the last release
|
- [ ] CHANGELOG.md should be updated to include a list of all major changes since the last release
|
||||||
|
@ -48,12 +47,6 @@ In case of the removal of a version:
|
||||||
* Consult the previous paragraph, but also `grep` your way around.
|
* Consult the previous paragraph, but also `grep` your way around.
|
||||||
2. Add a notice in our `CHANGELOG.md` about the version removal.
|
2. Add a notice in our `CHANGELOG.md` about the version removal.
|
||||||
|
|
||||||
## Bump the minimum Docker Desktop version
|
|
||||||
|
|
||||||
We embed the minimum docker desktop versions inside Dangerzone, as an incentive for our macOS and Windows users to upgrade to the latests version.
|
|
||||||
|
|
||||||
You can find the latest version at the time of the release by looking at [their release notes](https://docs.docker.com/desktop/release-notes/)
|
|
||||||
|
|
||||||
## Large Document Testing
|
## Large Document Testing
|
||||||
|
|
||||||
Parallel to the QA process, the release candidate should be put through the large document tests in a dedicated machine to run overnight.
|
Parallel to the QA process, the release candidate should be put through the large document tests in a dedicated machine to run overnight.
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
This project includes third-party components as follows:
|
|
||||||
|
|
||||||
1. gVisor APT Key
|
|
||||||
- URL: https://gvisor.dev/archive.key
|
|
||||||
- Last updated: 2025-01-21
|
|
||||||
- Description: This is the public key used for verifying packages from the gVisor repository.
|
|
||||||
|
|
||||||
2. Reproducible Containers Helper Script
|
|
||||||
- URL: https://github.com/reproducible-containers/repro-sources-list.sh/blob/d15cf12b26395b857b24fba223b108aff1c91b26/repro-sources-list.sh
|
|
||||||
- Last updated: 2025-01-21
|
|
||||||
- Description: This script is used for building reproducible Debian images.
|
|
||||||
|
|
||||||
Please refer to the respective sources for licensing information and further details regarding the use of these components.
|
|
||||||
|
|
|
@ -59,82 +59,17 @@ oci_config: dict[str, typing.Any] = {
|
||||||
"root": {"path": "/", "readonly": True},
|
"root": {"path": "/", "readonly": True},
|
||||||
"hostname": "dangerzone",
|
"hostname": "dangerzone",
|
||||||
"mounts": [
|
"mounts": [
|
||||||
# Mask almost every system directory of the outer container, by mounting tmpfs
|
|
||||||
# on top of them. This is done to avoid leaking any sensitive information,
|
|
||||||
# either mounted by Podman/Docker, or when gVisor runs, since we reuse the same
|
|
||||||
# rootfs. We basically mask everything except for `/usr`, `/bin`, `/lib`,
|
|
||||||
# and `/etc`.
|
|
||||||
#
|
|
||||||
# Note that we set `--root /home/dangerzone/.containers` for the directory where
|
|
||||||
# gVisor will create files at runtime, which means that in principle, we are
|
|
||||||
# covered by the masking of `/home/dangerzone` that follows below.
|
|
||||||
#
|
|
||||||
# Finally, note that the following list has been taken from the dirs in our
|
|
||||||
# container image, and double-checked against the top-level dirs listed in the
|
|
||||||
# Filesystem Hierarchy Standard (FHS) [1]. It would be nice to have an allowlist
|
|
||||||
# approach instead of a denylist, but FHS is such an old standard that we don't
|
|
||||||
# expect any new top-level dirs to pop up any time soon.
|
|
||||||
#
|
|
||||||
# [1] https://en.wikipedia.org/wiki/Filesystem_Hierarchy_Standard
|
|
||||||
{
|
|
||||||
"destination": "/boot",
|
|
||||||
"type": "tmpfs",
|
|
||||||
"source": "tmpfs",
|
|
||||||
"options": ["nosuid", "noexec", "nodev", "ro"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"destination": "/dev",
|
|
||||||
"type": "tmpfs",
|
|
||||||
"source": "tmpfs",
|
|
||||||
"options": ["nosuid", "noexec", "nodev"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"destination": "/home",
|
|
||||||
"type": "tmpfs",
|
|
||||||
"source": "tmpfs",
|
|
||||||
"options": ["nosuid", "noexec", "nodev", "ro"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"destination": "/media",
|
|
||||||
"type": "tmpfs",
|
|
||||||
"source": "tmpfs",
|
|
||||||
"options": ["nosuid", "noexec", "nodev", "ro"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"destination": "/mnt",
|
|
||||||
"type": "tmpfs",
|
|
||||||
"source": "tmpfs",
|
|
||||||
"options": ["nosuid", "noexec", "nodev", "ro"],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"destination": "/proc",
|
"destination": "/proc",
|
||||||
"type": "proc",
|
"type": "proc",
|
||||||
"source": "proc",
|
"source": "proc",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"destination": "/root",
|
"destination": "/dev",
|
||||||
"type": "tmpfs",
|
|
||||||
"source": "tmpfs",
|
|
||||||
"options": ["nosuid", "noexec", "nodev", "ro"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"destination": "/run",
|
|
||||||
"type": "tmpfs",
|
"type": "tmpfs",
|
||||||
"source": "tmpfs",
|
"source": "tmpfs",
|
||||||
"options": ["nosuid", "noexec", "nodev"],
|
"options": ["nosuid", "noexec", "nodev"],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"destination": "/sbin",
|
|
||||||
"type": "tmpfs",
|
|
||||||
"source": "tmpfs",
|
|
||||||
"options": ["nosuid", "noexec", "nodev", "ro"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"destination": "/srv",
|
|
||||||
"type": "tmpfs",
|
|
||||||
"source": "tmpfs",
|
|
||||||
"options": ["nosuid", "noexec", "nodev", "ro"],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"destination": "/sys",
|
"destination": "/sys",
|
||||||
"type": "tmpfs",
|
"type": "tmpfs",
|
||||||
|
@ -147,27 +82,6 @@ oci_config: dict[str, typing.Any] = {
|
||||||
"source": "tmpfs",
|
"source": "tmpfs",
|
||||||
"options": ["nosuid", "noexec", "nodev"],
|
"options": ["nosuid", "noexec", "nodev"],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"destination": "/var",
|
|
||||||
"type": "tmpfs",
|
|
||||||
"source": "tmpfs",
|
|
||||||
"options": ["nosuid", "noexec", "nodev"],
|
|
||||||
},
|
|
||||||
# Also mask some files that are usually mounted by Docker / Podman. These files
|
|
||||||
# should not contain any sensitive information, since we use the `--network
|
|
||||||
# none` flag, but we want to make sure in any case.
|
|
||||||
{
|
|
||||||
"destination": "/etc/hostname",
|
|
||||||
"type": "bind",
|
|
||||||
"source": "/dev/null",
|
|
||||||
"options": ["rbind", "ro"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"destination": "/etc/hosts",
|
|
||||||
"type": "bind",
|
|
||||||
"source": "/dev/null",
|
|
||||||
"options": ["rbind", "ro"],
|
|
||||||
},
|
|
||||||
# LibreOffice needs a writable home directory, so just mount a tmpfs
|
# LibreOffice needs a writable home directory, so just mount a tmpfs
|
||||||
# over it.
|
# over it.
|
||||||
{
|
{
|
||||||
|
|
|
@ -124,7 +124,6 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||||
|
|
||||||
self.setWindowTitle("Dangerzone")
|
self.setWindowTitle("Dangerzone")
|
||||||
self.setWindowIcon(self.dangerzone.get_window_icon())
|
self.setWindowIcon(self.dangerzone.get_window_icon())
|
||||||
self.alert: Optional[Alert] = None
|
|
||||||
|
|
||||||
self.setMinimumWidth(600)
|
self.setMinimumWidth(600)
|
||||||
if platform.system() == "Darwin":
|
if platform.system() == "Darwin":
|
||||||
|
@ -227,13 +226,6 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||||
# This allows us to make QSS rules conditional on the OS color mode.
|
# This allows us to make QSS rules conditional on the OS color mode.
|
||||||
self.setProperty("OSColorMode", self.dangerzone.app.os_color_mode.value)
|
self.setProperty("OSColorMode", self.dangerzone.app.os_color_mode.value)
|
||||||
|
|
||||||
if hasattr(self.dangerzone.isolation_provider, "check_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)
|
|
||||||
|
|
||||||
self.show()
|
self.show()
|
||||||
|
|
||||||
def show_update_success(self) -> None:
|
def show_update_success(self) -> None:
|
||||||
|
@ -287,46 +279,6 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||||
self.dangerzone.settings.set("updater_check", check)
|
self.dangerzone.settings.set("updater_check", check)
|
||||||
self.dangerzone.settings.save()
|
self.dangerzone.settings.save()
|
||||||
|
|
||||||
def handle_docker_desktop_version_check(
|
|
||||||
self, is_version_valid: bool, version: str
|
|
||||||
) -> None:
|
|
||||||
hamburger_menu = self.hamburger_button.menu()
|
|
||||||
sep = hamburger_menu.insertSeparator(hamburger_menu.actions()[0])
|
|
||||||
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
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
message = """
|
|
||||||
<p>A new version of Docker Desktop is available. Please upgrade your system.</p>
|
|
||||||
<p>Visit the <a href="https://www.docker.com/products/docker-desktop">Docker Desktop website</a> to download the latest version.</p>
|
|
||||||
<em>Keeping Docker Desktop up to date allows you to have more confidence that your documents are processed safely.</em>
|
|
||||||
"""
|
|
||||||
self.alert = Alert(
|
|
||||||
self.dangerzone,
|
|
||||||
title="Upgrade Docker Desktop",
|
|
||||||
message=message,
|
|
||||||
ok_text="Ok",
|
|
||||||
has_cancel=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
def _launch_alert() -> None:
|
|
||||||
if self.alert:
|
|
||||||
self.alert.launch()
|
|
||||||
|
|
||||||
upgrade_action.triggered.connect(_launch_alert)
|
|
||||||
hamburger_menu.insertAction(sep, upgrade_action)
|
|
||||||
|
|
||||||
self.hamburger_button.setIcon(
|
|
||||||
QtGui.QIcon(
|
|
||||||
load_svg_image("hamburger_menu_update_error.svg", width=64, height=64)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
def handle_updates(self, report: UpdateReport) -> None:
|
def handle_updates(self, report: UpdateReport) -> None:
|
||||||
"""Handle update reports from the update checker thread.
|
"""Handle update reports from the update checker thread.
|
||||||
|
|
||||||
|
@ -413,7 +365,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||||
self.content_widget.show()
|
self.content_widget.show()
|
||||||
|
|
||||||
def closeEvent(self, e: QtGui.QCloseEvent) -> None:
|
def closeEvent(self, e: QtGui.QCloseEvent) -> None:
|
||||||
self.alert = Alert(
|
alert_widget = Alert(
|
||||||
self.dangerzone,
|
self.dangerzone,
|
||||||
message="Some documents are still being converted.\n Are you sure you want to quit?",
|
message="Some documents are still being converted.\n Are you sure you want to quit?",
|
||||||
ok_text="Abort conversions",
|
ok_text="Abort conversions",
|
||||||
|
@ -427,7 +379,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||||
else:
|
else:
|
||||||
self.dangerzone.app.exit(0)
|
self.dangerzone.app.exit(0)
|
||||||
else:
|
else:
|
||||||
accept_exit = self.alert.launch()
|
accept_exit = alert_widget.launch()
|
||||||
if not accept_exit:
|
if not accept_exit:
|
||||||
e.ignore()
|
e.ignore()
|
||||||
return
|
return
|
||||||
|
@ -671,7 +623,7 @@ class ContentWidget(QtWidgets.QWidget):
|
||||||
|
|
||||||
def documents_selected(self, docs: List[Document]) -> None:
|
def documents_selected(self, docs: List[Document]) -> None:
|
||||||
if self.conversion_started:
|
if self.conversion_started:
|
||||||
self.alert = Alert(
|
Alert(
|
||||||
self.dangerzone,
|
self.dangerzone,
|
||||||
message="Dangerzone does not support adding documents after the conversion has started.",
|
message="Dangerzone does not support adding documents after the conversion has started.",
|
||||||
has_cancel=False,
|
has_cancel=False,
|
||||||
|
@ -681,7 +633,7 @@ class ContentWidget(QtWidgets.QWidget):
|
||||||
# Ensure all files in batch are in the same directory
|
# Ensure all files in batch are in the same directory
|
||||||
dirnames = {os.path.dirname(doc.input_filename) for doc in docs}
|
dirnames = {os.path.dirname(doc.input_filename) for doc in docs}
|
||||||
if len(dirnames) > 1:
|
if len(dirnames) > 1:
|
||||||
self.alert = Alert(
|
Alert(
|
||||||
self.dangerzone,
|
self.dangerzone,
|
||||||
message="Dangerzone does not support adding documents from multiple locations.\n\n The newly added documents were ignored.",
|
message="Dangerzone does not support adding documents from multiple locations.\n\n The newly added documents were ignored.",
|
||||||
has_cancel=False,
|
has_cancel=False,
|
||||||
|
@ -850,14 +802,14 @@ class DocSelectionDropFrame(QtWidgets.QFrame):
|
||||||
text = f"{num_unsupported_docs} files are not supported."
|
text = f"{num_unsupported_docs} files are not supported."
|
||||||
ok_text = "Continue without these files"
|
ok_text = "Continue without these files"
|
||||||
|
|
||||||
self.alert = Alert(
|
alert_widget = Alert(
|
||||||
self.dangerzone,
|
self.dangerzone,
|
||||||
message=f"{text}\nThe supported extensions are: "
|
message=f"{text}\nThe supported extensions are: "
|
||||||
+ ", ".join(get_supported_extensions()),
|
+ ", ".join(get_supported_extensions()),
|
||||||
ok_text=ok_text,
|
ok_text=ok_text,
|
||||||
)
|
)
|
||||||
|
|
||||||
return self.alert.exec_()
|
return alert_widget.exec_()
|
||||||
|
|
||||||
|
|
||||||
class SettingsWidget(QtWidgets.QWidget):
|
class SettingsWidget(QtWidgets.QWidget):
|
||||||
|
|
|
@ -3,7 +3,7 @@ import os
|
||||||
import platform
|
import platform
|
||||||
import shlex
|
import shlex
|
||||||
import subprocess
|
import subprocess
|
||||||
from typing import List, Tuple
|
from typing import List
|
||||||
|
|
||||||
from .. import container_utils, errors
|
from .. import container_utils, errors
|
||||||
from ..document import Document
|
from ..document import Document
|
||||||
|
@ -11,10 +11,7 @@ from ..util import get_resource_path, get_subprocess_startupinfo
|
||||||
from .base import IsolationProvider, terminate_process_group
|
from .base import IsolationProvider, terminate_process_group
|
||||||
|
|
||||||
TIMEOUT_KILL = 5 # Timeout in seconds until the kill command returns.
|
TIMEOUT_KILL = 5 # Timeout in seconds until the kill command returns.
|
||||||
MINIMUM_DOCKER_DESKTOP = {
|
|
||||||
"Darwin": "4.36.0",
|
|
||||||
"Windows": "4.36.0",
|
|
||||||
}
|
|
||||||
|
|
||||||
# Define startupinfo for subprocesses
|
# Define startupinfo for subprocesses
|
||||||
if platform.system() == "Windows":
|
if platform.system() == "Windows":
|
||||||
|
@ -124,7 +121,6 @@ class Container(IsolationProvider):
|
||||||
def is_available() -> bool:
|
def is_available() -> bool:
|
||||||
container_runtime = container_utils.get_runtime()
|
container_runtime = container_utils.get_runtime()
|
||||||
runtime_name = container_utils.get_runtime_name()
|
runtime_name = container_utils.get_runtime_name()
|
||||||
|
|
||||||
# Can we run `docker/podman image ls` without an error
|
# Can we run `docker/podman image ls` without an error
|
||||||
with subprocess.Popen(
|
with subprocess.Popen(
|
||||||
[container_runtime, "image", "ls"],
|
[container_runtime, "image", "ls"],
|
||||||
|
@ -139,28 +135,6 @@ class Container(IsolationProvider):
|
||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def check_docker_desktop_version(self) -> 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}}"],
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.PIPE,
|
|
||||||
startupinfo=get_subprocess_startupinfo(),
|
|
||||||
) as p:
|
|
||||||
stdout, stderr = p.communicate()
|
|
||||||
if p.returncode != 0:
|
|
||||||
# When an error occurs, consider that the check went
|
|
||||||
# through, 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_DESKTOP[platform.system()]:
|
|
||||||
return False, version
|
|
||||||
return True, version
|
|
||||||
|
|
||||||
def doc_to_pixels_container_name(self, document: Document) -> str:
|
def doc_to_pixels_container_name(self, document: Document) -> str:
|
||||||
"""Unique container name for the doc-to-pixels phase."""
|
"""Unique container name for the doc-to-pixels phase."""
|
||||||
return f"dangerzone-doc-to-pixels-{document.id}"
|
return f"dangerzone-doc-to-pixels-{document.id}"
|
||||||
|
|
|
@ -16,7 +16,6 @@ DIFFOCI_CHECKSUM = "01d25fe690196945a6bd510d30559338aa489c034d3a1b895a0d82a4b860
|
||||||
DIFFOCI_PATH = (
|
DIFFOCI_PATH = (
|
||||||
pathlib.Path.home() / ".local" / "share" / "dangerzone-dev" / "helpers" / "diffoci"
|
pathlib.Path.home() / ".local" / "share" / "dangerzone-dev" / "helpers" / "diffoci"
|
||||||
)
|
)
|
||||||
IMAGE_NAME = "dangerzone.rocks/dangerzone"
|
|
||||||
|
|
||||||
|
|
||||||
def run(*args):
|
def run(*args):
|
||||||
|
@ -33,10 +32,6 @@ def git_commit_get():
|
||||||
return run("git", "rev-parse", "--short", "HEAD").decode().strip()
|
return run("git", "rev-parse", "--short", "HEAD").decode().strip()
|
||||||
|
|
||||||
|
|
||||||
def git_determine_tag():
|
|
||||||
return run("git", "describe", "--long", "--first-parent").decode().strip()[1:]
|
|
||||||
|
|
||||||
|
|
||||||
def git_verify(commit, source):
|
def git_verify(commit, source):
|
||||||
if not commit in source:
|
if not commit in source:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
|
@ -113,23 +108,14 @@ def build_image(tag, use_cache=False):
|
||||||
|
|
||||||
|
|
||||||
def parse_args():
|
def parse_args():
|
||||||
image_tag = git_determine_tag()
|
|
||||||
# TODO: Remove the local "podman://" prefix once we have started pushing images to a
|
|
||||||
# remote.
|
|
||||||
default_image_name = f"podman://{IMAGE_NAME}:{image_tag}"
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
prog=sys.argv[0],
|
prog=sys.argv[0],
|
||||||
description="Dev script for verifying container image reproducibility",
|
description="Dev script for verifying container image reproducibility",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--source",
|
"--source",
|
||||||
default=default_image_name,
|
required=True,
|
||||||
help=(
|
help="The source image name that you want to reproduce (in diffoci format)",
|
||||||
"The name of the image that you want to reproduce. If the image resides in"
|
|
||||||
" the local Docker / Podman engine, you can prefix it with podman:// or"
|
|
||||||
f" docker:// accordingly (default: {default_image_name})"
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--use-cache",
|
"--use-cache",
|
||||||
|
@ -157,7 +143,7 @@ def main():
|
||||||
diffoci_download()
|
diffoci_download()
|
||||||
|
|
||||||
tag = f"reproduce-{commit}"
|
tag = f"reproduce-{commit}"
|
||||||
target = f"{IMAGE_NAME}:{tag}"
|
target = f"dangerzone.rocks/dangerzone:{tag}"
|
||||||
logger.info(f"Building container image and tagging it as '{target}'")
|
logger.info(f"Building container image and tagging it as '{target}'")
|
||||||
build_image(tag, args.use_cache)
|
build_image(tag, args.use_cache)
|
||||||
|
|
|
@ -45,23 +45,15 @@ trigger a CI error.
|
||||||
|
|
||||||
### Reproducing the image
|
### Reproducing the image
|
||||||
|
|
||||||
For a simple way to reproduce a Dangerzone container image, you can checkout the
|
For a simple way to reproduce a Dangerzone container image, either local or
|
||||||
commit this image was built from (you can find it from the image tag in its
|
pushed to a container registry, you can checkout the commit this image was built
|
||||||
`g<commit>` portion), and run the following command in a Linux environment:
|
from (you can find it from the image tag in its `g<commit>` portion), and run
|
||||||
|
the following command in a Linux environment:
|
||||||
|
|
||||||
```
|
```
|
||||||
./dev_scripts/reproduce-image.py --source <image>
|
./dev_scripts/reproduce.py <image>
|
||||||
```
|
```
|
||||||
|
|
||||||
This command will download the `diffoci` helper, build a container image from
|
This command will download the `diffoci` helper, build a container image from
|
||||||
the current Git commit, and ensure that the built image matches the source one,
|
the current Git commit, and ensure that the built image matches the source one,
|
||||||
with the exception of image names and file timestamps.
|
with the exception of image names and file timestamps.
|
||||||
|
|
||||||
> [!TIP]
|
|
||||||
> If the source image is not pushed to a registry, and is local instead, you
|
|
||||||
> can prefix it with `docker://` or `podman://` accordingly, so that `diffoci`
|
|
||||||
> can load it from the local Docker / Podman container engine. For example:
|
|
||||||
>
|
|
||||||
> ```
|
|
||||||
> ./dev_scripts/reproduce.py --source podman://dangerzone.rocks/dangerzone:0.8.0-125-g725ce3b
|
|
||||||
> ```
|
|
||||||
|
|
|
@ -587,57 +587,3 @@ def test_installation_failure_return_false(qtbot: QtBot, mocker: MockerFixture)
|
||||||
|
|
||||||
assert "the following error occured" in widget.label.text()
|
assert "the following error occured" in widget.label.text()
|
||||||
assert "The image cannot be found" in widget.traceback.toPlainText()
|
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"
|
|
||||||
)
|
|
||||||
|
|
||||||
alert_spy = mocker.spy(window.alert, "launch")
|
|
||||||
|
|
||||||
# Clicking the menu item should open a warning message
|
|
||||||
def _check_alert_displayed() -> None:
|
|
||||||
alert_spy.assert_any_call()
|
|
||||||
if window.alert:
|
|
||||||
window.alert.close()
|
|
||||||
|
|
||||||
QtCore.QTimer.singleShot(0, _check_alert_displayed)
|
|
||||||
menu_actions[0].trigger()
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import os
|
import os
|
||||||
import platform
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from pytest_mock import MockerFixture
|
from pytest_mock import MockerFixture
|
||||||
|
@ -109,92 +108,6 @@ class TestContainer(IsolationProviderTest):
|
||||||
with pytest.raises(errors.ImageNotPresentException):
|
with pytest.raises(errors.ImageNotPresentException):
|
||||||
provider.install()
|
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
|
|
||||||
) -> None:
|
|
||||||
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
|
|
||||||
) -> None:
|
|
||||||
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
|
|
||||||
) -> None:
|
|
||||||
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
|
|
||||||
) -> None:
|
|
||||||
assert (True, "") == provider.check_docker_desktop_version()
|
|
||||||
|
|
||||||
|
|
||||||
class TestContainerTermination(IsolationProviderTermination):
|
class TestContainerTermination(IsolationProviderTermination):
|
||||||
pass
|
pass
|
||||||
|
|
Loading…
Reference in a new issue