Skip to content

Commit

Permalink
Merge pull request #248 from Automattic/add-cron-control-runner
Browse files Browse the repository at this point in the history
feat: add cron-control-runner
  • Loading branch information
sjinks authored Jun 21, 2024
2 parents 22d296f + deaae25 commit 5b7928a
Show file tree
Hide file tree
Showing 11 changed files with 376 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/test-features.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ jobs:
feature:
- base
- cron
- cron-control-runner
- dev-tools
- elasticsearch
- entrypoints
Expand Down Expand Up @@ -65,6 +66,7 @@ jobs:
matrix:
test:
- feature: cron
- feature: cron-control-runner
- feature: elasticsearch
- feature: mailpit
- feature: memcached
Expand Down
35 changes: 35 additions & 0 deletions features/src/cron-control-runner/devcontainer-feature.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"id": "cron-control-runner",
"version": "1.0.0",
"description": "Installs Cron Control Runner into the Dev Environment",
"options": {
"enabled": {
"type": "boolean",
"default": true,
"description": "Whether to install Cron Control Runner"
},
"fpm-socket": {
"type": "string",
"default": "tcp://127.0.0.1:9000",
"description": "Address of the PHP-FPM socket. If not set, WP CLI will be used to run the cron jobs (slower)."
},
"wordpress-path": {
"type": "string",
"default": "/wp",
"description": "Path to the WordPress installation"
},
"wp-cli-path": {
"type": "string",
"default": "/usr/local/bin/wp",
"description": "Path to the WP CLI binary; required if `fpm-socket` is not set"
},
"install-runit-service": {
"type": "boolean",
"default": true,
"description": "Whether to install a runit service for the Cron Control Runner"
}
},
"installsAfter": [
"ghcr.io/automattic/vip-codespaces/wp-cli"
]
}
26 changes: 26 additions & 0 deletions features/src/cron-control-runner/entrypoint.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/bin/sh

set -e

# shellcheck disable=SC2269 # handled by envsubst
FPM_SOCKET="${FPM_SOCKET}"
# shellcheck disable=SC2269 # handled by envsubst
WORDPRESS_PATH="${WORDPRESS_PATH}"
# shellcheck disable=SC2269 # handled by envsubst
WP_CLI_PATH="${WP_CLI_PATH}"

OPTIONS=
if [ -n "${FPM_SOCKET}" ]; then
OPTIONS="${OPTIONS} -fpm-url ${FPM_SOCKET}"
fi

if [ -n "${WORDPRESS_PATH}" ]; then
OPTIONS="${OPTIONS} -wp-path ${WORDPRESS_PATH}"
fi

if [ -n "${WP_CLI_PATH}" ]; then
OPTIONS="${OPTIONS} -wp-cli-path ${WP_CLI_PATH}"
fi

# shellcheck disable=SC2086
exec /usr/local/bin/cron-control-runner ${OPTIONS} > /var/log/cron-control-runner.log 2>&1 &
81 changes: 81 additions & 0 deletions features/src/cron-control-runner/fpm-cron-runner.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

namespace WPCLI\FPM;
error_reporting( 0 );

if ( isset( $_POST['payload'] ) ) {
$payload_str = $_POST['payload'];
unset( $_POST['payload'] );
} elseif ( isset ( $_GET['payload'] ) ) {
$payload_str = $_GET['payload'];
unset( $_GET['payload'] );
} else {
header( 'Status: 400 Bad Request' );
header( 'Content-Type: text/plain' );
echo 'no payload given' . "\n";
exit( 1 );
}

try {
$payload = json_decode( $payload_str, null, 512, JSON_THROW_ON_ERROR | JSON_OBJECT_AS_ARRAY );
if ( ! is_array( $payload ) ) {
throw new \Exception( "not a json array" );
}
} catch ( \Exception $e ) {
header( 'Status: 400 Bad Request' );
header( 'Content-Type: text/plain' );
echo 'payload cannot be decoded as json: ' . $e->getMessage() . "\n";
exit( 1 );
}

