Skip to content

Commit f63bf31

Browse files
committed
fix: snapshot publishing race + v4.0 arm64 build failure
The snapshot workflow had two related defects: 1. Multi-arch tags ended up single-arch. Both arch runners pushed the same firebirdsql/firebird:6-snapshot tag without manifest assembly; whichever runner finished last won. After the 2026-04-28 run, 6-snapshot and 5-snapshot were arm64-only on Docker Hub, breaking amd64 users. 2. The v4.0 leg always failed on arm64 because FirebirdSQL/snapshots has no linux-arm64 asset for v4.0 (same root cause as D-015). Fixes: - Build-Snapshot now pushes by digest and saves the digest to generated/digests-snapshot-{arch}.json (mirrors Push-Digests for stable images). - New Publish-Snapshot-Manifests task assembles a multi-arch (or amd64-only) manifest from per-arch digest files. - snapshot.yaml: per-arch builds upload digests as artifacts, then a separate create-manifests job assembles the manifest. Login moved before build (push-by-digest needs it). Matrix excludes branch=v4.0, arch=arm64. - publish-fork.yaml uses the same flow (single-arch manifest since fork builds are amd64-only).
1 parent 4d03378 commit f63bf31

3 files changed

Lines changed: 143 additions & 46 deletions

File tree

.github/workflows/publish-fork.yaml

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -161,13 +161,11 @@ jobs:
161161
env:
162162
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
163163
run: |
164-
$assets = Get-Content -Raw ./assets.json | ConvertFrom-Json
165-
$defaultDistro = $assets.config.defaultDistro
164+
# Fork builds are amd64-only — Build-Snapshot pushes by digest, then
165+
# Publish-Snapshot-Manifests assembles a single-arch manifest from it.
166166
foreach ($branch in @('master', 'v5.0-release')) {
167167
Invoke-Build Build-Snapshot -Branch $branch -Registry '${{ env.REGISTRY }}'
168-
$tag = if ($branch -eq 'master') { '6-snapshot' } else { '5-snapshot' }
169-
docker push "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:$tag"
170-
docker push "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:$tag-$defaultDistro"
168+
Invoke-Build Publish-Snapshot-Manifests -Branch $branch -Registry '${{ env.REGISTRY }}'
171169
}
172170
173171
# Summary only for the amd64-only case (multi-arch summary is in create-manifests).

.github/workflows/snapshot.yaml

Lines changed: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ name: snapshot
66
#
77
# Snapshot images use tags like '6-snapshot', '5-snapshot', '4-snapshot'
88
# from the FirebirdSQL/snapshots GitHub repository.
9+
#
10+
# Multi-arch: each arch builds and pushes by digest, then a final job
11+
# assembles a multi-arch manifest via `docker buildx imagetools create`.
912

