-
Notifications
You must be signed in to change notification settings - Fork 0
345 lines (288 loc) · 10.7 KB
/
release.yml
File metadata and controls
345 lines (288 loc) · 10.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
name: Release
on:
push:
branches:
- main
workflow_dispatch:
inputs:
dry_run:
description: 'Perform a dry run without creating release'
type: boolean
default: false
permissions:
contents: write
packages: write
id-token: write # For OIDC signing
env:
REGISTRY: ghcr.io
jobs:
check-release:
name: Check Release Readiness
runs-on: ubuntu-latest
outputs:
is_release: ${{ steps.check.outputs.is_release }}
version: ${{ steps.check.outputs.version }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check if release
id: check
run: |
# Get the merge commit message
COMMIT_MSG=$(git log -1 --pretty=%B)
# Check if this is a merge from a release branch
if echo "$COMMIT_MSG" | grep -qE "^Merge pull request .* from .*/release/v[0-9]+\.[0-9]+\.[0-9]+"; then
# Extract version from merge commit
VERSION=$(echo "$COMMIT_MSG" | grep -oE "release/v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+)?" | head -1 | sed 's|release/v||')
echo "is_release=true" >> $GITHUB_OUTPUT
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Detected release version: $VERSION"
else
echo "is_release=false" >> $GITHUB_OUTPUT
echo "Not a release merge"
fi
validate-release:
name: Validate Release
needs: check-release
if: needs.check-release.outputs.is_release == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Validate version files
run: |
VERSION="${{ needs.check-release.outputs.version }}"
# Check version.h
HEADER_VERSION=$(grep "#define METAGRAPH_API_VERSION_STRING" include/metagraph/version.h | cut -d'"' -f2)
if [ "$HEADER_VERSION" != "$VERSION" ]; then
echo "ERROR: version.h shows $HEADER_VERSION, expected $VERSION"
exit 1
fi
# Check CMakeLists.txt
CMAKE_VERSION=$(grep "project(MetaGraph VERSION" CMakeLists.txt | sed 's/.*VERSION \([0-9.]*\).*/\1/')
VERSION_NO_RC=$(echo "$VERSION" | cut -d- -f1)
if [ "$CMAKE_VERSION" != "$VERSION_NO_RC" ]; then
echo "ERROR: CMakeLists.txt shows $CMAKE_VERSION, expected $VERSION_NO_RC"
exit 1
fi
echo "Version validation passed: $VERSION"
- name: Check tag doesn't exist
run: |
if git rev-parse "v${{ needs.check-release.outputs.version }}" >/dev/null 2>&1; then
echo "ERROR: Tag v${{ needs.check-release.outputs.version }} already exists"
exit 1
fi
build-release:
name: Build Release Artifacts
needs: [check-release, validate-release]
if: needs.check-release.outputs.is_release == 'true'
runs-on: ${{ matrix.os }}
strategy:
matrix:
include:
- os: ubuntu-latest
artifact: metagraph-linux-x86_64
- os: macos-latest
artifact: metagraph-macos-universal
- os: windows-latest
artifact: metagraph-windows-x86_64
steps:
- uses: actions/checkout@v4
- name: Setup build environment
uses: ./.github/actions/setup-build-env
with:
os: ${{ runner.os }}
- name: Build release
run: |
SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)
export SOURCE_DATE_EPOCH
cmake -B build-release \
-DCMAKE_BUILD_TYPE=Release \
-DMETAGRAPH_WERROR=ON \
-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON
cmake --build build-release --parallel
- name: Run tests
run: ctest --test-dir build-release --output-on-failure
- name: Package artifacts
run: |
mkdir -p dist
# Create tarball with deterministic attributes
if [ "${{ runner.os }}" != "Windows" ]; then
tar --sort=name \
--mtime="@${SOURCE_DATE_EPOCH}" \
--owner=0 --group=0 --numeric-owner \
-czf "dist/${{ matrix.artifact }}.tar.gz" \
-C build-release/bin .
else
# Windows ZIP
cd build-release/bin
7z a -tzip "../../dist/${{ matrix.artifact }}.zip" *
cd ../..
fi
- name: Generate checksums
run: |
cd dist
if [ "${{ runner.os }}" != "Windows" ]; then
shasum -a 256 *.tar.gz > SHA256SUMS
else
certutil -hashfile *.zip SHA256 > SHA256SUMS
fi
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: release-${{ matrix.artifact }}
path: dist/
retention-days: 7
generate-sbom:
name: Generate SBOM
needs: [check-release, validate-release]
if: needs.check-release.outputs.is_release == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install syft
run: |
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
- name: Generate SBOM
run: |
syft . -o spdx-json > metagraph-${{ needs.check-release.outputs.version }}-sbom.spdx.json
syft . -o cyclonedx-json > metagraph-${{ needs.check-release.outputs.version }}-sbom.cyclonedx.json
- name: Upload SBOM
uses: actions/upload-artifact@v4
with:
name: sbom
path: |
*.spdx.json
*.cyclonedx.json
sign-and-release:
name: Sign and Create Release
needs: [check-release, build-release, generate-sbom]
if: needs.check-release.outputs.is_release == 'true' && github.event.inputs.dry_run != 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts/
- name: Organize artifacts
run: |
mkdir -p release-assets
# Move all release artifacts
find artifacts -name "*.tar.gz" -o -name "*.zip" -exec mv {} release-assets/ \;
find artifacts -name "SHA256SUMS" -exec cat {} >> release-assets/SHA256SUMS.combined \;
find artifacts -name "*.json" -exec mv {} release-assets/ \;
# Sort and deduplicate checksums
sort -u release-assets/SHA256SUMS.combined > release-assets/SHA256SUMS
rm release-assets/SHA256SUMS.combined
- name: Setup Cosign
uses: sigstore/cosign-installer@v3
- name: Sign artifacts
run: |
cd release-assets
# Sign each artifact with cosign (keyless OIDC)
for file in *.tar.gz *.zip *.json; do
if [ -f "$file" ]; then
echo "Signing $file..."
cosign sign-blob \
--yes \
--output-signature="${file}.sig" \
--output-certificate="${file}.crt" \
"$file"
fi
done
# Create a manifest of all signatures
echo "# Signature Manifest" > SIGNATURES.md
echo "Generated: $(date -u +%Y-%m-%dT%H:%M:%SZ)" >> SIGNATURES.md
echo "" >> SIGNATURES.md
for sig in *.sig; do
base=$(basename "$sig" .sig)
echo "## $base" >> SIGNATURES.md
echo '```' >> SIGNATURES.md
cat "$sig" >> SIGNATURES.md
echo '```' >> SIGNATURES.md
echo "" >> SIGNATURES.md
done
- name: Create release tag
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
VERSION="${{ needs.check-release.outputs.version }}"
git tag -a "v$VERSION" -m "Release v$VERSION"
git push origin "v$VERSION"
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
tag_name: v${{ needs.check-release.outputs.version }}
name: MetaGraph v${{ needs.check-release.outputs.version }}
draft: false
prerelease: ${{ contains(needs.check-release.outputs.version, '-') }}
files: release-assets/*
body: |
# MetaGraph v${{ needs.check-release.outputs.version }}
## Installation
Download the appropriate binary for your platform below.
### Verify Downloads
All artifacts are signed with Cosign. To verify:
```bash
# Install cosign
brew install cosign # or see https://docs.sigstore.dev/cosign/installation/
# Verify artifact
cosign verify-blob \
--certificate metagraph-linux-x86_64.tar.gz.crt \
--signature metagraph-linux-x86_64.tar.gz.sig \
metagraph-linux-x86_64.tar.gz
```
### Checksums
Verify file integrity with SHA256:
```bash
shasum -a 256 -c SHA256SUMS
```
## What's Changed
See [CHANGELOG.md](https://github.com/${{ github.repository }}/blob/v${{ needs.check-release.outputs.version }}/CHANGELOG.md) for details.
## Software Bill of Materials
SBOM available in SPDX and CycloneDX formats.
build-container:
name: Build Container Image
needs: [check-release, sign-and-release]
if: needs.check-release.outputs.is_release == 'true' && github.event.inputs.dry_run != 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push container
uses: docker/build-push-action@v5
with:
context: .
file: docker/Dockerfile.runtime
push: true
tags: |
${{ env.REGISTRY }}/${{ github.repository }}:${{ needs.check-release.outputs.version }}
${{ env.REGISTRY }}/${{ github.repository }}:latest
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
SOURCE_DATE_EPOCH=${{ github.event.repository.pushed_at }}
notify-release:
name: Notify Release
needs: [check-release, sign-and-release, build-container]
if: always() && needs.check-release.outputs.is_release == 'true'
runs-on: ubuntu-latest
steps:
- name: Send notification
run: |
if [ "${{ needs.sign-and-release.result }}" == "success" ]; then
echo "✅ Release v${{ needs.check-release.outputs.version }} completed successfully!"
else
echo "❌ Release v${{ needs.check-release.outputs.version }} failed!"
exit 1
fi