array_unshift( $payload, '/usr/local/bin/wp' );

global $_SERVER;
$_SERVER['argv'] = $payload;
$_SERVER['SCRIPT_NAME'] = '/usr/local/bin/wp';
$_SERVER['SCRIPT_FILENAME'] = '/usr/local/bin/wp';
unset( $_SERVER['FCGI_ROLE'] );
unset( $_SERVER['GATEWAY_INTERFACE'] );
unset( $_SERVER['QUERY_STRING'] );
unset( $_SERVER['REQUEST_METHOD'] );

global $_ENV;
$_ENV['SCRIPT_NAME'] = '/usr/local/bin/wp';
$_ENV['SCRIPT_FILENAME'] = '/usr/local/bin/wp';
unset( $_ENV['FCGI_ROLE'] );
unset( $_ENV['GATEWAY_INTERFACE'] );
unset( $_ENV['QUERY_STRING'] );
unset( $_ENV['REQUEST_METHOD'] );

global $argv;
$argv = $payload;

$outfh = tmpfile();
$errfh = tmpfile();

register_shutdown_function( function () use ( $outfh, $errfh ) {
$result = [
'buf' => ob_get_contents(),
];
ob_end_clean();
fseek( $outfh, 0 );
$result['stdout'] = stream_get_contents( $outfh );
fclose( $outfh );
fseek( $errfh, 0 );
$result['stderr'] = stream_get_contents( $errfh );
fclose( $errfh );
header( 'Status: 200 OK' );
header( 'Content-Type: application/json' );
echo json_encode( $result );
} );

define( 'WP_CLI_ROOT', 'phar:///usr/local/bin/wp.phar/vendor/wp-cli/wp-cli' );
define( 'STDIN', fopen( '/dev/null', 'r' ) );
define( 'STDOUT', $outfh );
define( 'STDERR', $errfh );

ob_start();

require_once WP_CLI_ROOT . '/php/wp-cli.php';

exit( 0 );
116 changes: 116 additions & 0 deletions features/src/cron-control-runner/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#!/bin/sh

set -e

PATH=/usr/local/bin:/usr/local/sbin:/bin:/sbin:/usr/bin:/usr/sbin

if [ "$(id -u || true)" -ne 0 ]; then
echo 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
exit 1
fi

: "${_REMOTE_USER:?"_REMOTE_USER is required"}"
: "${ENABLED:=true}"
: "${FPM_SOCKET=/var/run/php-fpm.sock}"
: "${WORDPRESS_PATH:=/wp}"
: "${WP_CLI_PATH=/usr/local/bin/wp}"
: "${INSTALL_RUNIT_SERVICE:=true}"

if [ "${ENABLED}" = 'true' ]; then
echo '(*) Installing Cron Control Runner...'

# shellcheck source=/dev/null
. /etc/os-release

: "${ID:=}"
: "${ID_LIKE:=${ID}}"

case "${ID_LIKE}" in
"debian")
export DEBIAN_FRONTEND=noninteractive
PACKAGES=""
if ! hash curl >/dev/null 2>&1; then
PACKAGES="${PACKAGES} curl"
fi

if ! hash update-ca-certificates >/dev/null 2>&1; then
PACKAGES="${PACKAGES} ca-certificates"
fi

if ! hash envsubst >/dev/null 2>&1; then
PACKAGES="${PACKAGES} gettext"
fi

if [ -n "${PACKAGES}" ]; then
apt-get update
# shellcheck disable=SC2086
apt-get install -y --no-install-recommends ${PACKAGES}
fi