1013
on:
1114
schedule:
@@ -40,6 +43,11 @@ jobs:
4043
runner: ubuntu-latest
4144
- arch: arm64
4245
runner: ubuntu-24.04-arm
46+
# Firebird 4.x has no linux-arm64 snapshot tarball (FirebirdSQL/snapshots
47+
# ships Android arm64 only). Mirrors the FB3/FB4 amd64-only stable rule.
48+
exclude:
49+
- branch: v4.0
50+
arch: arm64
4351
runs-on: ${{ matrix.runner }}
4452
steps:
4553
- name: Checkout
@@ -54,29 +62,62 @@ jobs:
5462
Install-Module InvokeBuild -Force
5563
Install-Module PSFirebird -MinimumVersion '1.0.0' -Force
5664
57-
- name: Build snapshot
65+
- name: Login to Docker Hub
66+
uses: docker/login-action@v4
67+
with:
68+
username: ${{ secrets.DOCKERHUB_USERNAME }}
69+
password: ${{ secrets.DOCKERHUB_TOKEN }}
70+
71+
- name: Build and push snapshot (by digest)
5872
shell: pwsh
5973
env:
6074
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
6175
run: |
6276
Invoke-Build Build-Snapshot -Branch '${{ matrix.branch }}'
6377
78+
- name: Upload digest
79+
uses: actions/upload-artifact@v7
80+
with:
81+
name: digests-snapshot-${{ matrix.branch }}-${{ matrix.arch }}
82+
path: generated/digests-snapshot-*.json
83+
if-no-files-found: error
84+
retention-days: 1
85+
86+
create-manifests:
87+
if: ${{ github.repository == 'FirebirdSQL/firebird-docker' }}
88+
needs: build-snapshot
89+
strategy:
90+
fail-fast: false
91+
matrix:
92+
branch: ${{ github.event_name == 'workflow_dispatch' && fromJSON(format('["{0}"]', inputs.branch)) || fromJSON('["master", "v5.0-release"]') }}
93+
runs-on: ubuntu-latest
94+
steps:
95+
- name: Checkout
96+
uses: actions/checkout@v6
97+
98+
- name: Download digests
99+
uses: actions/download-artifact@v8
100+
with:
101+
path: generated
102+
pattern: digests-snapshot-${{ matrix.branch }}-*
103+
merge-multiple: true
104+
105+
- name: Set up Docker Buildx
106+
uses: docker/setup-buildx-action@v4
107+
108+
- name: Install tools
109+
shell: pwsh
110+
run: |
111+
Install-Module InvokeBuild -Force
112+
Install-Module PSFirebird -MinimumVersion '1.0.0' -Force
113+
64114
- name: Login to Docker Hub
65115
uses: docker/login-action@v4
66116
with:
67117
username: ${{ secrets.DOCKERHUB_USERNAME }}
68118
password: ${{ secrets.DOCKERHUB_TOKEN }}
69119

70-
- name: Push snapshot
120+
- name: Assemble multi-arch manifest
71121
shell: pwsh
72122
run: |
73-
$branch = '${{ matrix.branch }}'
74-
$tag = switch ($branch) {
75-
'master' { '6-snapshot' }
76-
'v5.0-release' { '5-snapshot' }
77-
'v4.0' { '4-snapshot' }
78-
}
79-
$assets = Get-Content -Raw ./assets.json | ConvertFrom-Json
80-
$defaultDistro = $assets.config.defaultDistro
81-
docker push "firebirdsql/firebird:$tag"
82-
docker push "firebirdsql/firebird:$tag-$defaultDistro"
123+
Invoke-Build Publish-Snapshot-Manifests -Branch '${{ matrix.branch }}'

firebird-docker.build.ps1

Lines changed: 87 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ param(
66
[string]$TestFilter, # Filter by test name (e.g., 'FIREBIRD_USER_can_create_user'). Used only in the 'Test' task.
77

88
[ValidateSet('master', 'v5.0-release', 'v4.0')]
9-
[string]$Branch, # Snapshot branch. Used only in the 'Build-Snapshot' task.
9+
[string]$Branch, # Snapshot branch. Used by 'Build-Snapshot' / 'Publish-Snapshot-Manifests'.
1010

1111
[string]$Registry # Image registry/owner prefix. Defaults to 'firebirdsql' (Docker Hub).
1212
# Override for forks: e.g. 'ghcr.io/myusername'
@@ -576,12 +576,34 @@ task Publish-Manifests FilteredAssets, {
576576
}
577577
}
578578

