Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Build and reproduce a multi-platform Dangerzone image in GitHub actions #1086

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 3 additions & 10 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,19 +85,12 @@ jobs:
id: cache-container-image
uses: actions/cache@v4
with:
key: v4-${{ steps.date.outputs.date }}-${{ hashFiles('Dockerfile', 'dangerzone/conversion/*.py', 'dangerzone/container_helpers/*', 'install/common/build-image.py') }}
key: v5-${{ steps.date.outputs.date }}-${{ hashFiles('Dockerfile', 'dangerzone/conversion/*.py', 'dangerzone/container_helpers/*', 'install/common/build-image.py') }}
path: |
share/container.tar.gz
share/container.tar
share/image-id.txt

- name: Build and push Dangerzone image
- name: Build Dangerzone image
if: ${{ steps.cache-container-image.outputs.cache-hit != 'true' }}
run: |
sudo apt-get install -y python3-poetry
python3 ./install/common/build-image.py
echo ${{ github.token }} | podman login ghcr.io -u USERNAME --password-stdin
gunzip -c share/container.tar.gz | podman load
tag=$(cat share/image-id.txt)
podman push \
dangerzone.rocks/dangerzone:$tag \
${{ env.IMAGE_REGISTRY }}/dangerzone/dangerzone:tag
47 changes: 10 additions & 37 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ jobs:
id: cache-container-image
uses: actions/cache@v4
with:
key: v4-${{ steps.date.outputs.date }}-${{ hashFiles('Dockerfile', 'dangerzone/conversion/*.py', 'dangerzone/container_helpers/*', 'install/common/build-image.py') }}
key: v5-${{ steps.date.outputs.date }}-${{ hashFiles('Dockerfile', 'dangerzone/conversion/*.py', 'dangerzone/container_helpers/*', 'install/common/build-image.py') }}
path: |-
share/container.tar.gz
share/container.tar
share/image-id.txt

- name: Build Dangerzone container image
Expand All @@ -72,8 +72,8 @@ jobs:
- name: Upload container image
uses: actions/upload-artifact@v4
with:
name: container.tar.gz
path: share/container.tar.gz
name: container.tar
path: share/container.tar

download-tessdata:
name: Download and cache Tesseract data
Expand Down Expand Up @@ -226,9 +226,9 @@ jobs:
- name: Restore container cache
uses: actions/cache/restore@v4
with:
key: v4-${{ steps.date.outputs.date }}-${{ hashFiles('Dockerfile', 'dangerzone/conversion/*.py', 'dangerzone/container_helpers/*', 'install/common/build-image.py') }}
key: v5-${{ steps.date.outputs.date }}-${{ hashFiles('Dockerfile', 'dangerzone/conversion/*.py', 'dangerzone/container_helpers/*', 'install/common/build-image.py') }}
path: |-
share/container.tar.gz
share/container.tar
share/image-id.txt
fail-on-cache-miss: true

Expand Down Expand Up @@ -333,9 +333,9 @@ jobs:
- name: Restore container image
uses: actions/cache/restore@v4
with:
key: v4-${{ steps.date.outputs.date }}-${{ hashFiles('Dockerfile', 'dangerzone/conversion/*.py', 'dangerzone/container_helpers/*', 'install/common/build-image.py') }}
key: v5-${{ steps.date.outputs.date }}-${{ hashFiles('Dockerfile', 'dangerzone/conversion/*.py', 'dangerzone/container_helpers/*', 'install/common/build-image.py') }}
path: |-
share/container.tar.gz
share/container.tar
share/image-id.txt
fail-on-cache-miss: true

Expand Down Expand Up @@ -428,9 +428,9 @@ jobs:
- name: Restore container image
uses: actions/cache/restore@v4
with:
key: v4-${{ steps.date.outputs.date }}-${{ hashFiles('Dockerfile', 'dangerzone/conversion/*.py', 'dangerzone/container_helpers/*', 'install/common/build-image.py') }}
key: v5-${{ steps.date.outputs.date }}-${{ hashFiles('Dockerfile', 'dangerzone/conversion/*.py', 'dangerzone/container_helpers/*', 'install/common/build-image.py') }}
path: |-
share/container.tar.gz
share/container.tar
share/image-id.txt
fail-on-cache-miss: true