apt-get clean
rm -rf /var/lib/apt/lists/*
;;

"alpine")
PACKAGES=""
if ! hash curl >/dev/null 2>&1; then
PACKAGES="${PACKAGES} curl"
fi

if ! hash envsubst >/dev/null 2>&1; then
PACKAGES="${PACKAGES} gettext"
fi

if [ -n "${PACKAGES}" ]; then
# shellcheck disable=SC2086
apk add --no-cache ${PACKAGES}
fi
;;

*)
echo "(!) Unsupported distribution: ${ID}"
exit 1
;;
esac

ARCH="$(arch)"
LATEST=$(curl -w '%{url_effective}' -ILsS https://github.com/Automattic/cron-control-runner/releases/latest -o /dev/null | sed -e 's|^.*/||')
if [ "${ARCH}" = "arm64" ] || [ "${ARCH}" = "aarch64" ]; then
ARCH="arm64"
elif [ "${ARCH}" = "x86_64" ] || [ "${ARCH}" = "amd64" ]; then
ARCH="amd64"
else
echo "(!) Unsupported architecture: ${ARCH}"
exit 1
fi
curl -SL "https://github.com/Automattic/cron-control-runner/releases/download/${LATEST}/cron-control-runner-linux-${ARCH}" -o /usr/local/bin/cron-control-runner
chmod 0755 /usr/local/bin/cron-control-runner

if [ -z "${WP_CLI_PATH}" ] || [ ! -f "${WP_CLI_PATH}" ]; then
curl -SLo /usr/local/bin/wp.phar https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
install -m 0755 -o root -g root /usr/local/bin/wp.phar /usr/local/bin/wp
WP_CLI_PATH=/usr/local/bin/wp
else
ln -sf "${WP_CLI_PATH}" /usr/local/bin/wp.phar
fi

if [ -n "${FPM_SOCKET}" ]; then
install -D -m 0644 -o root -g root fpm-cron-runner.php /var/wpvip/fpm-cron-runner.php
fi

if [ -d /var/lib/entrypoint.d ]; then
export FPM_SOCKET WORDPRESS_PATH WP_CLI_PATH
# shellcheck disable=SC2016
envsubst '$FPM_SOCKET $WORDPRESS_PATH $WP_CLI_PATH' < entrypoint.tpl > /var/lib/entrypoint.d/50-cron-control-runner
chmod 0755 /var/lib/entrypoint.d/50-cron-control-runner
fi

if [ "${INSTALL_RUNIT_SERVICE}" = 'true' ] && [ -d /etc/sv ]; then
install -D -d -m 0755 -o root -g root /etc/service /etc/sv/cron-control-runner
export FPM_SOCKET WORDPRESS_PATH WP_CLI_PATH
# shellcheck disable=SC2016
envsubst '$FPM_SOCKET $WORDPRESS_PATH $WP_CLI_PATH $_REMOTE_USER' < service-run.tpl > /etc/sv/cron-control-runner/run
chmod 0755 /etc/sv/cron-control-runner/run
ln -sf /etc/sv/cron-control-runner /etc/service/cron-control-runner
fi
fi
35 changes: 35 additions & 0 deletions features/src/cron-control-runner/service-run.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/bin/sh

PATH=/bin:/sbin:/usr/bin:/usr/sbin

set -eu
exec 2>&1

# shellcheck disable=SC2269 # handled by envsubst
FPM_SOCKET="${FPM_SOCKET}"
# shellcheck disable=SC2269 # handled by envsubst
WORDPRESS_PATH="${WORDPRESS_PATH}"
# shellcheck disable=SC2269 # handled by envsubst
WP_CLI_PATH="${WP_CLI_PATH}"
# shellcheck disable=SC2269 # handled by envsubst
_REMOTE_USER="${_REMOTE_USER}"

OPTIONS=
if [ -n "${FPM_SOCKET}" ]; then
OPTIONS="${OPTIONS} -fpm-url ${FPM_SOCKET}"
fi

if [ -n "${WORDPRESS_PATH}" ]; then
OPTIONS="${OPTIONS} -wp-path ${WORDPRESS_PATH}"
fi

