diff --git a/.github/actions/check-run/action.yml b/.github/actions/check-run/action.yml new file mode 100644 index 0000000000000..b4538647b1695 --- /dev/null +++ b/.github/actions/check-run/action.yml @@ -0,0 +1,70 @@ +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: "App access token. See https://docs.github.com/en/developers/apps/building-github-apps/authenticating-with-github-apps#authenticating-as-a-github-app for how to get one." +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: Bearer $TOKEN" --url "$URL" --data "$DATA")" + echo "$JSON" + echo "id=$(jq -r .id <<<"$JSON")" >> "$GITHUB_OUTPUT" diff --git a/.github/files/test-plugin-update/prepare-zips.sh b/.github/files/test-plugin-update/prepare-zips.sh index a6a77539b6acb..25db0f067af2d 100755 --- a/.github/files/test-plugin-update/prepare-zips.sh +++ b/.github/files/test-plugin-update/prepare-zips.sh @@ -39,6 +39,7 @@ while IFS=$'\t' read -r SRC MIRROR SLUG; do else echo "::error::Unexpected response from betadownload.jetpack.me for $SLUG" echo "$JSON" + echo "info=❌ Unexpected response from betadownload.jetpack.me for $SLUG" >> "$GITHUB_OUTPUT" exit 1 fi fi @@ -59,7 +60,10 @@ while IFS=$'\t' read -r SRC MIRROR SLUG; do else echo "::error::Unexpected response from WordPress.org API for $SLUG" echo "$JSON" + echo "info=❌ Unexpected response from WordPress.org API for $SLUG" >> "$GITHUB_OUTPUT" exit 1 fi echo "::endgroup::" done < build/plugins.tsv + +echo 'info=' >> "$GITHUB_OUTPUT" diff --git a/.github/files/test-plugin-update/test.sh b/.github/files/test-plugin-update/test.sh index 0b87180ff9fbb..fa019e354974a 100755 --- a/.github/files/test-plugin-update/test.sh +++ b/.github/files/test-plugin-update/test.sh @@ -2,6 +2,8 @@ set -eo pipefail +source "$GITHUB_WORKSPACE/monorepo/.github/files/gh-funcs.sh" + ZIPDIR="$GITHUB_WORKSPACE/zips" EXIT=0 @@ -11,20 +13,38 @@ for ZIP in *-dev.zip; do SLUGS+=( "${ZIP%-dev.zip}" ) done +FINISHED=false +OUTPUT=() + +function onexit { + if ! "$FINISHED"; then + OUTPUT+=( "💣 The testing script exited unexpectedly." ) + fi + gh_set_output info "$( printf "%s\n" "${OUTPUT[@]}" )" +} +trap "onexit" EXIT + +function failed { + ERRMSG="$1" + OUTPUT+=( "❌ $ERRMSG" ) + FAILED=1 + EXIT=1 +} + cd /var/www/html for SLUG in "${SLUGS[@]}"; do for FROM in stable trunk dev; do for HOW in web cli; do [[ -e "$ZIPDIR/$SLUG-$FROM.zip" ]] || continue + FAILED= printf '\n\e[1mTest upgrade of %s from %s via %s\e[0m\n' "$SLUG" "$FROM" "$HOW" ERRMSG= echo "::group::Installing $SLUG $FROM" : > /var/www/html/wp-content/debug.log if ! wp --allow-root plugin install --activate "$ZIPDIR/$SLUG-$FROM.zip"; then - ERRMSG="Plugin install failed for $SLUG $FROM!" - EXIT=1 + failed "Plugin install failed for $SLUG $FROM!" fi echo '== Debug log ==' cat /var/www/html/wp-content/debug.log @@ -46,8 +66,7 @@ for SLUG in "${SLUGS[@]}"; do : > /var/www/html/wp-content/debug.log if [[ "$HOW" == 'cli' ]]; then if ! wp --allow-root plugin upgrade "$SLUG" 2>&1 | tee "$GITHUB_WORKSPACE/out.txt"; then - ERRMSG="CLI upgrade of $SLUG from $FROM exited with a non-zero status" - EXIT=1 + failed "CLI upgrade of $SLUG from $FROM exited with a non-zero status" fi else # Everything needs to be owned by www-data for the web upgrade to proceed. @@ -63,19 +82,18 @@ for SLUG in "${SLUGS[@]}"; do fi ERR="$(grep -i 'Fatal error' /var/www/html/wp-content/debug.log || true)" if [[ -n "$ERR" ]]; then - echo "::error::Mid-upgrade fatal detected for $SLUG $HOW update from $FROM!%0A$ERR" - EXIT=1 + failed "Mid-upgrade fatal detected for $SLUG $HOW update from $FROM!%0A$ERR" + echo "::error::$ERRMSG" elif [[ ! -e "/var/www/html/wp-content/plugins/$SLUG/ci-flag.txt" ]]; then - echo "::error::Plugin $SLUG ($HOW update from $FROM) does not seem to have been updated?" - EXIT=1 + failed "Plugin $SLUG ($HOW update from $FROM) does not seem to have been updated?" + echo "::error::$ERRMSG" fi ERRMSG= echo "::group::Deactivating $SLUG" : > /var/www/html/wp-content/debug.log if ! wp --allow-root plugin deactivate "$SLUG"; then - ERRMSG="Plugin deactivate failed after $SLUG $HOW update from $FROM!" - EXIT=1 + failed "Plugin deactivate failed after $SLUG $HOW update from $FROM!" fi echo '== Debug log ==' cat /var/www/html/wp-content/debug.log @@ -88,8 +106,7 @@ for SLUG in "${SLUGS[@]}"; do echo "::group::Uninstalling $SLUG" : > /var/www/html/wp-content/debug.log if ! wp --allow-root plugin uninstall "$SLUG"; then - ERRMSG="Plugin uninstall failed after $SLUG $HOW update from $FROM!" - EXIT=1 + failed "Plugin uninstall failed after $SLUG $HOW update from $FROM!" rm -rf "/var/www/html/wp-content/plugins/$SLUG" fi echo '== Debug log ==' @@ -101,8 +118,13 @@ for SLUG in "${SLUGS[@]}"; do wp --allow-root --quiet option delete fake_plugin_update_plugin wp --allow-root --quiet option delete fake_plugin_update_url + + if [[ -z "$FAILED" ]]; then + OUTPUT+=( "✅ Upgrade of $SLUG from $FROM via $HOW succeeded!" ) + fi done done done +FINISHED=true exit $EXIT diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 627dc46943575..121760e2d33e3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -132,56 +132,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..f1c9dba18e2e2 --- /dev/null +++ b/.github/workflows/post-build.yml @@ -0,0 +1,281 @@ +name: Post-Build +run-name: Post-Build on ${{ github.event.workflow_run.head_branch }} +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. +# +# If you're wanting to add a new check, you'd want to do the following: +# - Add a step in the `setup` workflow to create your check, and a corresponding output for later steps to have the ID. +# - Add a step in the `build_failed` workflow to set your run to cancelled. +# - Add a job to run whatever tests you need to run, with steps similar to the `upgrade_test` workflow's "Get token", "Notify check in progress", and "Notify final status". +# - Add a step in the `no_plugins` workflow to set your run to skipped if your job only runs when there are plugins built. + +jobs: + setup: + name: Setup + runs-on: ubuntu-latest + timeout-minutes: 2 # 2022-12-20: Seems like it should be fast. + outputs: + upgrade_check: ${{ steps.upgrade_check.outputs.id }} + steps: + - uses: actions/checkout@v3 + + - name: Get token + id: get_token + uses: getsentry/action-github-app-token@v2.0.0 + with: + app_id: ${{ secrets.JP_LAUNCH_CONTROL_ID }} + private_key: ${{ secrets.JP_LAUNCH_CONTROL_KEY }} + + - name: 'Create "Test plugin upgrades" check' + id: upgrade_check + uses: ./.github/actions/check-run + with: + name: Test plugin upgrades + sha: ${{ github.event.workflow_run.head_sha }} + status: queued + title: Test queued... + summary: | + ${{ env.SUMMARY }} + token: ${{ steps.get_token.outputs.token }} + + build_failed: + name: Handle build failure + runs-on: ubuntu-latest + needs: setup + 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: Get token + id: get_token + uses: getsentry/action-github-app-token@v2.0.0 + with: + app_id: ${{ secrets.JP_LAUNCH_CONTROL_ID }} + private_key: ${{ secrets.JP_LAUNCH_CONTROL_KEY }} + + - name: 'Mark "Test plugin upgrades" cancelled' + uses: ./.github/actions/check-run + with: + id: ${{ needs.setup.outputs.upgrade_check }} + conclusion: cancelled + title: Build failed + summary: | + ${{ env.SUMMARY }} + + Post-build run aborted because the build did not succeed. + token: ${{ steps.get_token.outputs.token }} + + find_artifact: + name: Find artifact + runs-on: ubuntu-latest + needs: setup + 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 "zip_url=${ZIPURL}" >> "$GITHUB_OUTPUT" + if [[ -z "$PLUGINS" ]]; then + echo "Any plugins? No" + echo "any_plugins=false" >> "$GITHUB_OUTPUT" + else + echo "Any plugins? Yes" + echo "any_plugins=true" >> "$GITHUB_OUTPUT" + fi + + - name: Get token + id: get_token + if: ${{ ! success() }} + uses: getsentry/action-github-app-token@v2.0.0 + with: + app_id: ${{ secrets.JP_LAUNCH_CONTROL_ID }} + private_key: ${{ secrets.JP_LAUNCH_CONTROL_KEY }} + - name: 'Mark "Test plugin upgrades" failed' + if: ${{ ! success() }} + uses: ./.github/actions/check-run + with: + id: ${{ needs.setup.outputs.upgrade_check }} + conclusion: failure + title: Failed to find build artifact + summary: | + ${{ env.SUMMARY }} + + Post-build run aborted because the "Find artifact" step failed. + token: ${{ steps.get_token.outputs.token }} + + no_plugins: + name: Handle no-plugins + runs-on: ubuntu-latest + needs: [ setup, 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: Get token + id: get_token + uses: getsentry/action-github-app-token@v2.0.0 + with: + app_id: ${{ secrets.JP_LAUNCH_CONTROL_ID }} + private_key: ${{ secrets.JP_LAUNCH_CONTROL_KEY }} + + - name: 'Mark "Test plugin upgrades" skipped' + uses: ./.github/actions/check-run + with: + id: ${{ needs.setup.outputs.upgrade_check }} + conclusion: skipped + title: No plugins were built + summary: | + ${{ env.SUMMARY }} + + Post-build run skipped because no plugins were built. + token: ${{ steps.get_token.outputs.token }} + + upgrade_test: + name: Test plugin upgrades + runs-on: ubuntu-latest + needs: [ setup, 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: Get token + id: get_token + uses: getsentry/action-github-app-token@v2.0.0 + env: + # Work around a weird node 16/openssl 3 issue in the docker env + OPENSSL_CONF: '/dev/null' + with: + app_id: ${{ secrets.JP_LAUNCH_CONTROL_ID }} + private_key: ${{ secrets.JP_LAUNCH_CONTROL_KEY }} + + - name: Notify check in progress + uses: ./monorepo/.github/actions/check-run + with: + id: ${{ needs.setup.outputs.upgrade_check }} + 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. + token: ${{ steps.get_token.outputs.token }} + + - 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 + id: zips + run: monorepo/.github/files/test-plugin-update/prepare-zips.sh + + - name: Test upgrades + id: tests + run: monorepo/.github/files/test-plugin-update/test.sh + + - name: Notify final status + if: always() + uses: ./monorepo/.github/actions/check-run + with: + id: ${{ needs.setup.outputs.upgrade_check }} + conclusion: ${{ job.status }} + title: ${{ job.status == 'success' && 'Tests passed' || job.status == 'cancelled' && 'Cancelled' || 'Tests failed' }} + summary: | + ${{ env.SUMMARY }} + + ${{ steps.zips.outputs.info }}${{ steps.tests.outputs.info }} + + See run [#${{ github.run_id }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details. + token: ${{ steps.get_token.outputs.token }} diff --git a/.github/workflows/slack-workflow-failed.yml b/.github/workflows/slack-workflow-failed.yml index a3d245ff25ff7..f3d03a2696f7f 100644 --- a/.github/workflows/slack-workflow-failed.yml +++ b/.github/workflows/slack-workflow-failed.yml @@ -8,6 +8,7 @@ on: - Build Docker - Tests - Gardening + - Post-Build - PR is up-to-date branches: [ 'trunk', 'prerelease', '*/branch-*' ]