Expand Down Expand Up @@ -471,30 +471,3 @@ jobs:
# file successfully.
xvfb-run -s '-ac' ./dev_scripts/env.py --distro ${{ matrix.distro }} --version ${{ matrix.version }} run --dev \
bash -c 'cd dangerzone; poetry run make test'

check-reproducibility:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Install dev. dependencies
run: |-
sudo apt-get update
sudo apt-get install -y git python3-poetry --no-install-recommends
poetry install --only package

- name: Verify that the Dockerfile matches the commited template and params
run: |-
cp Dockerfile Dockerfile.orig
make Dockerfile
diff Dockerfile.orig Dockerfile

- name: Build Dangerzone container image
run: |
python3 ./install/common/build-image.py --no-save

- name: Reproduce the same container image
run: |
./dev_scripts/reproduce-image.py
237 changes: 237 additions & 0 deletions .github/workflows/release-container-image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
name: Release multi-arch container image

on:
workflow_dispatch:
push:
branches:
- main
- "test/**"
schedule:
- cron: "0 0 * * *" # Run every day at 00:00 UTC.

env:
REGISTRY: ghcr.io/${{ github.repository_owner }}
REGISTRY_USER: ${{ github.actor }}
REGISTRY_PASSWORD: ${{ github.token }}
IMAGE_NAME: dangerzone/dangerzone
BUILDKIT_IMAGE: "docker.io/moby/buildkit:v19.0@sha256:14aa1b4dd92ea0a4cd03a54d0c6079046ea98cd0c0ae6176bdd7036ba370cbbe"

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install dev. dependencies
run: |-
sudo apt-get update
sudo apt-get install -y git python3-poetry --no-install-recommends
poetry install --only package

- name: Verify that the Dockerfile matches the commited template and params
run: |-
cp Dockerfile Dockerfile.orig
make Dockerfile
diff Dockerfile.orig Dockerfile

