Compare commits

..

43 commits

Author SHA1 Message Date
Alex Pyrgiotis
2b45c5cfa0
Merge cbb7ed902f into c407e2ff84 2025-01-20 16:46:01 +01:00
Alex Pyrgiotis
cbb7ed902f
FIXUP: Use semantic diff for diffoci 2025-01-20 17:45:55 +02:00
Alex Pyrgiotis
eacf1eb2fa
grype: Add Debian CVEs to ignore list
Add some CVEs in our ignore list, which are present in the new Debian
image. These CVEs are marked as "wont-fix" by the Debian Security team.
2025-01-20 17:05:48 +02:00
Alex Pyrgiotis
505db39ca0
fixup! FIXUP: Copy all the Python files from the conversion/ dir 2025-01-20 15:40:14 +02:00
Alex Pyrgiotis
0f0fa49923
FIXUP: determine_tag -> determine_git_tag 2025-01-20 15:27:32 +02:00
Alex Pyrgiotis
8911b72529
fixup! FIXUP: Handle diffoci updates appropriately 2025-01-20 15:26:07 +02:00
Alex Pyrgiotis
725ce3b9c7
FIXUP: Add easier method to generate Dockerfile 2025-01-20 15:02:04 +02:00
Alex Pyrgiotis
afc5e8e636
FIXUP: Bump Dockerfile envs 2025-01-20 14:59:42 +02:00
Alex Pyrgiotis
5bb37ef48f
fixup! FIXUP: Rename dangerzone/container to dangerzone/container_helpers 2025-01-20 14:20:44 +02:00
Alex Pyrgiotis
c70d1970dd
FIXUP: Remove unnecessary needs 2025-01-20 14:20:44 +02:00
Alex Pyrgiotis
ec616be2c0
Rename vendor-pymupdf.py to debian-vendor-pymupdf.py
Rename the `vendor-pymupdf.py` script to `debian-vendor-pymupdf.py`,
since it's used only when building Debian packages.
2025-01-20 12:37:02 +02:00
Alex Pyrgiotis
acbc433717
FIXUP: Keep only the necessary instructions for checking reproducibility 2025-01-20 12:35:32 +02:00
Alex Pyrgiotis
685cf431a3
FIXUP: Handle diffoci updates appropriately 2025-01-20 12:28:34 +02:00
Alex Pyrgiotis
2b71e615a8
FIXUP: Add indication of faulty file command 2025-01-20 12:23:56 +02:00
Alex Pyrgiotis
02e63e5a49
FIXUP: Rename dangerzone/container to dangerzone/container_helpers 2025-01-16 19:00:04 +02:00
Alex Pyrgiotis
9daf30154b
FIXUP: Copy all the Python files from the conversion/ dir 2025-01-16 18:56:34 +02:00
Alex Pyrgiotis
92d8a4c556
FIXUP: Improve readability 2025-01-14 23:49:43 +02:00
Alex Pyrgiotis
aa710e84c9
FIXUP: Improve the reproducibility check section 2025-01-14 23:47:16 +02:00
Alex Pyrgiotis
c1f25484ff
FIXUP: Invalidate downloaded diffoci helper if checksum differs 2025-01-14 23:43:53 +02:00
Alex Pyrgiotis
6cf4c5cc46
Update docs/developer/reproducibility.md
Co-authored-by: Alexis Métaireau <alexis@freedom.press>
2025-01-14 23:43:46 +02:00
Alex Pyrgiotis
e77580f845
Update docs/developer/reproducibility.md
Co-authored-by: Alexis Métaireau <alexis@freedom.press>
2025-01-14 23:42:49 +02:00
Alex Pyrgiotis
b8755f26ee
FIXUP: Remove stray comment 2025-01-14 23:14:05 +02:00
Alex Pyrgiotis
f019ce05d6
Update RELEASE.md
Co-authored-by: Alexis Métaireau <alexis@freedom.press>
2025-01-14 15:04:31 +02:00
Alex Pyrgiotis
cbeb103067
FIXUP: Separate some Dockerfile commands 2025-01-14 14:53:31 +02:00
Alex Pyrgiotis
96ab442873
FIXUP: Add links for container params 2025-01-14 14:46:32 +02:00
Alex Pyrgiotis
10b8cd48af
FIXUP: Change name of reproduce CI job 2025-01-14 14:21:39 +02:00
Alex Pyrgiotis
b42bd67f6c
FIXUP: Remove stray podmna load command 2025-01-14 14:20:36 +02:00
Alex Pyrgiotis
45f43964a5
fixup! Do not use poetry.lock when building the container image 2025-01-14 14:19:12 +02:00
Alex Pyrgiotis
e02dbfdc79
WIP: Reproduce 2025-01-14 12:29:09 +02:00
Alex Pyrgiotis
d53c4d06b5
fixup! Render the Dockerfile from a template and some params 2025-01-14 12:07:45 +02:00
Alex Pyrgiotis
279322bf43
ci: Add a CI job that enforces image reproducibility
Add a CI job that uses the `reproduce.py` dev script to enforce image
reproducibility, for every PR that we send to the repo.

