Skip to content

Commit

Permalink
Add health check paths to scheduled update endpoint (#36990)
Browse files Browse the repository at this point in the history
* Add health paths to scheduled updates

* changelog

* Fix: typo

* Minor refactoring

* Add unit tests

* Set paths as required false

* Add option deletion

* Add limit to 5 paths

* Add unit tests fix

* Add items type check

* Fix: variable typo

* Move health paths code to a separate class

* Move health checks unit tests to separate class

* Fix phan static errors

* Add new unit tests

* Add esc_url_raw

* Minor change

* Add warning suppress
  • Loading branch information
zaerl authored Apr 24, 2024
1 parent d7b76e9 commit da8467e
Show file tree
Hide file tree
Showing 11 changed files with 556 additions and 86 deletions.
2 changes: 0 additions & 2 deletions projects/packages/scheduled-updates/.phan/baseline.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
// PhanTypeArraySuspiciousNullable : 2 occurrences
// PhanUndeclaredClassMethod : 2 occurrences
// PhanCompatibleAccessMethodOnTraitDefinition : 1 occurrence
// PhanNoopNew : 1 occurrence
// PhanTypeMismatchArgumentProbablyReal : 1 occurrence
// PhanUndeclaredFunction : 1 occurrence

Expand All @@ -25,7 +24,6 @@
'src/pluggable.php' => ['PhanTypeArraySuspiciousNullable'],
'src/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-update-schedules.php' => ['PhanPluginMixedKeyNoKey', 'PhanUndeclaredFunction'],
'tests/php/class-scheduled-updates-test.php' => ['PhanCompatibleAccessMethodOnTraitDefinition', 'PhanUndeclaredProperty'],
'tests/php/class-wpcom-rest-api-v2-endpoint-update-schedules-test.php' => ['PhanNoopNew'],
],
// 'directory_suppressions' => ['src/directory_name' => ['PhanIssueName1', 'PhanIssueName2']] can be manually added if needed.
// (directory_suppressions will currently be ignored by subsequent calls to --save-baseline, but may be preserved in future Phan releases)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: added

Add health paths to scheduled updates.
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
<?php
/**
* Scheduled Updates Health Paths class
*
* @package automattic/scheduled-updates
*/

namespace Automattic\Jetpack;

use WP_Error;

