From b7a0bee602f57b0680ae85ccc13bec13c92cfc2f Mon Sep 17 00:00:00 2001 From: Robert Reeves Date: Mon, 10 May 2021 14:33:26 -0500 Subject: [PATCH] Extension Release Automation --- .github/add-comment-to-pull-request.sh | 26 ++ .github/add-label-to-pull-request.sh | 26 ++ .github/dependabot.yml | 13 - .github/set-version-from-head.sh | 13 + .github/set-version-from-pom.sh | 6 + .../workflows/pull-request-labeled-closed.yml | 136 ++++++++ .../pull-request-labeled-release.yml | 33 ++ .github/workflows/pull-request.yml | 94 ++++++ .github/workflows/release-published.yml | 29 ++ RELEASE.md | 85 +++++ pom.xml | 301 +++++++++--------- 11 files changed, 599 insertions(+), 163 deletions(-) create mode 100755 .github/add-comment-to-pull-request.sh create mode 100755 .github/add-label-to-pull-request.sh create mode 100755 .github/set-version-from-head.sh create mode 100755 .github/set-version-from-pom.sh create mode 100644 .github/workflows/pull-request-labeled-closed.yml create mode 100644 .github/workflows/pull-request-labeled-release.yml create mode 100644 .github/workflows/pull-request.yml create mode 100644 .github/workflows/release-published.yml create mode 100644 RELEASE.md diff --git a/.github/add-comment-to-pull-request.sh b/.github/add-comment-to-pull-request.sh new file mode 100755 index 00000000..a562524d --- /dev/null +++ b/.github/add-comment-to-pull-request.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +set -ex +set -o pipefail + +if [[ -z "$GITHUB_TOKEN" ]]; then + echo "Set the GITHUB_TOKEN env variable." + exit 1 +fi + +if [[ -z "$GITHUB_ISSUE_URL" ]]; then + echo "Set the GITHUB_ISSUE_URL env variable." + exit 1 +fi + +if [[ -z "$GITHUB_COMMENT" ]]; then + echo "Set the GITHUB_COMMENT env variable." + exit 1 +fi + +data='{"body":"'${GITHUB_COMMENT}'"}' + +#Create Label on Pull Request +curl -X POST -H "Authorization: token $GITHUB_TOKEN" \ + --data "$data" \ + "$GITHUB_ISSUE_URL/comments" diff --git a/.github/add-label-to-pull-request.sh b/.github/add-label-to-pull-request.sh new file mode 100755 index 00000000..ecd564fc --- /dev/null +++ b/.github/add-label-to-pull-request.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +set -e +set -o pipefail + +if [[ -z "$GITHUB_TOKEN" ]]; then + echo "Set the GITHUB_TOKEN env variable." + exit 1 +fi + +if [[ -z "$GITHUB_ISSUE_URL" ]]; then + echo "Set the GITHUB_ISSUE_URL env variable." + exit 1 +fi + +if [[ -z "$GITHUB_LABEL" ]]; then + echo "Set the GITHUB_LABEL env variable." + exit 1 +fi + +data='["'${GITHUB_LABEL}'"]' + +#Create Label on Pull Request +curl -X POST -H "Authorization: token $GITHUB_TOKEN" \ + --data "$data" \ + "$GITHUB_ISSUE_URL/labels" diff --git a/.github/dependabot.yml b/.github/dependabot.yml index bfc2e271..a217b347 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,16 +5,3 @@ updates: schedule: interval: daily open-pull-requests-limit: 10 - ignore: - - dependency-name: org.hibernate:hibernate-envers - versions: - - 5.4.27.Final - - 5.4.29.Final - - dependency-name: org.hibernate:hibernate-entitymanager - versions: - - 5.4.27.Final - - 5.4.29.Final - - dependency-name: org.hibernate:hibernate-core - versions: - - 5.4.27.Final - - 5.4.29.Final diff --git a/.github/set-version-from-head.sh b/.github/set-version-from-head.sh new file mode 100755 index 00000000..29fb0f5e --- /dev/null +++ b/.github/set-version-from-head.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -e +set -o pipefail + +HEAD_REF=$1 + +if [[ -z "$HEAD_REF" ]]; then + echo "Set the HEAD_REF parameter" + exit 1 +fi + +echo "VERSION_TAG=${HEAD_REF#*org.liquibase-liquibase-core-}" >> $GITHUB_ENV \ No newline at end of file diff --git a/.github/set-version-from-pom.sh b/.github/set-version-from-pom.sh new file mode 100755 index 00000000..535e1fcd --- /dev/null +++ b/.github/set-version-from-pom.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +set -e +set -o pipefail + +echo "VERSION_TAG=$(mvn org.apache.maven.plugins:maven-help-plugin:3.1.0:evaluate -Dexpression=project.version -q -DforceStdout)" >> $GITHUB_ENV \ No newline at end of file diff --git a/.github/workflows/pull-request-labeled-closed.yml b/.github/workflows/pull-request-labeled-closed.yml new file mode 100644 index 00000000..ce55ebdb --- /dev/null +++ b/.github/workflows/pull-request-labeled-closed.yml @@ -0,0 +1,136 @@ +name: Prepare Release Candidate for Release + +on: + pull_request: + types: + - closed + +jobs: + verify: + name: Verify Closed PR is Release Candidate + runs-on: ubuntu-latest + if: | + ${{ github.ref == 'main' }} && + ${{ github.event.pull_request.merged }} && + contains( github.event.pull_request.labels.*.name, 'Extension Release Candidate :rocket:' ) + steps: + - run: echo "Starting Release ... " + + build: + needs: [verify] + name: Build Artifact + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Cache Local Maven Repository + uses: actions/cache@v2 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + + - name: Set up JDK 11 + uses: actions/setup-java@v2 + with: + java-version: '11' + distribution: 'adopt' + server-id: sonatype-nexus-staging + server-username: MAVEN_USERNAME + server-password: MAVEN_PASSWORD + gpg-private-key: ${{ secrets.GPG_SECRET }} + gpg-passphrase: GPG_PASSPHRASE + + - name: Build With Maven + run: mvn clean install -Dmaven.test.skip + env: + MAVEN_USERNAME: ${{ secrets.SONATYPE_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.SONATYPE_TOKEN }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + + - name: Upload Artifacts + uses: actions/upload-artifact@v2 + with: + name: liquibase-hibernate5 + path: target/*.jar + + draft-release: + needs: [ build ] + name: Draft Release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Cache Local Maven Repository + uses: actions/cache@v2 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + + - name: Set up JDK 11 + uses: actions/setup-java@v2 + with: + java-version: '11' + distribution: 'adopt' + + - name: Set Version Tag ENV from POM + run: ./.github/set-version-from-pom.sh + + - run: echo ::set-output name=version::$VERSION_TAG + id: version + + - name: Download Artifacts + uses: actions/download-artifact@v2 + with: + name: liquibase-hibernate5 + + - name: Release + uses: softprops/action-gh-release@v1 + with: + target_commitish: ${{ github.sha }} + name: v${{ steps.version.outputs.version }} + tag_name: liquibase-hibernate5-${{ steps.version.outputs.version }} + draft: true + body: Support for Liquibase ${{ steps.version.outputs.version }}. + files: liquibase-hibernate5-*.jar + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + bump-pom-to-snapshot: + name: Prepare POM for Development + runs-on: ubuntu-latest + needs: [ draft-release ] + steps: + - uses: actions/checkout@v2 + with: + persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal token + fetch-depth: 0 # otherwise, you will failed to push refs to dest repo + + - name: Cache Local Maven Repository + uses: actions/cache@v2 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + + - name: Set up JDK 11 + uses: actions/setup-java@v2 + with: + java-version: '11' + distribution: 'adopt' + + - name: Configure git user + run: | + git config user.name "liquibot" + git config user.email "liquibot@liquibase.org" + + - name: Bump POM Version for Development + run: | + mvn versions:set -DnextSnapshot=true + git add pom.xml + git commit -m "Version Bumped to Snapshot for Developent" + git push "https://liquibot:$GITHUB_TOKEN@github.com/$GITHUB_REPOSITORY.git" HEAD:${{ github.base_ref }} --follow-tags --tags + env: + GITHUB_TOKEN: ${{ secrets.BOT_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/pull-request-labeled-release.yml b/.github/workflows/pull-request-labeled-release.yml new file mode 100644 index 00000000..602a2a58 --- /dev/null +++ b/.github/workflows/pull-request-labeled-release.yml @@ -0,0 +1,33 @@ +name: Build, Test, and Prepare Release Candidate + +on: + pull_request: + types: + - labeled + - reopened + - synchronize + +jobs: + integration-tests: + name: Java ${{ matrix.java }} + runs-on: ubuntu-latest + if: contains( github.event.pull_request.labels.*.name, 'Extension Release Candidate :rocket:' ) + strategy: + matrix: + java: [8, 11, 16] + steps: + - uses: actions/checkout@v2 + - name: Cache Local Maven Repository + uses: actions/cache@v2 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + - name: Set up JDK ${{ matrix.java }} + uses: actions/setup-java@v2 + with: + java-version: ${{ matrix.java }} + distribution: 'adopt' + - name: Test With Maven + run: mvn clean verify --file pom.xml diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml new file mode 100644 index 00000000..df87398d --- /dev/null +++ b/.github/workflows/pull-request.yml @@ -0,0 +1,94 @@ +name: Run Unit Tests + +on: + pull_request: + types: + - opened + - reopened + - synchronize + +jobs: + unit-test: + name: Java ${{ matrix.java }} + runs-on: ubuntu-latest + # Only run job if untagged as release. Will run for all PRs and release PRs during the first run. + if: ${{ !contains( github.event.pull_request.labels.*.name, 'Extension Release Candidate :rocket:' ) }} + strategy: + matrix: + java: [8, 11, 16] + + steps: + - uses: actions/checkout@v2 + - name: Cache Local Maven Repository + uses: actions/cache@v2 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + - name: Set up JDK ${{ matrix.java }} + uses: actions/setup-java@v2 + with: + java-version: ${{ matrix.java }} + distribution: 'adopt' + - name: Run Unit Tests With Maven + run: mvn surefire:test --file pom.xml + + # This step is the automation to create a release based on a liquibase core bump + # If a manual release is required, the steps below will need to be done manually prior + # to adding the Release Candidate label. + label: + name: Label as Release Candidate + runs-on: ubuntu-latest + needs: [ unit-test ] + if: contains( github.event.pull_request.head.ref, 'dependabot/maven/org.liquibase-liquibase-core' ) + steps: + - uses: actions/checkout@v2 + with: + persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal token + fetch-depth: 0 # otherwise, you will failed to push refs to dest repo + + - name: Cache Local Maven Repository + uses: actions/cache@v2 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + + - name: Set up JDK 11 + uses: actions/setup-java@v2 + with: + java-version: '11' + distribution: 'adopt' + + - name: Set Version Tag ENV + run: ./.github/set-version-from-head.sh ${{ github.event.pull_request.head.ref }} + + - run: echo $VERSION_TAG + + - name: Configure git user + run: | + git config user.name "liquibot" + git config user.email "liquibot@liquibase.org" + + - name: Bump POM Version for Next Release + run: | + mvn versions:set -DnewVersion=$VERSION_TAG + git add pom.xml + git commit -m "Version Bumped to $VERSION_TAG" + git tag -a -m "Version Bumped to $VERSION_TAG" liquibase-hibernate5-$VERSION_TAG + git push "https://liquibot:$GITHUB_TOKEN@github.com/$GITHUB_REPOSITORY.git" HEAD:${{ github.event.pull_request.head.ref }} --follow-tags --tags + env: + GITHUB_TOKEN: ${{ secrets.BOT_TOKEN }} + + - run: ./.github/add-label-to-pull-request.sh + env: + GITHUB_TOKEN: ${{ secrets.BOT_TOKEN }} + GITHUB_ISSUE_URL: ${{ github.event.pull_request._links.issue.href }} + GITHUB_LABEL: "Extension Release Candidate :rocket:" + - run: ./.github/add-comment-to-pull-request.sh + env: + GITHUB_TOKEN: ${{ secrets.BOT_TOKEN }} + GITHUB_ISSUE_URL: ${{ github.event.pull_request._links.issue.href }} + GITHUB_COMMENT: "

