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 23 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
712b309
Bump container image parameters
apyrgio Feb 26, 2025
be8005f
Remove sources of non-determinism from our image
apyrgio Feb 26, 2025
a2acbef
Vendor `repro-build` script
apyrgio Feb 26, 2025
0042e13
Build container image using `repro-build`
apyrgio Feb 26, 2025
d3d04b2
Fix references to container.tar.gz
apyrgio Feb 26, 2025
038e95b
Fix a Podman regression regarding Buildkit images
apyrgio Feb 26, 2025
18ec475
Completely overhaul the `reproduce-image.py` script
apyrgio Feb 26, 2025
553b004
ci: Create a CI job that does the following
apyrgio Feb 26, 2025
f33b385
FIXUP: Make release image job reusable
apyrgio Mar 6, 2025
d9f2317
FIXUP: Remove command that checks github token
apyrgio Mar 10, 2025
3c90ad9
FIXUP: Move buildkit image alongw with other envvars
apyrgio Mar 10, 2025
79d9ae7
FIXUP: Change digest with manifest_type
apyrgio Mar 10, 2025
b8ef87a
FIXUP: Document removal of resolv.conf
apyrgio Mar 10, 2025
92267c7
FIXUP: Use 'load -i'
apyrgio Mar 10, 2025
542fe93
FIXUP: Rename compressed_container_path envvar
apyrgio Mar 10, 2025
ee95e86
FIXUP: Rename repro-build to repro-build.py
apyrgio Mar 10, 2025
b2ab898
FIXUP: Handle tarballs with ./ prefix
apyrgio Mar 10, 2025
222169d
FIXUP: Specify platform by full name
apyrgio Mar 10, 2025
0972542
FIXUP: Remove extraneous comments
apyrgio Mar 10, 2025
31b04b6
FIXUP: Add comment for needs: prepare
apyrgio Mar 10, 2025
49d4546
FIXUP: Rename XXX to NOTE
apyrgio Mar 11, 2025
f9dfbe9
FIXUP: Make tests work after 'podman load -i'
apyrgio Mar 11, 2025
abeab4b
fixup! FIXUP: Change digest with manifest_type
apyrgio Mar 11, 2025
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
244 changes: 244 additions & 0 deletions .github/workflows/build-push-image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
name: Build and push multi-arch container image

on:
workflow_call:
inputs:
registry:
required: true
type: string
registry_user:
required: true
type: string
image_name:
required: true
type: string
reproduce:
required: true
type: boolean
secrets:
registry_token:
required: true


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: |
source Dockerfile.env
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=${{ inputs.registry }}/${{ inputs.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
echo "buildkit_image=${BUILDKIT_IMAGE}" >> $GITHUB_OUTPUT

build:
name: Build ${{ matrix.platform.name }} image
runs-on: ${{ matrix.platform.runs-on }}
needs:
- prepare
strategy:
fail-fast: false
matrix:
platform:
- runs-on: "ubuntu-24.04"
name: "linux/amd64"
- runs-on: "ubuntu-24.04-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: ${{ inputs.registry_user }}
password: ${{ secrets.registry_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=${{ needs.prepare.outputs.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=${{ inputs.registry }}/${{ inputs.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 # implied by build, but required here to access image params
- 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: ${{ inputs.registry_user }}
password: ${{ secrets.registry_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 # implied by merge, but required here to access image params
- merge
strategy:
matrix:
manifest_type:
- 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.manifest_type)] }}
image: ${{ needs.prepare.outputs.image }}
registry-username: ${{ inputs.registry_user }}
secrets:
registry-password: ${{ secrets.registry_token }}

# This step ensures that the image is reproducible
check-reproducibility:
if: ${{ inputs.reproduce }}
needs:
- prepare # implied by merge, but required here to access image params
- merge
runs-on: ${{ matrix.platform.runs-on }}
strategy:
fail-fast: false
matrix:
platform:
- runs-on: "ubuntu-24.04"
name: "amd64"
- runs-on: "ubuntu-24.04-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)] }}
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
22 changes: 22 additions & 0 deletions .github/workflows/release-container-image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: Release multi-arch container image

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


jobs:
build-push-image:
uses: ./.github/workflows/build-push-image.yml
with:
registry: ghcr.io/${{ github.repository_owner }}
registry_user: ${{ github.actor }}
image_name: dangerzone/dangerzone
reproduce: true
secrets:
registry_token: ${{ secrets.GITHUB_TOKEN }}
Loading
Loading