👷 Include the version in the bundle and image build key

The bundle stamps the version into version.txt, so a commit that gets a
new tag (2.18.1-RC1 -> 2.18.1) needs a new bundle and image set. Keying
them by commit sha alone reused the RC artifacts for the final release.

Signed-off-by: David Barragán Merino <david.barragan@kaleidos.net>
This commit is contained in:
David Barragán Merino 2026-10-26 19:26:43 +01:00
parent 01e0b32850
commit dbcd1e0d03
3 changed files with 62 additions and 45 deletions

View File

@ -61,6 +61,7 @@ jobs:
outputs:
gh_ref: ${{ steps.vars.outputs.gh_ref }}
bundle_version: ${{ steps.vars.outputs.bundle_version }}
build_key: ${{ steps.vars.outputs.build_key }}
short_sha: ${{ steps.vars.outputs.short_sha }}
sha: ${{ steps.vars.outputs.sha }}
commit_title: ${{ steps.vars.outputs.commit_title }}
@ -79,16 +80,22 @@ jobs:
env:
GH_REF: ${{ inputs.gh_ref || github.ref_name }}
run: |
# Same command the build uses to stamp version.txt, so the key
# always matches what ends up inside the bundle.
BUNDLE_VERSION=$(./manage.sh version)
SHORT_SHA=$(git rev-parse --short=12 HEAD)
echo "gh_ref=$GH_REF" >> $GITHUB_OUTPUT
echo "bundle_version=$(git describe --tags --always)" >> $GITHUB_OUTPUT
echo "short_sha=$(git rev-parse --short=12 HEAD)" >> $GITHUB_OUTPUT
echo "bundle_version=$BUNDLE_VERSION" >> $GITHUB_OUTPUT
echo "build_key=sha-${SHORT_SHA}-${BUNDLE_VERSION}" >> $GITHUB_OUTPUT
echo "short_sha=$SHORT_SHA" >> $GITHUB_OUTPUT
echo "sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
echo "commit_title=$(git log -1 --pretty=%s)" >> $GITHUB_OUTPUT
# Keyed by commit sha, same as build-docker.yml's marker check, so
# both pipelines agree on what "already built" means: any ref that
# points at an already-bundled commit skips the build, regardless of
# which ref built it first. `force` bypasses this check entirely.
# Keyed by commit sha AND version, same as build-docker.yml's marker
# check, so both pipelines agree on what "already built" means. The
# version is stamped into the bundle, so a new tag on an already
# bundled commit (2.18.1-RC1 -> 2.18.1) must produce a new bundle.
# `force` bypasses this check entirely.
- name: Check if this bundle is already built
id: check
env:
@ -97,8 +104,7 @@ jobs:
AWS_DEFAULT_REGION: ${{ secrets.AWS_REGION }}
S3_BUCKET: ${{ secrets.S3_BUCKET }}
FORCE: ${{ inputs.force }}
SHORT_SHA: ${{ steps.vars.outputs.short_sha }}
BUNDLE_VERSION: ${{ steps.vars.outputs.bundle_version }}
BUILD_KEY: ${{ steps.vars.outputs.build_key }}
run: |
if [ "$FORCE" = "true" ]; then
echo "exists=false" >> $GITHUB_OUTPUT
@ -112,7 +118,7 @@ jobs:
BUNDLE_EXISTS=$(aws s3api head-object \
--bucket "$S3_BUCKET" \
--key "penpot-sha-${SHORT_SHA}.zip" \
--key "penpot-${BUILD_KEY}.zip" \
> /dev/null 2>&1 && echo "true" || echo "false")
if [ "$BUNDLE_EXISTS" = "true" ]; then
@ -120,7 +126,7 @@ jobs:
{
echo "### ⏭️ Bundle build skipped"
echo ""
echo "The bundle in S3 was already built from \`sha-${SHORT_SHA}\` (\`${BUNDLE_VERSION}\`)."
echo "The bundle in S3 was already built for \`${BUILD_KEY}\`."
} >> "$GITHUB_STEP_SUMMARY"
else
echo "exists=false" >> $GITHUB_OUTPUT
@ -164,11 +170,11 @@ jobs:
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: ${{ secrets.AWS_REGION }}
S3_BUCKET: ${{ secrets.S3_BUCKET }}
SHORT_SHA: ${{ needs.check.outputs.short_sha }}
BUILD_KEY: ${{ needs.check.outputs.build_key }}
BUNDLE_VERSION: ${{ needs.check.outputs.bundle_version }}
run: |
aws s3 cp zips/penpot.zip \
"s3://${S3_BUCKET}/penpot-sha-${SHORT_SHA}.zip" \
"s3://${S3_BUCKET}/penpot-${BUILD_KEY}.zip" \
--metadata "bundle-version=${BUNDLE_VERSION}"
- name: Write step summary
@ -180,7 +186,7 @@ jobs:
{
echo "### ✅ Bundle built"
echo ""
echo "- Version: \`${BUNDLE_VERSION}\` (\`git describe --tags --always\`)"
echo "- Version: \`${BUNDLE_VERSION}\` (\`./manage.sh version\`)"
echo "- Commit: [\`${SHORT_SHA}\`](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/commit/${SHORT_SHA}) — ${COMMIT_TITLE}"
echo "- Built at: $(date -u +'%Y-%m-%d %H:%M:%S UTC')"
} >> "$GITHUB_STEP_SUMMARY"

