-
Notifications
You must be signed in to change notification settings - Fork 4
Introduce command scheduler and runner #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
``` $ phpenv global 5.3 rbenv: version `5.3' not installed ```
The command validation needs work. Passing a flag right after |
Adopts `\WP_CLI\Configurator\extract_assoc()`
Much better care of 5b60aee, thanks to |
Some sort of kill switch is probably a good idea. Not sure how exactly to implement that for a command that's already running. |
If WP-CLI will support it, we should allow some sort of STDIN to be specified, as some commands use it. This might be something to pursue after the basics land, alongside file handling. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks really cool! Left some feedback here and there.
|
||
Yes, using either the `WP_CLI_CRON_CONTROL_OFFLOAD_COMMAND_BLACKLIST` constant or the `wp_cli_cron_control_offload_command_blacklist` filter. If defined, the constant takes precedence and the filter is ignored. | ||
|
||
Regardless of whether the constant or filter is used, either should provide an array of top-level commands to permit: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are the whitelist / blacklist examples reversed here? Blacklist should be the list of commands to block, and whitelist should be commands to permit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤦♂️
includes/class-cli.php
Outdated
/** | ||
* Offload WP-CLI commands to cron | ||
*/ | ||
class CLI extends WP_CLI_Command { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a thought - should this class be called Command
instead of CLI
? The former seems more descriptive, especially since it extends WP_CLI_Command
.
if ( defined( 'WP_CLI_CRON_CONTROL_OFFLOAD_COMMAND_BLACKLIST' ) && is_array( \WP_CLI_CRON_CONTROL_OFFLOAD_COMMAND_BLACKLIST ) ) { | ||
return \WP_CLI_CRON_CONTROL_OFFLOAD_COMMAND_BLACKLIST; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder here - there are probably going to be WP CLI commands that the hosting platform blacklists...then potentially additional commands that the developer of the site wishes to blacklist.
Should we support both? For example, on VIP we could blacklist a few (and those can never be reversed via filter), then clients could add additional commands to blacklist via a filter, and that's only additive.
$this->assertTrue( is_wp_error( WP_CLI_Cron_Control_Offload\validate_command( 'wp cli info' ) ) ); | ||
$this->assertTrue( is_wp_error( WP_CLI_Cron_Control_Offload\validate_command( 'cli info' ) ) ); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we add some tests for improperly constructed commands? I'm thinking things like:
- Missing
wp
(post list
), - Trying to sneak additional commands on (
wp post list && cat /etc/passwd
) (even though this might not actually do anything) - Poorly constructed arguments, like
wp do-thing --foo=bar--fubar=yes
- Backtick operators
- Trying to override
--url
, perhaps with a different site that's not part of this network - Anything else that isn't a standard
wp command subcommand [positional_args] [--assoc_args]
I'd like to be as defensive and paranoid as possible here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call. Pipes and ampersands should also be dropped.
The leading wp
is optional, mostly for convenience. WP_CLI::runcommand()
doesn't want it, so the parse_command()
method strips it. This has the added benefit of normalizing the command to ensure cron's duplicate scheduling kicks in when appropriate.
There's a limit to how much parsing we can do without recreating large portions of WP-CLI (or loading it, in the case of custom commands), but we clearly have room for improvement. That we trust the initial users for this (requires a Go sandbox) helps somewhat.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Much beyond this is difficult, as I haven't found a way yet to invoke WP-CLI's command validation outside of where WP-CLI uses it.
…s) to blacklists and whitelists set in constants
… system, amongst other "fun."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some suggestions on how to improve the tests.
$this->assertTrue( is_wp_error( WP_CLI_Cron_Control_Offload\validate_command( 'post list 2> /tmp/nope' ) ) ); | ||
$this->assertTrue( is_wp_error( WP_CLI_Cron_Control_Offload\validate_command( 'post list 1>&2 /tmp/nope' ) ) ); | ||
$this->assertTrue( is_wp_error( WP_CLI_Cron_Control_Offload\validate_command( 'post list 2>&1 /tmp/nope' ) ) ); | ||
$this->assertTrue( is_wp_error( WP_CLI_Cron_Control_Offload\validate_command( 'post list &> /tmp/nope' ) ) ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For tests like this when we are running through a list of inputs/outputs, it's better to use PHPUnit's data providers: https://phpunit.de/manual/current/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.data-providers -- each input gets executed as a separate test and we can label them so we know which ones fail.
Same thing applies to test_blocked_event_scheduling
and a bunch of other ones above where the main action and assertion between each test is the same.
Finally, it's nice to use this pattern for tests: http://wiki.c2.com/?ArrangeActAssert -- i.e. instead of squeezing everything into one line, opt for readability instead:
$input = 'post list & date';
$output = WP_CLI_Cron_Control_Offload\validate_command( $input );
$this->assertWpError( $output );
* Test each blocked bash operator | ||
*/ | ||
function test_for_invalid_bash_operators() { | ||
$this->assertTrue( is_wp_error( WP_CLI_Cron_Control_Offload\validate_command( 'post list & date' ) ) ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a assertWPError
method which might be nicer than doing is_wp_error
+ assertTrue
(https://make.wordpress.org/core/handbook/testing/automated-testing/writing-phpunit-tests/#assertions)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Noted a few things that should probably be changed. I think the biggest thing is probably the parse/implode stuff (I think longer term maintenance is going to be challenging).
The other thought I had: what's the benefit of using this vs. just executing the command via vipd + host action?
includes/class-command.php
Outdated
*/ | ||
public function create( $args, $assoc_args ) { | ||
$command = WP_CLI\Utils\get_flag_value( $assoc_args, 'command', '' ); | ||
$command = validate_command( $command ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is redundant, since schedule_cli_command
runs it as well.
* @param string $filter_tag String for list filter. | ||
* @return array | ||
*/ | ||
function _filter_list_allow_only_additions( $constant, $filter_tag ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should definitely add some tests for this.
* @return array | ||
*/ | ||
function get_command_blacklist() { | ||
if ( defined( 'WP_CLI_CRON_CONTROL_OFFLOAD_COMMAND_BLACKLIST' ) && is_array( \WP_CLI_CRON_CONTROL_OFFLOAD_COMMAND_BLACKLIST ) ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we be blocking cli-cron-offload
/ CLI_NAMESPACE
by default to avoid weird infinite loops?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes indeed. I thought I'd included that, but I can't find it. Adding to the is_command_allowed()
function.
includes/functions.php
Outdated
|
||
// If there's a whitelist, default to it. | ||
if ( ! empty( get_command_whitelist() ) ) { | ||
add_filter( 'wp_cli_cron_control_offload_is_command_allowed', __NAMESPACE__ . '\command_is_whitelisted', 9, 2 ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does this need to be a filter? Why can't we evaulate the in_array
and pass the value into apply_filters
below?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I used this approach to allow for no whitelist, if someone wanted to be dangerous. That said, they could hook __return_true
late, so there's no need for it.
*/ | ||
function validate_command( $command ) { | ||
$command = trim( $command ); | ||
$command = parse_command( $command ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the parse / implode feel a bit dangerous and maybe even unnecessary. Looking through, there are some things we want to do:
- find out which command is being called (so we can check against the whitelist/blacklist).
- prevent bad positionals (
&
, '|', etc.)
I think we could do both those things without the full complexity of breaking out the command and then rebuilding it.
The other option would be to change how the command is executed so that instead of accepting a freeform string, we ask for a explicit structure (i.e. the command needs to be formatted as {command} {subcommand} {flags}
; anything else fails validation).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most of the logic comes from WP-CLI, and arose from wanting to accomodate WP_CLI::runcommand()
. It doesn't expect the leading wp
(trivial), but also passes along many of the global associative arguments, so there's a bit of need to clean the command. WP-CLI also doesn't expose its validation methods for reuse.
I like your idea to change how the command is accepted. That'll remove a lot of the magic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm leaning towards breaking command
, subcommand
and flags
into their own associative arguments, removing all of the magic from parsing the command.
This came originally from the idea of eliminating future-dated hosted actions, as well as being able to run commands on the CLI containers rather than our sandboxes (sitemaps, for example). It was also somewhat inspired by a client already doing essentially this--they've built a UI of preset commands, and can run them via cron as needed. While I see this as initially meant for those with a Go sandbox, I could see a UI and reviewed commands permitted via the whitelist. |
If someone wants to run without a whitelist, hook `__return_true`.
Is this likely to get worked on at all? We've had a few examples recently where we've wanted to offload a CLI command to the CLI containers, and @david-binda came up with his own PHP-based workaround recently but it'd be good to something more permanent. |
@philipjohn We're going to stick with @david-binda's workaround for now until we get proper We may revisit this if that ends up being pushed out in terms of timelines. |
Introduces a way to create cron events to run whitelisted WP-CLI commands.
Does not yet support any commands that involve files, such as imports or exports.
Command output is directed to
error_log()
.