Repository navigation
214 lines (198 loc) · 10.3 KB
/
Copy pathpublish.yml
File metadata and controls
214 lines (198 loc) · 10.3 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
name: Publish
run-name: Publishing com.convertapi.client:convertapi ${{ github.event.release.tag_name }} to Maven Central
# A release is a GitHub Release, never a merge. Merging to main only builds (build.yml); publishing the
# Release v{semver} runs this file, which tests and builds that tag's commit with the tag's version, signs
# it and publishes it to Maven Central through the Central Portal. Maven Central versions are public and
# permanent - a published version can never be replaced or deleted - so nothing here retries, overwrites
# or skips a duplicate.
#
# gh release create v2.13.1 --target main --generate-notes -> 2.13.1
# gh release create v2.14.0-beta.1 --target main --prerelease ... -> 2.14.0-beta.1
#
# A stable version must be a commit on main. A pre-release may be any commit, so a branch can be tried
# out as a preview version before it is merged. Maven Central has no channels: a pre-release is public
# like any other version, and Maven orders it below the stable version of the same number.
on:
release:
types: [published]
permissions:
contents: read
env:
# Only this account may publish a Release that reaches Maven Central. GitHub cannot express this as a
# permission on the Team plan, so it is checked here; the real controls are the v* tag ruleset (only
# repository admins create v* tags) and the maven-central environment, which accepts only v* tags and
# holds the only credentials that can publish.
PUBLISHER: tomasr78
GROUP_PATH: com/convertapi/client
ARTIFACT: convertapi
CONVERT_API_BASE_URI: https://stag-v2.convertapi.com/
MAVEN_ARGS: --batch-mode --no-transfer-progress
jobs:
publish:
name: publish
runs-on: ubuntu-latest
timeout-minutes: 90
environment:
name: maven-central
url: https://central.sonatype.com/artifact/com.convertapi.client/convertapi/${{ steps.tag.outputs.version }}
concurrency:
group: publish-maven-central
cancel-in-progress: false
steps:
- name: Check who published the release
run: |
# A re-run keeps GITHUB_ACTOR as the original user; GITHUB_TRIGGERING_ACTOR is whoever pressed
# "Re-run", so both must be the publisher or anyone with write access could replay a release.
if [ "$GITHUB_ACTOR" != "$PUBLISHER" ] || [ "${GITHUB_TRIGGERING_ACTOR:-$GITHUB_ACTOR}" != "$PUBLISHER" ]; then
echo "::error::Only $PUBLISHER may publish to Maven Central - this release was published by $GITHUB_ACTOR and run by ${GITHUB_TRIGGERING_ACTOR:-$GITHUB_ACTOR}."
exit 1
fi
echo "Published by $GITHUB_ACTOR"
# Maven Central has no OIDC trusted publishing, so this job needs a Central Portal user token and a
# GPG signing key, held as secrets of the maven-central environment (see RELEASING.md).
- name: Check the credentials
env:
CENTRAL_TOKEN_USERNAME: ${{ secrets.CENTRAL_TOKEN_USERNAME }}
CENTRAL_TOKEN_PASSWORD: ${{ secrets.CENTRAL_TOKEN_PASSWORD }}
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
CONVERT_API_SECRET: ${{ secrets.CONVERTAPI_STAGING_TOKEN }}
run: |
MISSING=""
for name in CENTRAL_TOKEN_USERNAME CENTRAL_TOKEN_PASSWORD GPG_PRIVATE_KEY GPG_PASSPHRASE; do
[ -n "${!name}" ] || MISSING="$MISSING $name"
done
[ -n "$CONVERT_API_SECRET" ] || MISSING="$MISSING CONVERTAPI_STAGING_TOKEN"
if [ -n "$MISSING" ]; then
echo "::error::Missing secrets in the maven-central environment or the repository:$MISSING"
exit 1
fi
# The release event runs this file from the tag, so the workflow and the code come from the same commit.
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- name: Check the tag and the version
id: tag
env:
TAG: ${{ github.event.release.tag_name }}
PRERELEASE: ${{ github.event.release.prerelease }}
run: |
set -euo pipefail
# SemVer 2.0 without build metadata, as the v* tag ruleset enforces.
SEMVER='^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$'
if ! [[ "$TAG" =~ $SEMVER ]]; then
echo "::error::Tag '$TAG' is not v{major}.{minor}.{patch} or v{major}.{minor}.{patch}-{pre-release}, e.g. v2.13.1 or v2.14.0-beta.1"
exit 1
fi
if [ "$GITHUB_REF" != "refs/tags/$TAG" ]; then
echo "::error::The run is on $GITHUB_REF, not on the release tag refs/tags/$TAG"
exit 1
fi
VERSION="${TAG#v}"
SHA=$(git rev-parse "refs/tags/$TAG^{commit}")
if [ "$(git rev-parse HEAD)" != "$SHA" ]; then
echo "::error::Checked out $(git rev-parse HEAD), but $TAG points at $SHA"
exit 1
fi
# The GitHub "pre-release" tick and the version must agree, so a preview cannot pass as a
# stable release by a forgotten tick, and a stable version cannot hide as a pre-release.
if [[ "$VERSION" == *-* ]]; then
if [ "$PRERELEASE" != true ]; then
echo "::error::$TAG is a pre-release version, so the GitHub Release must be marked as a pre-release (gh release create --prerelease)"
exit 1
fi
else
if [ "$PRERELEASE" = true ]; then
echo "::error::$TAG is a stable version, but the GitHub Release is marked as a pre-release. Use a pre-release version such as ${TAG}-beta.1, or untick pre-release."
exit 1
fi
if ! git merge-base --is-ancestor "$SHA" origin/main; then
echo "::error::$TAG ($SHA) is not on main. Stable versions are released from main only; release a branch as a pre-release instead."
exit 1
fi
fi
# Maven Central never forgets a version. (The Portal would also reject the upload, but only
# after the tests and the signing.)
POM="https://repo1.maven.org/maven2/$GROUP_PATH/$ARTIFACT/$VERSION/$ARTIFACT-$VERSION.pom"
CODE=$(curl -sS -o /dev/null -w '%{http_code}' --retry 3 "$POM" || true)
if [ "$CODE" = 200 ]; then
echo "::error::$ARTIFACT $VERSION already exists on Maven Central and can never be published again. Release the next version instead."
exit 1
elif [ "$CODE" != 404 ]; then
echo "::error::Could not check whether $VERSION is on Maven Central ($POM answered $CODE)"
exit 1
fi
{
echo "version=$VERSION"
echo "sha=$SHA"
} >> "$GITHUB_OUTPUT"
echo "Publishing $ARTIFACT $VERSION from $SHA"
# Writes ~/.m2/settings.xml with the server `central` (credentials read from the environment at
# run time) and imports the signing key into a temporary keyring that is deleted after the job.
# No dependency cache: a publishing job must not restore anything another run could have written.
- uses: actions/setup-java@b843ebf9b913146b38da2eaa7aeddf76074ae1ad # v6.1.0
with:
distribution: temurin
java-version: 17
server-id: central
server-username-env-var: CENTRAL_TOKEN_USERNAME
server-password-env-var: CENTRAL_TOKEN_PASSWORD
gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}
gpg-passphrase-env-var: MAVEN_GPG_PASSPHRASE
- name: Set the version
env:
VERSION: ${{ steps.tag.outputs.version }}
run: |
set -euo pipefail
./mvnw versions:set -DnewVersion="$VERSION" -DgenerateBackupPoms=false
[ "$(./mvnw -q help:evaluate -Dexpression=project.version -DforceStdout)" = "$VERSION" ] || { echo "::error::pom.xml does not carry version $VERSION"; exit 1; }
- name: Test
env:
CONVERT_API_SECRET: ${{ secrets.CONVERTAPI_STAGING_TOKEN }}
# Surefire reruns a failed test twice; the staging API also answers 503 in bursts of up to a
# minute, so the tests get up to three attempts, 30 s apart.
run: |
for attempt in 1 2 3; do
./mvnw verify && exit 0
echo "::warning::Attempt $attempt: the tests failed, see above"
sleep 30
done
echo "::error::The tests failed three times"
exit 1
# Signs the jar, the sources, the javadoc and the pom, uploads them as one deployment, and waits
# until the Central Portal has validated and published it (autoPublish, waitUntil=published).
# No retry: a version that already exists must fail loudly, never pass as published.
- name: Publish to Maven Central
env:
CENTRAL_TOKEN_USERNAME: ${{ secrets.CENTRAL_TOKEN_USERNAME }}
CENTRAL_TOKEN_PASSWORD: ${{ secrets.CENTRAL_TOKEN_PASSWORD }}
MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
run: ./mvnw -Prelease deploy -DskipTests
# The Portal marks a deployment PUBLISHED before repo1.maven.org serves it, sometimes by half an
# hour. A slow mirror is not a failed release, so this only warns.
- name: Wait for Maven Central to serve the version
env:
VERSION: ${{ steps.tag.outputs.version }}
run: |
POM="https://repo1.maven.org/maven2/$GROUP_PATH/$ARTIFACT/$VERSION/$ARTIFACT-$VERSION.pom"
for _ in $(seq 1 40); do
if curl -fsS -o /dev/null "$POM"; then
echo "$ARTIFACT $VERSION is available on Maven Central"
exit 0
fi
sleep 30
done
echo "::warning::$ARTIFACT $VERSION was published but repo1.maven.org has not served it after 20 minutes. Check https://central.sonatype.com/artifact/com.convertapi.client/convertapi/$VERSION"
- name: Summary
env:
VERSION: ${{ steps.tag.outputs.version }}
SHA: ${{ steps.tag.outputs.sha }}
run: |
{
echo "| | |"
echo "|---|---|"
echo "| Package | [\`com.convertapi.client:convertapi:$VERSION\`](https://central.sonatype.com/artifact/com.convertapi.client/convertapi/$VERSION) |"
echo "| Commit | \`$SHA\` |"
echo "| Published with | Central Portal user token, GPG-signed |"
} >> "$GITHUB_STEP_SUMMARY"