-
Notifications
You must be signed in to change notification settings - Fork 5
291 lines (260 loc) · 11.7 KB
/
Copy pathrelease.yml
File metadata and controls
291 lines (260 loc) · 11.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
# Drives a release end-to-end from GitHub Actions.
#
# Click "Run workflow", enter a version like v1.2.3, and this will:
# 1. Validate the version and resolve the exact commit to release.
# 2. Create the candidate tag locally and run ./gradlew check against it.
# 3. Ask for approval through the protected `release` environment.
# 4. Create and push the annotated tag vX.Y.Z pointing at the tested commit
# (using GITHUB_TOKEN).
# 5. Build release artifacts at that tag.
# 6. Create the GitHub Release and upload the SDK / agent / OTel
# extension jars.
# 7. Publish to Maven Central via Sonatype, signed with the project
# GPG key.
# 8. Poll Maven Central until the new version is visible.
# 9. Ask javadoc.io to ingest the new Javadocs.
#
# The releaser must supply an explicit commit SHA (not a branch name) so
# that commits which land on main during the environment approval gate
# are NOT silently included in the release.
#
# Re-publishing a failed release: re-run this workflow with the same
# version. If the tag already exists, preflight tests that tag and the
# release job skips tag creation.
#
# Only the publishing job uses the protected `release` GitHub Environment,
# which holds the Sonatype / GPG secrets and requires reviewer approval.
# Input validation and the full CI gate finish before approval is requested.
name: Release
on:
workflow_dispatch:
inputs:
version:
description: 'Version to release (e.g., v1.2.3)'
required: true
type: string
sha:
description: 'Full 40-char commit SHA to tag. Required so the releaser controls exactly what ships, even if main advances during the approval gate. Ignored if the tag already exists.'
required: true
type: string
permissions:
contents: read
jobs:
preflight:
name: Validate and test
runs-on: ubuntu-24.04
outputs:
release_sha: ${{ steps.release-ref.outputs.release_sha }}
tag_existed: ${{ steps.release-ref.outputs.tag_existed }}
steps:
- name: Validate inputs
env:
VERSION: ${{ inputs.version }}
SHA: ${{ inputs.sha }}
run: |
if [[ ! "$VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Error: version must be semver (e.g. v1.2.3)" >&2
exit 1
fi
if [[ ! "$SHA" =~ ^[0-9a-f]{40}$ ]]; then
echo "Error: sha must be a full 40-character lowercase commit SHA. Got: '$SHA'" >&2
echo "Tip: copy the SHA from the commit page on GitHub (use the 'Copy full SHA' button)." >&2
exit 1
fi
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
fetch-depth: 0
- name: Resolve and check out release ref
id: release-ref
env:
TAG: ${{ inputs.version }}
SHA: ${{ inputs.sha }}
run: |
git fetch origin "+refs/heads/main:refs/remotes/origin/main" --tags --force --quiet
if git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then
TAG_EXISTED=true
RELEASE_SHA="$(git rev-parse "${TAG}^{commit}")"
echo "Tag '$TAG' already exists; testing the existing tag at $RELEASE_SHA."
else
TAG_EXISTED=false
if ! git cat-file -e "${SHA}^{commit}" 2>/dev/null; then
echo "Error: commit $SHA does not exist in the repository." >&2
exit 1
fi
RELEASE_SHA="$SHA"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag -a "$TAG" -m "Release $TAG" "$RELEASE_SHA"
echo "Created candidate tag '$TAG' locally at $RELEASE_SHA."
fi
if ! git merge-base --is-ancestor "$RELEASE_SHA" origin/main; then
echo "Error: release commit $RELEASE_SHA is not an ancestor of origin/main." >&2
echo "Releases must be cut from commits that have landed on main." >&2
exit 1
fi
echo "release_sha=$RELEASE_SHA" >> "$GITHUB_OUTPUT"
echo "tag_existed=$TAG_EXISTED" >> "$GITHUB_OUTPUT"
git checkout --detach "$TAG"
- name: Set up JDK 17
uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4.8.0
with:
java-version: '17'
distribution: 'temurin'
- name: Setup Gradle
uses: gradle/gradle-build-action@a8f75513eafdebd8141bd1cd4e30fcd194af8dfa # v2.12.0
- name: Run CI at candidate tag
run: ./gradlew check
release:
name: Release
needs: preflight
runs-on: ubuntu-24.04
permissions:
contents: write
# Only publishing is gated by the protected environment. Preflight input
# validation and tests must pass before GitHub requests reviewer approval.
environment: release
steps:
- name: Checkout tested commit
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
ref: ${{ needs.preflight.outputs.release_sha }}
fetch-depth: 0
- name: Verify tag state
id: tag-state
env:
TAG: ${{ inputs.version }}
TESTED_SHA: ${{ needs.preflight.outputs.release_sha }}
TAG_EXISTED_AT_PREFLIGHT: ${{ needs.preflight.outputs.tag_existed }}
run: |
git fetch origin --tags --force --quiet
if git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then
CURRENT_SHA="$(git rev-parse "${TAG}^{commit}")"
if [[ "$CURRENT_SHA" != "$TESTED_SHA" ]]; then
echo "Error: tag '$TAG' moved after preflight ($TESTED_SHA -> $CURRENT_SHA)." >&2
exit 1
fi
echo "exists=true" >> "$GITHUB_OUTPUT"
echo "Tag '$TAG' already exists at the tested commit."
else
if [[ "$TAG_EXISTED_AT_PREFLIGHT" == "true" ]]; then
echo "Error: tag '$TAG' was deleted after preflight." >&2
exit 1
fi
echo "exists=false" >> "$GITHUB_OUTPUT"
echo "Tag '$TAG' does not exist yet; will create it at $TESTED_SHA."
fi
- name: Set up JDK 17
uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4.8.0
with:
java-version: '17'
distribution: 'temurin'
- name: Setup Gradle
uses: gradle/gradle-build-action@a8f75513eafdebd8141bd1cd4e30fcd194af8dfa # v2.12.0
- name: Configure git identity
if: steps.tag-state.outputs.exists == 'false'
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Create and push tag
if: steps.tag-state.outputs.exists == 'false'
env:
TAG: ${{ inputs.version }}
TESTED_SHA: ${{ needs.preflight.outputs.release_sha }}
run: |
git tag -a "$TAG" -m "Release $TAG" "$TESTED_SHA"
git push origin "$TAG"
- name: Checkout tag
env:
TAG: ${{ inputs.version }}
run: git checkout "$TAG"
- name: Build release artifacts
run: ./gradlew build publishToMavenLocal
- name: Find built artifacts
id: find-artifacts
run: |
TAG="${{ inputs.version }}"
# Strip 'v' prefix to get the actual version used by Gradle
VERSION="${TAG#v}"
# braintrust-sdk artifacts
SDK_MAIN_JAR=$(find braintrust-sdk/build/libs -name "*-${VERSION}.jar" ! -name "*-sources.jar" ! -name "*-javadoc.jar" | head -1)
SDK_SOURCES_JAR=$(find braintrust-sdk/build/libs -name "*-${VERSION}-sources.jar" | head -1)
SDK_JAVADOC_JAR=$(find braintrust-sdk/build/libs -name "*-${VERSION}-javadoc.jar" | head -1)
# braintrust-java-agent artifact (single fat jar, no sources/javadoc)
AGENT_JAR=$(find braintrust-java-agent/build/libs -name "braintrust-java-agent-${VERSION}.jar" | head -1)
# braintrust-otel-extension artifact (fat jar, no sources/javadoc)
OTL_EXT_JAR=$(find braintrust-otel-extension/build/libs -name "braintrust-otel-extension-${VERSION}.jar" | head -1)
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "sdk-main-jar=$SDK_MAIN_JAR" >> $GITHUB_OUTPUT
echo "sdk-sources-jar=$SDK_SOURCES_JAR" >> $GITHUB_OUTPUT
echo "sdk-javadoc-jar=$SDK_JAVADOC_JAR" >> $GITHUB_OUTPUT
echo "agent-jar=$AGENT_JAR" >> $GITHUB_OUTPUT
echo "otel-ext-jar=$OTL_EXT_JAR" >> $GITHUB_OUTPUT
echo "Found artifacts:"
echo " SDK Main JAR: $SDK_MAIN_JAR"
echo " SDK Sources JAR: $SDK_SOURCES_JAR"
echo " SDK Javadoc JAR: $SDK_JAVADOC_JAR"
echo " Agent JAR: $AGENT_JAR"
echo " OTel Extension JAR: $OTL_EXT_JAR"
- name: Create or update GitHub Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG="${{ inputs.version }}"
# Create the release if it doesn't already exist (re-publish path).
if ! gh release view "$TAG" >/dev/null 2>&1; then
gh release create "$TAG" \
--generate-notes \
--title "Release $TAG"
else
echo "Release '$TAG' already exists; will upload (clobber) assets."
fi
# Upload artifacts, clobbering any partial uploads from a prior run.
for jar in \
"${{ steps.find-artifacts.outputs.sdk-main-jar }}" \
"${{ steps.find-artifacts.outputs.sdk-sources-jar }}" \
"${{ steps.find-artifacts.outputs.sdk-javadoc-jar }}" \
"${{ steps.find-artifacts.outputs.agent-jar }}" \
"${{ steps.find-artifacts.outputs.otel-ext-jar }}"; do
if [[ -n "$jar" && -f "$jar" ]]; then
gh release upload "$TAG" "$jar" --clobber
fi
done
- name: Publish to Sonatype
env:
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }}
GPG_SIGNING_PASSWORD: ${{ secrets.GPG_SIGNING_PASSWORD }}
run: |-
if [ -z "$SONATYPE_USERNAME" ]; then
echo "Error: SONATYPE_USERNAME is not set"
exit 1
fi
if [ -z "$SONATYPE_PASSWORD" ]; then
echo "Error: SONATYPE_PASSWORD is not set"
exit 1
fi
if [ -z "$GPG_SIGNING_KEY" ]; then
echo "Error: GPG_SIGNING_KEY is not set"
exit 1
fi
if [ -z "$GPG_SIGNING_PASSWORD" ]; then
echo "Error: GPG_SIGNING_PASSWORD is not set"
exit 1
fi
echo "All required credentials are set"
export -- GPG_SIGNING_KEY_ID
printenv -- GPG_SIGNING_KEY | gpg --batch --passphrase-fd 3 --import 3<<< "$GPG_SIGNING_PASSWORD"
GPG_SIGNING_KEY_ID="$(gpg --with-colons --list-keys | awk -F : -- '/^pub:/ { getline; print "0x" substr($10, length($10) - 7) }')"
./gradlew publishAndReleaseToMavenCentral --stacktrace -PmavenCentralUsername="$SONATYPE_USERNAME" -PmavenCentralPassword="$SONATYPE_PASSWORD" --no-configuration-cache
- name: Wait for Maven Central sync
run: |
TAG="${{ inputs.version }}"
VERSION="${TAG#v}"
echo "Waiting for version $VERSION to sync to Maven Central. THIS CAN TAKE MANY HOURS! Godspeed"
./scripts/wait-for-maven.sh "$VERSION"
# javadoc.io is no longer auto-ingesting doc jars, so we'll poke it to trigger an update
- name: Trigger javadoc.io ingestion
continue-on-error: true
run: ./scripts/trigger-javadoc-io.sh "${{ steps.find-artifacts.outputs.version }}"