-
Notifications
You must be signed in to change notification settings - Fork 3
233 lines (208 loc) · 9.45 KB
/
release.yml
File metadata and controls
233 lines (208 loc) · 9.45 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
# Drives a release end-to-end from GitHub Actions in a single workflow.
#
# Click "Run workflow", enter a version like v1.2.3, and this will:
# 1. Validate the version (semver, no -SNAPSHOT) and the SHA.
# 2. Run ./gradlew check on the pinned SHA as a final gate.
# 3. Create and push the annotated tag vX.Y.Z pointing at the SHA
# (using GITHUB_TOKEN).
#
# 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.
# 4. Build release artifacts at that tag.
# 5. Create the GitHub Release and upload the SDK / agent / OTel
# extension jars.
# 6. Publish to Maven Central via Sonatype, signed with the project
# GPG key.
# 7. Poll Maven Central until the new version is visible.
#
# Re-publishing a failed release: re-run this workflow with the same
# version. If the tag already exists, the tag-creation step is skipped
# and the rest of the pipeline runs against the existing tag.
#
# The entire job runs in the protected `release` GitHub Environment,
# which holds the Sonatype / GPG secrets and requires reviewer approval
# before any tag is pushed or any artifact is published.
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: write
jobs:
release:
name: Release
runs-on: ubuntu-24.04
# Gate the entire release behind a protected GitHub Environment.
# Required reviewers, deployment branch/tag rules, and the Sonatype /
# GPG secrets are configured on the environment itself in repo
# settings (Settings → Environments → release).
environment: release
steps:
- name: Validate inputs
run: |
V="${{ inputs.version }}"
if [[ ! "$V" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Error: version must be semver (e.g. v1.2.3)" >&2
exit 1
fi
if [[ "$V" == *-SNAPSHOT ]]; then
echo "Error: version cannot end with -SNAPSHOT" >&2
exit 1
fi
SHA="${{ inputs.sha }}"
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:
ref: ${{ inputs.sha }}
fetch-depth: 0
- name: Verify SHA is reachable from main
run: |
SHA="${{ inputs.sha }}"
git fetch origin main --quiet
if ! git merge-base --is-ancestor "$SHA" origin/main; then
echo "Error: commit $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 "Commit $SHA is reachable from origin/main."
- name: Determine whether tag already exists
id: tag-state
run: |
TAG="${{ inputs.version }}"
git fetch --tags --quiet
if git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then
echo "exists=true" >> "$GITHUB_OUTPUT"
echo "Tag '$TAG' already exists; will publish from the existing tag."
elif git ls-remote --tags origin | grep -q "refs/tags/${TAG}$"; then
echo "exists=true" >> "$GITHUB_OUTPUT"
echo "Tag '$TAG' exists on origin but not locally; fetching."
git fetch origin "refs/tags/$TAG:refs/tags/$TAG"
else
echo "exists=false" >> "$GITHUB_OUTPUT"
echo "Tag '$TAG' does not exist yet; will create at $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: Run CI (pre-tag, on chosen ref)
if: steps.tag-state.outputs.exists == 'false'
run: ./gradlew check
- 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'
run: |
TAG="${{ inputs.version }}"
SHA="${{ inputs.sha }}"
git tag -a "$TAG" -m "Release $TAG" "$SHA"
git push origin "$TAG"
- name: Checkout tag
run: git checkout "${{ inputs.version }}"
- name: Run CI (at tag)
run: ./gradlew check
- 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 "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"