Skip to content

Commit

Permalink
Resolves Prod ready curl step #167
Browse files Browse the repository at this point in the history
  • Loading branch information
marcghaly committed Jul 11, 2022
1 parent ff7a595 commit 4119718
Show file tree
Hide file tree
Showing 3 changed files with 390 additions and 2 deletions.
172 changes: 170 additions & 2 deletions classes/local/step/connector_curl.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@

namespace tool_dataflows\local\step;
use tool_dataflows\local\execution\engine_step;
use Symfony\Component\Yaml\Yaml;

/**
* CURL connector step type
*
* @package tool_dataflows
* @author Kevin Pham <[email protected]>
* @author Ghaly Marc-Alexandre <[email protected]>
* @copyright Catalyst IT, 2022
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
Expand All @@ -30,15 +32,181 @@ class connector_curl extends connector_step {
/** @var bool whether or not this step type (potentially) contains a side effect or not */
protected $hassideeffect = true;

/** @var int Time after which curl request is aborted */
protected $timeout = 60;

/**
* Return the definition of the fields available in this form.
*
* @return array
*/
protected static function form_define_fields(): array {
return [
'curl' => ['type' => PARAM_URL],
'destination' => ['type' => PARAM_PATH],
'headers' => ['type' => PARAM_RAW],
'method' => ['type' => PARAM_TEXT],
'rawpostdata' => ['type' => PARAM_RAW],
'sideeffects' => ['type' => PARAM_RAW],
'timeout' => ['type' => PARAM_INT],
];
}

/**
* Allows each step type to determine a list of optional/required form
* inputs for their configuration
*
* It's recommended you prefix the additional config related fields to avoid
* conflicts with any existing fields.
*
* @param \MoodleQuickForm &$mform
*/
public function form_add_custom_inputs(\MoodleQuickForm &$mform) {
$jsonexample = [
'header-key' => 'header value',
'another-key' => '1234'
];
$yamlexample = '<pre>header-key: header value<br>another-key: 1234</pre>';
$ex['json'] = \html_writer::nonempty_tag('pre', json_encode($jsonexample, JSON_PRETTY_PRINT));
$ex['yaml'] = $yamlexample;
$mform->addElement('text', 'config_curl', get_string('connector_curl:curl', 'tool_dataflows'),
['cols' => 50, 'rows' => 7]);
$mform->addElement('text', 'config_destination', get_string('connector_curl:destination', 'tool_dataflows'));
$mform->addHelpButton('config_destination', 'connector_curl:destination', 'tool_dataflows');
$mform->addElement('textarea', 'config_headers', get_string('connector_curl:headers', 'tool_dataflows'),
['cols' => 50, 'rows' => 7]);
$mform->addElement('static', 'headers_help', '', get_string('connector_curl:field_headers_help', 'tool_dataflows', $ex));
$mform->addElement('select', 'config_method', get_string('connector_curl:method', 'tool_dataflows'),
['get' => 'GET', 'post' => 'POST', 'put' => 'PUT']);
$mform->addElement('textarea' , 'config_rawpostdata', get_string('connector_curl:rawpostdata', 'tool_dataflows'),
['cols' => 50, 'rows' => 7]);
$mform->addElement('checkbox', 'config_sideeffects', get_string('connector_curl:sideeffects', 'tool_dataflows'),
get_string('yes'));
$mform->hideIf('config_rawpostdata', 'config_method', 'eq', 'get');
$mform->disabledIf('config_rawpostdata', 'config_method', 'eq', 'get');
$mform->addElement('text', 'config_timeout', get_string('connector_curl:timeout', 'tool_dataflows'));
$mform->addHelpButton('config_timeout', 'connector_curl:timeout', 'tool_dataflows');
}

/**
* Validate the configuration settings.
*
* @param object $config
* @return true|\lang_string[] true if valid, an array of errors otherwise
*/
public function validate_config($config) {
$errors = [];
if (empty($config->curl)) {
$errors['config_curl'] = get_string('config_field_missing', 'tool_dataflows', 'curl', true);
}
if (empty($config->rawpostdata) && ($config->method === 'put' || $config->method === 'post')) {
$errors['config_rawpostdata'] = get_string('config_field_missing', 'tool_dataflows', 'rawpostdata', true);
}
return empty($errors) ? true : $errors;
}

