Skip to content

Commit 485d859

Browse files
authored
refactor(release): separate binary preparation and attachment (#11106)
2 parents 24b3ee4 + 0070123 commit 485d859

6 files changed

Lines changed: 133 additions & 62 deletions

File tree

.github/workflows/README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ graph TB
3131
%% Reusable build
3232
subgraph Build
3333
BuildDocker[zfnd-build-docker-image.yml]
34+
PrepareBinaries[zfnd-release-binaries.yml]
35+
AttachBinaries[zfnd-attach-release-binaries.yml]
3436
end
3537
3638
%% Release automation
@@ -69,14 +71,16 @@ graph TB
6971
Manual --> IT & DeployNodes & Cleanup
7072
7173
%% Build dependency
74+
ReleaseBinaries --> BuildDocker & PrepareBinaries
75+
PrepareBinaries --> AttachBinaries
7276
BuildDocker --> IT
7377
IT --> FindDisks --> Deploy
7478
7579
%% Styling
7680
classDef primary fill:#2374ab,stroke:#2374ab,color:white
7781
classDef secondary fill:#48a9a6,stroke:#48a9a6,color:white
7882
classDef trigger fill:#95a5a6,stroke:#95a5a6,color:white
79-
class BuildDocker primary
83+
class BuildDocker,PrepareBinaries,AttachBinaries primary
8084
class ReleaseWorkflow,ReleaseBinaries primary
8185
class Unit,Lint,Coverage,DockerCfg,CrateBuild,PRGate,Docs,Security secondary
8286
class IT,FindDisks,Deploy,DeployNodes,Cleanup secondary
@@ -160,12 +164,14 @@ _The diagram above illustrates the parallel execution patterns in our CI/CD syst
160164
- **Docs (Book + internal)** (`book.yml`): Builds mdBook and internal rustdoc, publishes to Pages
161165
- **Security Analysis** (`zizmor.yml`): GitHub Actions security lint (SARIF)
162166
- **Release** (`release.yml`): Creates or updates Release PRs with release-plz, then uses `ZcashFoundation/cargo-release` and native Cargo to reconcile crates, tags, and one `zebrad` GitHub Release. See the [release process](../../book/src/dev/release-process.md#release-candidate--release-process) for operational instructions.
163-
- **Release Binaries** (`release-binaries.yml`): Build and publish release artifacts
167+
- **Release Binaries** (`release-binaries.yml`): Orchestrates release images, prepares and attaches downloadable binaries, and supports manual preparation validation without release attachment
164168
- **Integration Tests on GCP** (`zfnd-ci-integration-tests-gcp.yml`): Stateful tests, E2E tests, cached disks, lwd flows
165169

166170
### Supporting/Re-usable Workflows
167171

168172
- **Build docker image** (`zfnd-build-docker-image.yml`): Reusable image build with caching and tagging
173+
- **Prepare release binaries** (`zfnd-release-binaries.yml`): Builds, attests, checksums, signs, and uploads the immutable binary bundle
174+
- **Attach release binaries** (`zfnd-attach-release-binaries.yml`): Attaches the prepared binary bundle to an existing GitHub Release
169175
- **Find cached disks** (`zfnd-find-cached-disks.yml`): Discovers GCP disks for stateful tests
170176
- **Deploy integration tests** (`zfnd-deploy-integration-tests-gcp.yml`): Orchestrates GCP VMs and test runs
171177
- **Deploy nodes** (`zfnd-deploy-nodes-gcp.yml`): Provision long-lived nodes

.github/workflows/release-binaries.yml

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@
77
# This workflow is triggered if:
88
# - A release is published
99
# - A pre-release is changed to a release
10+
# - A maintainer manually validates binary preparation before a release
1011
name: Release binaries
1112

1213
on:
1314
release:
1415
types:
1516
- released
17+
workflow_dispatch:
1618

1719
permissions:
1820
contents: read
@@ -51,15 +53,52 @@ jobs:
5153
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
5254
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
5355

54-
# Build and attach downloadable zebrad binaries to the GitHub release.
55-
binaries:
56-
name: Attach release binaries
56+
# Resolve the release tag into the version used in binary asset names.
57+
binary-version:
58+
name: Resolve release binary version
5759
if: github.repository_owner == 'ZcashFoundation' && startsWith(github.event.release.tag_name, 'v')
60+
runs-on: ubuntu-latest
61+
timeout-minutes: 1
62+
outputs:
63+
version: ${{ steps.release.outputs.version }}
64+
steps:
65+
- name: Resolve release inputs
66+
id: release
67+
env:
68+
RELEASE_TAG: ${{ github.event.release.tag_name }}
69+
run: |
70+
printf 'version=%s\n' "${RELEASE_TAG#v}" >> "${GITHUB_OUTPUT}"
71+
72+
prepare-binaries:
73+
name: Prepare release binaries
74+
needs: binary-version
5875
permissions:
59-
contents: write
60-
id-token: write
61-
attestations: write
76+
contents: read
77+
id-token: write # authenticate binary provenance and signatures
78+
attestations: write # store binary provenance
6279
uses: ./.github/workflows/zfnd-release-binaries.yml
80+
with:
81+
version: ${{ needs.binary-version.outputs.version }}
82+
features: default-release-binaries
83+
84+
validate-binaries:
85+
name: Validate release binary preparation
86+
if: github.event_name == 'workflow_dispatch' && github.repository_owner == 'ZcashFoundation'
87+
permissions:
88+
contents: read
89+
id-token: write # authenticate test provenance and signatures
90+
attestations: write # store test provenance
91+
uses: ./.github/workflows/zfnd-release-binaries.yml
92+
with:
93+
version: 0.0.0-test
94+
features: default-release-binaries
95+
96+
attach-binaries:
97+
name: Attach release binaries
98+
needs: prepare-binaries
99+
permissions:
100+
contents: write # attach assets to the existing release
101+
uses: ./.github/workflows/zfnd-attach-release-binaries.yml
63102
with:
64103
release_tag: ${{ github.event.release.tag_name }}
65104

@@ -95,7 +134,9 @@ jobs:
95134
}}
96135
needs:
97136
- build
98-
- binaries
137+
- binary-version
138+
- prepare-binaries
139+
- attach-binaries
99140
timeout-minutes: 1
100141
steps:
101142
- name: Decide whether the needed jobs succeeded or failed
@@ -106,7 +147,7 @@ jobs:
106147
failure-issue:
107148
name: Open or update issues for release binaries failures
108149
# When a new job is added to this workflow, add it to this list.
109-
needs: [ build, binaries ]
150+
needs: [ build, binary-version, prepare-binaries, attach-binaries ]
110151
# Open tickets for any failed build in this workflow.
111152
if: failure() || cancelled()
112153
runs-on: ubuntu-latest
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Attaches a prepared `release-binaries` workflow artifact to an existing
2+
# GitHub release. Binary construction and signing belong to
3+
# `zfnd-release-binaries.yml`.
4+
name: Attach release binaries (reusable)
5+
6+
on:
7+
workflow_call:
8+
inputs:
9+
release_tag:
10+
description: "Existing GitHub release tag that receives the binaries"
11+
required: true
12+
type: string
13+
14+
permissions: {}
15+
16+
jobs:
17+
attach:
18+
name: Attach release binaries
19+
if: >-
20+
${{
21+
github.repository == 'ZcashFoundation/zebra' &&
22+
github.event_name == 'release' &&
23+
github.event.action == 'released' &&
24+
startsWith(inputs.release_tag, 'v') &&
25+
inputs.release_tag == github.event.release.tag_name
26+
}}
27+
runs-on: ubuntu-latest
28+
timeout-minutes: 15
29+
permissions:
30+
contents: write # attach assets to the existing release
31+
env:
32+
RELEASE_TAG: ${{ inputs.release_tag }}
33+
REPOSITORY: ${{ github.repository }}
34+
steps:
35+
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c #v8.0.1
36+
with:
37+
name: release-binaries
38+
path: dist
39+
40+
- name: Upload binaries to the release
41+
env:
42+
GH_TOKEN: ${{ github.token }}
43+
run: |
44+
gh release upload "${RELEASE_TAG}" \
45+
dist/*.tar.gz dist/*.sha256 dist/SHA256SUMS dist/SHA256SUMS.sigstore.json \
46+
--repo "${REPOSITORY}" --clobber

.github/workflows/zfnd-release-binaries.yml

Lines changed: 25 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,22 @@
1-
# Builds the zebrad binary for Linux and packages it into signed, checksummed
2-
# `.tar.gz` archives. Reused by the release path and by the validation harness.
1+
# Builds zebrad for Linux and packages it into signed, checksummed `.tar.gz`
2+
# archives. The completed bundle is uploaded as the `release-binaries`
3+
# workflow artifact for a separate attachment job to consume.
34
#
45
# Binaries are built on Ubuntu 22.04 for a low glibc floor, with RocksDB and the
56
# zcash FFI linked statically, so they run on most current distributions.
6-
#
7-
# With `release_tag`, the archives are uploaded to that GitHub release; without
8-
# it, they are uploaded as a CI artifact for inspection.
9-
name: Release binaries (reusable)
7+
name: Prepare release binaries (reusable)
108

119
on:
1210
workflow_call:
1311
inputs:
14-
release_tag:
15-
description: "Release tag to upload assets to; empty uploads a CI artifact instead"
16-
required: false
17-
type: string
18-
default: ""
1912
version:
20-
description: "Version used in asset filenames; defaults to release_tag without its `v`"
21-
required: false
13+
description: "Version used in asset filenames"
14+
required: true
2215
type: string
23-
default: ""
2416
features:
2517
description: "Cargo features to build zebrad with"
26-
required: false
18+
required: true
2719
type: string
28-
default: "default-release-binaries"
2920

3021
permissions: {}
3122

@@ -47,12 +38,11 @@ jobs:
4738
# ubuntu-22.04-arm), not the downstream publisher's runner.
4839
permissions:
4940
contents: read
50-
id-token: write
51-
attestations: write
41+
id-token: write # authenticate build provenance
42+
attestations: write # store build provenance
5243
env:
5344
TARGET: ${{ matrix.target }}
54-
RELEASE_TAG: ${{ inputs.release_tag }}
55-
VERSION_INPUT: ${{ inputs.version }}
45+
VERSION: ${{ inputs.version }}
5646
FEATURES: ${{ inputs.features }}
5747
steps:
5848
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
@@ -74,9 +64,11 @@ jobs:
7464
- name: Build and package zebrad
7565
run: |
7666
set -euo pipefail
77-
version="${VERSION_INPUT}"
78-
if [ -z "${version}" ]; then version="${RELEASE_TAG#v}"; fi
79-
if [ -z "${version}" ]; then echo "::error::no version or release_tag provided"; exit 1; fi
67+
68+
if [ -z "${VERSION}" ]; then
69+
echo "::error::version must not be empty"
70+
exit 1
71+
fi
8072
8173
SOURCE_DATE_EPOCH="$(git log -1 --pretty=%ct)"
8274
export SOURCE_DATE_EPOCH
@@ -103,7 +95,7 @@ jobs:
10395
exit 1
10496
fi
10597
106-
stage="zebrad-${version}-${TARGET}"
98+
stage="zebrad-${VERSION}-${TARGET}"
10799
mkdir -p "dist/${stage}"
108100
cp target/release/zebrad "dist/${stage}/zebrad"
109101
cp LICENSE-APACHE LICENSE-MIT README.md "dist/${stage}/"
@@ -116,10 +108,7 @@ jobs:
116108
117109
# Generated in the build job so the attestation reflects the matrix
118110
# runner (ubuntu-22.04 / ubuntu-22.04-arm), not the publish job's runner.
119-
# Gated to release runs so the test harness cannot mint attestations
120-
# under the same --signer-workflow path users are told to trust.
121111
- name: Attest build provenance
122-
if: inputs.release_tag != ''
123112
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
124113
with:
125114
subject-path: dist/*.tar.gz
@@ -130,17 +119,16 @@ jobs:
130119
path: dist/
131120
retention-days: 1
132121

133-
publish:
134-
name: Sign and publish binaries
122+
bundle:
123+
name: Sign release binary bundle
135124
needs: build
136125
runs-on: ubuntu-latest
137126
timeout-minutes: 15
138127
permissions:
139-
contents: write
140-
id-token: write
141-
attestations: read
128+
contents: read
129+
id-token: write # authenticate the checksum signature
130+
attestations: read # verify archive provenance
142131
env:
143-
RELEASE_TAG: ${{ inputs.release_tag }}
144132
REPOSITORY: ${{ github.repository }}
145133
steps:
146134
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c #v8.0.1
@@ -157,16 +145,13 @@ jobs:
157145
( cd dist && sha256sum -- *.tar.gz > SHA256SUMS )
158146
159147
- name: Install Cosign
160-
if: inputs.release_tag != ''
161148
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
162149

163150
- name: Sign the checksum manifest
164-
if: inputs.release_tag != ''
165151
working-directory: dist
166152
run: cosign sign-blob --yes --bundle SHA256SUMS.sigstore.json SHA256SUMS
167153

168154
- name: Verify signatures
169-
if: inputs.release_tag != ''
170155
working-directory: dist
171156
env:
172157
GH_TOKEN: ${{ github.token }}
@@ -175,25 +160,16 @@ jobs:
175160
set -euo pipefail
176161
cosign verify-blob SHA256SUMS \
177162
--bundle SHA256SUMS.sigstore.json \
178-
--certificate-identity-regexp="^https://github\\.com/${REPOSITORY}/\\.github/workflows/zfnd-release-binaries\\.yml@" \
163+
--certificate-identity="https://github.com/${REPOSITORY}/.github/workflows/zfnd-release-binaries.yml@${GITHUB_REF}" \
179164
--certificate-oidc-issuer='https://token.actions.githubusercontent.com'
180165
for archive in *.tar.gz; do
181166
gh attestation verify "${archive}" \
182167
--repo "${REPOSITORY}" \
183-
--signer-workflow "${REPOSITORY}/.github/workflows/zfnd-release-binaries.yml"
168+
--signer-workflow "${REPOSITORY}/.github/workflows/zfnd-release-binaries.yml" \
169+
--source-digest "${GITHUB_SHA}"
184170
done
185171
186-
- name: Upload binaries to the release
187-
if: inputs.release_tag != ''
188-
env:
189-
GH_TOKEN: ${{ github.token }}
190-
run: |
191-
gh release upload "${RELEASE_TAG}" \
192-
dist/*.tar.gz dist/*.sha256 dist/SHA256SUMS dist/SHA256SUMS.sigstore.json \
193-
--repo "${REPOSITORY}" --clobber
194-
195-
- name: Upload binaries as a CI artifact
196-
if: inputs.release_tag == ''
172+
- name: Upload release binary bundle
197173
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a #v7.0.1
198174
with:
199175
name: release-binaries

book/src/user/install.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,11 @@ Or download an archive, verify it, and extract `zebrad`:
2424
```bash
2525
gh attestation verify zebrad-<version>-x86_64-unknown-linux-gnu.tar.gz \
2626
--repo ZcashFoundation/zebra \
27-
--signer-workflow ZcashFoundation/zebra/.github/workflows/zfnd-release-binaries.yml
27+
--signer-workflow ZcashFoundation/zebra/.github/workflows/zfnd-release-binaries.yml \
28+
--source-ref 'refs/tags/v<version>'
2829
cosign verify-blob SHA256SUMS \
2930
--bundle SHA256SUMS.sigstore.json \
30-
--certificate-identity-regexp='^https://github\.com/ZcashFoundation/zebra/\.github/workflows/zfnd-release-binaries\.yml@' \
31+
--certificate-identity='https://github.com/ZcashFoundation/zebra/.github/workflows/zfnd-release-binaries.yml@refs/tags/v<version>' \
3132
--certificate-oidc-issuer='https://token.actions.githubusercontent.com'
3233
sha256sum --ignore-missing -c SHA256SUMS
3334
tar xzf zebrad-<version>-x86_64-unknown-linux-gnu.tar.gz

docs/decisions/devops/0008-release-binary-artifacts.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ Per release, for `x86_64-unknown-linux-gnu` and `aarch64-unknown-linux-gnu`:
2929
- `zebrad-<version>-<target>.tar.gz` (the binary plus `LICENSE-APACHE`, `LICENSE-MIT`, `README.md`) and a `.tar.gz.sha256` sidecar.
3030
- One `SHA256SUMS` manifest covering both archives.
3131
- `SHA256SUMS.sigstore.json`: a Cosign keyless (Sigstore) signature over the manifest, with the Fulcio certificate identity pinned to the release workflow.
32-
- SLSA v1 build provenance per archive via `actions/attest-build-provenance`, stored in GitHub's attestation API and verified with `gh attestation verify --signer-workflow`.
32+
- SLSA v1 build provenance per archive via `actions/attest-build-provenance`, stored in GitHub's attestation API and verified against the expected signer workflow and release source ref.
3333

3434
Six assets. Integrity and provenance for the whole release rest on one signed manifest plus one attestation per archive, not on per-file signatures. `cargo binstall zebrad` consumes these archives, giving a package-manager-style one-liner with no hosted repository to operate.
3535

@@ -86,6 +86,7 @@ That augmentation has to be re-validated on every bump of those crates, and the
8686
gh attestation verify <archive> \
8787
--repo ZcashFoundation/zebra \
8888
--signer-workflow ZcashFoundation/zebra/.github/workflows/zfnd-release-binaries.yml \
89+
--source-ref 'refs/tags/v<version>' \
8990
--predicate-type https://spdx.dev/Document/v2.3
9091
```
9192

0 commit comments

Comments
 (0)