Skip to content

Commit ee8c192

Browse files
authored
Publish by using Portal OSSRH Staging API (#416)
* Publish by using Portal OSSRH Staging API Updates the current release process to publish artifacts to the Sonatype Central Portal instead of Sonatype OSSRH using their staging API that is intended to reduce friction while migrating. * Udpate releasing information * Fix spotless checks * Update publishing information in RELEASING * Address comments
1 parent 4a5876d commit ee8c192

File tree

2 files changed

+190
-89
lines changed

2 files changed

+190
-89
lines changed

RELEASING.md

Lines changed: 136 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -2,66 +2,75 @@
22

33
## Prerequisites
44

5-
### Setup OSSRH and Signing
5+
### Setup Maven Central Portal Publishing
66

7-
If you haven't deployed artifacts to Maven Central before, you need to set up
8-
your OSSRH (OSS Repository Hosting) account and signing keys.
9-
10-
- Follow the instructions on [this
11-
page](http://central.sonatype.org/pages/ossrh-guide.html) to set up an account
12-
with OSSRH.
13-
- You only need to create the account, not set up a new project
14-
- Contact an OpenTelemetry Operations Java maintainer to add your account
15-
after you have created it.
16-
- (For release deployment only) [Install
17-
GnuPG](http://central.sonatype.org/pages/working-with-pgp-signatures.html#installing-gnupg)
18-
and [generate your key
19-
pair](http://central.sonatype.org/pages/working-with-pgp-signatures.html#generating-a-key-pair).
20-
You'll also need to [publish your public
21-
key](http://central.sonatype.org/pages/working-with-pgp-signatures.html#distributing-your-public-key)
22-
to make it visible to the Sonatype servers. For gpg 2.1 or newer, you also
23-
need to [export the
24-
keys](https://docs.gradle.org/current/userguide/signing_plugin.html#sec:signatory_credentials)
25-
with command `gpg --keyring secring.gpg --export-secret-keys >
26-
~/.gnupg/secring.gpg`.
27-
- Put your GnuPG key password and OSSRH account information in
28-
`<your-home-directory>/.gradle/gradle.properties`:
29-
30-
```text
31-
# You need the signing properties only if you are making release deployment
32-
signing.keyId=<8-character-public-key-id>
33-
signing.password=<key-password>
34-
signing.secretKeyRingFile=<your-home-directory>/.gnupg/secring.gpg
35-
36-
ossrhUsername=<ossrh-username>
37-
ossrhPassword=<ossrh-password>
38-
checkstyle.ignoreFailures=false
39-
```
7+
> [!IMPORTANT]
8+
> The OSSRH service will reach the end-of-life sunset date on June 30th, 2025.
9+
> After this, it is recommended to only use Sonatype's Central Publisher Portal
10+
> to publish artifacts to Maven Central. See
11+
> [the official notice](https://central.sonatype.org/news/20250326_ossrh_sunset/)
12+
> for more details.
4013
41-
> [!TIP]
42-
> If your key-generation is failing, checkout the [help section](#help-timeout-during-key-generation-process) at the bottom of this document.
14+
If you do not have a Central Portal account on Sonatype, you need to set up your
15+
account to publish via the Central Portal.
4316

44-
### Using GPG-Agent for artifact signing
17+
- Follow the instructions on [this
18+
page](https://central.sonatype.org/register/central-portal/) to set up an
19+
account with Central Portal.
20+
- You only need to create the account, not set up a new project.
21+
- Contact an OpenTelemetry Operations Java maintainer to add your account
22+
after you have created it.
23+
24+
## Setup artifact signing
25+
26+
### Generate the GPG key
27+
The artifacts must be signed before being published for consumption. Follow
28+
these steps to set up artifact signing:
29+
- [Install
30+
GnuPG](http://central.sonatype.org/pages/working-with-pgp-signatures.html#installing-gnupg)
31+
and [generate your key
32+
pair](http://central.sonatype.org/pages/working-with-pgp-signatures.html#generating-a-key-pair).
33+
- You'll also need to [publish your public
34+
key](http://central.sonatype.org/pages/working-with-pgp-signatures.html#distributing-your-public-key)
35+
to make it visible to the Sonatype servers.
36+
37+
### Configuring Gradle to use GPG
4538

4639
> [!NOTE]
4740
> These instructions are for modern linux where `gpg` refers to the 2.0 version.
4841
49-
If you're running in linux and would like to use the GPG agent to remember your PGP key passwords instead of keeping them in a plain-text file on your home directory,
50-
you can configure the following in `<your-home-directory>/.gradle/gradle.properties`:
42+
You can configure Gradle to use GPG by adding the following in
43+
`<your-home-directory>/.gradle/gradle.properties`:
5144

5245
```text
53-
ossrhUsername=<generated-token-user>
54-
ossrhPassword=<generated-token-key>
46+
centralPortalUsername=<generated-token-user>
47+
centralPortalPassword=<generated-token-key>
5548

5649
signingUseGpgCmd=true
5750
signing.gnupg.executable=gpg
5851
signing.gnupg.keyName=<secret key id (large hash)>
52+
53+
checkstyle.ignoreFailures=false
5954
```
60-
Note: You can retrieve the list of previously created GPG keys on your machine by using `gpg --list-secret-keys`.
55+
56+
Note: You can retrieve the list of previously created GPG keys on your machine
57+
by using `gpg --list-secret-keys`. Additionally, you can use a GPG Agent and/or
58+
a password manager (or the built-in Keyring) to avoid entering the password
59+
manually.\
60+
For more details, checkout the
61+
[help section](#help-timeout-with-gpg-operations) on the bottom
62+
of this guide.
6163

6264
> [!IMPORTANT]
63-
> Starting June 2024, due to a change to the OSSRH authentication backend, the maven publish plugin now requires [a user token](https://central.sonatype.org/publish/generate-token/) instead of a typical username and password used in the Nexus UI.
64-
> Follow the steps in the [link](https://central.sonatype.org/publish/generate-token/) to generate a user token, if not done already - this will provide you with a `tokenuser` and `tokenkey`. Replace `<generated-token-user>` and `<generated-token-key>` with the generated `tokenuser` and `tokenkey` in your `gradle.properties` file to successfully publish artifacts.
65+
> The user tokens for publishing to the Central Portal are different from those
66+
> used for OSSRH. If you haven't already, you must generate a new Portal Token
67+
> to publish to the Central Portal.
68+
> Follow the steps in this
69+
> [link](https://central.sonatype.org/publish/generate-portal-token/) to
70+
> generate a user token - this will provide you with a Portal token containing a
71+
> `username` and `password`. Replace `<generated-token-user>` and
72+
> `<generated-token-key>` with the generated `username` and `password` in your
73+
> `gradle.properties` file to successfully publish artifacts.
6574
6675
### Ensuring you can push tags to GitHub upstream
6776

@@ -71,15 +80,33 @@ token](https://help.github.com/articles/creating-a-personal-access-token-for-the
7180

7281
## Release a Snapshot
7382

74-
If you've followed the above steps, you can release snapshots for consumption using the following:
83+
If you've followed the above steps, you can release snapshots for consumption
84+
using the following:
7585

7686
```bash
7787
$ ./gradlew snapshot
7888
```
7989

80-
## Releasing a Candidate (Optional)
90+
SNAPSHOT releases are intended for developers to make pre-release versions of
91+
their projects available for testing. Published snapshots should be visible
92+
using the
93+
[directory listing for com.google.cloud.opentelemetry](https://central.sonatype.com/service/rest/repository/browse/maven-snapshots/com/google/cloud/opentelemetry/)
94+
namespace.
95+
96+
See
97+
[Publishing Snapshot Releases](https://central.sonatype.org/publish/publish-portal-snapshots/#publishing-snapshot-releases)
98+
for more details.
99+
100+
## Preparing a release candidate (Optional)
101+
102+
> [!TIP]
103+
> Preparing a release candidate involves the same steps as preparing a final
104+
> version. The only difference is in how a release candidate is tagged.\
105+
> Release candidates are pre-release version of libraries, close to the final
106+
> stable release, this is typically not required for this repository.
81107
82-
After following the above steps, you can release candidates from `main` or `v<major>.<minor>.x` branches.
108+
After following the above steps, you can release candidates from `main` or
109+
`v<major>.<minor>.x` branches.
83110

84111
For example, to release the v0.14.0-RC1 candidate, do the following:
85112

@@ -93,12 +120,18 @@ $ git push origin v0.14.0-RC1
93120
Next follow [Releasing on Maven Central](#releasing-on-maven-central) to close + publish the
94121
[repository on OSSRH](https://oss.sonatype.org/#stagingRepositories).
95122

96-
97123
Note: In the future, the `-Prelease.version` flag should not be required.
98124

99-
## Release a final verison
125+
## Preparing a final verison
126+
127+
> [!IMPORTANT]
128+
> The nebula-release plugin automatically tags the current release with the
129+
> appropriate number based on the previous release. Make sure that the release
130+
> version being provided in the argument for the candidate task matches the
131+
> latest tag.
100132
101-
After following the above steps, you can release candidates from `main` or `v<major>.<minor>.x` branches.
133+
After following the above steps, you can release candidates from `main` or
134+
`v<major>.<minor>.x` branches.
102135

103136
For example, to release the v0.14.0 candidate, do the following:
104137

@@ -109,14 +142,11 @@ $ ./gradlew candidate -Prelease.version=0.14.0
109142
$ git push origin v0.14.0
110143
```
111144

112-
*Note: If you do not have a CredentialsProvider registered for GitHub, the `candidate` task may fail to upload tags to the GitHub repository and the overall command may take a long time to report completion on the task. In this case, before moving forward - check if tags were pushed to GitHub. If not, manually push the tags before continuing.*\
113-
*Next, check if the staging repository is created on the [nexus repository manager](https://oss.sonatype.org/#stagingRepositories). If the repository is already created, continue with the next steps.*
114-
115-
Follow [Releasing on Maven Central](#releasing-on-maven-central) to close + publish the
116-
[repository on OSSRH](https://oss.sonatype.org/#stagingRepositories).
117-
118-
After this, follow the [Announcment](#Announcement) documentation to advertise the release and update README files.
119-
145+
*Note: If you do not have a CredentialsProvider registered for GitHub, the
146+
`candidate` task may fail to upload tags to the GitHub repository and the
147+
overall command may take a long time to report completion on the task.
148+
In this case, before moving forward - check if tags were pushed to GitHub.
149+
If not, manually push the tags before continuing.*\
120150

121151
Note: In the future, the `-Prelease.version` flag should not be required.
122152

@@ -130,37 +160,61 @@ gone through code review. For the current release use:
130160
$ git checkout -b v$MAJOR.$MINOR.$PATCH tags/v$MAJOR.$MINOR.$PATCH
131161
```
132162

133-
## Releasing on Maven Central
163+
## Uploading the release artifacts to Central Portal
164+
165+
> [!IMPORTANT]
166+
> This task will create a deployment on the Central Portal, visible on their UI.
167+
> It should only be run after the release is prepared.
168+
169+
After preparing the release, the release artifacts need to be uploaded to
170+
Central Portal so that they can be released on Maven Central.\
171+
To upload the prepared release artifacts, run the following gradle task:
134172

135-
Once all the artifacts have been pushed to the staging repository, the
136-
repository must first be `closed`, which will trigger several sanity checks on
137-
the repository. If this completes successfully, the repository can then be
138-
`released`, which will begin the process of pushing the new artifacts to Maven
139-
Central (the staging repository will be destroyed in the process). You can see
140-
the complete process for releasing to Maven Central on the [OSSRH
141-
site](http://central.sonatype.org/pages/releasing-the-deployment.html).
173+
```shell
174+
./gradlew sonatypeUploadDefaultRepository
175+
```
142176

143-
Note: This can/will be automated in the future.
177+
The task will respond with an HTTP status code. If the status code is 200, the
178+
artifacts are successfully uploaded on the Central Portal and should be visible
179+
on the UI.
144180

145-
### Things to check before 'closing' on Maven Central
181+
## Releasing on Maven Central
146182

147-
Before closing the staging repository, it is important to verify that the contents of all the
148-
published modules are looking good. Particularly, the version numbers should be what are expected,
149-
and they include any custom release qualifiers (like 'alpha') which are set. Make sure that:
150-
- The generated POM files for the individual module have the correct version number.
151-
- The dependencies for an individual module in the POM file are the expected ones & they dependencies have the correct versions.
152-
- The module content includes all the artifacts that are expected to be published - for instance, sourcesJar, javadocs, additional variants like a shaded JAR in some cases, etc.
183+
Once all the artifacts have been pushed to the Central Portal, a `deployment`
184+
will be created in the Central Portal. This deployment is visible under the
185+
"Deployments" tab on https://central.sonatype.com/publishing (you will have to
186+
log in with your account).\
187+
At this point, you can either manually 'Drop' or 'Publish' the deployment.
188+
- Publishing the deployment will make the new release available on Maven
189+
Central.
190+
- Dropping the deployment will close the deployment and abandon the release.
191+
You should drop the deployment if you do not wish to proceed with release
192+
process for any reason.
193+
194+
### Things to check before 'Publishing' on Maven Central
195+
196+
Before publishing the release, it is important to verify that the
197+
contents of all the published modules are looking good. Particularly, the
198+
version numbers should be what are expected, and they include any custom release
199+
qualifiers (like 'alpha') which are set. Make sure that:
200+
- The generated POM files for the individual module have the correct version
201+
number.
202+
- The dependencies for an individual module in the POM file are the expected
203+
ones & the dependencies have the correct versions.
204+
- The module content includes all the artifacts that are expected to be
205+
published - for instance, sourcesJar, javadocs, additional variants like a
206+
shaded JAR in some cases, etc.
153207
- The file sizes for the published artifacts should seem reasonable.
154208

155209
## Announcement
156210

157-
Once deployment finishes, go to GitHub [release
158-
page](https://github.com/GoogleCloudPlatform/opentelemetry-operations-java/releases),
211+
Once deployment finishes, go to GitHub
212+
[release page](https://github.com/GoogleCloudPlatform/opentelemetry-operations-java/releases),
159213
press `Draft a new release` to write release notes about the new release.
160214

161215
You can use `git log upstream/v$MAJOR.$((MINOR-1)).x..upstream/v$MAJOR.$MINOR.x
162-
--graph --first-parent` or the GitHub [compare
163-
tool](https://github.com/GoogleCloudPlatform/opentelemetry-operations-java/compare/)
216+
--graph --first-parent` or the GitHub
217+
[compare tool](https://github.com/GoogleCloudPlatform/opentelemetry-operations-java/compare/)
164218
to view a summary of all commits since last release as a reference.
165219

166220
Please pick major or important user-visible changes only.
@@ -176,10 +230,7 @@ $ COMMIT=1224f0a # Set the right commit hash.
176230
$ git cherry-pick -x $COMMIT
177231
```
178232

179-
### Help: Timeout during key-generation process
180-
If you see timeout errors when you run `gpg --gen-key` to generate your keys, it maybe because you are running the command on a server and do not have access to a UI.
181-
A common example is - running this command on a remote machine over ssh.
182-
183-
The issue here is that this command opens up a UI dialog asking for you to set a passphrase, waiting for input for a fixed time.
184-
185-
The easiest way to fix this is to run it on a machine for which you have UI access.
233+
### Help: Timeout with gpg operations
234+
If you see a timeout error when running `gpg` commands, then you probably have a
235+
graphical session with a gpg agent that is prompting you for a password. Check
236+
your graphical sessions for a password prompt.

build.gradle

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
*/
1616
import nebula.plugin.release.git.opinion.Strategies
1717

18+
import java.net.http.HttpClient
19+
import java.net.http.HttpRequest
20+
import java.net.http.HttpResponse
21+
1822
plugins {
1923
id "com.diffplug.spotless"
2024
id 'nebula.release'
@@ -314,12 +318,14 @@ subprojects {
314318
}
315319
repositories {
316320
maven {
317-
def ossrhRelease = "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
318-
def ossrhSnapshot = "https://oss.sonatype.org/content/repositories/snapshots/"
321+
// Publish using Portal OSSRH Staging API.
322+
// For more information see https://central.sonatype.org/publish/publish-portal-ossrh-staging-api/#publishing-by-using-the-portal-ossrh-staging-api
323+
def ossrhRelease = "https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/"
324+
def ossrhSnapshot = "https://central.sonatype.com/repository/maven-snapshots/"
319325
url = isReleaseVersion ? ossrhRelease : ossrhSnapshot
320326
credentials {
321-
username = rootProject.hasProperty('ossrhUsername') ? rootProject.ossrhUsername : "Unknown user"
322-
password = rootProject.hasProperty('ossrhPassword') ? rootProject.ossrhPassword : "Unknown password"
327+
username = rootProject.hasProperty('centralPortalUsername') ? rootProject.centralPortalUsername : "Unknown user"
328+
password = rootProject.hasProperty('centralPortalPassword') ? rootProject.centralPortalPassword : "Unknown password"
323329
}
324330
}
325331
}
@@ -335,3 +341,47 @@ subprojects {
335341
}
336342
}
337343
}
344+
345+
tasks.register('sonatypeUploadDefaultRepository') {
346+
group = 'Deployment'
347+
description = 'Uploads the artifact repository published by the maven publish plugin to Central Portal so that it is visible in the UI.'
348+
349+
if (!rootProject.hasProperty('centralPortalUsername') || !rootProject.hasProperty('centralPortalPassword')) {
350+
throw new GradleException("Unable to find the username and password. Please check ~/.gradle/gradle.properties file.")
351+
}
352+
353+
// The logic is placed in a doLast block to ensure it runs during the execution phase.
354+
doLast {
355+
// Create authentication string to be used in the bearer token for the API.
356+
// See https://central.sonatype.org/publish/publish-portal-ossrh-staging-api/#manual-api-endpoints
357+
String authString = "${rootProject.centralPortalUsername}:${rootProject.centralPortalPassword}"
358+
359+
// Encode the auth string and construct the bearer token to be passed in the auth header.
360+
String encodedAuthString = Base64.getEncoder().encodeToString(authString.getBytes('UTF-8'))
361+
362+
String centralPortalUploadEndpoint = 'https://ossrh-staging-api.central.sonatype.com/manual/upload/defaultRepository/com.google.cloud.opentelemetry?publishing_type=user_managed'
363+
364+
// Make a manual POST request to the Central Portal Endpoint
365+
def client = HttpClient.newHttpClient()
366+
def request = HttpRequest.newBuilder()
367+
.uri(URI.create(centralPortalUploadEndpoint))
368+
.header('Authorization', "Bearer ${encodedAuthString}")
369+
.header('accept', '*/*')
370+
.POST(HttpRequest.BodyPublishers.ofString(''))
371+
.build()
372+
373+
println "Sending POST request to: ${centralPortalUploadEndpoint}"
374+
375+
// The response body will be handled as a String.
376+
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString())
377+
println "Response Status Code: ${response.statusCode()}"
378+
println "Response Body: ${response.body()}"
379+
380+
// Optionally, fail the build if the request was not successful
381+
if (response.statusCode() >= 400) {
382+
throw new GradleException("Deployment failed! Received HTTP status ${response.statusCode()}")
383+
} else {
384+
println "Default repository uploaded successfully! Please visit https://central.sonatype.com/publishing to complete release process."
385+
}
386+
}
387+
}

0 commit comments

Comments
 (0)