-
Notifications
You must be signed in to change notification settings - Fork 0
383 lines (353 loc) · 14.2 KB
/
Copy pathrelease.yml
File metadata and controls
383 lines (353 loc) · 14.2 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
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
name: Release — Build, Bundle & Publish
# UbeROS release pipeline (Theme B, FR-B1/FR-B2/FR-B3). On a `v*` version tag,
# build every service image for linux/amd64 and push it to GitHub Container
# Registry (ghcr.io) tagged with the release version. Pre-release tags (a `-`
# in the version, e.g. v0.4.0-beta) additionally publish a moving `:beta` tag so
# testers can track the latest pre-release. The version flows into each image as
# the UBEROS_VERSION build-arg, which stamps the OCI version label (FR-A5, PR-3)
# and the control /version endpoint default.
#
# After every image is available, the pipeline generates the pinned Compose
# definition, assembles and verifies both bundle formats, then publishes the
# checksummed artifacts and generated notes to the tag's GitHub Release.
on:
push:
tags:
- 'v*'
permissions:
contents: read
packages: write
env:
REGISTRY: ghcr.io
IMAGE_NAMESPACE: ghcr.io/jmservera/uberos
jobs:
publish-images:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write # keyless signing via GitHub OIDC (issue #15)
attestations: write # store SLSA build provenance beside the image
strategy:
fail-fast: false
matrix:
include:
- service: ros
image: ros
context: ./services/ros
- service: gazebo
image: gazebo
context: ./services/gazebo
- service: turtlesim
image: learning-turtlesim
context: ./services/turtlesim
- service: editor
image: editor
context: ./services/editor
- service: frontend
image: frontend
context: ./services/frontend
- service: gzweb-client
image: gzweb-client
context: ./services/gazebo/client
- service: control
image: control
context: ./services/control
- service: proxy
image: proxy
context: ./services/proxy
steps:
- uses: actions/checkout@v4
- name: Derive version and image tags
id: meta
run: |
set -euo pipefail
version="${GITHUB_REF_NAME#v}"
image="${IMAGE_NAMESPACE}/${{ matrix.image }}"
tags="${image}:${version}"
# A hyphen marks a semver pre-release (e.g. 0.4.0-beta); publish the
# moving :beta tag alongside the pinned version (docs/VERSIONING.md).
case "${version}" in
*-*) tags="$(printf '%s\n%s' "${tags}" "${image}:beta")" ;;
esac
# docker/build-push-action expects one tag per line, so `tags` must be
# emitted as a multiline output (heredoc form), never comma-joined.
{
echo "version=${version}"
echo "tags<<TAGS_EOF"
echo "${tags}"
echo "TAGS_EOF"
} >> "${GITHUB_OUTPUT}"
echo "Publishing ${{ matrix.service }} as:"
echo "${tags}"
- 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 ${{ matrix.service }}
id: build
uses: docker/build-push-action@v6
with:
context: ${{ matrix.context }}
platforms: linux/amd64
push: true
tags: ${{ steps.meta.outputs.tags }}
build-args: |
UBEROS_VERSION=${{ steps.meta.outputs.version }}
NPM_REGISTRY=https://registry.npmjs.org/
# Signed SLSA build provenance (issue #15). Keyless via GitHub OIDC; the
# attestation is keyed by digest, so one covers the pinned version tag and
# the moving :beta reference at once.
- name: Attest build provenance
uses: actions/attest-build-provenance@v4.1.1
with:
subject-name: ${{ env.IMAGE_NAMESPACE }}/${{ matrix.image }}
subject-digest: ${{ steps.build.outputs.digest }}
push-to-registry: true
# Generate the pinned release compose once every image exists, and fail the
# release if any reference is unpinned or floating (FR-B4, FR-B9). Bundle
# assembly consumes this artifact in a later pipeline stage (PR-12).
compose-release:
needs: publish-images
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Generate compose.release.yaml
env:
# Keep the generator on the same registry the images were pushed to;
# without this the script would fall back to its own literal default.
UBEROS_IMAGE_NAMESPACE: ${{ env.IMAGE_NAMESPACE }}
run: sh scripts/gen-compose-release.sh "${GITHUB_REF_NAME}"
- name: Validate generated compose
run: docker compose -f compose.release.yaml config >/dev/null
- name: Enforce pinned image references
run: sh scripts/gen-compose-release.sh --check compose.release.yaml
- name: Upload compose.release.yaml
uses: actions/upload-artifact@v4
with:
name: compose-release
path: compose.release.yaml
if-no-files-found: error
publish-release:
needs: compose-release
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write # keyless cosign sign-blob via GitHub OIDC (issue #15)
steps:
- uses: actions/checkout@v4
- name: Download compose.release.yaml
uses: actions/download-artifact@v4
with:
name: compose-release
path: release-input
- name: Derive release metadata
id: meta
run: |
set -euo pipefail
version="${GITHUB_REF_NAME#v}"
archive_base="uberos-${version}"
prerelease=false
case "${version}" in
*-*) prerelease=true ;;
esac
{
echo "version=${version}"
echo "archive_base=${archive_base}"
echo "prerelease=${prerelease}"
} >> "${GITHUB_OUTPUT}"
- name: Generate release notes
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
gh api \
--method POST \
"repos/${GITHUB_REPOSITORY}/releases/generate-notes" \
-f tag_name="${GITHUB_REF_NAME}" \
-f target_commitish="${GITHUB_SHA}" \
--jq .body > RELEASE_NOTES.md
test -s RELEASE_NOTES.md
- name: Assemble release bundle
env:
ARCHIVE_BASE: ${{ steps.meta.outputs.archive_base }}
VERSION: ${{ steps.meta.outputs.version }}
run: |
set -euo pipefail
bundle="dist/${ARCHIVE_BASE}"
mkdir -p "${bundle}/config" "${bundle}/simulation"
cp release-input/compose.release.yaml "${bundle}/"
cp compose.override.gpu.yaml compose.override.intel.yaml compose.override.wsl.yaml "${bundle}/"
cp install.sh .env.template RELEASE_NOTES.md "${bundle}/"
git archive --format=tar HEAD -- config/nginx simulation \
| tar -x -C "${bundle}"
printf '%s\n' "${VERSION}" > "${bundle}/VERSION"
(
cd "${bundle}"
find . -type f ! -name checksums.txt -print0 \
| sort -z \
| xargs -0 sha256sum > ../payload-checksums.tmp
mv ../payload-checksums.tmp checksums.txt
sha256sum --check checksums.txt
)
- name: Assert bundle contains no secrets
env:
ARCHIVE_BASE: ${{ steps.meta.outputs.archive_base }}
run: |
set -euo pipefail
bundle="dist/${ARCHIVE_BASE}"
test ! -e "${bundle}/.env"
test ! -e "${bundle}/config/nginx/.htpasswd"
if grep -RIE \
--exclude='.htpasswd.example' \
'(ghp_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,}|AKIA[0-9A-Z]{16}|BEGIN (RSA |EC |OPENSSH )?PRIVATE KEY|([A-Z0-9_]*(PASSWORD|TOKEN|SECRET|API_KEY)[A-Z0-9_]*)[[:space:]]*=[[:space:]]*[^[:space:]#]+)' \
"${bundle}"; then
echo '::error::Potential secret found in release bundle'
exit 1
fi
- name: Install Cosign
uses: sigstore/cosign-installer@v4.1.0
# Sign the in-bundle checksums.txt and ship the signature inside the
# bundle so the release-mode installer can verify authenticity offline
# (issue #15). checksums.txt already vouches for every bundled file, so one
# signature covers the extracted payload.
- name: Sign bundle checksums for offline verification
env:
ARCHIVE_BASE: ${{ steps.meta.outputs.archive_base }}
run: |
set -euo pipefail
bundle="dist/${ARCHIVE_BASE}"
cosign sign-blob --yes --new-bundle-format \
--bundle "${bundle}/checksums.txt.cosign.bundle" \
"${bundle}/checksums.txt"
- name: Create archives and checksums
env:
ARCHIVE_BASE: ${{ steps.meta.outputs.archive_base }}
run: |
set -euo pipefail
tar -C dist -czf "dist/${ARCHIVE_BASE}.tar.gz" "${ARCHIVE_BASE}"
(
cd dist
zip -qr "${ARCHIVE_BASE}.zip" "${ARCHIVE_BASE}"
sha256sum "${ARCHIVE_BASE}.tar.gz" "${ARCHIVE_BASE}.zip" > checksums.txt
sha256sum --check checksums.txt
)
- name: Verify archive contents and version
env:
ARCHIVE_BASE: ${{ steps.meta.outputs.archive_base }}
VERSION: ${{ steps.meta.outputs.version }}
run: |
set -euo pipefail
rm -rf verify-tar verify-zip
mkdir verify-tar verify-zip
tar -xzf "dist/${ARCHIVE_BASE}.tar.gz" -C verify-tar
unzip -q "dist/${ARCHIVE_BASE}.zip" -d verify-zip
for root in verify-tar verify-zip; do
bundle="${root}/${ARCHIVE_BASE}"
test "$(cat "${bundle}/VERSION")" = "${VERSION}"
test -f "${bundle}/compose.release.yaml"
test -f "${bundle}/compose.override.gpu.yaml"
test -f "${bundle}/compose.override.intel.yaml"
test -f "${bundle}/compose.override.wsl.yaml"
test -f "${bundle}/install.sh"
test -f "${bundle}/.env.template"
test -f "${bundle}/RELEASE_NOTES.md"
test -f "${bundle}/checksums.txt"
test -d "${bundle}/config/nginx"
test -d "${bundle}/simulation/models"
test -d "${bundle}/simulation/worlds"
(cd "${bundle}" && sha256sum --check checksums.txt)
done
# Keyless detached signatures for the release archives and checksums
# (issue #15). Each .cosign.bundle is portable and verifiable offline with
# `cosign verify-blob` against the workflow identity and OIDC issuer.
- name: Sign release bundle (keyless)
env:
ARCHIVE_BASE: ${{ steps.meta.outputs.archive_base }}
run: |
set -euo pipefail
for f in \
"dist/${ARCHIVE_BASE}.tar.gz" \
"dist/${ARCHIVE_BASE}.zip" \
dist/checksums.txt; do
cosign sign-blob --yes --new-bundle-format \
--bundle "${f}.cosign.bundle" \
"${f}"
done
- name: Create or update GitHub Release
env:
GH_TOKEN: ${{ github.token }}
ARCHIVE_BASE: ${{ steps.meta.outputs.archive_base }}
PRERELEASE: ${{ steps.meta.outputs.prerelease }}
run: |
set -euo pipefail
prerelease_flag=''
[ "${PRERELEASE}" = false ] || prerelease_flag='--prerelease'
if gh release view "${GITHUB_REF_NAME}" >/dev/null 2>&1; then
gh release edit "${GITHUB_REF_NAME}" \
--notes-file RELEASE_NOTES.md \
${prerelease_flag}
else
gh release create "${GITHUB_REF_NAME}" \
--title "UbeROS ${GITHUB_REF_NAME}" \
--notes-file RELEASE_NOTES.md \
${prerelease_flag}
fi
gh release upload "${GITHUB_REF_NAME}" --clobber \
"dist/${ARCHIVE_BASE}.tar.gz" \
"dist/${ARCHIVE_BASE}.zip" \
dist/checksums.txt \
"dist/${ARCHIVE_BASE}.tar.gz.cosign.bundle" \
"dist/${ARCHIVE_BASE}.zip.cosign.bundle" \
dist/checksums.txt.cosign.bundle
verify-signing:
# Prove the freshly published signatures actually verify against the release
# workflow identity, so signing can never silently break (issue #15). This
# gate consumes exactly what a consumer would: the pushed image attestation
# and the bundle signature attached to the GitHub Release.
needs: [publish-images, publish-release]
runs-on: ubuntu-latest
permissions:
contents: read # download release assets via gh
packages: read # resolve image manifests on ghcr
attestations: read # read SLSA provenance for `gh attestation verify`
steps:
- name: Install Cosign
uses: sigstore/cosign-installer@v4.1.0
- name: Derive version
id: meta
run: |
set -euo pipefail
version="${GITHUB_REF_NAME#v}"
echo "version=${version}" >> "${GITHUB_OUTPUT}"
- name: Verify image build provenance
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ steps.meta.outputs.version }}
run: |
set -euo pipefail
gh attestation verify \
"oci://${IMAGE_NAMESPACE}/frontend:${VERSION}" \
--repo "${GITHUB_REPOSITORY}"
- name: Verify release bundle signature
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
work="$(mktemp -d)"
gh release download "${GITHUB_REF_NAME}" \
--repo "${GITHUB_REPOSITORY}" \
--pattern checksums.txt \
--pattern checksums.txt.cosign.bundle \
--dir "${work}"
cosign verify-blob "${work}/checksums.txt" \
--new-bundle-format \
--bundle "${work}/checksums.txt.cosign.bundle" \
--certificate-identity-regexp \
"https://github.com/${GITHUB_REPOSITORY}/.github/workflows/release.yml@.*" \
--certificate-oidc-issuer \
https://token.actions.githubusercontent.com