View File

@ -85,6 +85,7 @@ jobs:
bundle_version: ${{ steps.vars.outputs.bundle_version }}
sha: ${{ steps.vars.outputs.sha }}
short_sha: ${{ steps.vars.outputs.short_sha }}
build_key: ${{ steps.vars.outputs.build_key }}
commit_title: ${{ steps.vars.outputs.commit_title }}
exists: ${{ steps.check.outputs.exists }}
@ -105,10 +106,14 @@ jobs:
REF_SLUG=$(printf '%s' "$GH_REF" | tr -c 'A-Za-z0-9._-' '-' | cut -c1-100)
echo "gh_ref=$GH_REF" >> $GITHUB_OUTPUT
echo "ref_slug=$REF_SLUG" >> $GITHUB_OUTPUT
# Must match the key computed by build-bundle.yml.
BUNDLE_VERSION=$(./manage.sh version)
SHORT_SHA=$(git rev-parse --short=12 HEAD)
echo "sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
echo "short_sha=$(git rev-parse --short=12 HEAD)" >> $GITHUB_OUTPUT
echo "short_sha=$SHORT_SHA" >> $GITHUB_OUTPUT
echo "commit_title=$(git log -1 --pretty=%s)" >> $GITHUB_OUTPUT
echo "bundle_version=$(git describe --tags --always)" >> $GITHUB_OUTPUT
echo "bundle_version=$BUNDLE_VERSION" >> $GITHUB_OUTPUT
echo "build_key=sha-${SHORT_SHA}-${BUNDLE_VERSION}" >> $GITHUB_OUTPUT
# The image set is a single block, so a single set-level check is
# enough: `promote` drops a marker object in S3 only after every
@ -123,14 +128,14 @@ jobs:
AWS_DEFAULT_REGION: ${{ secrets.AWS_REGION }}
S3_BUCKET: ${{ secrets.S3_BUCKET }}
FORCE: ${{ inputs.force }}
SHORT_SHA: ${{ steps.vars.outputs.short_sha }}
BUILD_KEY: ${{ steps.vars.outputs.build_key }}
run: |
if [ "$FORCE" = "true" ]; then
echo "exists=false" >> $GITHUB_OUTPUT
mkdir -p "$BUNDLE_CACHE"
find "$BUNDLE_CACHE" -type f -mtime +1 -delete || true
ZIP="$BUNDLE_CACHE/penpot-sha-${SHORT_SHA}.zip"
aws s3 cp "s3://${S3_BUCKET}/penpot-sha-${SHORT_SHA}.zip" "$ZIP.$$.tmp"
ZIP="$BUNDLE_CACHE/penpot-${BUILD_KEY}.zip"
aws s3 cp "s3://${S3_BUCKET}/penpot-${BUILD_KEY}.zip" "$ZIP.$$.tmp"
mv "$ZIP.$$.tmp" "$ZIP"
{
echo "### 🔁 Image set build forced"
@ -142,13 +147,13 @@ jobs:
if aws s3api head-object \
--bucket "$S3_BUCKET" \
--key "markers/images-sha-${SHORT_SHA}" \
--key "markers/images-${BUILD_KEY}" \
> /dev/null 2>&1; then
echo "exists=true" >> $GITHUB_OUTPUT
{
echo "### ⏭️ Image set build skipped"
echo ""
echo "The whole set was already built and promoted for \`sha-${SHORT_SHA}\`."
echo "The whole set was already built and promoted for \`${BUILD_KEY}\`."
} >> "$GITHUB_STEP_SUMMARY"
else
echo "exists=false" >> $GITHUB_OUTPUT
@ -158,9 +163,9 @@ jobs:
# prune stale bundles while at it.
mkdir -p "$BUNDLE_CACHE"
find "$BUNDLE_CACHE" -type f -mtime +1 -delete || true
ZIP="$BUNDLE_CACHE/penpot-sha-${SHORT_SHA}.zip"
ZIP="$BUNDLE_CACHE/penpot-${BUILD_KEY}.zip"
if [ ! -f "$ZIP" ]; then
aws s3 cp "s3://${S3_BUCKET}/penpot-sha-${SHORT_SHA}.zip" "$ZIP.$$.tmp"
aws s3 cp "s3://${S3_BUCKET}/penpot-${BUILD_KEY}.zip" "$ZIP.$$.tmp"
mv "$ZIP.$$.tmp" "$ZIP"
fi
fi
@ -231,14 +236,14 @@ jobs:
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: ${{ secrets.AWS_REGION }}
S3_BUCKET: ${{ secrets.S3_BUCKET }}
SHORT_SHA: ${{ needs.prepare.outputs.short_sha }}
BUILD_KEY: ${{ needs.prepare.outputs.build_key }}
IMAGE: ${{ matrix.image }}
run: |
ZIP="$BUNDLE_CACHE/penpot-sha-${SHORT_SHA}.zip"
ZIP="$BUNDLE_CACHE/penpot-${BUILD_KEY}.zip"
if [ ! -f "$ZIP" ]; then
echo "Bundle not found in host cache; falling back to S3."
mkdir -p "$BUNDLE_CACHE"
aws s3 cp "s3://${S3_BUCKET}/penpot-sha-${SHORT_SHA}.zip" "$ZIP.$$.tmp"
aws s3 cp "s3://${S3_BUCKET}/penpot-${BUILD_KEY}.zip" "$ZIP.$$.tmp"
mv "$ZIP.$$.tmp" "$ZIP"
fi
# Extract only the bundle this job needs.
@ -274,7 +279,7 @@ jobs:
sbom: true
# Immutable tag only; branch tags are moved atomically for the
# whole image set by the `promote` job.
tags: ${{ secrets.DOCKER_REGISTRY }}/${{ matrix.image }}:sha-${{ needs.prepare.outputs.short_sha }}
tags: ${{ secrets.DOCKER_REGISTRY }}/${{ matrix.image }}:${{ needs.prepare.outputs.build_key }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=${{ secrets.DOCKER_REGISTRY }}/${{ matrix.image }}:buildcache
cache-to: type=registry,ref=${{ secrets.DOCKER_REGISTRY }}/${{ matrix.image }}:buildcache,mode=max
@ -305,13 +310,13 @@ jobs:
env:
DOCKER_REGISTRY: ${{ secrets.DOCKER_REGISTRY }}
REF_SLUG: ${{ needs.prepare.outputs.ref_slug }}
SHORT_SHA: ${{ needs.prepare.outputs.short_sha }}
BUILD_KEY: ${{ needs.prepare.outputs.build_key }}
run: |
set -e
for image in $ALL_IMAGES; do
docker buildx imagetools create \
-t "${DOCKER_REGISTRY}/${image}:${REF_SLUG}" \
"${DOCKER_REGISTRY}/${image}:sha-${SHORT_SHA}"
"${DOCKER_REGISTRY}/${image}:${BUILD_KEY}"
done
# The marker is written LAST: its presence certifies that all five
@ -322,26 +327,27 @@ jobs:
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: ${{ secrets.AWS_REGION }}
S3_BUCKET: ${{ secrets.S3_BUCKET }}
SHORT_SHA: ${{ needs.prepare.outputs.short_sha }}
BUILD_KEY: ${{ needs.prepare.outputs.build_key }}
run: |
echo "$GITHUB_RUN_ID" | aws s3 cp - \
"s3://${S3_BUCKET}/markers/images-sha-${SHORT_SHA}"
"s3://${S3_BUCKET}/markers/images-${BUILD_KEY}"
- name: Write step summary
env:
REF_SLUG: ${{ needs.prepare.outputs.ref_slug }}
SHORT_SHA: ${{ needs.prepare.outputs.short_sha }}
BUILD_KEY: ${{ needs.prepare.outputs.build_key }}
BUNDLE_VERSION: ${{ needs.prepare.outputs.bundle_version }}
COMMIT_TITLE: ${{ needs.prepare.outputs.commit_title }}
run: |
{
echo "### ✅ Image set promoted"
echo ""
echo "- Version: \`${BUNDLE_VERSION}\` (\`git describe --tags --always\`)"
echo "- Version: \`${BUNDLE_VERSION}\` (\`./manage.sh version\`)"
echo "- Commit: [\`${SHORT_SHA}\`](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/commit/${SHORT_SHA}) — ${COMMIT_TITLE}"
echo "- Built at: $(date -u +'%Y-%m-%d %H:%M:%S UTC')"
echo ""
echo "All \`:${REF_SLUG}\` tags now point to \`sha-${SHORT_SHA}\`."
echo "All \`:${REF_SLUG}\` tags now point to \`${BUILD_KEY}\`."
} >> "$GITHUB_STEP_SUMMARY"
# ── 3b. Skip path: make sure THIS ref's tags point to the existing
@ -374,30 +380,31 @@ jobs:
env:
DOCKER_REGISTRY: ${{ secrets.DOCKER_REGISTRY }}
REF_SLUG: ${{ needs.prepare.outputs.ref_slug }}
SHORT_SHA: ${{ needs.prepare.outputs.short_sha }}
BUILD_KEY: ${{ needs.prepare.outputs.build_key }}
run: |
set -e
for image in $ALL_IMAGES; do
docker buildx imagetools create \
-t "${DOCKER_REGISTRY}/${image}:${REF_SLUG}" \
"${DOCKER_REGISTRY}/${image}:sha-${SHORT_SHA}"
"${DOCKER_REGISTRY}/${image}:${BUILD_KEY}"
done
- name: Write step summary
env:
REF_SLUG: ${{ needs.prepare.outputs.ref_slug }}
SHORT_SHA: ${{ needs.prepare.outputs.short_sha }}
BUILD_KEY: ${{ needs.prepare.outputs.build_key }}
BUNDLE_VERSION: ${{ needs.prepare.outputs.bundle_version }}
COMMIT_TITLE: ${{ needs.prepare.outputs.commit_title }}
run: |
{
echo "### ✅ Image set already built (branch tags ensured)"
echo ""
echo "- Version: \`${BUNDLE_VERSION}\` (\`git describe --tags --always\`)"
echo "- Version: \`${BUNDLE_VERSION}\` (\`./manage.sh version\`)"
echo "- Commit: [\`${SHORT_SHA}\`](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/commit/${SHORT_SHA}) — ${COMMIT_TITLE}"
echo "- Checked at: $(date -u +'%Y-%m-%d %H:%M:%S UTC')"
echo ""
echo "All \`:${REF_SLUG}\` tags now point to \`sha-${SHORT_SHA}\`."
echo "All \`:${REF_SLUG}\` tags now point to \`${BUILD_KEY}\`."
} >> "$GITHUB_STEP_SUMMARY"
# ── 4. Single failure notification for the whole workflow ─────────────

View File

@ -27,7 +27,7 @@ jobs:
permissions:
contents: read
outputs:
short_sha: ${{ steps.bundle.outputs.short_sha }}
bundle_key: ${{ steps.bundle.outputs.bundle_key }}
images: ${{ steps.changes.outputs.images }}
devenv: ${{ steps.changes.outputs.devenv }}
imagemagick: ${{ steps.changes.outputs.imagemagick }}
@ -60,7 +60,9 @@ jobs:
} >> "$GITHUB_OUTPUT"
# Renovate only changes Dockerfiles and package manager versions, so
# the most recent develop bundle is enough to build the images.
# the most recent develop bundle is enough to build the images. The
# version part of the key does not matter here: take any bundle of
# the commit.
- name: Find the latest develop bundle
id: bundle
env:
@ -72,10 +74,12 @@ jobs:
run: |
for SHA in $(git rev-list --first-parent --max-count=50 "$BASE_SHA"); do
SHORT_SHA=$(git rev-parse --short=12 "$SHA")
if aws s3api head-object --bucket "$S3_BUCKET" \
--key "penpot-sha-${SHORT_SHA}.zip" > /dev/null 2>&1; then
echo "short_sha=$SHORT_SHA" >> "$GITHUB_OUTPUT"
echo "Using bundle \`penpot-sha-${SHORT_SHA}.zip\`" >> "$GITHUB_STEP_SUMMARY"
BUNDLE_KEY=$(aws s3api list-objects-v2 --bucket "$S3_BUCKET" \
--prefix "penpot-sha-${SHORT_SHA}-" \
--query 'Contents[0].Key' --output text)
if [ -n "$BUNDLE_KEY" ] && [ "$BUNDLE_KEY" != "None" ]; then
echo "bundle_key=$BUNDLE_KEY" >> "$GITHUB_OUTPUT"
echo "Using bundle \`${BUNDLE_KEY}\`" >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
done
@ -135,13 +139,13 @@ jobs:
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: ${{ secrets.AWS_REGION }}
S3_BUCKET: ${{ secrets.S3_BUCKET }}
SHORT_SHA: ${{ needs.prepare.outputs.short_sha }}
BUNDLE_KEY: ${{ needs.prepare.outputs.bundle_key }}
IMAGE: ${{ matrix.image }}
run: |
ZIP="$BUNDLE_CACHE/penpot-sha-${SHORT_SHA}.zip"
ZIP="$BUNDLE_CACHE/${BUNDLE_KEY}"
if [ ! -f "$ZIP" ]; then
mkdir -p "$BUNDLE_CACHE"
aws s3 cp "s3://${S3_BUCKET}/penpot-sha-${SHORT_SHA}.zip" "$ZIP.$$.tmp"
aws s3 cp "s3://${S3_BUCKET}/${BUNDLE_KEY}" "$ZIP.$$.tmp"
mv "$ZIP.$$.tmp" "$ZIP"
fi
pushd docker/images