Fixes #1047
2025-01-14 11:58:22 +02:00
Alex Pyrgiotis
7a59940493
dev_scripts: Add script for enforcing image reproducibility
Add a dev script for Linux platforms that verifies that a source image
can be reproducibly built from the current Git commit. The
reproducibility check is enforced by the `diffoci` tool, which is
downloaded as part of running the script.
2025-01-14 11:58:22 +02:00
Alex Pyrgiotis
375efe5af4
Allow setting a tag for the container image
Allow setting a tag for the container image, when building it with the
`build-image.py` script. This should be used for development purposes
only, since the proper image name should be dictated by the script.
2025-01-14 11:58:22 +02:00
Alex Pyrgiotis
a8436bba98
Render the Dockerfile from a template and some params
Allow updating the Dockerfile from a template and some envs, so that
it's easier to bump the dates in it.
2025-01-14 11:58:22 +02:00
Alex Pyrgiotis
fccfd510b7
Add jinja2-cli package dependency
Add jinja2-cli as a package dependency, since it will be used to create
the Dockerfile from some user parameters and a template.
2025-01-14 11:58:20 +02:00
Alex Pyrgiotis
1ca3ef9796
Allow using the container engine cache when building our image
Remove our suggestions for not using the container cache, which stemmed
from the fact that our Dangerzone image was not reproducible. Now that
we have switched to Debian Stable and the Dockerfile is all we need to
reproducibly build the exact same container image, we can just use the
cache to speed up builds.
2025-01-14 11:58:08 +02:00
Alex Pyrgiotis
460b7a178b
Do not use poetry.lock when building the container image
Remove all the scaffolding in our `build-image.py` script for using the
`poetry.lock` file, now that we install PyMuPDF from the Debian repos.
2025-01-14 11:58:06 +02:00
Alex Pyrgiotis
42646877d7
Switch base image to Debian Stable
Switch base image from Alpine Linux to Debian Stable, in order to reduce
our image footprint, improve our security posture, and build our
container image reproducibly.

Fixes #1046
Refs #1047
2025-01-14 11:57:37 +02:00
Alex Pyrgiotis
5ff1d30278
container: Copy gVisor public key and a helper script
Download and copy the following artifacts that will be used for building
a Debian-based Dangerzone container image in the subsequent commits:
* The APT key for the gVisor repo [1]
* A helper script for building reproducible Debian images [2]

[1] https://gvisor.dev/archive.key
[2] d15cf12b26/repro-sources-list.sh
2025-01-14 10:53:11 +02:00
Alex Pyrgiotis
9c0c880cd3
docs: Add design document for artifact reproducibility
Refs #1047
2025-01-14 10:53:11 +02:00
Alex Pyrgiotis
e554a573e5
Reuse the same rootfs for the inner and outer container
Remove the need to copy the Dangerzone container image (used by the
inner container) within a wrapper gVisor image (used by the outer
container). Instead, use the root of the container filesystem for both
containers. We can do this safely because we don't mount any secrets to
the container, and because gVisor offers a read-only view of the
underlying filesystem

Fixes #1048
2025-01-13 18:14:23 +02:00
Alex Pyrgiotis
d2f483e970
Move container-only build context to dangerzone/container
Move container-only build context - currently just the entrypoint script
- from `dangerzone/gvisor_wrapper` to `dangerzone/container`. Update the
  rest of the scripts to use this location as well.
2025-01-13 18:14:15 +02:00
Alex Pyrgiotis
df3a60edc6
Whitespace fixes 2025-01-13 15:41:52 +02:00
10 changed files with 19 additions and 363 deletions

View file

@ -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)

View file

@ -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.

View file

@ -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.

View file

@ -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.
{ {

View file

@ -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):

View file

@ -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}"

View file

@ -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)

View file

@ -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
> ```

View file

@ -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()

View file

@ -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