first draft WIP POC #1735
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: Verify consumer contracts | |
# The purpose of this workflow is to verify ANY consumer contract(s) dependent on Rawls provider using Pact framework. | |
# | |
# The workflow meets the criteria of Pact Broker *Platinum* as described in https://docs.pact.io/pact_nirvana/step_6. | |
# The can-i-deploy job has been added to this workflow to gate the merge of PRs into develop branch. | |
# | |
# This workflow is triggered when | |
# | |
# 1. Consumer makes a change that results in a new pact published to Pact Broker (will verify ONLY the changed pact and publish the verification results back to the broker) | |
# 2. Provider makes a change (runs verification tests against ALL DEPLOYED consumer pact versions and publishes corresponding verification results) | |
# | |
# | |
# The workflow requires Pact broker credentials | |
# - PACT_BROKER_USERNAME - the Pact Broker username | |
# - PACT_BROKER_PASSWORD - the Pact Broker password | |
on: | |
pull_request: | |
branches: | |
- develop | |
paths-ignore: | |
- 'README.md' | |
push: | |
branches: | |
- develop | |
paths-ignore: | |
- 'README.md' | |
workflow_dispatch: | |
inputs: | |
pb-event-type: | |
description: 'the Pact Broker event type that triggers this workflow' | |
required: true | |
type: string | |
consumer-name: | |
description: 'the consumer name' | |
required: true | |
type: string | |
consumer-version-number: | |
description: 'the version number of the most recent consumer version associated with the pact content' | |
required: true | |
type: string | |
provider-version-number: | |
description: 'the provider version number for the verification result' | |
required: false | |
type: string | |
consumer-version-tags: | |
description: 'the list of tag names for the most recent consumer version associated with the pact content, separated by ", "' | |
required: true | |
type: string | |
consumer-version-branch: | |
description: 'the name of the branch for most recent consumer version associated with the pact content' | |
required: true | |
type: string | |
provider-version-branch: | |
description: 'the name of the branch for the provider version associated with the verification result' | |
required: false | |
type: string | |
consumer-labels: | |
description: 'the list of labels for the consumer associated with the pact content, separated by ", "' | |
required: false | |
type: string | |
provider-labels: | |
description: 'the list of labels for the provider associated with the pact content, separated by ", "' | |
required: false | |
type: string | |
pact-url: | |
description: 'the "permalink" URL to the newly published pact (the URL specifying the consumer version URL, rather than the "/latest" format' | |
required: true | |
type: string | |
env: | |
PACT_BROKER_URL: https://pact-broker.dsp-eng-tools.broadinstitute.org | |
CAN_I_DEPLOY_RUN_NAME: 'can-i-deploy-${{ github.event.repository.name }}-${{ github.run_id }}-${{ github.run_attempt }}' | |
COURSIER_CACHE: $HOME/.cache | |
SBT_CACHE: $HOME/.sbt | |
jobs: | |
# We only need a new version tag when this workflow is triggered by opening, updating a PR or PR merge. | |
# When triggered by a Pact Broker webhook, the provider version (GIT hash or release tag) | |
# is already included in the payload, then a new version tag wouldn't be needed. | |
bump-check: | |
runs-on: ubuntu-latest | |
outputs: | |
is-bump: ${{ steps.skiptest.outputs.is-bump }} | |
steps: | |
- uses: actions/checkout@v4 | |
- name: Skip version bump merges | |
id: skiptest | |
uses: ./.github/actions/bump-skip | |
with: | |
event-name: ${{ github.event_name }} | |
regulated-tag-job: | |
needs: [ bump-check ] | |
if: ${{ needs.bump-check.outputs.is-bump == 'no' }} | |
uses: ./.github/workflows/tag.yml | |
with: | |
# The 'ref' parameter ensures that the provider version is postfixed with the HEAD commit of the PR branch, | |
# facilitating cross-referencing of a pact between Pact Broker and GitHub. | |
ref: ${{ github.head_ref || '' }} | |
# The 'dry-run' parameter prevents the new tag from being dispatched. | |
dry-run: true | |
release-branches: develop | |
secrets: inherit | |
verify-consumer-pact: | |
runs-on: ubuntu-latest | |
needs: [ regulated-tag-job ] | |
permissions: | |
contents: 'read' | |
id-token: 'write' | |
outputs: | |
provider-version: ${{ steps.verification-test.outputs.provider-version }} | |
steps: | |
- name: Checkout current code | |
uses: actions/checkout@v4 | |
with: | |
fetch-depth: 0 | |
- name: Extract branch | |
id: extract-branch | |
run: | | |
GITHUB_EVENT_NAME=${{ github.event_name }} | |
if [[ "$GITHUB_EVENT_NAME" == "push" ]]; then | |
GITHUB_REF=${{ github.ref }} | |
elif [[ "$GITHUB_EVENT_NAME" == "pull_request" ]]; then | |
GITHUB_REF=refs/heads/${{ github.head_ref }} | |
elif [[ "$GITHUB_EVENT_NAME" == "workflow_dispatch" ]]; then | |
GITHUB_REF=${{ github.ref }} # The Git Ref that this workflow runs on | |
else | |
echo "Failed to extract branch information" | |
exit 1 | |
fi | |
echo "CURRENT_BRANCH=${GITHUB_REF/refs\/heads\//""}" >> $GITHUB_ENV | |
- name: Capture webhook event payload as envvars | |
if: ${{ inputs.pb-event-type != '' }} | |
run: | | |
echo "pb-event-type=${{ inputs.pb-event-type }}" | |
echo "consumer-name=${{ inputs.consumer-name }}" | |
# The consumer-version-branch and consumer-version-number identify the most recent | |
# consumer branch and version associated with the pact content. | |
echo "consumer-version-branch/consumer-version-number=${{ inputs.consumer-version-branch }}/${{ inputs.consumer-version-number }}" | |
# The provider-version-number represents the provider version number in the webhook event payload. | |
# This corresponds to the GitHub release recorded by Sherlock for the corresponding | |
# deployment environment (dev, sing, and prod). | |
echo "provider-version-branch/provider-version-number=${{ inputs.provider-version-branch }}/${{ inputs.provider-version-number }}" | |
# The pact-url is included here in case future pact4s client supports it. | |
echo "pact-url=${{ inputs.pact-url }}" | |
# Save webhook event parameters as envvars | |
echo "PROVIDER_BRANCH=${{ inputs.provider-version-branch }}" >> $GITHUB_ENV | |
echo "PROVIDER_TAG=${{ inputs.provider-version-number }}" >> $GITHUB_ENV | |
echo "CONSUMER_BRANCH=${{ inputs.consumer-version-branch }}" >> $GITHUB_ENV | |
echo "CONSUMER_NAME=${{ inputs.consumer-name }}" >> $GITHUB_ENV | |
echo "CONSUMER_VERSION=${{ inputs.consumer-version-number }}" >> $GITHUB_ENV | |
- name: Set PROVIDER_VERSION envvar | |
run: | | |
# The PROVIDER_VERSION envvar is used to identify the provider version | |
# for publishing the results of provider verification. | |
if [[ -z "${{ inputs.pb-event-type }}" ]]; then | |
echo "PROVIDER_BRANCH=${{ env.CURRENT_BRANCH }}" >> $GITHUB_ENV | |
echo "PROVIDER_VERSION=${{ needs.regulated-tag-job.outputs.app-version }}" >> $GITHUB_ENV | |
else | |
echo "PROVIDER_VERSION=${{ env.PROVIDER_TAG }}" >> $GITHUB_ENV | |
fi | |
- name: Switch to appropriate provider branch | |
run: | | |
echo "This workflow has been triggered by '${{ github.event_name }}' event." | |
# If the PROVIDER_TAG envvar exists, switch to the corresponding tag. | |
# This condition is true when the workflow is triggered by a Pact Broker webhook event. | |
if [[ -n "${{ env.PROVIDER_TAG }}" ]]; then | |
echo "git checkout tags/${{ env.PROVIDER_TAG }}" | |
git checkout tags/${{ env.PROVIDER_TAG }} | |
# Otherwise, switch to CURRENT_BRANCH if the workflow has been triggered by a | |
# PR commit or merge onto the main branch. | |
elif [[ "${{ github.event_name }}" == "pull_request" ]] || [[ "${{ github.event_name }}" == "push" ]]; then | |
echo "git checkout ${{ env.CURRENT_BRANCH }}" | |
git checkout ${{ env.CURRENT_BRANCH }} | |
fi | |
# Echo the HEAD commit of the provider branch that has been switched to. | |
echo "git rev-parse HEAD" | |
git rev-parse HEAD | |
- name: Setup envvars for Pact Broker | |
run: | | |
echo "PACT_BROKER_USERNAME=${{ secrets.PACT_BROKER_USERNAME }}" >> $GITHUB_ENV | |
echo "PACT_BROKER_PASSWORD=${{ secrets.PACT_BROKER_PASSWORD }}" >> $GITHUB_ENV | |
# Caching Coursier | |
# Recent sbt versions use Coursier instead of Ivy for dependency management. | |
# The .coursier cache in the sbtscala docker container is located under its .cache directory, which is mounted to the host $PWD/.cache dir. | |
# The hashFiles function calculates the hash based on the file contents from | |
# .sbt, .scala, build.properties | |
# under the project root directory. | |
# | |
# Any changes to these files will result in a different cache key, triggering a cache update. | |
- name: Coursier cache | |
id: coursier-cache | |
uses: actions/cache@v4 | |
with: | |
path: | | |
~/.cache | |
key: rawls-${{ runner.os }}-coursier-cache-${{ hashFiles('**/*.sbt', '**/project/*.scala', '**/project/build.properties') }} | |
- name: Check coursier-cache hit | |
run: | | |
if [ "${{ steps.coursier-cache.outputs.cache-hit }}" == 'true' ]; then | |
echo "Coursier cache ${{ env.COURSIER_CACHE }} restored!" | |
else | |
echo "Coursier cache ${{ env.COURSIER_CACHE }} needs update!" | |
fi | |
# Caching .sbt directory | |
# The hashFiles function calculates the hash based on the file contents from all | |
# .sbt, .scala | |
# files under the project root directory. | |
# | |
# In general, you would include all relevant files that affect the build, | |
# such as build definition files (build.sbt), project files (*.scala), | |
# and any other files that impact the build process. | |
# | |
# Any changes to these files will result in a different cache key, triggering a cache update. | |
- name: Cache .sbt directory | |
id: sbt-cache | |
uses: actions/cache@v4 | |
with: | |
path: | | |
~/.sbt | |
key: rawls-${{ runner.os }}-sbt-cache-${{ hashFiles('**/*.sbt', '**/*.scala') }} | |
- name: Check sbt cache hit | |
run: | | |
if [ "${{ steps.sbt-cache.outputs.cache-hit }}" == 'true' ]; then | |
echo "${{ env.SBT_CACHE }} cache restored!" | |
else | |
echo "${{ env.SBT_CACHE }} cache needs update!" | |
fi | |
# Caching sbt target directories | |
# The hashFiles function calculates the hash based on the file contents from all | |
# .sbt, src/, project/ | |
# files under the project root directory. | |
# | |
# In general, you would include all relevant files that affect the build targets, | |
# such as build definition files (build.sbt), project files (*.scala), | |
# and any other files that impact the build process. | |
# | |
# Any changes to these files will result in a different cache key, triggering a cache update. | |
# | |
# Caching target directories offers significant performance improvements by avoiding | |
# unnecessary compilation and task executions, enabling incremental compilation, and ensuring | |
# consistent and reproducible build outputs. | |
- name: Cache sbt targets | |
id: sbt-targets | |
uses: actions/cache@v4 | |
with: | |
path: | | |
**/target | |
key: rawls-${{ runner.os }}-sbt-targets-${{ hashFiles('*.sbt', 'src/**/*', '**/project/*.sbt', '**/project/*.scala', '**/project/build.properties') }} | |
- name: Check sbt targets cache hit | |
run: | | |
if [ "${{ steps.sbt-targets.outputs.cache-hit }}" == 'true' ]; then | |
echo "sbt targets restored!" | |
else | |
echo "sbt targets need update!" | |
fi | |
# Caching sbt pact4s target directory | |
# The hashFiles function calculates the hash based on the file contents from all | |
# pact4s/src/ | |
# files under the project root directory. | |
# | |
# This is similar to sbt-targets cache but limited to pact4s targets. | |
- name: Cache pact4s targets | |
id: sbt-pact4s-targets | |
uses: actions/cache@v4 | |
with: | |
path: | | |
pact4s/**/target | |
key: rawls-${{ runner.os }}-sbt-pact4s-targets-${{ hashFiles('pact4s/src/**/*') }} | |
- name: Check sbt pact4s targets cache hit | |
run: | | |
if [ "${{ steps.sbt-pact4s-targets.outputs.cache-hit }}" == 'true' ]; then | |
echo "sbt pact4s targets restored!" | |
else | |
echo "sbt pact4s targets need update!" | |
fi | |
- name: Verify consumer pacts and publish verification status to Pact Broker | |
id: verification-test | |
run: | | |
echo "provider-version=${{ env.PROVIDER_VERSION }}" >> $GITHUB_OUTPUT | |
echo "env.CONSUMER_NAME=${{ env.CONSUMER_NAME }} # This reflects the consumer branch for pact verification (generated by Pact Broker)" | |
echo "env.CONSUMER_BRANCH=${{ env.CONSUMER_BRANCH }} # This reflects the consumer branch for pact verification (generated by Pact Broker)" | |
echo "env.PROVIDER_BRANCH=${{ env.PROVIDER_BRANCH }} # This reflects the provider branch to switch to for pact verification" | |
echo "env.CONSUMER_VERSION=${{ env.CONSUMER_VERSION }} # This reflects the consumer version for pact verification (generated by Pact Broker)" | |
echo "env.PROVIDER_VERSION=${{ env.PROVIDER_VERSION }} # Deprecate env.PACT_PROVIDER_COMMIT. This new envvar is used for migrating GIT hash to app versioning" | |
# Refer to https://github.com/sbt/docker-sbt on this Docker image | |
# Recent sbt versions use Coursier instead of Ivy for dependency management. | |
# On Linux, the location is | |
# sbt "show csrCacheDirectory" | |
# with a default value of ~/.cache/coursier/v1 | |
docker run --rm -w /working \ | |
-v "$PWD":/working \ | |
-v ${{ env.COURSIER_CACHE }}:/root/.cache \ | |
-v ${{ env.SBT_CACHE }}:/root/.sbt \ | |
-e PACT_PUBLISH_RESULTS=true \ | |
-e PROVIDER_BRANCH=${{ env.PROVIDER_BRANCH }} \ | |
-e PROVIDER_VERSION=${{ env.PROVIDER_VERSION }} \ | |
-e CONSUMER_NAME=${{ env.CONSUMER_NAME }} \ | |
-e CONSUMER_BRANCH=${{ env.CONSUMER_BRANCH }} \ | |
-e CONSUMER_VERSION=${{ env.CONSUMER_VERSION }} \ | |
-e PACT_BROKER_URL=${{ env.PACT_BROKER_URL }} \ | |
-e PACT_BROKER_USERNAME=${{ env.PACT_BROKER_USERNAME }} \ | |
-e PACT_BROKER_PASSWORD=${{ env.PACT_BROKER_PASSWORD }} \ | |
-e POSTGRES_READ_URL=localhost:5432 \ | |
-e POSTGRES_USERNAME=rawls-test \ | |
-e POSTGRES_PASSWORD=rawls-test \ | |
sbtscala/scala-sbt:openjdk-17.0.2_1.7.2_2.13.10 \ | |
sbt -J-Xmx2g -J-XX:+UseG1GC \ | |
"project pact4s" clean coverage "testOnly org.broadinstitute.dsde.rawls.provider.*" coverageReport | |
can-i-deploy: # The can-i-deploy job will run as a result of a Rawls PR. It reports the pact verification statuses on all deployed environments. | |
runs-on: ubuntu-latest | |
if: ${{ inputs.pb-event-type == '' }} | |
needs: [ verify-consumer-pact ] | |
steps: | |
- name: Verify provider version | |
id: verify-version | |
run: | | |
echo "provider-version-output=${{ needs.verify-consumer-pact.output.provider-version }}" | |
echo "provider-version-env=${{env.PROVIDER_VERSION}}" | |
- name: Dispatch to terra-github-workflows | |
uses: broadinstitute/workflow-dispatch@v4.0.0 | |
with: | |
run-name: "${{ env.CAN_I_DEPLOY_RUN_NAME }}" | |
workflow: .github/workflows/can-i-deploy.yaml | |
repo: broadinstitute/terra-github-workflows | |
ref: refs/heads/main | |
token: ${{ secrets.BROADBOT_TOKEN }} # github token for access to kick off a job in the private repo | |
inputs: '{ | |
"run-name": "${{ env.CAN_I_DEPLOY_RUN_NAME }}", | |
"pacticipant": "rawls", | |
"version": "${{ needs.verify-consumer-pact.outputs.provider-version }}" | |
}' |