if [ -n "${WP_CLI_PATH}" ]; then
OPTIONS="${OPTIONS} -wp-cli-path ${WP_CLI_PATH}"
fi

rm -f /var/log/cron-control-runner.log
install -o "${_REMOTE_USER}" -g "${_REMOTE_USER}" -m 0644 /dev/null /var/log/cron-control-runner.log

# shellcheck disable=SC2086
exec chpst -u "${_REMOTE_USER}:${_REMOTE_USER}" \
/usr/local/bin/cron-control-runner ${OPTIONS} > /var/log/cron-control-runner.log 2>&1
8 changes: 8 additions & 0 deletions features/test/cron-control-runner/alpine-base.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/bash

# shellcheck source=/dev/null
source dev-container-features-test-lib

source ./checks.sh

reportResults
16 changes: 16 additions & 0 deletions features/test/cron-control-runner/checks.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/bin/bash

check "cron-control-runner is running" sudo sh -c 'sv status cron-control-runner | grep -E ^run:'
sudo sv stop cron-control-runner
check "cron-control-runner is stopped" sudo sh -c 'sv status cron-control-runner | grep -E ^down:'
rm -f /var/log/cron-control-runner.log
sudo sv start cron-control-runner
check "cron-control-runner is running" sudo sh -c 'sv status cron-control-runner | grep -E ^run:'

check "cron-control-runner logs events" sh -c 'grep -qF "cron runner has started all processes" /var/log/cron-control-runner.log'

# Microsoft's base images contain zsh. We don't want to run this check for MS images because we have no control over the installed services.
if test -d /etc/rc2.d && ! test -e /usr/bin/zsh; then
dir="$(ls -1 /etc/rc2.d)"
check "/etc/rc2.d is empty" test -z "${dir}"
fi
42 changes: 42 additions & 0 deletions features/test/cron-control-runner/scenarios.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"alpine-base": {
"image": "ghcr.io/automattic/vip-codespaces/alpine-base:latest",
"overrideCommand": false,
"features": {
"php": {},
"wp-cli": {},
"mariadb": {},
"wordpress": {},
"vip-go-mu-plugins": {},
"cron-control-runner": {}
},
"overrideFeatureInstallOrder": [
"./php",
"./wp-cli",
"./mariadb",
"./wordpress",
"./vip-go-mu-plugins",
"./cron-control-runner"
]
},
"ubuntu-base": {
"image": "ghcr.io/automattic/vip-codespaces/ubuntu-base:latest",
"overrideCommand": false,
"features": {
"php": {},
"wp-cli": {},
"mariadb": {},
"wordpress": {},
"vip-go-mu-plugins": {},
"cron-control-runner": {}
},
"overrideFeatureInstallOrder": [
"./php",
"./wp-cli",
"./mariadb",
"./wordpress",
"./vip-go-mu-plugins",
"./cron-control-runner"
]
}
}
14 changes: 14 additions & 0 deletions features/test/cron-control-runner/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/bin/bash

# shellcheck source=/dev/null
source dev-container-features-test-lib

check "/usr/local/bin/cron-control-runner exists and is executable" test -x /usr/local/bin/cron-control-runner
check "/usr/local/bin/wp exists and is executable" test -x /usr/local/bin/wp
check "/usr/local/bin/wp.phar exists and is a symlink" test -L /usr/local/bin/wp.phar
check "/var/wpvip/fpm-cron-runner.php exists" test -f /var/wpvip/fpm-cron-runner.php

if [[ -d /etc/sv ]]; then
check "/etc/sv/cron-control-runner/run exists and is executable" test -x /etc/sv/cron-control-runner/run
check "/etc/service/cron-control-runner exists and is a symlink" test -L /etc/service/cron-control-runner
fi
Loading

0 comments on commit 5b7928a

Please sign in to comment.