Repository navigation
453 lines (411 loc) · 17.1 KB
/
Copy pathrelease.yml
File metadata and controls
453 lines (411 loc) · 17.1 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
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
name: Release
on:
push:
tags: ["v*"]
# Manual escape hatch — re-run an existing tag's publish flow. Used
# to backfill v0.15.1 to npm after the original push-triggered run
# failed at publish-npm (expired NPM_TOKEN; OIDC ships post-merge),
# and to resume a crates.io publish that ran out of wall clock (the
# crate loop skips everything already in the index, so a resume costs
# nothing for the crates already up). Binary build, GitHub Release,
# PyPI, Maven, Packagist and Go stay push-only because they either
# can't be re-published (Maven Central) or the existing artifact is
# already there.
workflow_dispatch:
inputs:
tag:
description: 'Existing tag to re-publish from (e.g. v0.15.1).'
required: true
type: string
npm:
description: 'Publish the npm package.'
required: false
default: true
type: boolean
crates:
description: 'Resume the crates.io publish (skips crates already indexed).'
required: false
default: false
type: boolean
permissions:
contents: write
packages: write
env:
CARGO_TERM_COLOR: always
jobs:
check:
name: Check
if: github.event_name == 'push'
runs-on: ubuntu-latest
timeout-minutes: 90
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
with:
components: rustfmt, clippy
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
- run: cargo fmt --check
- run: cargo clippy --workspace -- -D warnings
- run: cargo test --workspace --exclude fakecloud-e2e --exclude fakecloud-conformance --exclude fakecloud-tfacc --exclude fakecloud-parity
build:
name: Build ${{ matrix.name }}
if: github.event_name == 'push'
needs: check
runs-on: ${{ matrix.runner }}
timeout-minutes: 90
strategy:
fail-fast: false
matrix:
include:
- name: linux-amd64
runner: ubuntu-latest
target: x86_64-unknown-linux-gnu
- name: linux-arm64
runner: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
- name: darwin-amd64
runner: macos-14
target: x86_64-apple-darwin
- name: darwin-arm64
runner: macos-14
target: aarch64-apple-darwin
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
with:
targets: ${{ matrix.target }}
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
key: ${{ matrix.target }}
- name: Build
run: cargo build --release --locked --target ${{ matrix.target }} --bin fakecloud
- name: Package
shell: bash
run: |
TAG="${GITHUB_REF#refs/tags/}"
DIR="fakecloud-${TAG}-${{ matrix.name }}"
mkdir -p "${DIR}"
cp "target/${{ matrix.target }}/release/fakecloud" "${DIR}/"
cp LICENSE "${DIR}/"
tar czf "${DIR}.tar.gz" "${DIR}"
shasum -a 256 "${DIR}.tar.gz" > "${DIR}.tar.gz.sha256"
- name: Upload artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ${{ matrix.name }}
path: fakecloud-*.tar.gz*
if-no-files-found: error
release:
name: Release
if: github.event_name == 'push'
needs: build
runs-on: ubuntu-latest
timeout-minutes: 90
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: artifacts
merge-multiple: true
- name: Create release
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
with:
generate_release_notes: true
files: artifacts/*
make_latest: true
publish-crates:
name: Publish Crates
needs: check
# Runs on push (release flow) and on workflow_dispatch with
# crates=true (resume a publish that a rate-limit wait pushed past
# the job's wall clock). On dispatch `check` is skipped because that
# job is itself gated on push, so allow the skipped result.
if: |
always()
&& (needs.check.result == 'success' || needs.check.result == 'skipped')
&& (github.event_name == 'push' || inputs.crates)
runs-on: ubuntu-latest
# crates.io hands out publish tokens on a refill schedule, so the
# tail of a ~110-crate release is spent waiting, not uploading (see
# scripts/publish-crates.sh). Budget for that; the script's own
# DEADLINE_MINUTES bails out with a resume hint before GitHub's
# hard 6h job ceiling kills the step mid-crate.
timeout-minutes: 350
# Two releases publishing at once would drain the same rate-limit
# buckets and 429 each other. Queue them instead of racing.
concurrency:
group: publish-crates
cancel-in-progress: false
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
# On push the default ref is the tag; on a dispatched resume,
# whatever tag the operator supplied.
ref: ${{ inputs.tag || github.ref }}
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
- name: Resolve release version
run: |
if [ -n "${{ inputs.tag }}" ]; then
TAG="${{ inputs.tag }}"
TAG="${TAG#v}"
else
TAG="${GITHUB_REF#refs/tags/v}"
fi
VERSION=$(grep -m1 '^version' Cargo.toml | sed 's/.*"\(.*\)".*/\1/')
if [ "$TAG" != "$VERSION" ]; then
echo "::error::Tag v${TAG} does not match Cargo.toml version ${VERSION}"
exit 1
fi
echo "Tag v${TAG} matches Cargo.toml version ${VERSION}"
echo "VERSION=${VERSION}" >> "$GITHUB_ENV"
- name: Verify publish list covers the workspace
run: |
if [ ! -f scripts/publish-crates.sh ]; then
echo "::error::this tag predates scripts/publish-crates.sh — publish it by hand from a detached checkout of the tag (cargo publish -p <crate>)"
exit 1
fi
bash scripts/publish-crates.sh --check
- name: Publish all crates
run: bash scripts/publish-crates.sh "$VERSION"
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
publish-npm:
name: Publish npm
needs: check
# Runs on both push (release flow) and workflow_dispatch (manual
# backfill of an existing tag whose original publish-npm failed).
# When dispatched manually `needs: check` is skipped because the
# check job itself is gated on push; allow that via the if guard.
# npm=false lets a dispatch resume only the crates.io publish.
if: |
always()
&& (needs.check.result == 'success' || needs.check.result == 'skipped')
&& (github.event_name == 'push' || inputs.npm)
runs-on: ubuntu-latest
timeout-minutes: 90
# OIDC auth via npm Trusted Publishing (configured at
# https://www.npmjs.com/package/fakecloud/access). No NPM_TOKEN —
# the GitHub OIDC token is exchanged for a short-lived publish
# credential. id-token write is the only extra permission needed.
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
# On push the default ref is the tag; on workflow_dispatch
# we check out whatever tag the operator supplied.
ref: ${{ inputs.tag || github.ref }}
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: "22"
registry-url: https://registry.npmjs.org
# setup-node@v4 with node 22 ships npm 10.x. Trusted Publishing
# OIDC support landed in npm 11.5.1; upgrade explicitly.
- name: Install npm 11.5+ (Trusted Publishing support)
run: npm install -g npm@latest
- name: Verify tag matches package.json version
working-directory: sdks/typescript
run: |
if [ -n "${{ inputs.tag }}" ]; then
TAG="${{ inputs.tag }}"
TAG="${TAG#v}"
else
TAG="${GITHUB_REF#refs/tags/v}"
fi
VERSION=$(node -p "require('./package.json').version")
if [ "$TAG" != "$VERSION" ]; then
echo "::error::Tag v${TAG} does not match package.json version ${VERSION}"
exit 1
fi
echo "Tag v${TAG} matches package.json version ${VERSION}"
- name: Build
working-directory: sdks/typescript
run: |
npm ci
npm run build
- name: Publish
working-directory: sdks/typescript
# --provenance ships the sigstore-signed link between this
# workflow run and the tarball, visible on npmjs as the
# "Provenance" badge. Free with OIDC, no extra setup.
run: npm publish --provenance --access public || { echo "Package may already be published"; npm view fakecloud@"$(node -p "require('./package.json').version")" version && echo "Already published, skipping" || exit 1; }
publish-pypi:
name: Publish PyPI
if: github.event_name == 'push'
needs: check
runs-on: ubuntu-latest
timeout-minutes: 90
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.12"
- name: Verify tag matches pyproject.toml version
working-directory: sdks/python
run: |
TAG="${GITHUB_REF#refs/tags/v}"
VERSION=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
if [ "$TAG" != "$VERSION" ]; then
echo "::error::Tag v${TAG} does not match pyproject.toml version ${VERSION}"
exit 1
fi
echo "Tag v${TAG} matches pyproject.toml version ${VERSION}"
- name: Build
working-directory: sdks/python
run: |
pip install build twine
python -m build
- name: Publish
working-directory: sdks/python
run: twine upload dist/* || { echo "Package may already be published"; pip install fakecloud=="${GITHUB_REF#refs/tags/v}" --dry-run 2>/dev/null && echo "Already published, skipping" || exit 1; }
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
publish-maven:
name: Publish Maven Central
if: github.event_name == 'push'
needs: check
runs-on: ubuntu-latest
timeout-minutes: 90
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: temurin
java-version: "17"
cache: gradle
- name: Verify tag matches build.gradle.kts version
working-directory: sdks/java
run: |
TAG="${GITHUB_REF#refs/tags/v}"
VERSION=$(grep -E '^version = ' build.gradle.kts | head -1 | sed -E 's/.*"(.*)".*/\1/')
if [ "$TAG" != "$VERSION" ]; then
echo "::error::Tag v${TAG} does not match build.gradle.kts version ${VERSION}"
exit 1
fi
echo "Tag v${TAG} matches build.gradle.kts version ${VERSION}"
- name: Publish to Maven Central
working-directory: sdks/java
run: ./gradlew publishToMavenCentral --no-daemon --no-configuration-cache
env:
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.MAVEN_SIGNING_KEY }}
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.MAVEN_SIGNING_PASSWORD }}
publish-packagist:
name: Publish Packagist
if: github.event_name == 'push'
needs: check
runs-on: ubuntu-latest
timeout-minutes: 90
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 0
# Don't persist the default GITHUB_TOKEN as a git credential helper,
# otherwise `git push` to the mirror repo ignores our PAT and uses
# the default token (which can't push to faiscadev/fakecloud-php).
persist-credentials: false
- name: Verify tag matches composer.json version
working-directory: sdks/php
run: |
TAG="${GITHUB_REF#refs/tags/v}"
VERSION=$(php -r "echo json_decode(file_get_contents('composer.json'))->version ?? 'none';")
# composer.json version field is optional — skip check if absent
if [ "$VERSION" != "none" ] && [ "$TAG" != "$VERSION" ]; then
echo "::error::Tag v${TAG} does not match composer.json version ${VERSION}"
exit 1
fi
- name: Split and push to mirror repo
run: |
TAG="${GITHUB_REF#refs/tags/v}"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
# Split sdks/php/ into its own branch with rewritten history
SHA=$(git subtree split --prefix=sdks/php)
# Push to mirror repo
git remote add php-mirror https://x-access-token:${MIRROR_TOKEN}@github.com/faiscadev/fakecloud-php.git
git push php-mirror "${SHA}:refs/heads/main" --force
# Create version tag on mirror (-f because v${TAG} exists in this checkout)
git tag -f "v${TAG}" "${SHA}"
git push php-mirror "v${TAG}" --force
env:
MIRROR_TOKEN: ${{ secrets.PHP_MIRROR_TOKEN }}
publish-go:
name: Publish Go module
if: github.event_name == 'push'
needs: check
runs-on: ubuntu-latest
timeout-minutes: 90
# Creating the sdks/go/vX.Y.Z ref via the API needs write access to repo
# contents; the default job token is read-only, which 403s the tag create.
permissions:
contents: write
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Create Go module tag
run: |
TAG="${GITHUB_REF#refs/tags/v}"
GO_TAG="sdks/go/v${TAG}"
# Check if tag already exists
if gh api "repos/${{ github.repository }}/git/refs/tags/${GO_TAG}" >/dev/null 2>&1; then
echo "Tag ${GO_TAG} already exists, skipping"
exit 0
fi
# Create tag via API (avoids git push workflow permission issues)
gh api "repos/${{ github.repository }}/git/refs" \
-f ref="refs/tags/${GO_TAG}" \
-f sha="${GITHUB_SHA}"
echo "Created tag ${GO_TAG}"
env:
GH_TOKEN: ${{ github.token }}
publish-nuget:
name: Publish NuGet
if: github.event_name == 'push'
needs: check
runs-on: ubuntu-latest
timeout-minutes: 90
# Trusted Publishing (OIDC): NuGet/login exchanges the GitHub OIDC token
# for a short-lived API key, so no NUGET_API_KEY secret is stored.
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1
with:
dotnet-version: |
8.0.x
10.0.x
- name: Verify tag matches FakeCloud.csproj version
working-directory: sdks/dotnet
run: |
TAG="${GITHUB_REF#refs/tags/v}"
VERSION=$(sed -n 's:.*<Version>\(.*\)</Version>.*:\1:p' src/FakeCloud/FakeCloud.csproj | head -1)
if [ "$TAG" != "$VERSION" ]; then
echo "::error::Tag v${TAG} does not match FakeCloud.csproj version ${VERSION}"
exit 1
fi
echo "Tag v${TAG} matches FakeCloud.csproj version ${VERSION}"
- name: Pack
working-directory: sdks/dotnet
run: dotnet pack src/FakeCloud/FakeCloud.csproj --configuration Release --output artifacts
- name: NuGet login (OIDC)
id: nuget-login
uses: NuGet/login@8d196754b4036150537f80ac539e15c2f1028841 # v1
with:
# nuget.org account username (member of the faiscadev package-owner
# org); the trusted-publishing policy scopes the temporary key.
user: vieiralucas
- name: Publish to NuGet
working-directory: sdks/dotnet
# --skip-duplicate makes a re-run of an already-published version a
# no-op instead of a hard failure (mirrors the other SDK publish jobs).
run: |
dotnet nuget push "artifacts/*.nupkg" \
--api-key "${NUGET_API_KEY}" \
--source https://api.nuget.org/v3/index.json \
--skip-duplicate
env:
NUGET_API_KEY: ${{ steps.nuget-login.outputs.NUGET_API_KEY }}