579-
# Synopsis: Build a snapshot image from a Firebird pre-release branch.
580-
task Build-Snapshot LoadAssets, {
579+
# Helper: produces { snapshotTag, major, defaultDistro } for a given snapshot branch.
580+
function Get-SnapshotMeta($Branch) {
581581
if (-not $Branch) {
582-
throw "The -Branch parameter is required for Build-Snapshot. Use: Invoke-Build Build-Snapshot -Branch master"
582+
throw "The -Branch parameter is required. Use: -Branch master|v5.0-release|v4.0"
583+
}
584+
$snapshotTag = switch ($Branch) {
585+
'master' { '6-snapshot' }
586+
'v5.0-release' { '5-snapshot' }
587+
'v4.0' { '4-snapshot' }
588+
default { throw "Unknown snapshot branch: $Branch" }
589+
}
590+
$major = switch ($Branch) {
591+
'master' { '6' }
592+
'v5.0-release' { '5' }
593+
'v4.0' { '4' }
594+
}
595+
[pscustomobject]@{
596+
SnapshotTag = $snapshotTag
597+
Major = $major
598+
DefaultDistro = $script:assetsData.config.defaultDistro
583599
}
600+
}
584601

602+
# Synopsis: Build a snapshot image and push it by digest to the registry.
603+
# Saves the digest to generated/digests-snapshot-{arch}.json so a later
604+
# Publish-Snapshot-Manifests run can assemble the multi-arch manifest.
605+
# Caller must be logged into the registry before invoking this task.
606+
task Build-Snapshot LoadAssets, {
585607
# PSFirebird is required for this task
586608
if (-not (Get-Module PSFirebird -ListAvailable)) {
587609
Install-Module PSFirebird -MinimumVersion '1.0.0' -Force -Scope CurrentUser
@@ -591,7 +613,10 @@ task Build-Snapshot LoadAssets, {
591613
$PSStyle.OutputRendering = 'PlainText'
592614
$imagePrefix = $script:imagePrefix
593615
$imageName = 'firebird'
594-
$defaultDistro = $script:assetsData.config.defaultDistro
616+
$meta = Get-SnapshotMeta $Branch
617+
$snapshotTag = $meta.SnapshotTag
618+
$major = $meta.Major
619+
$defaultDistro = $meta.DefaultDistro
595620

596621
# Detect host architecture
597622
$hostArch = if ($IsLinux) { (dpkg --print-architecture 2>$null) ?? 'amd64' } else { 'amd64' }
@@ -602,20 +627,6 @@ task Build-Snapshot LoadAssets, {
602627

603628
Write-Build Cyan "Found: $($snapshot.FileName) (uploaded: $($snapshot.UploadedAt))"
604629

605-
# Determine version tag from branch
606-
$snapshotTag = switch ($Branch) {
607-
'master' { '6-snapshot' }
608-
'v5.0-release' { '5-snapshot' }
609-
'v4.0' { '4-snapshot' }
610-
}
611-
612-
# Determine major version for Dockerfile template
613-
$major = switch ($Branch) {
614-
'master' { '6' }
615-
'v5.0-release' { '5' }
616-
'v4.0' { '4' }
617-
}
618-
619630
# Prepare snapshot Dockerfile
620631
$snapshotFolder = Join-Path $outputFolder "snapshot-$Branch" $defaultDistro
621632
New-Item -ItemType Directory $snapshotFolder -Force > $null
@@ -645,24 +656,71 @@ task Build-Snapshot LoadAssets, {
645656
Write-GeneratedFile -Content $dockerfile -Destination "$snapshotFolder/Dockerfile"
646657
Copy-Item './src/entrypoint.sh' $snapshotFolder
647658

648-
# Tag with both the bare alias (e.g. `6-snapshot`) and the base-qualified form
649-
# (e.g. `6-snapshot-trixie`) so users can tell which distro the snapshot was built on.
650-
$snapshotTagWithDistro = "$snapshotTag-$defaultDistro"
651-
652-
# Build
659+
# Build and push by digest. Final tags (e.g. `6-snapshot`, `6-snapshot-trixie`)
660+
# are assembled from per-arch digests by Publish-Snapshot-Manifests.
661+
$metadataFile = Join-Path ([System.IO.Path]::GetTempPath()) "metadata-snapshot-$Branch.json"
653662
$buildArgs = @(
654-
'buildx', 'build', '--load'
655-
'--tag', "$imagePrefix/${imageName}:$snapshotTag"
656-
'--tag', "$imagePrefix/${imageName}:$snapshotTagWithDistro"
663+
'buildx', 'build'
664+
'--output', "type=image,name=$imagePrefix/$imageName,push-by-digest=true,name-canonical=true,push=true"
665+
'--metadata-file', $metadataFile
657666
'--label', 'org.opencontainers.image.description=Firebird Database (snapshot)'
658667
'--label', "org.opencontainers.image.version=$snapshotTag"
659668
'--progress=plain'
660669
$snapshotFolder
661670
)
662-
Write-Build Cyan "----- [snapshot / $Branch / $defaultDistro / $hostArch] -----"
671+
Write-Build Cyan "----- [snapshot / $Branch / $defaultDistro / $hostArch → push-by-digest] -----"
663672
exec { & docker $buildArgs *>&1 }
664673

665-
Write-Build Green "Snapshot image built: $imagePrefix/${imageName}:$snapshotTag (also tagged $snapshotTagWithDistro)"
674+
$metadata = Get-Content $metadataFile -Raw | ConvertFrom-Json
675+
$digest = $metadata.'containerimage.digest'
676+
677+
# Save digest for later manifest assembly
678+
$digestFile = Join-Path $outputFolder "digests-snapshot-$hostArch.json"
679+
New-Item -ItemType Directory (Split-Path $digestFile) -Force > $null
680+
@{ $snapshotTag = $digest } | ConvertTo-Json | Out-File $digestFile -Encoding UTF8
681+
682+
Write-Build Green "Snapshot $snapshotTag pushed by digest: $digest"
683+
Write-Build Green "Digest saved to $digestFile"
684+
}
685+
686+
# Synopsis: Assemble a multi-arch (or single-arch) manifest for a snapshot branch
687+
# using digests previously written by Build-Snapshot. Reads
688+
# generated/digests-snapshot-amd64.json (required) and
689+
# generated/digests-snapshot-arm64.json (optional).
690+
task Publish-Snapshot-Manifests LoadAssets, {
691+
$imagePrefix = $script:imagePrefix
692+
$imageName = 'firebird'
693+
$meta = Get-SnapshotMeta $Branch
694+
$snapshotTag = $meta.SnapshotTag
695+
$defaultDistro = $meta.DefaultDistro
696+
$snapshotTagWithDistro = "$snapshotTag-$defaultDistro"
697+
698+
$amd64File = Join-Path $outputFolder 'digests-snapshot-amd64.json'
699+
$arm64File = Join-Path $outputFolder 'digests-snapshot-arm64.json'
700+
701+
if (-not (Test-Path $amd64File)) {
702+
throw "Required digest file not found: $amd64File. Run Build-Snapshot on amd64 first."
703+
}
704+
$amd64Digest = (Get-Content $amd64File -Raw | ConvertFrom-Json).$snapshotTag
705+
if (-not $amd64Digest) {
706+
throw "No digest for '$snapshotTag' in $amd64File."
707+
}
708+
709+
$sources = @("$imagePrefix/${imageName}@$amd64Digest")
710+
if (Test-Path $arm64File) {
711+
$arm64Digest = (Get-Content $arm64File -Raw | ConvertFrom-Json).$snapshotTag
712+
if ($arm64Digest) {
713+
$sources += "$imagePrefix/${imageName}@$arm64Digest"
714+
}
715+
}
716+
717+
foreach ($tag in @($snapshotTag, $snapshotTagWithDistro)) {
718+
Write-Build Cyan " $tag → manifest ($($sources.Count) arch)"
719+
$tagArgs = @('buildx', 'imagetools', 'create', '--tag', "$imagePrefix/${imageName}:$tag") + $sources
720+
exec { & docker $tagArgs *>&1 }
721+
}
722+
723+
Write-Build Green "Snapshot manifests published: $snapshotTag, $snapshotTagWithDistro"
666724
}
667725

668726
# Synopsis: Default task.

0 commit comments

Comments
 (0)