/**
* Scheduled_Updates_Health_Paths class
*
* This class provides static methods to get/save health paths for scheduled updates.
*/
class Scheduled_Updates_Health_Paths {

/**
* The name of the WordPress option where the health check paths are stored.
*/
const OPTION_NAME = 'jetpack_scheduled_update_health_check_paths';

/**
* Get the health check paths for a scheduled update.
*
* @param string $schedule_id Request ID.
* @return array List of health check paths.
*/
public static function get( $schedule_id ) {
$option = get_option( self::OPTION_NAME, array() );

return $option[ $schedule_id ] ?? array();
}

/**
* Update the health check paths for a scheduled update.
*
* @param string $schedule_id Request ID.
* @param array $paths List of paths to save.
* @return bool
*/
public static function update( $schedule_id, $paths ) {
$option = get_option( self::OPTION_NAME, array() );
$parsed_paths = array();

foreach ( $paths as $path ) {
$parsed = self::validate( $path );

if ( is_string( $parsed ) ) {
$parsed_paths[] = $parsed;
}
}

$parsed_paths = array_values( array_unique( $parsed_paths ) );

if ( count( $parsed_paths ) ) {
$option[ $schedule_id ] = $parsed_paths;
}

return update_option( self::OPTION_NAME, $option );
}

/**
* Clear the health check paths for a scheduled update.
*
* @param string|null $schedule_id Request ID.
* @return bool
*/
public static function clear( $schedule_id ) {
$option = get_option( self::OPTION_NAME, array() );

if ( isset( $option[ $schedule_id ] ) ) {
unset( $option[ $schedule_id ] );
}

if ( count( $option ) ) {
return update_option( self::OPTION_NAME, $option );
} else {
return delete_option( self::OPTION_NAME );
}
}

/**
* Validate a path.
*
* @param string $path An health path.
* @return string|WP_Error
*/
public static function validate( $path ) {
if ( ! is_string( $path ) ) {
return new WP_Error( 'rest_invalid_path', __( 'The path must be a string.', 'jetpack-scheduled-updates' ) );
}

$site_url = wp_parse_url( get_site_url() );
$path = trim( $path );

if (
! str_starts_with( $path, $site_url['host'] ) &&
! str_starts_with( $path, $site_url['scheme'] . '://' . $site_url['host'] )
) {
// The user sent 'test/test.php' instead of '/test/test.php' and not
// 'http://example.com/test/test.php' or 'example.com/test/test.php'.
$path = '/' . ltrim( $path, '/\\' );
}

$path = esc_url_raw( trim( $path ) );
$parsed = wp_parse_url( $path );

if ( false === $parsed ) {
return new WP_Error( 'rest_invalid_path', __( 'The path must be a valid URL.', 'jetpack-scheduled-updates' ) );
}

if ( array_key_exists( 'host', $parsed ) ) {
if ( $site_url['host'] !== $parsed['host'] ) {
return new WP_Error( 'rest_invalid_path', __( 'The URL is not from the current site.', 'jetpack-scheduled-updates' ) );
}

if ( array_key_exists( 'scheme', $parsed ) && $site_url['scheme'] !== $parsed['scheme'] ) {
return new WP_Error( 'rest_invalid_path', __( 'The URL scheme must match the current site.', 'jetpack-scheduled-updates' ) );
}
}

if ( ! array_key_exists( 'path', $parsed ) ) {
$parsed['path'] = '';
} else {
$parsed['path'] = trim( $parsed['path'] );
}

$ret = '/' . ltrim( $parsed['path'], '/\\' );

if ( array_key_exists( 'query', $parsed ) ) {
$ret .= '?' . trim( $parsed['query'] );
}

return $ret;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,9 @@ public static function run_scheduled_update( ...$plugins ) {
'2',
array( 'method' => 'POST' ),
array(
'plugins' => $plugins_to_update,
'schedule_id' => $schedule_id,
'health_check_paths' => Scheduled_Updates_Health_Paths::get( $schedule_id ),
'plugins' => $plugins_to_update,
'schedule_id' => $schedule_id,
),
'wpcom'
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*/

use Automattic\Jetpack\Scheduled_Updates;
use Automattic\Jetpack\Scheduled_Updates_Health_Paths;
use Automattic\Jetpack\Scheduled_Updates_Logs;

/**
Expand Down Expand Up @@ -266,6 +267,7 @@ public function create_item( $request ) {
}

$id = Scheduled_Updates::generate_schedule_id( $plugins );
Scheduled_Updates_Health_Paths::update( $id, $schedule['health_check_paths'] ?? array() );

/**
* Fires when a scheduled update is created.
Expand Down Expand Up @@ -502,6 +504,8 @@ public function delete_item( $request ) {
*/
do_action( 'jetpack_scheduled_update_deleted', $request['schedule_id'], $event, $request );

Scheduled_Updates_Health_Paths::clear( $request['schedule_id'] );

return rest_ensure_response( true );
}

Expand Down Expand Up @@ -545,7 +549,8 @@ public function prepare_item_for_response( $item, $request ) {
);
}

$item = array_merge( $item, $status );
$item = array_merge( $item, $status );
$item['health_check_paths'] = Scheduled_Updates_Health_Paths::get( $item['schedule_id'] );

$item = $this->add_additional_fields_to_object( $item, $request );

Expand Down Expand Up @@ -637,6 +642,26 @@ public function validate_themes_param( $themes ) {
return true;
}

/**
* Checks that the "paths" parameter is a valid array of paths.
*
* @param array $paths List of paths to check.
* @return bool|WP_Error
*/
public function validate_paths_param( $paths ) {
foreach ( $paths as $path ) {
$valid = Scheduled_Updates_Health_Paths::validate( $path );

if ( is_wp_error( $valid ) ) {
$valid->add_data( array( 'status' => 400 ) );

return $valid;
}
}

return true;
}

/**
* Validates the submitted schedule.
*
Expand Down Expand Up @@ -745,6 +770,10 @@ public function get_item_schema() {
'type' => 'string',
'enum' => array( 'success', 'failure-and-rollback', 'failure-and-rollback-fail' ),
),
'health_check_paths' => array(
'description' => 'Paths to check for site health.',
'type' => 'array',
),
),
);

Expand Down Expand Up @@ -777,17 +806,28 @@ public function get_object_params() {
'type' => 'object',
'required' => true,
'properties' => array(
'interval' => array(
'interval' => array(
'description' => 'Interval for the schedule.',
'type' => 'string',
'enum' => array( 'daily', 'weekly' ),
'required' => true,
),
'timestamp' => array(
'timestamp' => array(
'description' => 'Unix timestamp (UTC) for when to first run the schedule.',
'type' => 'integer',
'required' => true,
),
'health_check_paths' => array(
'description' => 'List of paths to check for site health after the update.',
'type' => 'array',
'maxItems' => 5,
'items' => array(
'type' => 'string',
),
'required' => false,
'default' => array(),
'validate_callback' => array( $this, 'validate_paths_param' ),
),
),
),
);
Expand Down
Loading

0 comments on commit da8467e

Please sign in to comment.