prepare:
runs-on: ubuntu-latest
outputs:
debian_archive_date: ${{ steps.params.outputs.debian_archive_date }}
source_date_epoch: ${{ steps.params.outputs.source_date_epoch }}
image: ${{ steps.params.outputs.full_image_name }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Compute image parameters
id: params
run: |
DEBIAN_ARCHIVE_DATE=$(date -u +'%Y%m%d')
SOURCE_DATE_EPOCH=$(date -u -d ${DEBIAN_ARCHIVE_DATE} +"%s")
TAG=${DEBIAN_ARCHIVE_DATE}-$(git describe --long --first-parent | tail -c +2)
FULL_IMAGE_NAME=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${TAG}

echo "debian_archive_date=${DEBIAN_ARCHIVE_DATE}" >> $GITHUB_OUTPUT
echo "source_date_epoch=${SOURCE_DATE_EPOCH}" >> $GITHUB_OUTPUT
echo "tag=${DEBIAN_ARCHIVE_DATE}-${TAG}" >> $GITHUB_OUTPUT
echo "full_image_name=${FULL_IMAGE_NAME}" >> $GITHUB_OUTPUT

build:
name: Build ${{ matrix.platform.name }} image
runs-on: ubuntu-24.04${{ matrix.platform.suffix }}
needs:
- prepare
strategy:
fail-fast: false
matrix:
platform:
- suffix: ""
name: "linux/amd64"
- suffix: "-arm"
name: "linux/arm64"
steps:
- uses: actions/checkout@v4

- name: Prepare
run: |
platform=${{ matrix.platform.name }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV

- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}

# Instructions for reproducibly building a container image are taken from:
# https://github.com/freedomofpress/repro-build?tab=readme-ov-file#build-and-push-a-container-image-on-github-actions
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
driver-opts: image=${{ env.BUILDKIT_IMAGE }}

- name: Build and push by digest
id: build
uses: docker/build-push-action@v6
with:
context: ./dangerzone/
file: Dockerfile
build-args: |
DEBIAN_ARCHIVE_DATE=${{ needs.prepare.outputs.debian_archive_date }}
SOURCE_DATE_EPOCH=${{ needs.prepare.outputs.source_date_epoch }}
provenance: false
outputs: type=image,"name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}",push-by-digest=true,push=true,rewrite-timestamp=true,name-canonical=true
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Export digest
run: |
mkdir -p ${{ runner.temp }}/digests
digest="${{ steps.build.outputs.digest }}"
touch "${{ runner.temp }}/digests/${digest#sha256:}"
echo "Image digest is: ${digest}"

- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digests-${{ env.PLATFORM_PAIR }}
path: ${{ runner.temp }}/digests/*
if-no-files-found: error
retention-days: 1

merge:
runs-on: ubuntu-latest
needs:
- prepare
- build
outputs:
digest_root: ${{ steps.image.outputs.digest_root }}
digest_amd64: ${{ steps.image.outputs.digest_amd64 }}
digest_arm64: ${{ steps.image.outputs.digest_arm64 }}
steps:
- uses: actions/checkout@v4

- name: Download digests
uses: actions/download-artifact@v4
with:
path: ${{ runner.temp }}/digests
pattern: digests-*
merge-multiple: true

- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
driver-opts: image=${{ env.BUILDKIT_IMAGE }}

- name: Create manifest list and push
working-directory: ${{ runner.temp }}/digests
run: |
DIGESTS=$(printf '${{ needs.prepare.outputs.image }}@sha256:%s ' *)
docker buildx imagetools create -t ${{ needs.prepare.outputs.image }} ${DIGESTS}

- name: Inspect image
id: image
run: |
# Inspect the image
docker buildx imagetools inspect ${{ needs.prepare.outputs.image }}
docker buildx imagetools inspect ${{ needs.prepare.outputs.image }} --format "{{json .Manifest}}" > manifest

# Calculate and print the digests
digest_root=$(jq -r .digest manifest)
digest_amd64=$(jq -r .manifests[0].digest manifest)
digest_arm64=$(jq -r .manifests[1].digest manifest)

echo "The image digests are:"
echo " Root: $digest_root"
echo " linux/amd64: $digest_amd64"
echo " linux/arm64: $digest_arm64"

# NOTE: Set the digests as an output because the `env` context is not
# available to the inputs of a reusable workflow call.
echo "digest_root=$digest_root" >> "$GITHUB_OUTPUT"
echo "digest_amd64=$digest_amd64" >> "$GITHUB_OUTPUT"
echo "digest_arm64=$digest_arm64" >> "$GITHUB_OUTPUT"

# This step calls the container workflow to generate provenance and push it to
# the container registry.
provenance:
needs:
- prepare
- merge
strategy:
matrix:
digest:
- root
- amd64
- arm64
permissions:
actions: read # for detecting the Github Actions environment.
id-token: write # for creating OIDC tokens for signing.
packages: write # for uploading attestations.
uses: slsa-framework/slsa-github-generator/.github/workflows/[email protected]
with:
digest: ${{ needs.merge.outputs[format('digest_{0}', matrix.digest)] }}
image: ${{ needs.prepare.outputs.image }}
registry-username: ${{ github.actor }}
secrets:
registry-password: ${{ secrets.GITHUB_TOKEN }}

# This step ensures that the image is reproducible
check-reproducibility:
needs:
- prepare
- merge
runs-on: ubuntu-24.04${{ matrix.platform.suffix }}
strategy:
fail-fast: false
matrix:
platform:
- suffix: ""
name: "amd64"
- suffix: "-arm"
name: "arm64"
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Reproduce the same container image
run: |
./dev_scripts/reproduce-image.py \
--runtime \
docker \
--debian-archive-date \
${{ needs.prepare.outputs.debian_archive_date }} \
--platform \
linux/${{ matrix.platform.name }} \
${{ needs.merge.outputs[format('digest_{0}', matrix.platform.name)] }}
17 changes: 5 additions & 12 deletions .github/workflows/scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,12 @@ jobs:
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install container build dependencies
run: |
sudo apt install pipx
pipx install poetry
pipx inject poetry poetry-plugin-export
poetry install --only package
- name: Bump date of Debian snapshot archive
run: |
date=$(date "+%Y%m%d")
sed -i "s/DEBIAN_ARCHIVE_DATE=[0-9]\+/DEBIAN_ARCHIVE_DATE=${date}/" Dockerfile.env
make Dockerfile
- name: Build container image
run: python3 ./install/common/build-image.py --runtime docker --no-save
run: |
python3 ./install/common/build-image.py \
--debian-archive-date $(date "+%Y%m%d") \
--runtime docker
docker load -i share/container.tar
- name: Get image tag
id: tag
run: echo "tag=$(cat share/image-id.txt)" >> $GITHUB_OUTPUT
Expand Down
Loading
Loading