diff --git a/.github/workflows/maven_central_release.yml b/.github/workflows/maven_central_release.yml new file mode 100644 index 00000000..174b018e --- /dev/null +++ b/.github/workflows/maven_central_release.yml @@ -0,0 +1,142 @@ +name: Release to Maven Central + +on: + push: + tags: + - '[0-9]+.[0-9]+.[0-9]+' + +permissions: + contents: read + id-token: write + +concurrency: + group: maven-central-release + cancel-in-progress: false + +jobs: + release: + runs-on: ubuntu-latest + environment: maven-release + timeout-minutes: 30 + env: + MAVEN_RELEASE_AWS_REGION: ${{ vars.MAVEN_RELEASE_AWS_REGION }} + MAVEN_RELEASE_AWS_ROLE_ARN: ${{ secrets.MAVEN_RELEASE_AWS_ROLE_ARN }} + MAVEN_RELEASE_AWS_SECRET_ARN: ${{ secrets.MAVEN_RELEASE_AWS_SECRET_ARN }} + steps: + - name: Validate workflow configuration + run: | + required_vars=( + MAVEN_RELEASE_AWS_REGION + ) + + for var_name in "${required_vars[@]}"; do + if [[ -z "${!var_name:-}" ]]; then + echo "::error::Repository variable ${var_name} is required." + exit 1 + fi + done + + required_secrets=( + MAVEN_RELEASE_AWS_ROLE_ARN + MAVEN_RELEASE_AWS_SECRET_ARN + ) + + for secret_name in "${required_secrets[@]}"; do + if [[ -z "${!secret_name:-}" ]]; then + echo "::error::GitHub secret ${secret_name} is required." + exit 1 + fi + done + + - name: Check out tag + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + with: + ref: ${{ github.ref }} + + - name: Set up Java 11 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 + with: + distribution: temurin + java-version: "11" + cache: maven + + - name: Verify tag matches POM version + run: | + POM_VERSION=$(mvn -B -q -N -DforceStdout help:evaluate -Dexpression=project.version) + if [[ "${POM_VERSION}" == *-SNAPSHOT ]]; then + echo "::error::Refusing to release SNAPSHOT version ${POM_VERSION}." + exit 1 + fi + if [[ "${GITHUB_REF_NAME}" != "${POM_VERSION}" ]]; then + echo "::error::Tag ${GITHUB_REF_NAME} does not match POM version ${POM_VERSION}." + exit 1 + fi + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@d979d5b3a71173a29b74b5b88418bfda9437d885 # v6.1.1 + with: + aws-region: ${{ env.MAVEN_RELEASE_AWS_REGION }} + role-to-assume: ${{ env.MAVEN_RELEASE_AWS_ROLE_ARN }} + role-session-name: java-questdb-client-release + + - name: Fetch release credentials + uses: aws-actions/aws-secretsmanager-get-secrets@a9a7eb4e2f2871d30dc5b892576fde60a2ecc802 # v2.0.10 + with: + secret-ids: | + ,${{ env.MAVEN_RELEASE_AWS_SECRET_ARN }} + parse-json-secrets: true + + - name: Validate release credentials + run: | + required_vars=( + MAVEN_GPG_PRIVATE_KEY + MAVEN_GPG_PASSPHRASE + MAVEN_CENTRAL_USERNAME + MAVEN_CENTRAL_PASSWORD + ) + + for var_name in "${required_vars[@]}"; do + if [[ -z "${!var_name:-}" ]]; then + echo "::error::AWS secret ${MAVEN_RELEASE_AWS_SECRET_ARN} must define ${var_name}." + exit 1 + fi + done + + - name: Configure Maven settings.xml + run: | + mkdir -p "$HOME/.m2" + cat > "$HOME/.m2/settings.xml" <<'EOF' + + + + central + ${env.MAVEN_CENTRAL_USERNAME} + ${env.MAVEN_CENTRAL_PASSWORD} + + + gpg.passphrase + ${env.MAVEN_GPG_PASSPHRASE} + + + + EOF + + - name: Import release signing key + run: | + export GNUPGHOME="$(mktemp -d)" + chmod 700 "$GNUPGHOME" + printf '%s\n' "$MAVEN_GPG_PRIVATE_KEY" | gpg --batch --import + echo "GNUPGHOME=$GNUPGHOME" >> "$GITHUB_ENV" + + - name: Publish release to Maven Central + run: | + mvn -B -ntp deploy -P maven-central-release -DskipTests + + - name: Remove imported signing key + if: always() + run: | + if [[ -n "${GNUPGHOME:-}" && -d "${GNUPGHOME}" ]]; then + rm -rf "$GNUPGHOME" + fi \ No newline at end of file diff --git a/RELEASE.md b/RELEASE.md deleted file mode 100644 index 38fa0356..00000000 --- a/RELEASE.md +++ /dev/null @@ -1,108 +0,0 @@ -# Release Guide - -This document describes how to release `org.questdb:client` to Maven Central. - -## Overview - -Releases are performed using the [Maven Release Plugin](https://maven.apache.org/maven-release/maven-release-plugin/) combined with the [Sonatype Central Publishing Plugin](https://central.sonatype.org/publish/publish-portal-maven/). The `maven-central-release` profile handles signing, Javadoc generation, source attachment, and publishing. - -## Prerequisites - -### 1. GPG Key - -A GPG key is required to sign the release artifacts. If you don't have one: - -```bash -gpg --gen-key -gpg --keyserver keyserver.ubuntu.com --send-keys -``` - -More details on GPG key generation can be found in the [Sonatype guide](https://central.sonatype.org/publish/requirements/gpg/). - -### 2. Sonatype Credentials - -You need credentials for the Sonatype Central Portal (https://central.sonatype.com/). - -### 3. Maven `settings.xml` - -Configure your `~/.m2/settings.xml` with the Sonatype server credentials: - -```xml - - - - central - YOUR_SONATYPE_USERNAME - YOUR_SONATYPE_PASSWORD - - - -``` - -More details can be found in the [Sonatype guide](https://central.sonatype.org/publish/publish-portal-maven/). - -### 4. Repository Access - -You need push access to the `questdb/java-questdb-client` repository on GitHub. - -## Release Process - -### Step 1: Prepare the Release - -This bumps the version, creates a tag, and commits the changes: - -```bash -mvn release:prepare -``` - -The plugin will prompt for: - -- **Release version** (e.g., `9.3.1`) — the version to release -- **SCM tag** (e.g., `9.3.1`) — the Git tag name (uses the `tagNameFormat` of `@{project.version}`) -- **Next development version** (e.g., `9.3.2-SNAPSHOT`) — the next snapshot version - -This creates two commits: - -1. `[maven-release-plugin] prepare release 9.3.1` — sets the release version -2. `[maven-release-plugin] prepare for next development iteration` — sets the next snapshot version - -And a Git tag (e.g., `9.3.1`). - -### Step 2: Perform the Release - -This builds, signs, and publishes the artifacts to Maven Central: - -```bash -mvn release:perform -``` - -The `maven-central-release` profile is activated automatically. It: - -- Compiles the source -- Generates Javadoc -- Attaches sources JAR -- Signs all artifacts with GPG -- Publishes to Maven Central via the Sonatype Central Publishing Plugin -- Waits until the artifacts are published (`waitUntil=published`) - -### Step 3: Push Tags - -If not pushed automatically: - -```bash -git push origin main --tags -``` - -## Post-Release - -### Verify on Maven Central - -Check that the new version appears on [Maven Central](https://central.sonatype.com/artifact/org.questdb/client). Propagation may take some time after publishing. - -### Create a GitHub Release - -1. Go to [GitHub Releases](https://github.com/questdb/java-questdb-client/releases). -2. Click **Draft a new release**. -3. Select the tag created by the release plugin. -4. Add release notes describing the changes. -5. Publish the release. diff --git a/artifacts/release/README.md b/artifacts/release/README.md new file mode 100644 index 00000000..918fab65 --- /dev/null +++ b/artifacts/release/README.md @@ -0,0 +1,109 @@ +# Release steps + +Steps to release `org.questdb:questdb-client` to Maven Central. Examples below use `1.2.2` (release) and +`1.2.3-SNAPSHOT` (next snapshot); substitute the actual versions when running. + +**Prerequisite:** tag creation is restricted by an org-wide ruleset, so you must be a member of the `questdb/release` +team to push the release tag. Confirm membership before starting. + +## Edit release notes + +Create a draft release with the intended version and notes. Do not create the git tag up front -- pick the tag name +in the draft and let GitHub create it when the release is published. Match the style of previous release notes. + +## Create a release branch + +Direct pushes to `main` are blocked by the org ruleset (one-approval squash-merged PR is the only path), so release +commits live on a dedicated branch. + +```bash +git fetch +git checkout main +git pull +git checkout -b release/1.2.2 +``` + +Make sure your working tree is clean. + +## Clear previous release "memory" + +```bash +mvn release:clean +``` + +Removes any `release.properties` and `*.releaseBackup` files left over from a previous attempt. + +## Roll versions and create the tag + +`release:prepare` will: + +- roll parent and module versions from snapshot to release (`1.2.2-SNAPSHOT` -> `1.2.2`) +- commit the release POMs +- create the release tag locally +- roll the versions to the next snapshot (`1.2.3-SNAPSHOT`) +- commit the next-snapshot POMs + +```bash +mvn -B release:prepare \ + -DautoVersionSubmodules=true \ + -DpushChanges=false \ + -DreleaseVersion=1.2.2 \ + -DdevelopmentVersion=1.2.3-SNAPSHOT \ + -Dtag=1.2.2 +``` + +`-B` runs non-interactively; drop it for special versions (e.g. a new major) to get the prompts. `-DpushChanges=false` +keeps the commits and tag local until you have verified them. + +If `release:prepare` fails partway through: + +```bash +mvn release:rollback +git tag -d 1.2.2 +``` + +`release:rollback` reverts the prepare commits and removes the backup files but does **not** delete the tag -- drop +it manually or the next attempt at the same version fails. If `release.properties` is already gone, use +`git reset --hard ` instead (and still drop the tag). + +## Push the release branch and tag + +```bash +git push origin release/1.2.2 +git push origin 1.2.2 +``` + +The tag push triggers the Maven Central workflow (see below). The branch is merged to `main` afterwards -- see +[Merge the release branch to `main`](#merge-the-release-branch-to-main). + +## Publish to Maven Central + +The [`Release to Maven Central`](../../.github/workflows/maven_central_release.yml) workflow fires automatically when +a tag matching `X.Y.Z` is pushed. No manual dispatch. It: + +- checks out the pushed tag +- assumes an AWS IAM role via OIDC and reads the GPG key and Sonatype credentials from AWS Secrets Manager +- verifies the tag matches the parent POM version and is not a snapshot +- signs the artifacts and uploads them through the Sonatype Central Portal + +The workflow returns once Sonatype has validated the upload and taken ownership of the artifacts. Physical +propagation to Maven Central happens asynchronously after the workflow finishes, so a green run does **not** mean the +artifacts are visible on `central.sonatype.com` yet -- that step is covered under [Post-release](#post-release). + +## Merge the release branch to `main` + +Once the workflow finishes, open a PR from `release/1.2.2` to `main` and squash-merge it after approval. Delete the +release branch afterwards. You do not need to wait for Maven Central propagation before merging -- once the workflow +is green, Sonatype owns the artifacts and the next snapshot version on `main` is the source of truth for ongoing +development. + +Squash-merge is the only merge method allowed by the org ruleset on `main`, so the original `[maven-release-plugin]` +commits will not appear in `main`'s history. The tag remains the canonical pointer to the released code; `main` +carries a single squashed commit that bumps the snapshot version. + +## Post-release + +After the workflow completes, Sonatype still has to propagate the artifacts to Maven Central. This typically takes a +few minutes but can occasionally run longer. Check +[Maven Central](https://central.sonatype.com/artifact/org.questdb/questdb-client) until the new version is listed, +then finalize the GitHub release draft against the new tag and add the release notes. diff --git a/core/pom.xml b/core/pom.xml index 7b9a2c20..addab542 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -61,8 +61,9 @@ - scm:git:https://github.com/questdb/java-questdb-client.git + https://github.com/questdb/java-questdb-client scm:git:https://github.com/questdb/java-questdb-client.git + scm:git:https://github.com/questdb/java-questdb-client.git HEAD @@ -352,12 +353,12 @@ org.sonatype.central central-publishing-maven-plugin - 0.8.0 + 0.9.0 true central true - published + validated