diff --git a/RELEASING.md b/RELEASING.md index 12c28269..131123e9 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -2,66 +2,75 @@ ## Prerequisites -### Setup OSSRH and Signing +### Setup Maven Central Portal Publishing -If you haven't deployed artifacts to Maven Central before, you need to set up -your OSSRH (OSS Repository Hosting) account and signing keys. - -- Follow the instructions on [this - page](http://central.sonatype.org/pages/ossrh-guide.html) to set up an account - with OSSRH. - - You only need to create the account, not set up a new project - - Contact an OpenTelemetry Operations Java maintainer to add your account - after you have created it. -- (For release deployment only) [Install - GnuPG](http://central.sonatype.org/pages/working-with-pgp-signatures.html#installing-gnupg) - and [generate your key - pair](http://central.sonatype.org/pages/working-with-pgp-signatures.html#generating-a-key-pair). - You'll also need to [publish your public - key](http://central.sonatype.org/pages/working-with-pgp-signatures.html#distributing-your-public-key) - to make it visible to the Sonatype servers. For gpg 2.1 or newer, you also - need to [export the - keys](https://docs.gradle.org/current/userguide/signing_plugin.html#sec:signatory_credentials) - with command `gpg --keyring secring.gpg --export-secret-keys > - ~/.gnupg/secring.gpg`. -- Put your GnuPG key password and OSSRH account information in - `/.gradle/gradle.properties`: - - ```text - # You need the signing properties only if you are making release deployment - signing.keyId=<8-character-public-key-id> - signing.password= - signing.secretKeyRingFile=/.gnupg/secring.gpg - - ossrhUsername= - ossrhPassword= - checkstyle.ignoreFailures=false - ``` +> [!IMPORTANT] +> The OSSRH service will reach the end-of-life sunset date on June 30th, 2025. +> After this, it is recommended to only use Sonatype's Central Publisher Portal +> to publish artifacts to Maven Central. See +> [the official notice](https://central.sonatype.org/news/20250326_ossrh_sunset/) +> for more details. -> [!TIP] -> If your key-generation is failing, checkout the [help section](#help-timeout-during-key-generation-process) at the bottom of this document. +If you do not have a Central Portal account on Sonatype, you need to set up your +account to publish via the Central Portal. -### Using GPG-Agent for artifact signing +- Follow the instructions on [this + page](https://central.sonatype.org/register/central-portal/) to set up an + account with Central Portal. + - You only need to create the account, not set up a new project. + - Contact an OpenTelemetry Operations Java maintainer to add your account + after you have created it. + +## Setup artifact signing + +### Generate the GPG key +The artifacts must be signed before being published for consumption. Follow +these steps to set up artifact signing: +- [Install + GnuPG](http://central.sonatype.org/pages/working-with-pgp-signatures.html#installing-gnupg) + and [generate your key + pair](http://central.sonatype.org/pages/working-with-pgp-signatures.html#generating-a-key-pair). +- You'll also need to [publish your public + key](http://central.sonatype.org/pages/working-with-pgp-signatures.html#distributing-your-public-key) + to make it visible to the Sonatype servers. + +### Configuring Gradle to use GPG > [!NOTE] > These instructions are for modern linux where `gpg` refers to the 2.0 version. -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, -you can configure the following in `/.gradle/gradle.properties`: +You can configure Gradle to use GPG by adding the following in +`/.gradle/gradle.properties`: ```text - ossrhUsername= - ossrhPassword= + centralPortalUsername= + centralPortalPassword= signingUseGpgCmd=true signing.gnupg.executable=gpg signing.gnupg.keyName= + + checkstyle.ignoreFailures=false ``` -Note: You can retrieve the list of previously created GPG keys on your machine by using `gpg --list-secret-keys`. + +Note: You can retrieve the list of previously created GPG keys on your machine +by using `gpg --list-secret-keys`. Additionally, you can use a GPG Agent and/or +a password manager (or the built-in Keyring) to avoid entering the password +manually.\ +For more details, checkout the +[help section](#help-timeout-with-gpg-operations) on the bottom +of this guide. > [!IMPORTANT] -> 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. -> 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 `` and `` with the generated `tokenuser` and `tokenkey` in your `gradle.properties` file to successfully publish artifacts. +> The user tokens for publishing to the Central Portal are different from those +> used for OSSRH. If you haven't already, you must generate a new Portal Token +> to publish to the Central Portal. +> Follow the steps in this +> [link](https://central.sonatype.org/publish/generate-portal-token/) to +> generate a user token - this will provide you with a Portal token containing a +> `username` and `password`. Replace `` and +> `` with the generated `username` and `password` in your +> `gradle.properties` file to successfully publish artifacts. ### Ensuring you can push tags to GitHub upstream @@ -71,15 +80,33 @@ token](https://help.github.com/articles/creating-a-personal-access-token-for-the ## Release a Snapshot -If you've followed the above steps, you can release snapshots for consumption using the following: +If you've followed the above steps, you can release snapshots for consumption +using the following: ```bash $ ./gradlew snapshot ``` -## Releasing a Candidate (Optional) +SNAPSHOT releases are intended for developers to make pre-release versions of +their projects available for testing. Published snapshots should be visible +using the +[directory listing for com.google.cloud.opentelemetry](https://central.sonatype.com/service/rest/repository/browse/maven-snapshots/com/google/cloud/opentelemetry/) +namespace. + +See +[Publishing Snapshot Releases](https://central.sonatype.org/publish/publish-portal-snapshots/#publishing-snapshot-releases) +for more details. + +## Preparing a release candidate (Optional) + +> [!TIP] +> Preparing a release candidate involves the same steps as preparing a final +> version. The only difference is in how a release candidate is tagged.\ +> Release candidates are pre-release version of libraries, close to the final +> stable release, this is typically not required for this repository. -After following the above steps, you can release candidates from `main` or `v..x` branches. +After following the above steps, you can release candidates from `main` or +`v..x` branches. For example, to release the v0.14.0-RC1 candidate, do the following: @@ -93,12 +120,18 @@ $ git push origin v0.14.0-RC1 Next follow [Releasing on Maven Central](#releasing-on-maven-central) to close + publish the [repository on OSSRH](https://oss.sonatype.org/#stagingRepositories). - Note: In the future, the `-Prelease.version` flag should not be required. -## Release a final verison +## Preparing a final verison + +> [!IMPORTANT] +> The nebula-release plugin automatically tags the current release with the +> appropriate number based on the previous release. Make sure that the release +> version being provided in the argument for the candidate task matches the +> latest tag. -After following the above steps, you can release candidates from `main` or `v..x` branches. +After following the above steps, you can release candidates from `main` or +`v..x` branches. For example, to release the v0.14.0 candidate, do the following: @@ -109,14 +142,11 @@ $ ./gradlew candidate -Prelease.version=0.14.0 $ git push origin v0.14.0 ``` -*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.*\ -*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.* - -Follow [Releasing on Maven Central](#releasing-on-maven-central) to close + publish the -[repository on OSSRH](https://oss.sonatype.org/#stagingRepositories). - -After this, follow the [Announcment](#Announcement) documentation to advertise the release and update README files. - +*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.*\ Note: In the future, the `-Prelease.version` flag should not be required. @@ -130,37 +160,61 @@ gone through code review. For the current release use: $ git checkout -b v$MAJOR.$MINOR.$PATCH tags/v$MAJOR.$MINOR.$PATCH ``` -## Releasing on Maven Central +## Uploading the release artifacts to Central Portal + +> [!IMPORTANT] +> This task will create a deployment on the Central Portal, visible on their UI. +> It should only be run after the release is prepared. + +After preparing the release, the release artifacts need to be uploaded to +Central Portal so that they can be released on Maven Central.\ +To upload the prepared release artifacts, run the following gradle task: -Once all the artifacts have been pushed to the staging repository, the -repository must first be `closed`, which will trigger several sanity checks on -the repository. If this completes successfully, the repository can then be -`released`, which will begin the process of pushing the new artifacts to Maven -Central (the staging repository will be destroyed in the process). You can see -the complete process for releasing to Maven Central on the [OSSRH -site](http://central.sonatype.org/pages/releasing-the-deployment.html). +```shell +./gradlew sonatypeUploadDefaultRepository +``` -Note: This can/will be automated in the future. +The task will respond with an HTTP status code. If the status code is 200, the +artifacts are successfully uploaded on the Central Portal and should be visible +on the UI. -### Things to check before 'closing' on Maven Central +## Releasing on Maven Central -Before closing the staging repository, it is important to verify that the contents of all the -published modules are looking good. Particularly, the version numbers should be what are expected, -and they include any custom release qualifiers (like 'alpha') which are set. Make sure that: - - The generated POM files for the individual module have the correct version number. - - The dependencies for an individual module in the POM file are the expected ones & they dependencies have the correct versions. - - 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. +Once all the artifacts have been pushed to the Central Portal, a `deployment` +will be created in the Central Portal. This deployment is visible under the +"Deployments" tab on https://central.sonatype.com/publishing (you will have to +log in with your account).\ +At this point, you can either manually 'Drop' or 'Publish' the deployment. + - Publishing the deployment will make the new release available on Maven + Central. + - Dropping the deployment will close the deployment and abandon the release. + You should drop the deployment if you do not wish to proceed with release + process for any reason. + +### Things to check before 'Publishing' on Maven Central + +Before publishing the release, it is important to verify that the +contents of all the published modules are looking good. Particularly, the +version numbers should be what are expected, and they include any custom release +qualifiers (like 'alpha') which are set. Make sure that: + - The generated POM files for the individual module have the correct version + number. + - The dependencies for an individual module in the POM file are the expected + ones & the dependencies have the correct versions. + - 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. - The file sizes for the published artifacts should seem reasonable. ## Announcement -Once deployment finishes, go to GitHub [release -page](https://github.com/GoogleCloudPlatform/opentelemetry-operations-java/releases), +Once deployment finishes, go to GitHub +[release page](https://github.com/GoogleCloudPlatform/opentelemetry-operations-java/releases), press `Draft a new release` to write release notes about the new release. You can use `git log upstream/v$MAJOR.$((MINOR-1)).x..upstream/v$MAJOR.$MINOR.x ---graph --first-parent` or the GitHub [compare -tool](https://github.com/GoogleCloudPlatform/opentelemetry-operations-java/compare/) +--graph --first-parent` or the GitHub +[compare tool](https://github.com/GoogleCloudPlatform/opentelemetry-operations-java/compare/) to view a summary of all commits since last release as a reference. Please pick major or important user-visible changes only. @@ -176,10 +230,7 @@ $ COMMIT=1224f0a # Set the right commit hash. $ git cherry-pick -x $COMMIT ``` -### Help: Timeout during key-generation process -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. -A common example is - running this command on a remote machine over ssh. - -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. - -The easiest way to fix this is to run it on a machine for which you have UI access. +### Help: Timeout with gpg operations +If you see a timeout error when running `gpg` commands, then you probably have a +graphical session with a gpg agent that is prompting you for a password. Check +your graphical sessions for a password prompt. diff --git a/build.gradle b/build.gradle index dd2438c5..fc307294 100644 --- a/build.gradle +++ b/build.gradle @@ -15,6 +15,10 @@ */ import nebula.plugin.release.git.opinion.Strategies +import java.net.http.HttpClient +import java.net.http.HttpRequest +import java.net.http.HttpResponse + plugins { id "com.diffplug.spotless" id 'nebula.release' @@ -314,12 +318,14 @@ subprojects { } repositories { maven { - def ossrhRelease = "https://oss.sonatype.org/service/local/staging/deploy/maven2/" - def ossrhSnapshot = "https://oss.sonatype.org/content/repositories/snapshots/" + // Publish using Portal OSSRH Staging API. + // For more information see https://central.sonatype.org/publish/publish-portal-ossrh-staging-api/#publishing-by-using-the-portal-ossrh-staging-api + def ossrhRelease = "https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/" + def ossrhSnapshot = "https://central.sonatype.com/repository/maven-snapshots/" url = isReleaseVersion ? ossrhRelease : ossrhSnapshot credentials { - username = rootProject.hasProperty('ossrhUsername') ? rootProject.ossrhUsername : "Unknown user" - password = rootProject.hasProperty('ossrhPassword') ? rootProject.ossrhPassword : "Unknown password" + username = rootProject.hasProperty('centralPortalUsername') ? rootProject.centralPortalUsername : "Unknown user" + password = rootProject.hasProperty('centralPortalPassword') ? rootProject.centralPortalPassword : "Unknown password" } } } @@ -335,3 +341,47 @@ subprojects { } } } + +tasks.register('sonatypeUploadDefaultRepository') { + group = 'Deployment' + description = 'Uploads the artifact repository published by the maven publish plugin to Central Portal so that it is visible in the UI.' + + if (!rootProject.hasProperty('centralPortalUsername') || !rootProject.hasProperty('centralPortalPassword')) { + throw new GradleException("Unable to find the username and password. Please check ~/.gradle/gradle.properties file.") + } + + // The logic is placed in a doLast block to ensure it runs during the execution phase. + doLast { + // Create authentication string to be used in the bearer token for the API. + // See https://central.sonatype.org/publish/publish-portal-ossrh-staging-api/#manual-api-endpoints + String authString = "${rootProject.centralPortalUsername}:${rootProject.centralPortalPassword}" + + // Encode the auth string and construct the bearer token to be passed in the auth header. + String encodedAuthString = Base64.getEncoder().encodeToString(authString.getBytes('UTF-8')) + + String centralPortalUploadEndpoint = 'https://ossrh-staging-api.central.sonatype.com/manual/upload/defaultRepository/com.google.cloud.opentelemetry?publishing_type=user_managed' + + // Make a manual POST request to the Central Portal Endpoint + def client = HttpClient.newHttpClient() + def request = HttpRequest.newBuilder() + .uri(URI.create(centralPortalUploadEndpoint)) + .header('Authorization', "Bearer ${encodedAuthString}") + .header('accept', '*/*') + .POST(HttpRequest.BodyPublishers.ofString('')) + .build() + + println "Sending POST request to: ${centralPortalUploadEndpoint}" + + // The response body will be handled as a String. + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()) + println "Response Status Code: ${response.statusCode()}" + println "Response Body: ${response.body()}" + + // Optionally, fail the build if the request was not successful + if (response.statusCode() >= 400) { + throw new GradleException("Deployment failed! Received HTTP status ${response.statusCode()}") + } else { + println "Default repository uploaded successfully! Please visit https://central.sonatype.com/publishing to complete release process." + } + } +}