Skip to content
Merged
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
165 changes: 165 additions & 0 deletions .github/workflows/delivery.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ on:
- main
pull_request:

# The cryptify image keeps the name the cryptify repo publishes today, so no
# deployment has to be repointed. Changing this one line is the whole cost of
# publishing under a different name instead.
env:
CRYPTIFY_IMAGE: ghcr.io/${{ github.repository_owner }}/cryptify

jobs:

# ---------------------------------------------------------------------------
Expand All @@ -34,6 +40,7 @@ jobs:
pg_pkg_version: ${{ steps.parse.outputs.pg_pkg_version }}
pg_core_version: ${{ steps.parse.outputs.pg_core_version }}
pg_ffi_version: ${{ steps.parse.outputs.pg_ffi_version }}
cryptify_version: ${{ steps.parse.outputs.cryptify_version }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
Expand All @@ -60,6 +67,8 @@ jobs:
echo "pg_core_version=$PG_CORE_VERSION" >> "$GITHUB_OUTPUT"
PG_FFI_VERSION=$(echo "$RELEASES" | jq -r '.[] | select(.package_name == "pg-ffi") | .version // empty')
echo "pg_ffi_version=$PG_FFI_VERSION" >> "$GITHUB_OUTPUT"
CRYPTIFY_VERSION=$(echo "$RELEASES" | jq -r '.[] | select(.package_name == "cryptify") | .version // empty')
echo "cryptify_version=$CRYPTIFY_VERSION" >> "$GITHUB_OUTPUT"

# Create a PR with the new versions and changelog, preparing the next release.
release-plz-pr:
Expand Down Expand Up @@ -232,6 +241,162 @@ jobs:
# ---------------------------------------------------------------------------

# Build pg-ffi native libraries and upload as GitHub release assets.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new block landed inside the pg-ffi section. Lines 239-241 are the pg-ffi: build native libraries for all platforms banner and line 243 is # Build pg-ffi native libraries and upload as GitHub release assets. — both now head the cryptify Docker block that starts at line 244, and build-ffi (line 400) is left with no comment at all, sitting under a banner that says cryptify. Moving lines 244-398 above line 239 fixes both ends.

# ---------------------------------------------------------------------------
# Docker: build, scan, and publish multi-arch image for cryptify
#
# Mirrors the pg-pkg jobs above rather than sharing them: the two images have
# different Dockerfiles, different names and independent version outputs, and
# a matrix over both would have to carry all three as matrix values, which
# buys less than it costs in readability.
#
# IMAGE NAME: this publishes to the SAME name the cryptify repo publishes to
# today, so nothing that pulls the image has to change. That requires granting
# this repository push access to the existing `cryptify` GHCR package
# (package settings -> Manage Actions access -> add encryption4all/postguard
# with the Write role). Until that is done these jobs cannot push, so all
# three are gated on the repo variable PUBLISH_CRYPTIFY_IMAGE. The
# alternative, publishing under a new name, moves the problem to every
# deployment that pulls it.
# ---------------------------------------------------------------------------

build-cryptify:
name: Build cryptify (${{ matrix.name }})
# Off until the GHCR package grants this repo push access. Set the repo
# variable PUBLISH_CRYPTIFY_IMAGE to `true` to turn the three jobs on; with
# it unset they skip, so merging this changes nothing that runs today.
if: vars.PUBLISH_CRYPTIFY_IMAGE == 'true'
runs-on: ${{ matrix.runner }}
permissions:
contents: read
packages: write
strategy:
fail-fast: false
matrix:
include:
- platform: linux/amd64
runner: ubuntu-24.04
name: amd64
- platform: linux/arm64
runner: ubuntu-24.04-arm
name: arm64
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Log in to GHCR
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push by digest
id: build
uses: docker/build-push-action@v7
with:
# Repo root, not cryptify/: the crate is a workspace member and cannot
# be planned or built without the root manifest and its siblings.
context: .
file: cryptify/Dockerfile
platforms: ${{ matrix.platform }}
build-args: CARGO_PROFILE=edge
outputs: type=image,name=${{ env.CRYPTIFY_IMAGE }},push-by-digest=true,name-canonical=true,push=true
cache-from: type=gha,scope=cryptify-${{ matrix.name }}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit — CI cost once the variable is on.

(a) Two more cache-to: type=gha,mode=max scopes on top of pg-pkg's two. mode=max stores every intermediate layer including cargo-chef's cooked target/, and GitHub's per-repo Actions cache is 10 GB with LRU eviction, so four full Rust dep caches (2 arches x 2 images) can evict each other and leave both images cache-cold. mode=min on the cryptify scopes would avoid that.

(b) build-cryptify has no path filter, so every postguard PR — docs-only included — runs two extra multi-arch cargo-chef builds and pushes two extra :pr-N tags, doubling PR-time Docker cost.

If you do add a filter for (b), put it in a step (dorny/paths-filter + per-step if), not in on: paths: — an on: paths:-skipped job reports no status at all, which permanently blocks any PR where one of these is a required check.

cache-to: type=gha,mode=max,scope=cryptify-${{ matrix.name }}
- name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v7
with:
name: cryptify-digest-${{ matrix.name }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1

scan-cryptify:
name: Scan cryptify image
needs: build-cryptify
if: vars.PUBLISH_CRYPTIFY_IMAGE == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
packages: read
security-events: write
steps:
- name: Download amd64 digest
uses: actions/download-artifact@v8
with:
name: cryptify-digest-amd64
path: /tmp/digests
- name: Resolve image reference
id: ref
run: |
DIGEST=$(ls /tmp/digests | head -1)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit — this run: block has a pipe (ls | head -1) and no shell: key, so it executes under bash -e {0}, which has no pipefail; only an explicit shell: bash gets -eo pipefail. If ls fails, head still exits 0, the step goes green, and DIGEST is empty, so the next step emits ...cryptify@sha256: and the failure surfaces later as a confusing anchore pull error instead of here.

Honest caveat on the severity: this is byte-identical to scan-docker (line 173), so it is a copied pre-existing pattern, and the realistic failure mode (artifact downloaded but directory empty) leaves ls exiting 0 anyway, which pipefail would not catch either. Cheap to add shell: bash to both while the file is open; not worth a round on its own.

echo "image=${{ env.CRYPTIFY_IMAGE }}@sha256:${DIGEST}" >> "$GITHUB_OUTPUT"
- name: Log in to GHCR
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Scan image
uses: anchore/scan-action@v6
id: scan
with:
image: ${{ steps.ref.outputs.image }}
only-fixed: true
fail-build: true
severity-cutoff: critical
output-format: sarif
- name: Upload Anchore scan SARIF report
uses: github/codeql-action/upload-sarif@v4
if: ${{ !cancelled() }}
with:
sarif_file: ${{ steps.scan.outputs.sarif }}
category: cryptify

finalize-cryptify:
name: Finalize cryptify manifest
needs: [build-cryptify, scan-cryptify, release-plz-release]
if: always() && vars.PUBLISH_CRYPTIFY_IMAGE == 'true' && needs.build-cryptify.result == 'success'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitfinalize-cryptify gates on needs.build-cryptify.result == 'success' only. scan-cryptify is in needs but its result is never checked, and always() drops the implicit success gate, so a critical CVE (the scan runs fail-build: true, severity-cutoff: critical) fails the scan job and the manifest is still pushed to :edge / :0.1.x. This is copied from finalize-docker (line 203), so it is pre-existing rather than introduced here — but adding && needs.scan-cryptify.result == 'success' to both is cheap while the file is open.

runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Download digests
uses: actions/download-artifact@v8
with:
path: /tmp/digests
pattern: cryptify-digest-*
merge-multiple: true
- name: Docker metadata
id: meta
uses: docker/metadata-action@v6
with:
images: ${{ env.CRYPTIFY_IMAGE }}
tags: |
type=edge,branch=main

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tag collision with the cryptify repo's still-live pipeline. Confirmed rather than inferred:

  • encryption4all/cryptify is not archived (last push 2026-07-29) and its .github/workflows/ci.yml still runs on push: main and pull_request, pushing the same three tag types (type=edge,branch=main, type=ref,event=pr, type=raw,value=<version>) to ghcr.io/encryption4all/cryptify.
  • Both trees are at 0.1.27; that repo has an open chore: release v0.1.28 PR, and this repo's release-plz.toml:28-29 says this workspace cuts cryptify-v0.1.28 onward. Both pipelines would push :0.1.28 from different trees.

The PR body's "both build the same source from the same lockfile, redundant but not harmful" does not hold. The cryptify repo's build job passes no build-args, so it uses the Dockerfile's CARGO_PROFILE=release default (LTO on) against that repo's own Cargo.lock; this job passes CARGO_PROFILE=edge (LTO off) against the workspace lockfile. Different binaries under one mutable tag. :edge and :pr-N then flip depending on which repo pushed last, and PR numbers are per-repo, so postguard #N and cryptify #N write the same :pr-N.

The gate keeps this inert, so it is not a merge blocker — but step 4 of the enablement checklist (retire the cryptify repo's build) has to land before or together with step 2, not after it.

type=ref,event=pr
type=raw,value=${{ needs.release-plz-release.outputs.cryptify_version }},enable=${{ needs.release-plz-release.outputs.cryptify_version != '' }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Log in to GHCR
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create and push manifest
working-directory: /tmp/digests
run: |
docker buildx imagetools create \
$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ env.CRYPTIFY_IMAGE }}@sha256:%s ' *)

build-ffi:
name: Build pg-ffi (${{ matrix.name }})
needs: release-plz-release
Expand Down
Loading