/**
* Executes the step
*
* TODO: This will perform a curl call
* Performs a curl call according to given parameters.
*
* @return bool Returns true if successful, false otherwise.
*/
public function execute(): bool {
// TODO: Implement.
// Get variables.
$config = $this->enginestep->stepdef->config;
$isdryrun = $this->enginestep->engine->isdryrun;
$method = $config->method;
$dbgcommand = 'curl -X ' . strtoupper($method) . ' ' . $config->curl;
$result = null;

if (!empty($config->timeout)) {
$this->timeout = (int) $config->timeout;
$dbgcommand .= ' --max-time ' . $config->timeout;
}

$options = ['CURLOPT_TIMEOUT' => $this->timeout];

if ($method === 'post') {
$options['CURLOPT_POST'] = 1;
}

if ($method === 'put') {
$options['CURLOPT_PUT'] = 1;
}

$curl = new \curl();

$this->hassideeffect = !empty($config->sideeffects);

// For put and post methods automatically overrides/has side effects.
if ($config->method != 'get') {
$this->hassideeffect = true;
}

// Provided a header is specified add header to request.
if (!empty($config->headers)) {
$headers = $config->headers;
if (!is_array($headers)) {
$headers = json_decode($headers, true);
}
if (is_null($headers)) {
$headers = Yaml::parse($config->headers);
}
$curl->setHeader($headers);
$dbgcommand .= ' -H \'' . $config->headers . '\'';
}

// Sets post data.
if (!empty($config->rawpostdata)) {
$options['CURLOPT_POSTFIELDS'] = $config->rawpostdata;
$dbgcommand .= ' -d \'' . $config->rawpostdata . '\'';
}

// Download response to file provided destination is set.
if (!empty($config->destination) && !$isdryrun) {
if ($config->destination[0] === '/') {
$config->destination = ltrim($config->destination, '/');
}
$config->destination = $this->enginestep->engine->resolve_path($config->destination);
$file = fopen($config->destination, 'w');
$options['CURLOPT_FILE'] = $file;
}

// Perform call.
if (!$isdryrun) {
$result = $curl->$method($config->curl, [], $options);
}

if (!empty($file)) {
fclose($file);
}

$info = $curl->get_info();
// Stores response to be reusable by other steps.
// TODO : Once set_var api is refactored add response.
$response = $curl->getResponse();
$httpcode = $info['http_code'] ?? null;
$connecttime = $info['connect_time'] ?? null;
$totaltime = $info['total_time'] ?? null;
$sizeupload = $info['size_upload'] ?? null;
$destination = !empty($config->destination) ? $config->destination : null;
$errno = $curl->get_errno();

if (($httpcode >= 400 || empty($response) || $errno == 28) && !$isdryrun) {
throw new \moodle_exception($httpcode . ':' . $result);
}

if (!$isdryrun) {
$this->enginestep->stepdef->set_var('result', $result);
$this->enginestep->stepdef->set_var('httpcode', $httpcode);
$this->enginestep->stepdef->set_var('connecttime', $connecttime);
$this->enginestep->stepdef->set_var('totaltime', $totaltime);
$this->enginestep->stepdef->set_var('sizeupload', $sizeupload);
$this->enginestep->stepdef->set_var('destination', $destination);
} else {
$this->enginestep->stepdef->set_var('dbgcommand', $dbgcommand);
}
return true;
}
}
12 changes: 12 additions & 0 deletions lang/en/tool_dataflows.php
Original file line number Diff line number Diff line change
Expand Up @@ -282,3 +282,15 @@
$string['connector_s3:missing_s3_source_or_target'] = 'At least one source or target path must reference a location in S3.';
$string['connector_s3:source_is_a_directory'] = 'The source path is a directory but a file path is expected.';

// Connector cURL.
$string['connector_curl:curl'] = 'Client URL';
$string['connector_curl:destination'] = 'File / Response destination';
$string['connector_curl:destination_help'] = 'Should this field be left blank,
response will be stored in step definition to be reused subsequently by other steps.';
$string['connector_curl:headers'] = 'Headers';
$string['connector_curl:method'] = 'HTTP request method';
$string['connector_curl:rawpostdata'] = 'Raw post data';
$string['connector_curl:sideeffects'] = 'Does this request have side effects?';
$string['connector_curl:timeout'] = 'Timeout';
$string['connector_curl:timeout_help'] = 'Time after which curl request will abort, default is 60 seconds.';
$string['connector_curl:field_headers_help'] = 'Headers should be in the following JSON format: {$a->json} You can also use the following YAML format: {$a->yaml}';
Loading

0 comments on commit 4119718

Please sign in to comment.