diff --git a/.github/files/test-plugin-update/mu-plugin.php b/.github/files/test-plugin-update/mu-plugin.php new file mode 100644 index 0000000000000..0aeb54cf2a82a --- /dev/null +++ b/.github/files/test-plugin-update/mu-plugin.php @@ -0,0 +1,59 @@ + array(), + 'no_update' => array(), + ); + } + if ( ! isset( $value->response[ $plugin ] ) ) { + if ( isset( $value->no_update[ $plugin ] ) ) { + $value->response[ $plugin ] = $value->no_update[ $plugin ]; + unset( $value->no_update[ $plugin ] ); + } else { + $value->response[ $plugin ] = (object) array( + 'plugin' => dirname( $plugin ), + 'slug' => dirname( $plugin ), + ); + } + } + $value->response[ $plugin ]->new_version = '1000000.0.0'; + $value->response[ $plugin ]->package = $url; + } + return $value; + } +); diff --git a/.github/files/test-plugin-update/prepare-zips.sh b/.github/files/test-plugin-update/prepare-zips.sh new file mode 100755 index 0000000000000..e3e38b5b43c96 --- /dev/null +++ b/.github/files/test-plugin-update/prepare-zips.sh @@ -0,0 +1,65 @@ +#!/bin/bash + +set -eo pipefail + +BETAJSON="$(curl -L --fail --url "https://betadownload.jetpack.me/plugins.json")" +jq -e '.' <<<"$BETAJSON" &>/dev/null + +mkdir work +mkdir zips +while IFS=$'\t' read -r SRC MIRROR SLUG; do + echo "::group::Creating $SLUG-dev.zip" + mv "build/$MIRROR" "work/$SLUG" + touch "work/$SLUG/ci-flag.txt" + (cd work && zip -r "../zips/${SLUG}-dev.zip" "$SLUG") + rm -rf "work/$SLUG" + echo "::endgroup::" + + echo "::group::Fetching $SLUG-master.zip..." + BETASLUG="$(jq -r '.extra["beta-plugin-slug"] // .extra["wp-plugin-slug"] // ""' "monorepo/$SRC/composer.json")" + if [[ -z "$BETASLUG" ]]; then + echo "No beta-plugin-slug or wp-plugin-slug in composer.json, skipping" + else + URL="$(jq -r --arg slug "$BETASLUG" '.[$slug].manifest_url // ""' <<<"$BETAJSON")" + if [[ -z "$URL" ]]; then + echo "Beta slug $BETASLUG is not in plugins.json, skipping" + else + JSON="$(curl -L --fail --url "$URL")" + if jq -e '.' <<<"$JSON" &>/dev/null; then + URL="$(jq -r '.master.download_url // ""' <<<"$JSON")" + if [[ -z "$URL" ]]; then + echo "Plugin has no master build." + else + curl -L --fail --url "$URL" --output "work/tmp.zip" 2>&1 + (cd work && unzip -q tmp.zip) + mv "work/$BETASLUG-dev" "work/$SLUG" + (cd work && zip -qr "../zips/${SLUG}-master.zip" "$SLUG") + rm -rf "work/$SLUG" "work/tmp.zip" + fi + else + echo "::error::Unexpected response from betadownload.jetpack.me for $SLUG" + echo "$JSON" + exit 1 + fi + fi + fi + echo "::endgroup::" + + echo "::group::Fetching $SLUG-stable.zip..." + JSON="$(curl "https://api.wordpress.org/plugins/info/1.0/$SLUG.json")" + if jq -e --arg slug "$SLUG" '.slug == $slug' <<<"$JSON" &>/dev/null; then + URL="$(jq -r '.download_link // ""' <<<"$JSON")" + if [[ -z "$URL" ]]; then + echo "Plugin has no stable release." + else + curl -L --fail --url "$URL" --output "zips/$SLUG-stable.zip" 2>&1 + fi + elif jq -e '.error == "Plugin not found."' <<<"$JSON" &>/dev/null; then + echo "Plugin is not published." + else + echo "::error::Unexpected response from WordPress.org API for $SLUG" + echo "$JSON" + exit 1 + fi + echo "::endgroup::" +done < build/plugins.tsv diff --git a/.github/files/test-plugin-update/setup.sh b/.github/files/test-plugin-update/setup.sh new file mode 100755 index 0000000000000..7d844aaf392c2 --- /dev/null +++ b/.github/files/test-plugin-update/setup.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +set -eo pipefail + +echo "::group::Setup database" +cat < ~/.my.cnf +[client] +host=${MYSQL_HOST%:*} +port=${MYSQL_HOST#*:} +user=$MYSQL_USER +password=$MYSQL_PASSWORD +EOF +chmod 0600 ~/.my.cnf +mysql -e "set global wait_timeout = 3600;" +mysql -e "drop database if exists wordpress;" +mysql -e "create database wordpress;" +echo "::endgroup::" + +echo "::group::Setup WordPress" +mkdir -p /var/log/php/ /var/scripts/ +cd /var/www/html +sed -i 's/apachectl -D FOREGROUND/apachectl start/' /usr/local/bin/run +echo '#!/bin/bash' > /var/scripts/run-extras.sh +/usr/local/bin/run +echo "::endgroup::" + +echo "::group::Install WordPress" +wp --allow-root core install --url="$WP_DOMAIN" --title="$WP_TITLE" --admin_user="$WP_ADMIN_USER" --admin_password="$WP_ADMIN_PASSWORD" --admin_email="$WP_ADMIN_EMAIL" --skip-email +rm -f index.html +mkdir -p wp-content/mu-plugins +cp "$GITHUB_WORKSPACE/monorepo/.github/files/test-plugin-update/mu-plugin.php" wp-content/mu-plugins/hack.php +echo "::endgroup::" diff --git a/.github/files/test-plugin-update/test.sh b/.github/files/test-plugin-update/test.sh new file mode 100755 index 0000000000000..48ec24e8b7801 --- /dev/null +++ b/.github/files/test-plugin-update/test.sh @@ -0,0 +1,64 @@ +#!/bin/bash + +set -eo pipefail + +ZIPDIR="$GITHUB_WORKSPACE/zips" +EXIT=0 + +SLUGS=() +cd "$ZIPDIR" +for ZIP in *-dev.zip; do + SLUGS+=( "${ZIP%-dev.zip}" ) +done + +cd /var/www/html +for SLUG in "${SLUGS[@]}"; do + for FROM in stable master dev; do + for HOW in web cli; do + [[ -e "$ZIPDIR/$SLUG-$FROM.zip" ]] || continue + + echo "::group::Installing $SLUG $FROM" + wp --allow-root plugin install --activate "$ZIPDIR/$SLUG-$FROM.zip" + rm -f "/var/www/html/wp-content/plugins/$SLUG/ci-flag.txt" + # TODO: Connect Jetpack, since most upgrades will happen while connected. + echo "::endgroup::" + + echo "::group::Upgrading $SLUG via $HOW" + P="$(wp --allow-root plugin path "$SLUG" | sed 's!^/var/www/html/wp-content/plugins/!!')" + wp --allow-root --quiet option set fake_plugin_update_plugin "$P" + wp --allow-root --quiet option set fake_plugin_update_url "$ZIPDIR/$SLUG-dev.zip" + : > /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 + echo "::error::CLI upgrade of $SLUG from $FROM exited with a non-zero status" + EXIT=1 + fi + else + # Everything needs to be owned by www-data for the web upgrade to proceed. + chown -R www-data:www-data /var/www/html + curl -v --get --url 'http://localhost/wp-admin/update.php?action=upgrade-plugin&_wpnonce=bogus' --data "plugin=$P" --output "$GITHUB_WORKSPACE/out.txt" 2>&1 + cat "$GITHUB_WORKSPACE/out.txt" + fi + echo '== Debug log ==' + cat /var/www/html/wp-content/debug.log + echo "::endgroup::" + 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 + 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 + fi + + echo "::group::Uninstalling $SLUG" + wp --allow-root plugin deactivate "$SLUG" + wp --allow-root plugin uninstall "$SLUG" + wp --allow-root --quiet option delete fake_plugin_update_plugin + wp --allow-root --quiet option delete fake_plugin_update_url + echo "::endgroup::" + done + done +done + +exit $EXIT diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 01e8229a09df0..3c02ff98d5d58 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,6 +22,8 @@ jobs: env: # Hard-code a specific directory to avoid paths in vendor/composer/installed.json changing every build. BUILD_BASE: /tmp/jetpack-build + outputs: + any_plugins: ${{ steps.plugins.outputs.any }} steps: - uses: actions/checkout@v2 @@ -60,6 +62,28 @@ jobs: pnpx jetpack build -v --no-pnpm-install --for-mirrors="$BUILD_BASE" "${PROJECTS[@]}" fi + - name: Filter mirror list for release branch + if: contains( github.ref, '/branch-' ) + env: + BUILD_BASE: ${{ github.workspace }}/build + run: .github/files/filter-mirrors-for-release-branch.sh + + - name: Determine plugins to publish + id: plugins + run: | + jq -r 'if .extra["mirror-repo"] and ( .extra["beta-plugin-slug"] // .extra["wp-plugin-slug"] ) then [ ( input_filename | sub( "/composer\\.json$"; "" ) ), .extra["mirror-repo"], .extra["beta-plugin-slug"] // .extra["wp-plugin-slug"] ] else empty end | @tsv' projects/plugins/*/composer.json | while IFS=$'\t' read -r SRC MIRROR SLUG; do + if [[ -e "$BUILD_BASE/$MIRROR" ]] && grep -q --fixed-strings --line-regexp "$MIRROR" "$BUILD_BASE/mirrors.txt"; then + printf '%s\t%s\t%s\n' "$SRC" "$MIRROR" "$SLUG" + fi + done > "$BUILD_BASE/plugins.tsv" + if [[ -s "$BUILD_BASE/plugins.tsv" ]]; then + cat "$BUILD_BASE/plugins.tsv" + echo "::set-output name=any::true" + else + echo "No plugins were built" + echo "::set-output name=any::false" + fi + # GitHub's artifact stuff doesn't preserve permissions or file case. Sigh. # This is the official workaround: https://github.com/actions/upload-artifact#maintaining-file-permissions-and-case-sensitive-files # It'll also make it faster to upload and download though, so maybe it's a win anyway. @@ -74,11 +98,61 @@ jobs: # Only need to retain for a day since the beta builder slurps it up to distribute. retention-days: 1 + 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@v2 + with: + path: monorepo + + - name: Download build artifact + uses: actions/download-artifact@v2 + 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 needs: build - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'Automattic/jetpack' + if: ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'Automattic/jetpack' ) && needs.build.outputs.any_plugins == 'true' timeout-minutes: 10 # 2021-06-24: Successful runs should take just a few seconds now. But sometimes the upload is slow. steps: - uses: actions/checkout@v2 @@ -92,22 +166,6 @@ jobs: - name: Extract build archive run: tar --xz -xvvf build.tar.xz - - name: Filter mirror list for release branch - if: contains( github.ref, '/branch-' ) - working-directory: monorepo - env: - BUILD_BASE: ${{ github.workspace }}/build - run: .github/files/filter-mirrors-for-release-branch.sh - - - name: Determine plugins to publish - run: | - jq -r 'if .extra["mirror-repo"] and ( .extra["beta-plugin-slug"] // .extra["wp-plugin-slug"] ) then [ ( input_filename | sub( "/composer\\.json$"; "" ) ), .extra["mirror-repo"], .extra["beta-plugin-slug"] // .extra["wp-plugin-slug"] ] else empty end | @tsv' monorepo/projects/plugins/*/composer.json > plugins.tsv - cat plugins.tsv - if [[ ! -s plugins.tsv ]]; then - echo "No plugins were detected?!" - exit 1 - fi - - name: Prepare plugin zips id: prepare run: | @@ -160,7 +218,7 @@ jobs: PLUGIN_DATA=$( jq -c --arg slug "$SLUG" --arg ver "$CURRENT_VERSION" '.[ $slug ] = { version: $ver }' <<<"$PLUGIN_DATA" ) echo "::endgroup::" - done < plugins.tsv + done < build/plugins.tsv if [[ "$PLUGIN_DATA" == "{}" ]]; then echo "No plugins were built" fi @@ -212,14 +270,6 @@ jobs: run: tar --xz -xvvf build.tar.xz timeout-minutes: 1 # 2021-01-18: Successful runs seem to take a few seconds - - name: Filter mirror list for release branch - if: contains( github.ref, '/branch-' ) - working-directory: monorepo - env: - BUILD_BASE: ${{ github.workspace }}/build - run: .github/files/filter-mirrors-for-release-branch.sh - timeout-minutes: 1 # 2021-01-29: Guessing a successful run should only take a few seconds - - name: Wait for prior instances of the workflow to finish uses: softprops/turnstyle@v1 env: