Issue #27 - CLI implemented. Added full test coverage to CLI classes.

This commit is contained in:
Daniel Thee Roperto
2016-09-16 17:26:48 +10:00
parent 2cfda63922
commit f569157368
16 changed files with 1429 additions and 15 deletions

133
classes/cli/clibase.php Normal file
View File

@@ -0,0 +1,133 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace auth_outage\cli;
use core\session\manager;
use InvalidArgumentException;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/clilib.php');
/**
* Outage CLI base class.
*
* @package auth_outage
* @author Daniel Thee Roperto <daniel.roperto@catalyst-au.net>
* @copyright Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class clibase {
/**
* @var array Options passed as parameters to the CLI.
*/
protected $options;
/**
* @var int The reference time to use when creating an outage.
*/
protected $time;
/**
* clibase constructor.
* @param array|null $options The parameters to use or null to read from the command line.
* @throws cliexception
*/
public function __construct(array $options = null) {
$this->becomeadmin();
if (is_null($options)) {
// Using Moodle CLI API to read the parameters.
list($options, $unrecognized) = cli_get_params($this->generateoptions(), $this->generateshortcuts());
if ($unrecognized) {
$unrecognized = implode("\n ", $unrecognized);
throw new cliexception(get_string('cliunknowoption', 'admin', $unrecognized));
}
} else {
// If not using Moodle CLI API to read parameters, ensure all keys exist.
$default = $this->generateoptions();
foreach ($options as $k => $v) {
if (!array_key_exists($k, $default)) {
throw new cliexception(get_string('cliunknowoption', 'admin', $k));
}
$default[$k] = $v;
}
$options = $default;
}
$this->options = $options;
$this->time = time();
}
/**
* Sets the reference time for creating outages.
* @param int $time Timestamp for the reference time.
*/
public function set_referencetime($time) {
if (!is_int($time) || ($time <= 0)) {
throw new InvalidArgumentException('$time must be a positive int.');
}
$this->time = $time;
}
/**
* Generates all options (parameters) available for the CLI command.
* @return array Options.
*/
public abstract function generateoptions();
/**
* Generate all short forms for the available options.
* @return array Short form options.
*/
public abstract function generateshortcuts();
/**
* Executes the CLI script.
*/
public abstract function execute();
/**
* Change session to admin user.
*/
protected function becomeadmin() {
global $DB;
$user = $DB->get_record('user', array('id' => 2));
unset($user->description);
unset($user->access);
unset($user->preference);
manager::init_empty_session();
manager::set_user($user);
}
/**
* Outputs a help message.
* @param string $cliname Name of CLI used in the language file.
*/
protected function showhelp($cliname) {
$options = $this->generateoptions();
$shorts = array_flip($this->generateshortcuts());
printf("%s\n\n", get_string('cli' . $cliname . 'help', 'auth_outage'));
foreach (array_keys($options) as $long) {
$text = get_string('cli' . $cliname . 'param' . $long, 'auth_outage');
$short = isset($shorts[$long]) ? ('-' . $shorts[$long] . ',') : '';
$long = '--' . $long;
printf(" %-4s %-20s %s\n", $short, $long, $text);
}
}
}

View File

@@ -0,0 +1,30 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace auth_outage\cli;
use Exception;
/**
* Exception executing CLI.
*
* @package auth_outage
* @author Daniel Thee Roperto <daniel.roperto@catalyst-au.net>
* @copyright Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cliexception extends Exception {
}

208
classes/cli/create.php Normal file
View File

@@ -0,0 +1,208 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace auth_outage\cli;
use auth_outage\models\outage;
use auth_outage\outagedb;
defined('MOODLE_INTERNAL') || die();
/**
* Outage CLI to create outage.
*
* @package auth_outage
* @author Daniel Thee Roperto <daniel.roperto@catalyst-au.net>
* @copyright Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class create extends clibase {
/**
* @var array Defaults to use if given option is null.
*/
private $defaults;
/**
* Generates all options (parameters) available for the CLI command.
* @return array Options.
*/
public function generateoptions() {
// Do not provide some defaults, if cloning an outage we need to know which parameters were provided.
$options = [
'help' => false,
'clone' => null,
'warn' => null,
'start' => null,
'duration' => null,
'title' => null,
'description' => null,
'onlyid' => false,
'block' => false,
];
return $options;
}
/**
* Generate all short forms for the available options.
* @return array Short form options.
*/
public function generateshortcuts() {
return [
'b' => 'block',
'c' => 'clone',
'd' => 'duration',
'e' => 'description',
'h' => 'help',
's' => 'start',
't' => 'title',
'w' => 'warn',
];
}
/**
* Sets the default values for options.
* @param array $defaults Defaults.
*/
public function set_defaults(array $defaults) {
$this->defaults = $defaults;
}
/**
* Executes the CLI.
*/
public function execute() {
// Help always overrides any other parameter.
if ($this->options['help']) {
$this->showhelp('create');
return;
}
// If not help mode, 'start' is required and cannot use default.
if (is_null($this->options['start'])) {
throw new cliexception(get_string('clierrormissingparamaters', 'auth_outage'));
}
// If cloning, set defaults to outage being cloned.
if (!is_null($this->options['clone'])) {
$this->clonedefaults();
}
// Merge provided parameters with defaults then create outage.
$options = $this->mergeoptions();
$id = $this->createoutage($options);
if ($options['block']) {
$block = new waitforit(['outageid' => $id]);
$block->execute();
}
}
/**
* Merges provided options with defaults, checking and converting types as needed.
* @return array Parameters to use.
* @throws cliexception
*/
private function mergeoptions() {
$options = $this->options;
// Merge with defaults.
if (!is_null($this->defaults)) {
foreach ($options as $k => $v) {
if (is_null($v) && array_key_exists($k, $this->defaults)) {
$options[$k] = $this->defaults[$k];
}
}
}
return $this->mergeoptions_checkparameters($options);
}
/**
* Creates an outages based on the provided options.
* @param array $options Options used to create the outage.
* @return int Id of the new outage.
*/
private function createoutage(array $options) {
// We need to become an admin to avoid permission problems.
$this->becomeadmin();
// Create the outage.
$start = $this->time + ($options['start'] * 60);
$outage = new outage([
'warntime' => $start - ($options['warn'] * 60),
'starttime' => $start,
'stoptime' => $start + ($options['duration'] * 60),
'title' => $options['title'],
'description' => $options['description'],
]);
$id = outagedb::save($outage);
// All done!
if ($options['onlyid']) {
printf("%d\n", $id);
} else {
printf("%s\n", get_string('clioutagecreated', 'auth_outage', ['id' => $id]));
}
return $id;
}
private function clonedefaults() {
$id = $this->options['clone'];
if (!is_number($id) || ($id <= 0)) {
throw new cliexception(get_string('clierrorinvalidvalue', 'auth_outage', ['param' => 'clone']));
}
$outage = outagedb::get_by_id((int)$id);
$this->set_defaults([
'warn' => (int)($outage->get_warning_duration() / 60),
'duration' => (int)($outage->get_duration() / 60),
'title' => $outage->title,
'description' => $outage->description,
]);
}
/**
* Check parameters converting their type as needed.
* @param array $options Input options.
* @return array Output options.
* @throws cliexception
*/
private function mergeoptions_checkparameters(array $options) {
// Check parameters that must be a non-negative int while converting their type to int.
foreach (['start', 'warn', 'duration'] as $param) {
if (!is_number($options[$param])) {
throw new cliexception(get_string('clierrorinvalidvalue', 'auth_outage', ['param' => $param]));
}
$options[$param] = (int)$options[$param];
if ($options[$param] < 0) {
throw new cliexception(get_string('clierrorinvalidvalue', 'auth_outage', ['param' => $param]));
}
}
// Check parameters that must be a non empty string.
foreach (['title', 'description'] as $param) {
if (!is_string($options[$param])) {
throw new cliexception(get_string('clierrorinvalidvalue', 'auth_outage', ['param' => $param]));
}
$options[$param] = trim($options[$param]);
if (strlen($options[$param]) == 0) {
throw new cliexception(get_string('clierrorinvalidvalue', 'auth_outage', ['param' => $param]));
}
}
return $options;
}
}

106
classes/cli/finish.php Normal file
View File

@@ -0,0 +1,106 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace auth_outage\cli;
use auth_outage\models\outage;
use auth_outage\outagedb;
defined('MOODLE_INTERNAL') || die();
/**
* Outage CLI to finish an outage.
*
* @package auth_outage
* @author Daniel Thee Roperto <daniel.roperto@catalyst-au.net>
* @copyright Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class finish extends clibase {
/**
* Generates all options (parameters) available for the CLI command.
* @return array Options.
*/
public function generateoptions() {
// Do not provide some defaults, if cloning an outage we need to know which parameters were provided.
$options = [
'help' => false,
'outageid' => null,
'active' => false,
];
return $options;
}
/**
* Generate all short forms for the available options.
* @return array Short form options.
*/
public function generateshortcuts() {
return [
'h' => 'help',
'id' => 'outageid',
'a' => 'active',
];
}
/**
* Executes the CLI.
*/
public function execute() {
// Help always overrides any other parameter.
if ($this->options['help']) {
$this->showhelp('finish');
return;
}
// Requires outageid or active but not both at the same time.
$byid = !is_null($this->options['outageid']);
$byactive = $this->options['active'];
if ($byid == $byactive) {
throw new cliexception(get_string('cliwaitforiterroridxoractive', 'auth_outage'));
}
$outage = $this->get_outage();
if (!$outage->is_ongoing()) {
throw new cliexception(get_string('clifinishnotongoing', 'auth_outage'));
}
outagedb::finish($outage->id, $this->time);
}
/**
* Gets the outage to finish.
* @return outage|null The outage to wait for.
* @throws cliexception
*/
private function get_outage() {
if ($this->options['active']) {
$outage = outagedb::get_active();
} else {
$id = $this->options['outageid'];
if (!is_number($id) || ($id <= 0)) {
throw new cliexception(get_string('clierrorinvalidvalue', 'auth_outage', ['param' => 'outageid']));
}
$outage = outagedb::get_by_id((int)$id);
}
if (is_null($outage)) {
throw new cliexception(get_string('clierroroutagenotfound', 'auth_outage'));
}
return $outage;
}
}

178
classes/cli/waitforit.php Normal file
View File

@@ -0,0 +1,178 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace auth_outage\cli;
use auth_outage\models\outage;
use auth_outage\outagedb;
defined('MOODLE_INTERNAL') || die();
/**
* Outage CLI to wait for an outage to start.
*
* @package auth_outage
* @author Daniel Thee Roperto <daniel.roperto@catalyst-au.net>
* @copyright Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class waitforit extends clibase {
/**
* Default value if --sleep no provided.
*/
const DEFAULT_SLEEP_SECONDS = 300;
/**
* @var callable Alternative callback for sleeping thread, must return the new reference timestamp.
*/
private $sleepcallback = null;
/**
* Generates all options (parameters) available for the CLI command.
* @return array Options.
*/
public function generateoptions() {
// Do not provide some defaults, if cloning an outage we need to know which parameters were provided.
$options = [
'help' => false,
'outageid' => null,
'active' => false,
'verbose' => false,
'sleep' => self::DEFAULT_SLEEP_SECONDS,
];
return $options;
}
/**
* Generate all short forms for the available options.
* @return array Short form options.
*/
public function generateshortcuts() {
return [
'h' => 'help',
'id' => 'outageid',
'a' => 'active',
'v' => 'verbose',
's' => 'sleep',
];
}
/**
* Sets a callback to be used instead of the sleep method.
* @param callable $callback Callback function.
*/
public function set_sleepcallback(callable $callback) {
$this->sleepcallback = $callback;
}
/**
* Executes the CLI.
*/
public function execute() {
// Help always overrides any other parameter.
if ($this->options['help']) {
$this->showhelp('waitforit');
return;
}
// Requires outageid or active but not both at the same time.
$byid = !is_null($this->options['outageid']);
$byactive = $this->options['active'];
if ($byid == $byactive) {
throw new cliexception(get_string('cliwaitforiterroridxoractive', 'auth_outage'));
}
$this->verbose('Verbose mode activated.');
$outage = $this->get_outage();
while ($sleep = $this->waitforoutagestart($outage)) {
if (is_null($this->sleepcallback)) {
$this->verbose('Sleeping for ' . $sleep . ' second(s).');
sleep($sleep);
$this->time = time();
} else {
$this->verbose('Calling callback to sleep ' . $sleep . ' second(s).');
$callback = $this->sleepcallback;
$this->time = $callback($sleep);
}
}
}
/**
* Shows a message if in verbose mode.
* @param string $message Message.
*/
private function verbose($message) {
if (!$this->options['verbose']) {
return;
}
$time = strftime('%F %T %Z');
printf("[%s] %s\n", $time, $message);
}
/**
* Gets the outage to wait for.
* @return outage|null The outage to wait for.
* @throws cliexception
*/
private function get_outage() {
if ($this->options['active']) {
$this->verbose('Querying database for active outage...');
$outage = outagedb::get_active();
} else {
$id = $this->options['outageid'];
if (!is_number($id) || ($id <= 0)) {
throw new cliexception(get_string('clierrorinvalidvalue', 'auth_outage', ['param' => 'outageid']));
}
$this->verbose('Querying database for outage #' . $id . '...');
$outage = outagedb::get_by_id((int)$id);
}
if (is_null($outage)) {
throw new cliexception(get_string('clierroroutagenotfound', 'auth_outage'));
}
$this->verbose('Found outage #' . $outage->id . ': ' . $outage->get_title());
return $outage;
}
private function waitforoutagestart(outage $outage) {
$this->verbose('Checking outage status...');
// Outage should not change while waiting to start.
if (outagedb::get_by_id($outage->id) != $outage) {
throw new cliexception(get_string('clierroroutagechanged', 'auth_outage'));
}
// Outage cannot have already ended.
if ($outage->has_ended($this->time)) {
throw new cliexception(get_string('clierroroutageended', 'auth_outage'));
}
// If outage has started, do not wait.
if ($outage->is_ongoing($this->time)) {
printf("%s\n", get_string('cliwaitforitoutagestarted', 'auth_outage'));
return 0;
}
// Outage nas not started yet.
$countdown = $outage->starttime - $this->time;
printf("%s\n", get_string(
'cliwaitforitoutagestartingin',
'auth_outage',
['countdown' => format_time($countdown)]
));
return min($countdown, $this->options['sleep']);
}
}