diff --git a/.github/workflows/maven-release.yml b/.github/workflows/maven-release.yml new file mode 100644 index 00000000..2d69fecf --- /dev/null +++ b/.github/workflows/maven-release.yml @@ -0,0 +1,248 @@ +name: Maven Releasing + +on: + + release: + types: [published] + +jobs: + release: + permissions: + contents: write # may want to update version + runs-on: ubuntu-latest + env: + SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} + SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} + MAVEN_SETTINGS_EXPERIM: ${{ vars.MAVEN_SETTINGS_FILE }} + GPG_PASSPHRASE_PRELIM: ${{ secrets.GPG_PASSPHRASE }} + RELEASE_TAG_NAME: ${{ github.event.release.tag_name }} + IS_PRERELEASE: ${{ github.event.release.prerelease }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_REPO: ${{ github.repository }} + steps: + - name: Checkout + uses: actions/checkout@v6 + - name: Setup JDK 21 + uses: actions/setup-java@v5 + with: + java-version: '21' + distribution: 'temurin' + cache: maven + gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} + - name: Add dependencies + run: | + sudo apt-get update + sudo apt-get -y install libxml2-utils + - name: Verify Env + run: | + if [[ -z "${{ secrets.GPG_PRIVATE_KEY }}" ]] + then + echo "ERROR GPG_PRIVATE_KEY is missing." + exit 1 + fi + # ensure scm properties match current project - don't want to modify other repository inadvertently + POM_SITE_URL=$(xmllint --xpath "//*[local-name()='distributionManagement']/*[local-name()='site']/*[local-name()='url']/text()" ./pom.xml) + POM_SCM_CONNECTION=$(xmllint --xpath "//*[local-name()='scm']/*[local-name()='connection']/text()" ./pom.xml) + POM_SCM_DEVELOPER_CONNECTION=$(xmllint --xpath "//*[local-name()='scm']/*[local-name()='developerConnection']/text()" ./pom.xml) + + if [[ ! "${POM_SITE_URL}" != "scm:git:https://github.com/${GITHUB_REPOSITORY}" ]] + then + echo "POM_SITE_URL ${POM_SITE_URL} does not match GITHUB_REPOSITORY ${GITHUB_REPOSITORY}" + exit 1 + else + echo "POM_SITE_URL OK ✓." + fi + + if [[ ! "${POM_SCM_CONNECTION}" =~ .*${GITHUB_REPOSITORY}\.git$ ]] + then + echo "POM_SCM_CONNECTION ${POM_SCM_CONNECTION} does not match GITHUB_REPOSITORY ${GITHUB_REPOSITORY}" + exit 1 + else + echo "POM_SCM_CONNECTION OK ✓." + fi + + if [[ ! "${POM_SCM_DEVELOPER_CONNECTION}" =~ .*${GITHUB_REPOSITORY}\.git$ ]] + then + echo "POM_SCM_DEVELOPER_CONNECTION ${POM_SCM_DEVELOPER_CONNECTION} does not match GITHUB_REPOSITORY ${GITHUB_REPOSITORY}" + exit 1 + else + echo "POM_SCM_DEVELOPER_CONNECTION OK ✓." + fi + + if [[ -n "$SONATYPE_USERNAME" ]] + then + echo "have SONATYPE_USERNAME" + else + echo "Release requires SONATYPE_USERNAME, which was not found. exiting" + exit 1 + fi + + if [[ -n "$SONATYPE_PASSWORD" ]] + then + echo "have SONATYPE_PASSWORD" + else + echo "Release requires SONATYPE_PASSWORD, which was not found. exiting" + exit 1 + fi + + if [[ -n "$GPG_PASSPHRASE_PRELIM" ]] + then + echo "have GPG_PASSPHRASE_PRELIM" + else + echo "Release requires GPG_PASSPHRASE, which was not found. exiting" + exit 1 + fi + + if [[ "$GPG_PASSPHRASE_PRELIM" == "EMPTY" ]] + then + echo "GPG_PASSPHRASE empty requested" + export GPG_PASSPHRASE="" + else + export GPG_PASSPHRASE="$GPG_PASSPHRASE_PRELIM" + fi + + echo "GPG_PASSPHRASE=${GPG_PASSPHRASE}" >> "$GITHUB_ENV" + + # Verify GPG2 environment + if ! command -v gpg2 &> /dev/null + then + echo "Failed to locate gpg2 on this host. GPG2 is required to continue." + exit 1 + fi + + gpg2 --version + GPG_EXECUTABLE=$(command -v gpg2) + export GPG_EXECUTABLE + echo "GPG_EXECUTABLE=${GPG_EXECUTABLE}" >> "$GITHUB_ENV" + + # Verify tag and next version + if ! echo "${RELEASE_TAG_NAME}" | grep -Ei '^v[0-9]+\.[0-9]+\.[0-9]+(-(rc|beta)[0-9]*)?$' + then + echo "RELEASE_TAG_NAME ${RELEASE_TAG_NAME} does not match expected pattern, e.g. (v1.9.0). Exiting." + exit 1 + fi + VERSION_CLEAN="${RELEASE_TAG_NAME:1}" + # shellcheck disable=SC2206 + PARTS=(${VERSION_CLEAN//./ }) + # TODO Check if PARTS[2] starts with 0 or [1-9] if not 0 then this is hot release and should not trigger publishing + if ! echo "${PARTS[2]}" | grep -q "^0.*" + then + echo "Detected hot fix release ${RELEASE_TAG_NAME}" + echo "IS_HOT_FIX=true" >> "${GITHUB_ENV}" + fi + NEW_MINOR=$((PARTS[1]+1)) + NEXT_RELEASE="${PARTS[0]}.${NEW_MINOR}.0" + echo "NEXT_RELEASE=${NEXT_RELEASE}" >> "${GITHUB_ENV}" + NEXT_RELEASE_BRANCH="ci/next-cycle-${NEXT_RELEASE}" + git config user.email "${GITHUB_ACTOR}@users.noreply.github.com" + git config user.name "${GITHUB_ACTOR}" + + if [[ "${IS_PRERELEASE}" != "true" && "${RELEASE_TAG_NAME,,}" =~ ^v[0-9]+\.[0-9]+\.0$ ]] + then + BRANCH_CHECK=$(git ls-remote --heads origin "refs/heads/${NEXT_RELEASE_BRANCH}") + if [ -n "${BRANCH_CHECK}" ] + then + echo "branch ${NEXT_RELEASE_BRANCH} already exists in project. Cannot recreate it automatically." + echo "If you wish to use this automatically created branch, please delete the existing branch and start the release again" + exit 1 + fi + else + echo "Skipping next release cycle branch check because this is either a pre-release or a patch fix." + fi + + echo "NEXT_RELEASE_BRANCH=${NEXT_RELEASE_BRANCH}" >> "$GITHUB_ENV" + + - name: Script + run: | + . ./scripts/on-release.sh + echo "RC_OR_BETA=${RC_OR_BETA}" >> "$GITHUB_ENV" + - name: Release + run: | + if [[ "${IS_PRERELEASE}" == "true" ]]; then + echo "This is a pre release and will not be deployed to Maven Central." + exit 0 + fi + if [[ "${RC_OR_BETA}" == "true" ]] + then + echo "This is an RC or BETA release and will not be deployed to Maven Central." + exit 0 + fi + cat << EOF > release.properties + scm.url=scm\:git\:https\://github.com/${GITHUB_REPOSITORY}.git + scm.tag=${RELEASE_TAG_NAME} + EOF + cat release.properties + mvn release:perform \ + -Dgoals=deploy \ + -s ./deploy-settings.xml \ + -Darguments="-DskipTests -DskipITs -DperformRelease=true" + - name: Prepare next cycle + run: | + if [[ "${IS_PRERELEASE}" == "true" ]]; then + echo "This is a prerelease so the next release cycle will not be prepared." + exit 0 + fi + if [[ "${RC_OR_BETA}" == "true" ]] + then + echo "This is an RC or BETA release so next release cycle will not be prepared." + exit 0 + fi + if [[ "${IS_HOT_FIX}" == "true" ]] + then + echo "This is a hot fix release ${RELEASE_TAG_NAME}, so next release cycle will not be prepared." + exit 0 + fi + + echo "Preparing next release cycle" + + git config user.email "${GITHUB_ACTOR}@users.noreply.github.com" + git config user.name "${GITHUB_ACTOR}" + echo "checking out next release branch ${NEXT_RELEASE_BRANCH}" + git checkout -b "${NEXT_RELEASE_BRANCH}" + mvn versions:set-scm-tag -DnewTag="HEAD" + mvn versions:set -DnextSnapshot=true -DnextSnapshotIndexToIncrement=1 + sed -i "1s/^/## ${NEXT_RELEASE:1} [unreleased]\n\n/" CHANGELOG.md + sed -i -e "s/${RELEASE_TAG_NAME:1}<\/version>/${NEXT_RELEASE:1}-SNAPSHOT<\/version>/" examples/pom.xml + git add pom.xml examples/pom.xml CHANGELOG.md + git commit -m "ci: setting up release cycle ${NEXT_RELEASE:1}" + git push --set-upstream origin "${NEXT_RELEASE_BRANCH}" + # The following requires special permissions on the organizational and project level to work. Switching off for now. + # gh pr create -B main -H ${NEXT_RELEASE_BRANCH} --title "Merge ${NEXT_RELEASE_BRANCH} into main" --body 'Created by Github action' + # The alternative is to create the PR by hand + echo "The branch ${NEXT_RELEASE_BRANCH} has been created. You can create and merge a PR manually from it to start the next release cycle." + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Publish documentation + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + if [[ "${IS_PRERELEASE}" == "true" ]]; then + echo "This is a pre release, so site documentation will not be updated to Github pages." + exit 0 + fi + if [[ "${RC_OR_BETA}" == "true" ]] + then + echo "This is an RC or BETA release, so site documentation will not be updated to Github pages." + exit 0 + fi + if [[ "${IS_HOT_FIX}" == "true" ]] + then + echo "This is a hot fix release ${RELEASE_TAG_NAME}, so site documentation will not be updated to Github pages." + exit 0 + fi + cat << EOF > scm-settings.xml + + + + github + x-access-token + ${GITHUB_TOKEN} + + + + EOF + git config --global user.email "${GITHUB_TRIGGERING_ACTOR}@noreply.github.com" + git config --global user.name "Github Action" + git checkout "${RELEASE_TAG_NAME}" + mvn clean site site:stage -DskipTests + mvn -s scm-settings.xml --batch-mode scm-publish:publish-scm -Dscmpublish.serverId=github diff --git a/CHANGELOG.md b/CHANGELOG.md index ce56bab6..f923d669 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## 1.12.0 [unreleased] +### CI + +1. [#440](https://github.com/InfluxCommunity/influxdb3-java/pull/440): Sets up automated releasing to Maven Central + ## 1.11.0 [2026-08-27] ### Breaking Changes diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 00000000..0191f798 --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,257 @@ +## Releasing + +This document contains a general description and tips for releasing Influxdb3-java using Github Actions with the ultimate goal of pushing the release to Maven Central. + +### Overview + +Releasing involves three general steps. + +1. Preparing the release in a new release branch +2. Triggering the automatic release by creating a new tag and release in Github. +3. Preparing the next release cycle by merging the next release branch created by the automated release script back into `main`. + +### Preparing the release + +1. from `main` create a new release branch, e.g. `git checkout -b chore/release-1.12.0` +1. In the new branch, update the version settings in `pom.xml` + + ```bash + $ mvn versions:set -DremoveSnapshot=true + ... + [INFO] Scanning for projects... + [INFO] + [INFO] --------------------< com.influxdb:influxdb3-java >--------------------- + [INFO] Building InfluxDB 3 Java Client 1.12.0-SNAPSHOT + [INFO] --------------------------------[ jar ]--------------------------------- + ... + $ mvn versions:set-scm-tag -DnewTag="v1.12.0" + ... + [INFO] --- versions-maven-plugin:2.21.0:set-scm-tag (default-cli) @ influxdb3-java --- + [INFO] Updating tag: HEAD -> v1.12.0 + [INFO] ------------------------------------------------------------------------ + [INFO] BUILD SUCCESS + [INFO] ------------------------------------------------------------------------ + ... + ``` + +1. In `README.md` update the `` tag value in the Maven dependency example. +1. In `CHANGELOG.md` verify all commit information for the current cycle is up-to-date, fix any discrepancies, then update the date for this release to the current date. +1. In `examples/pom.xml` update the version of the Influxdb3-java dependency to the version to be released. +1. Commit and push these changes to Github. + +### Initiating the release + +In Github `influxCommunity/influxdb3-java`... + +1. In the project home page open the _Releases_ section. +1. Click _Draft a new release_ +1. In the _Target_ dropdown check the new release branch. +1. In the _Tag_ dropdown click `Create new tag`, supply the tag matching the value added in the `set-scm-tag` command above. Note that the tag should be prefixed with the letter _v_. Click _Create_. +1. In the _Release title_ text control set the title to match the `` tag in `pom.xml`. +1. In the _Release notes_ text field copy changes added since the last release from `CHANGELOG.md`. +1. If this is a pre-release check the _pre-release_ radio button. Note that for "pre-release" releases the release workflow will not publish modules to Maven Central, will not publish site documentation and will not prepare the next release cycle. This setting can be useful for debugging the workflow. +1. Click `Publish release`. + +The creation of the new release will trigger the `maven-release.yml` workflow. It will: + +1. Verify required secrets and environment variables are set, and that SCM values in `pom.xml` match the repository from which the release is being triggered. +1. Check that the `pom.xml` version matches the release tag and that versioning references in documentation and examples are up-to-date and valid. +1. Check and sign the build. +1. Upload the archives and pom files to Maven Central. +1. Publish site documentation to [Github Pages](https://github.com/influxcommunity/influxdb3-java/tree/gh-pages) +1. prepare the `pom.xml` and `CHANGELOG.md` files for the next release cycle and push them to a new branch named `ci/next-cycle-`. + +### Publish release in Maven Central + +From the Maven Central account, review and publish the release to make it available to the public or drop it if the release workflow failed or if there are discrepancies in the files to be published. + +### Preparing the next release cycle + +A new branch `ci/next-cycle-` with an updated `pom.xml` file will have been created when the Maven Release action completes successfully. + +1. create a PR from the branch `ci/next-cycle-` to merge it into `main`. +2. review the PR and if everything has been generated correctly, squash and merge it. + +## The Release environment + +The release workflow is managed by `.github/workflows/maven-release.yml`. In order for the release workflow to succeed a number of environment secrets need to be set in the project. It may be necessary to update these in the future. + +- `GPG_PASSPHRASE` - password for the key used to sign archives and pom files to be uploaded to Maven Central. See _Generating a new GPG signing key_ below. +- `GPG_PRIVATE_KEY` - private key associated with public key pulled from a GPG repository and used to sign archives and pom files. +- `SONATYPE_PASSWORD` - password for the user account used to upload archives and pom files to Maven Central. +- `SONATYPE_USERNAME` - name of user account used to upload archives and pom files to Maven Central. + +### Addenda + +#### Generating a new GPG signing key + +1. Generate a random password for the key. + + e.g. On a Linux box... + + ```bash + $ head -c 6 /dev/urandom | base64 | tr -dc 'a-zA-Z0-9+-' + jEtkLlyG + ``` + + Store this somewhere safe. + +1. Generate a key with a passphrase and no expiration. Note you will be required to enter the passphrase from above. + + ```bash + $ gpg --batch --quick-generate-key "your-user-name@users.noreply.github.com" default default never + gpg: revocation certificate stored as '/home//.gnupg/openpgp-revocs.d/REDACTED_KEY_ID.rev' + ``` + +1. Verify key. + + ```bash + $ gpg --list-keys + gpg: checking the trustdb + gpg: marginals needed: 3 completes needed: 1 trust model: pgp + gpg: depth: 0 valid: 1 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u + /home//.gnupg/pubring.kbx + ----------------------------- + pub ed25519 2026-09-03 [SC] + REDACTED_KEY_ID + uid [ultimate] your-user-name@users.noreply.github.com + sub cv25519 2026-09-03 [E] + ``` + +1. Distribute the key. + + ```bash + $ gpg2 --keyserver keyserver.ubuntu.com --send-keys REDACTED_KEY_ID + gpg: sending key REDACTED to hkp://keyserver.ubuntu.com + ``` + +1. Verify key is on remote. Note, that it may take a few minutes to be registered. + + ```bash + $ gpg2 --keyserver keyserver.ubuntu.com --search-keys REDACTED_KEY_ID + gpg: data source: http://185.125.188.27:11371 + (1) your-user-name@users.noreply.github.com + 263 bit EDDSA key REDACTED, created: 2026-09-03 + Keys 1-1 of 1 for "REDACTED_KEY_ID". Enter number(s), N)ext, or Q)uit > 1 + gpg: key REDACTED: "your-user-name@users.noreply.github.com" not changed + gpg: Total number processed: 1 + gpg: unchanged: 1 + ``` + +1. Get the secret key associated with this key. It will need to be copied then pasted to the Github project secret GPG_PRIVATE_KEY. Note you will be prompted for the passphrase. + + ```bash + $ gpg2 --export-secret-keys --armor REDACTED_KEY_ID + -----BEGIN PGP PRIVATE KEY BLOCK----- + + ...REDACTED... + + -----END PGP PRIVATE KEY BLOCK----- + ``` + +#### Revoking a compromised GPG2 key + +1. verify that the key is on the local server. + + ```bash + $ gpg2 --list-keys + /home//.gnupg/pubring.kbx + ----------------------------- + pub ed25519 2026-07-27 [SC] [expires: 2029-07-26] + KEY_ID_REDACTED + uid [ultimate] your_user_name@users.noreply.github.com + sub REDACTED 2026-07-27 [E] + + ``` + + or... + + ```bash + $ gpg2 --list-keys KEY_ID_REDACTED + pub ed25519 2026-07-27 [SC] [expires: 2029-07-26] + KEY_ID_REDACTED + uid [ultimate] your-user-name@users.noreply.github.com + sub REDACTED 2026-07-27 [E] + ``` + +1. Verify that the key is on the remote server. + + ```bash + $ gpg2 --keyserver keyserver.ubuntu.com --search-keys KEY_ID_REDACTED + gpg: data source: http://185.125.188.27:11371 + (1) your-user-name@users.noreply.github.com + 263 bit EDDSA key REDACTED, created: 2026-07-27 + Keys 1-1 of 1 for "KEY_ID_REDACTED". Enter number(s), N)ext, or Q)uit > 1 + gpg: key REDACTED: "your-user-name@users.noreply.github.com" not changed + gpg: Total number processed: 1 + gpg: unchanged: 1 + ``` + +1. Create a revocation request locally. + + ```bash + $ gpg2 --output revoke-.asc --gen-revoke KEY_ID_REDACTED + ... + ``` + +1. Revoke the key locally. + + ```bash + $ gpg2 --import revoke-.asc + gpg: key REDACTED: "your-user-name@users.noreply.github.com" revocation certificate imported + gpg: Total number processed: 1 + gpg: new key revocations: 1 + gpg: marginals needed: 3 completes needed: 1 trust model: pgp + gpg: depth: 0 valid: 1 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u + gpg: next trustdb check due at 2029-07-26 + ``` + +1. Verify revocation succeeded. + + ```bash + $ gpg2 --list-keys + /home//.gnupg/pubring.kbx + ----------------------------- + pub ed25519 2026-07-27 [SC] [revoked: 2026-09-03] + KEY_ID_REDACTED + uid [ revoked] your-user-name@users.noreply.github.com + ``` + +1. Push change of key state to remote server. + + ```bash + $ gpg2 --keyserver keyserver.ubuntu.com --send-keys KEY_ID_REDACTED + gpg: sending key REDACTED to hkp://keyserver.ubuntu.com + ``` + +1. Verify key state on remote server. + + ```bash + $ gpg2 --keyserver keyserver.ubuntu.com --search-keys KEY_ID_REDACTED + gpg: data source: http://185.125.188.27:11371 + gpg: key "KEY_ID_REDACTED" not found on keyserver + ``` + +1. Delete secret key locally. + + ```bash + $ gpg2 --delete-secret-key KEY_ID_REDACTED + ... + # Confirmation required + ``` + +1. Delete key locally + + ```bash + $ gpg2 --delete-key 01EAECEC736391172C6520F48B5778484117B952 + ... + # Confirmation required + ``` + +1. Verify key is deleted + + ```bash + $ gpg2 --list-keys + gpg: checking the trustdb + gpg: no ultimately trusted keys found + ``` diff --git a/pom.xml b/pom.xml index e68d9732..9a2098ce 100644 --- a/pom.xml +++ b/pom.xml @@ -64,8 +64,8 @@ - scm:git:git@github.com:InfluxCommunity/influxdb3-java.git - scm:git:git@github.com:InfluxCommunity/influxdb3-java.git + scm:git:https://github.com/InfluxCommunity/influxdb3-java.git + scm:git:https://github.com/InfluxCommunity/influxdb3-java.git https://github.com/InfluxCommunity/influxdb3-java/tree/main HEAD @@ -81,7 +81,7 @@ GitHubPages - https://InfluxCommunity.github.io/influxdb3-java/ + scm:git:https://github.com/InfluxCommunity/influxdb3-java @@ -408,6 +408,12 @@ + + org.apache.maven.plugins + maven-release-plugin + 3.3.1 + + org.apache.maven.plugins maven-checkstyle-plugin @@ -632,11 +638,8 @@ maven-scm-publish-plugin 3.3.0 - target/staging - scm:git:git@github.com:InfluxCommunity/influxdb3-java.git - ${project.basedir}/../influxdb3-scmpublish-checkout + ${project.build.directory}/scm-publish gh-pages - UTF-8 @@ -749,20 +752,16 @@ - org.sonatype.plugins - nexus-staging-maven-plugin - 1.7.0 + org.sonatype.central + central-publishing-maven-plugin + 0.9.0 true - ossrh - https://ossrh-staging-api.central.sonatype.com/ - true - 15 + ossrh - diff --git a/scripts/on-release.sh b/scripts/on-release.sh new file mode 100755 index 00000000..47d58233 --- /dev/null +++ b/scripts/on-release.sh @@ -0,0 +1,234 @@ +#!/usr/bin/env bash +# +# The MIT License +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +set -e + +echo "Starting ${0}" + +if [ -z "${GITHUB_ACTIONS}" ] +then + echo "This script can only be run in a Github action container." + echo "Local runs are not yet supported." + exit 1 +fi +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +PROJECT_DIR="${SCRIPT_DIR}/.." +CHANGELOG_PATH="${PROJECT_DIR}/CHANGELOG.md" +README_PATH="${PROJECT_DIR}/README.md" +POM_XML_PATH="${PROJECT_DIR}/pom.xml" +EXAMPLE_POM_XML_PATH="${PROJECT_DIR}/examples/pom.xml" + +RELEASE_NUM="" +RC_OR_BETA=false + +FAILURE_BOILERPLATE="Please delete the tag ${RELEASE_TAG_NAME} and the related release, and start again." + +github_check(){ + + if [ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]; then + echo "WARNING: This script is targeted for 'release' not ${GITHUB_EVENT_NAME}." + echo "Continuing for debugging." + return + fi + + if [ "${GITHUB_EVENT_NAME}" == "push" ]; then + RELEASE_TAG_MATCH="^v[0-9]+(\.[0-9]+){2}(-(rc|beta)[0-9]+)?$" + echo "WARNING: this script is targeted for 'release' not ${GITHUB_EVENT_NAME}" + + if [[ "$GITHUB_REF_NAME" =~ $RELEASE_TAG_MATCH ]]; then + echo "Detected matching tag value in GITHUB_REF_NAME (${GITHUB_REF_NAME}). Continuing for debugging purposes." + RELEASE_TAG_NAME="${GITHUB_REF_NAME}" + return + else + echo " Checking env" + env + exit 1 + fi + fi + + if [ "${GITHUB_EVENT_NAME}" != "release" ]; then + echo "This script can run only on 'release'. Detected Github event ${GITHUB_EVENT_NAME}." + exit 1 + fi + + if ! echo "${RELEASE_TAG_NAME}" | grep -Ei '^v[0-9]+\.[0-9]+\.[0-9]+(-(rc|beta|snapshot)[0-9]*)?$' + then + echo "This script requires a valid release tag (e.g. v1.9.0), but RELEASE_TAG_NAME=${RELEASE_TAG_NAME} was found." + exit 1 + fi + + echo "Running ${GITHUB_EVENT_NAME} with tag ${RELEASE_TAG_NAME}." +} + +setup(){ + ADDITIONAL_INSTALLS="" + if ! [ -x "$(command -v xmllint)" ] + then + ADDITIONAL_INSTALLS="${ADDITIONAL_INSTALLS} libxml2-utils" + else + printf "have xmllint\n" + fi + + if [ -n "${ADDITIONAL_INSTALLS}" ] + then + printf "This script requires the following unavailable libraries: %s\n" "${ADDITIONAL_INSTALLS}" + printf "Please install them and then continue.\n" + printf "%s\n" "${FAILURE_BOILERPLATE}" + exit 1 + else + printf "Additional requirements already satisfied.\n" + fi +} + +set_release_number(){ + RELEASE_NUM=$(echo "${RELEASE_TAG_NAME}" | sed -r "/-(rc|beta)[0-9]*/Is///" | sed -r "s/^v//") +} + +verify_rc_or_beta(){ + LOWER_TAG_NAME=$(echo "${RELEASE_TAG_NAME}" | tr '[:upper:]' '[:lower:]') + if echo "${LOWER_TAG_NAME}" | grep -q "rc\|beta" + then + RC_OR_BETA=true + fi +} + +verify_changelog() { + + CHANGELOG_RELEASE_HEADERS=$(sed -n '/^#.*[0-9].[0-9]*.[0-9].*/p' "${CHANGELOG_PATH}") + + mapfile -t HEADER_ARRAY <<< "$CHANGELOG_RELEASE_HEADERS" + + mapfile -td ' ' HEADER_LINE <<< "${HEADER_ARRAY[0]}" + + HEADER_TAG="${HEADER_LINE[1]}" + HEADER_DATE="${HEADER_LINE[2]:1:-2}" + + if [ "$HEADER_TAG" != "$RELEASE_NUM" ]; then + printf "ERROR: Latest HEADER_TAG in CHANGELOG.md (%s) does not match release number (%s) from git tag (%s)\n" \ + "$HEADER_TAG" \ + "$RELEASE_NUM" \ + "$RELEASE_TAG_NAME" + printf "Please update the latest HEADER_TAG in CHANGELOG.md\n" + printf "%s" "${FAILURE_BOILERPLATE}" + exit 1 + else + printf "CHANGELOG.md release number check: OK ✓\n" + fi + + if ! date --date="${HEADER_DATE}" + then + printf "ERROR invalid commit date (%s) in last CHANGELOG.md entry\n" "$HEADER_DATE" + printf "Please update the commit date in CHANGELOG.md\n" + printf "%s\n" "${FAILURE_BOILERPLATE}" + exit 1 + else + printf "CHANGELOG.md release date check: OK ✓\n" + fi +} + +verify_version(){ + printf "verifying version\n" + PROJECT_VERSION=$(xmllint --xpath "//*[local-name()='project']/*[local-name()='version']/text()" "${POM_XML_PATH}") + printf "Project version from pom.xml is %s\n" "${PROJECT_VERSION}" + + if [[ "${PROJECT_VERSION}" == *SNAPSHOT ]] + then + printf "Version in %s (%s) is a snapshot.\n" "${POM_XML_PATH}" "${PROJECT_VERSION}" + printf "This script does not release snapshots.\n" + printf "%s\n" "${FAILURE_BOILERPLATE}" + exit 1 + fi + + if [ "${PROJECT_VERSION}" != "${RELEASE_NUM}" ] + then + printf "PROJECT_VERSION %s in pom.xml does not match tag %s" "${PROJECT_VERSION}" "${RELEASE_TAG_NAME}" + printf "%s\n" "${FAILURE_BOILERPLATE}" + exit 1 + fi + + printf "pom.xml project version (%s) checks with release tag (%s): OK ✓\n" "${PROJECT_VERSION}" "${RELEASE_TAG_NAME}" + + SCM_TAG=$(xmllint --xpath "//*[local-name()='project']/*[local-name()='scm']/*[local-name()='tag']/text()" "${POM_XML_PATH}") + + if [ "${SCM_TAG}" != "${RELEASE_TAG_NAME}" ] + then + printf "SCM_TAG %s in pom.xml does not match tag %s" "${SCM_TAG}" "${RELEASE_TAG_NAME}" + printf "%s\n" "${FAILURE_BOILERPLATE}" + exit 1 + fi + +} + +verify_example_pom(){ + EXAMPLE_DEPENDENCY_VERSION=$(xmllint --xpath "//*[local-name()='dependencies']/*[local-name()='dependency']/*[local-name()='version']/text()" "${EXAMPLE_POM_XML_PATH}") + if [ "${RELEASE_NUM}" != "${EXAMPLE_DEPENDENCY_VERSION}" ] + then + printf "Example dependency version %s does not match the release number %s\n" "${EXAMPLE_DEPENDENCY_VERSION}" "${RELEASE_NUM}" + printf "Please update the project dependency version in %s\n" "${EXAMPLE_POM_XML_PATH}" + printf "%s\n" "${FAILURE_BOILERPLATE}" + exit 1 + fi +} + +verify_readme(){ + printf "Verifying README %s\n" "${README_PATH}" + README_NODE_RAW="$(sed -n "/.*<\/version>/p" "${README_PATH}")" + + if [ -z "${README_NODE_RAW}" ] + then + printf "Example in %s with tag not found.\n" "${README_PATH}" + printf "The %s file should include an example with the current release version.\n" "${README_PATH}" + printf "Exiting...\n" + printf "%s\n" "${FAILURE_BOILERPLATE}" + exit 1 + fi + + README_NODE="${README_NODE_RAW#"${README_NODE_RAW%%[![:space:]]*}"}" + README_VERSION="$(echo "${README_NODE}" | sed "s///" | sed "s/<\/version>//")" + + if ! [ "${RELEASE_NUM}" == "${README_VERSION}" ] + then + VERSION_LINE=$(grep -n ".*" "${README_PATH}" | awk -F '[:]' '{ print $1 }') + printf "Release tag (%s) does not match example in README.md (%s) on line %s.\n" "${RELEASE_NUM}" "${README_VERSION}" "${VERSION_LINE}" + printf "Please update README.md to the current release before continuing.\n" + printf "%s\n" "${FAILURE_BOILERPLATE}" + exit 1 + fi + + printf "Version in README.md (%s) OK ✓.\n" "${README_VERSION}" +} + +echo "Running in Github Actions container." + +github_check + +verify_rc_or_beta +export RC_OR_BETA +set_release_number + +verify_changelog +verify_example_pom +verify_readme +verify_version + +setup