⚠️ Reminder

Release Candidate pull requests will automatically release when merged.

Please review and merge all other pull requests prior to merging this request." \ No newline at end of file diff --git a/.github/workflows/release-published.yml b/.github/workflows/release-published.yml new file mode 100644 index 00000000..eb9d2ffb --- /dev/null +++ b/.github/workflows/release-published.yml @@ -0,0 +1,29 @@ +name: Release Extension to Sonatype + +on: + release: + types: [published] + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Set up Java for publishing to Maven Central Repository + uses: actions/setup-java@v2 + with: + java-version: '11' + distribution: 'adopt' + server-id: sonatype-nexus-staging + server-username: MAVEN_USERNAME + server-password: MAVEN_PASSWORD + gpg-private-key: ${{ secrets.GPG_SECRET }} + gpg-passphrase: GPG_PASSPHRASE + + - name: Publish to the Maven Central Repository + run: mvn clean deploy -Dmaven.test.skip -P release + env: + MAVEN_USERNAME: ${{ secrets.SONATYPE_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.SONATYPE_TOKEN }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} \ No newline at end of file diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 00000000..ada62786 --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,85 @@ +# Release Workflow +The release automation is designed to quickly release updates to liquibase extensions. This routinely happens when there is an update to liquibase core. There are unique automates automated steps when a pull requests is created by dependabot for a `Bump liquibase-core from *.*.* to *.*.*`, but these steps can also be taken manually for a patch or other manual release. + +## Triggers +### Pull Request Opened +When all pull requests are opened the Unit Tests will run and they must pass before the PR can be merged. For a liquibase core bump PR, the application version in the POM will automatically be set to match the liquibase core version. If creating a manual PR for release, the `*.*.*` tag in the POM will need to be set to the correct version without the `SNAPSHOT` suffix in order to release to Sonatype Nexus. For example, `4.3.5.1/version>` to release a patch version for the extension release for liquibase core 4.3.5. +### Pull Request Labeled as Release Candidate +If the `Extension Release Candidate :rocket:` label is applied to the PR, this is the trigger for GitHub Actions to run the full Integration Test suite matrix on the pull requests because this commit will become the next release. For a liquibase core bump, this label will automatically be applied to the dependabot PR. If this is a manual release, manually applying the label will also start the release testing and subsequent automation. +### Pull Request is Approved and Merged to Main +If a Pull Request is merged into main and is labeled as release candidate the following automation steps will be taken: +* Signed artifact is built +* A draft GitHub Release is created proper tagging, version name, and artifact +* The application version in the POM is bumped to be the next SNAPSHOT version for development +### Draft Release is Published +Once the GitHub release is published, the signed artifact is uploaded to Sonatype Nexus. The `true` option is defined in the POM, so for all releases without the `SNAPSHOT` suffix, they will automatically release after all the staging test have passed. If everything goes well, no further manual action is required. + +## Testing +The workflow separates Unit Test from Integration Tests and runs them at separate times, as mentioned above. In order to separate the tests, they must be in separate files. Put all Unit Tests into files that end with `Test.java` and Integration Test files should end with `IT.java`. For example the tests for the Liquibase Postgresql Extension now look like: +``` +> src + > test + > java + > liquibase.ext + > copy + CopyChangeIT.java + CopyChangeTest.java + > vacuum + VacuumChangeTest.java +``` +Any tests that require a JDBC connection to a running database are integration tests and should be in the `IT.java` files. + +## Repository Configuration +The automation requires the below secrets and configuration in order to run. +### BOT TOKEN +Github secret named: `BOT_TOKEN` + +Github Actions bot cannot trigger events, so a liquibase robot user is needed to trigger automated events. An access token belonging to the liquibase robot user should be added to the repository secrets and named `BOT_TOKEN`. + +### GPG SECRET +Github secret named: `GPG_SECRET` + +According to [the advanced java setup docs for github actions](https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#gpg) the GPG key should be exported by: `gpg --armor --export-secret-keys YOUR_ID`. From the datical/build-maven:jdk-8 docker container, this can be export by the following: +```bash +$ docker run -it -u root docker.artifactory.datical.net/datical/build-maven:jdk-8 bash + +$ gpg -k +/home/jenkins/.gnupg/pubring.kbx +-------------------------------- +pub rsa2048 2020-02-12 [SC] [expires: 2022-02-11] + **** OBFUSCATED ID **** +uid [ultimate] Liquibase +sub rsa2048 2020-02-12 [E] [expires: 2022-02-11] + +$ gpg --armor --export-secret-keys --pinentry-mode loopback **** OBFUSCATED ID **** +Enter passphrase: *** GPG PASSPHRASE *** +-----BEGIN PGP PRIVATE KEY BLOCK----- +****** +****** +=XCvo +-----END PGP PRIVATE KEY BLOCK----- +``` + +### GPG PASSPHRASE +Github secret named: `GPG_PASSPHRASE` +The passphrase is the same one used previously for the manual release and is documented elsewhere for the manual release process. + +### SONATYPE USERNAME +Github secret named: `SONATYPE_USERNAME` + +The username or token for the sonatype account. Current managed and shared via lastpass for the Shared-DevOps group. + +### SONATYPE TOKEN +Github secret named: `SONATYPE_TOKEN` + +The password or token for the sonatype account. Current managed and shared via lastpass for the Shared-DevOps group. + +### Label Settings +Create a label with the following settings: +* Label name: `Extension Release Candidate :rocket:` +* Description: `Release Candidate for Extension` +* Color: `#ff3d00` + +## Useful Links +* [Advanced Java Setup for GitHub Actions](https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#gpg) +* [Deploying to Sonatype Nexus with Apache Maven](https://central.sonatype.org/publish/publish-maven/) \ No newline at end of file diff --git a/pom.xml b/pom.xml index c6f13958..625cd36c 100644 --- a/pom.xml +++ b/pom.xml @@ -162,171 +162,172 @@ - - - - maven-resources-plugin - - UTF-8 - - - - maven-compiler-plugin - - 1.8 - 1.8 - true - true - ${project.build.sourceEncoding} - - + - - maven-surefire-plugin - 2.22.2 - - true - plain - - - - org.apache.maven.plugins - maven-enforcer-plugin - 1.4.1 - - - enforce-java - compile - - enforce - - - - - 1.8 - - - - - - - - org.codehaus.mojo - build-helper-maven-plugin - 3.2.0 - + + maven-resources-plugin + + UTF-8 + + - - org.apache.maven.plugins - maven-release-plugin - 2.5.3 - - /tmp/maven-snapshot - forked-path - false - - + + maven-compiler-plugin + + 1.8 + 1.8 + true + true + ${project.build.sourceEncoding} + + + + maven-surefire-plugin + + false + true + plain + + + + unit-tests + test + + test + + + + **/*Test.java + + + + + integration-tests + integration-test + + test + + + + **/*IT.java + **/*Test.java + + + + + - - maven-deploy-plugin - - false - - + + org.apache.maven.plugins + maven-enforcer-plugin + + + enforce-java + compile + + enforce + + + + + 1.8 + + + + + + - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.8 - true - - - default-deploy - deploy - - deploy - - - - - sonatype-nexus-staging - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - + + org.codehaus.mojo + build-helper-maven-plugin + 3.2.0 + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + true + + sonatype-nexus-staging + https://oss.sonatype.org/ + true + + + + - - org.apache.maven.plugins - maven-javadoc-plugin - - false - Liquibase CDI ${project.version} API - true - none - UTF-8 - ${project.basedir}/target - - - - jar-javadoc - - jar - - package - - - - - - - - sonatype-nexus-staging - Nexus Release Repository - https://oss.sonatype.org/service/local/staging/deploy/maven2 - - - sonatype-nexus-snapshots - Sonatype Nexus Snapshots - https://oss.sonatype.org/content/repositories/snapshots - - + + + sonatype-nexus-staging + Nexus Release Repository + https://oss.sonatype.org/service/local/staging/deploy/maven2 + + + sonatype-nexus-staging + Sonatype Nexus Snapshots + https://oss.sonatype.org/content/repositories/snapshots + + - java8-doclint-disabled - - [1.8,) - - - -Xdoclint:none - - - - release-sign-artifacts - - - performRelease - true - - + + release + + org.apache.maven.plugins + maven-source-plugin + 2.2.1 + + + attach-sources + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + false + Liquibase CDI ${project.version} API + true + none + UTF-8 + ${project.basedir}/target + + + + jar-javadoc + + jar + + package + + + org.apache.maven.plugins maven-gpg-plugin - 1.6 - - ${env.GPG_PASSPHRASE} - - - --batch - --no-tty - --pinentry-mode - loopback - - + 1.6 + + ${env.GPG_PASSPHRASE} + + + --batch + --no-tty + --pinentry-mode + loopback + + sign-artifacts @@ -337,9 +338,9 @@ - +