From 68ce2c0ce1ff221578410e0fdfc5f401f99380d3 Mon Sep 17 00:00:00 2001 From: Brad Jorsch Date: Fri, 26 Aug 2022 15:08:29 -0400 Subject: [PATCH] build: Move "Test plugin updates" to a post-build workflow GitHub has an unfortunate behavior in that workflow artifacts are not available via the API until the entire workflow has completed. This means that the "Test plugin updates" job in the build workflow unnecessarily delays the availability of the build artifact for Jetpack Live Branches and future wpcom automated testing for 6 or so minutes. To get around this, we create a new "Post-Build" workflow that is triggered by `workflow_run` after the Build workflow completes. But since this new workflow isn't automatically attached to the PR, we have to make API calls to get it to show up there. --- .github/actions/check-run/action.yml | 71 +++++++ .github/workflows/build.yml | 50 ----- .github/workflows/post-build.yml | 206 ++++++++++++++++++++ .github/workflows/slack-workflow-failed.yml | 1 + 4 files changed, 278 insertions(+), 50 deletions(-) create mode 100644 .github/actions/check-run/action.yml create mode 100644 .github/workflows/post-build.yml diff --git a/.github/actions/check-run/action.yml b/.github/actions/check-run/action.yml new file mode 100644 index 0000000000000..3b666809dab5d --- /dev/null +++ b/.github/actions/check-run/action.yml @@ -0,0 +1,71 @@ +name: "Check Run" +description: "Create or update a check run. This is a simple wrapper around GitHub's checks API, https://docs.github.com/en/rest/checks/runs." +inputs: + id: + description: "Check run ID to update. If not given, a new run will be created" + sha: + description: "The SHA of the commit." + name: + description: "The name of the check. For example, \"code-coverage\"." + status: + description: "The current status. Can be one of: `queued`, `in_progress`, `completed`" + conclusion: + description: "The final conclusion of the check. Can be one of: `action_required`, `cancelled`, `failure`, `neutral`, `success`, `skipped`, `timed_out`" + title: + description: "Title of the check. Shown in the PR's checks list." + summary: + description: "Summary of the check. Can contain Markdown." + token: + description: "Access token. Defaults to `github.token`." + default: ${{ github.token }} +outputs: + id: + description: "Check run ID." + value: ${{ steps.run.outputs.id }} +runs: + using: composite + steps: + - id: run + shell: bash + env: + ID: ${{ inputs.id }} + SHA: ${{ inputs.sha }} + NAME: ${{ inputs.name }} + STATUS: ${{ inputs.status }} + CONCLUSION: ${{ inputs.conclusion }} + TITLE: ${{ inputs.title }} + SUMMARY: ${{ inputs.summary }} + TOKEN: ${{ inputs.token }} + run: | + if [[ -n "$ID" ]]; then + METHOD=PATCH + URL="${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/check-runs/$ID" + else + METHOD=POST + URL="${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/check-runs" + fi + + DATA="{}" + if [[ -n "$NAME" ]]; then + DATA="$(jq --arg v "$NAME" '.name |= $v' <<<"$DATA")" + fi + if [[ -n "$SHA" ]]; then + DATA="$(jq --arg v "$SHA" '.head_sha |= $v' <<<"$DATA")" + fi + if [[ -n "$STATUS" ]]; then + DATA="$(jq --arg v "$STATUS" '.status |= $v' <<<"$DATA")" + fi + if [[ -n "$CONCLUSION" ]]; then + DATA="$(jq --arg v "$CONCLUSION" '.conclusion |= $v' <<<"$DATA")" + fi + if [[ -n "$TITLE" ]]; then + DATA="$(jq --arg v "$TITLE" '.output.title |= $v' <<<"$DATA")" + fi + if [[ -n "$SUMMARY" ]]; then + DATA="$(jq --arg v "$SUMMARY" '.output.summary |= $v' <<<"$DATA")" + fi + + echo "Data: $DATA" + JSON="$(curl -v -X "$METHOD" --header "authorization: token $TOKEN" --url "$URL" --data "$DATA")" + echo "$JSON" + echo "::set-output name=id::$(jq -r .id <<<"$JSON")" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4da3f0f829b1b..1dd9c2bfb999a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -128,56 +128,6 @@ jobs: comment_id: +COMMENT_ID, } ); - upgrade_test: - name: Test plugin upgrades - runs-on: ubuntu-latest - needs: build - if: needs.build.outputs.any_plugins == 'true' - timeout-minutes: 10 # 2022-03-04: Successful runs seem to take about 3 minutes, but give some extra time for the downloads. - services: - db: - image: mariadb:latest - env: - MARIADB_ROOT_PASSWORD: wordpress - ports: - - 3306:3306 - options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=5 - container: - image: ghcr.io/automattic/jetpack-wordpress-dev:latest - env: - WP_DOMAIN: localhost - WP_ADMIN_USER: wordpress - WP_ADMIN_EMAIL: wordpress@example.com - WP_ADMIN_PASSWORD: wordpress - WP_TITLE: Hello World - MYSQL_HOST: db:3306 - MYSQL_DATABASE: wordpress - MYSQL_USER: root - MYSQL_PASSWORD: wordpress - HOST_PORT: 80 - ports: - - 80:80 - steps: - - uses: actions/checkout@v3 - with: - path: monorepo - - - name: Download build artifact - uses: actions/download-artifact@v3 - with: - name: jetpack-build - - name: Extract build archive - run: tar --xz -xvvf build.tar.xz - - - name: Setup WordPress - run: monorepo/.github/files/test-plugin-update/setup.sh - - - name: Prepare plugin zips - run: monorepo/.github/files/test-plugin-update/prepare-zips.sh - - - name: Test upgrades - run: monorepo/.github/files/test-plugin-update/test.sh - jetpack_beta: name: Create artifact for Jetpack Beta plugin runs-on: ubuntu-latest diff --git a/.github/workflows/post-build.yml b/.github/workflows/post-build.yml new file mode 100644 index 0000000000000..1b845d3bafee2 --- /dev/null +++ b/.github/workflows/post-build.yml @@ -0,0 +1,206 @@ +name: Post-Build +on: + workflow_run: + types: [ 'completed' ] + workflows: + - Build +concurrency: + # Cancel concurrent jobs on pull_request but not push, by including the run_id in the concurrency group for the latter. + group: post-build-${{ github.event.workflow_run.event == 'push' && github.run_id || 'pr' }}-${{ github.event.workflow_run.head_branch }} + cancel-in-progress: true + +env: + COMPOSER_ROOT_VERSION: "dev-trunk" + SUMMARY: Post-Build run [#${{ github.run_id }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for Build run [#${{ github.event.workflow_run.id }}](${{ github.event.workflow_run.html_url }}) + +# Note the job logic here is a bit unusual. That's because this workflow is triggered by `workflow_run`, and so is not shown on the PR by default. +# Instead we have to manually report back, including where we could normally just skip or let a failure be handled. +# - If the "Build" job failed, we need to set our status as failed too (build_failed). +# - If the find_artifact job fails for some reason, we need a step to explicitly report that back. +# - If no plugins are found, we need to explicitly report back a "skipped" status. +# - And the upgrade_test job both explicitly sets "in progress" at its start and updates at its end. + +jobs: + build_failed: + name: Handle build failure + runs-on: ubuntu-latest + if: github.event.workflow_run.conclusion != 'success' + timeout-minutes: 2 # 2022-08-26: Seems like it should be fast. + steps: + - uses: actions/checkout@v3 + - name: Create failed checks + uses: ./.github/actions/check-run + with: + name: Test plugin upgrades + sha: ${{ github.event.workflow_run.head_sha }} + conclusion: cancelled + title: Build failed + summary: | + ${{ env.SUMMARY }} + + Post-build run aborted because the build did not succeed. + + find_artifact: + name: Find artifact + runs-on: ubuntu-latest + if: github.event.workflow_run.conclusion == 'success' + timeout-minutes: 2 # 2022-08-26: Seems like it should be fast. + outputs: + zip_url: ${{ steps.run.outputs.zip_url }} + any_plugins: ${{ steps.run.outputs.any_plugins }} + steps: + - uses: actions/checkout@v3 + - name: Find artifact + id: run + env: + TOKEN: ${{ github.token }} + URL: ${{ github.event.workflow_run.artifacts_url }} + run: | + for (( i=1; i<=5; i++ )); do + [[ $i -gt 1 ]] && sleep 10 + echo "::group::Fetch list of artifacts (attempt $i/5)" + JSON="$(curl -v -L --get \ + --header "Authorization: token $TOKEN" \ + --url "$URL" + )" + echo "$JSON" + echo "::endgroup::" + ZIPURL="$(jq -r '.artifacts[] | select( .name == "jetpack-build" ) | .archive_download_url' <<<"$JSON")" + PLUGINS="$(jq -r '.artifacts[] | select( .name == "plugins" )' <<<"$JSON")" + if [[ -n "$ZIPURL" ]]; then + break + fi + done + [[ -z "$ZIPURL" ]] && { echo "::error::Failed to find artifact."; exit 1; } + echo "Zip URL: $ZIPURL" + echo "::set-output name=zip_url::${ZIPURL}" + if [[ -z "$PLUGINS" ]]; then + echo "Any plugins? No" + echo "::set-output name=any_plugins::false" + else + echo "Any plugins? Yes" + echo "::set-output name=any_plugins::true" + fi + + - name: Create failed checks + if: ${{ ! success() }} + uses: ./.github/actions/check-run + with: + name: Test plugin upgrades + sha: ${{ github.event.workflow_run.head_sha }} + conclusion: failure + title: Failed to find build artifact + summary: | + ${{ env.SUMMARY }} + + Post-build run aborted because the "Find artifact" step failed. + + no_plugins: + name: Handle no-plugins + runs-on: ubuntu-latest + needs: find_artifact + if: needs.find_artifact.outputs.any_plugins == 'false' + timeout-minutes: 2 # 2022-08-26: Seems like it should be fast. + steps: + - uses: actions/checkout@v3 + - name: Create skipped checks + uses: ./.github/actions/check-run + with: + name: Test plugin upgrades + sha: ${{ github.event.workflow_run.head_sha }} + conclusion: skipped + title: No plugins were built + summary: | + ${{ env.SUMMARY }} + + Post-build run skipped because no plugins were built. + + upgrade_test: + name: Test plugin upgrades + runs-on: ubuntu-latest + needs: find_artifact + if: needs.find_artifact.outputs.any_plugins == 'true' + timeout-minutes: 15 # 2022-08-26: Successful runs seem to take about 6 minutes, but give some extra time for the downloads. + services: + db: + image: mariadb:latest + env: + MARIADB_ROOT_PASSWORD: wordpress + ports: + - 3306:3306 + options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=5 + container: + image: ghcr.io/automattic/jetpack-wordpress-dev:latest + env: + WP_DOMAIN: localhost + WP_ADMIN_USER: wordpress + WP_ADMIN_EMAIL: wordpress@example.com + WP_ADMIN_PASSWORD: wordpress + WP_TITLE: Hello World + MYSQL_HOST: db:3306 + MYSQL_DATABASE: wordpress + MYSQL_USER: root + MYSQL_PASSWORD: wordpress + HOST_PORT: 80 + ports: + - 80:80 + steps: + - uses: actions/checkout@v3 + with: + path: monorepo + + - name: Notify check in progress + id: create_run + uses: ./monorepo/.github/actions/check-run + with: + name: Test plugin upgrades + sha: ${{ github.event.workflow_run.head_sha }} + status: in_progress + title: Test started... + summary: | + ${{ env.SUMMARY }} + + See run [#${{ github.run_id }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details. + + - name: Download build artifact + env: + TOKEN: ${{ github.token }} + ZIPURL: ${{ needs.find_artifact.outputs.zip_url }} + shell: bash + run: | + for (( i=1; i<=2; i++ )); do + [[ $i -gt 1 ]] && sleep 10 + echo "::group::Downloading artifact (attempt $i/2)" + curl -v -L --get \ + --header "Authorization: token $TOKEN" \ + --url "$ZIPURL" \ + --output "artifact.zip" + echo "::endgroup::" + if [[ -e "artifact.zip" ]] && zipinfo artifact.zip &>/dev/null; then + break + fi + done + [[ ! -e "artifact.zip" ]] && { echo "::error::Failed to download artifact."; exit 1; } + unzip artifact.zip + tar --xz -xvvf build.tar.xz + + - name: Setup WordPress + run: monorepo/.github/files/test-plugin-update/setup.sh + + - name: Prepare plugin zips + run: monorepo/.github/files/test-plugin-update/prepare-zips.sh + + - name: Test upgrades + run: monorepo/.github/files/test-plugin-update/test.sh + + - name: Notify final status + if: always() && steps.create_run.outputs.id + uses: ./monorepo/.github/actions/check-run + with: + id: ${{ steps.create_run.outputs.id }} + conclusion: ${{ job.status }} + title: ${{ job.status == 'success' && 'Tests passed' || job.status == 'cancelled' && 'Cancelled' || 'Tests failed' }} + summary: | + ${{ env.SUMMARY }} + + See run [#${{ github.run_id }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details. diff --git a/.github/workflows/slack-workflow-failed.yml b/.github/workflows/slack-workflow-failed.yml index f720b806c8067..c93f21544c702 100644 --- a/.github/workflows/slack-workflow-failed.yml +++ b/.github/workflows/slack-workflow-failed.yml @@ -7,6 +7,7 @@ on: - Build Docker - Tests - Gardening + - Post-Build - PR is up-to-date branches: [ 'trunk', '*/branch-*' ]