ImportSourceRestApi: Add header and deeper extract_property

Allows a user to add additional headers, e.g. by setting a specific `Accept` or any authentication header.

Also `extract_property` now has a logic for deeper keys like "result.objects", "key.deeper_key.very_deep"
This commit is contained in:
Markus Frosch 2020-06-05 16:35:47 +02:00 committed by Thomas Gelf
parent d5c94d8757
commit 50521bdecb
3 changed files with 141 additions and 17 deletions

View File

@ -2,6 +2,7 @@
namespace Icinga\Module\Director\Import;
use Icinga\Exception\InvalidPropertyException;
use Icinga\Module\Director\Hook\ImportSourceHook;
use Icinga\Module\Director\RestApi\RestApiClient;
use Icinga\Module\Director\Web\Form\QuickForm;
@ -16,18 +17,12 @@ class ImportSourceRestApi extends ImportSourceHook
public function fetchData()
{
$result = $this->getRestApi()->get($this->getUrl());
if ($property = $this->getSetting('extract_property')) {
if (\property_exists($result, $property)) {
$result = $result->$property;
} else {
throw new \RuntimeException(sprintf(
'Result has no "%s" property. Available keys: %s',
$property,
\implode(', ', \array_keys((array) $result))
));
}
}
$result = $this->getRestApi()->get(
$this->getUrl(),
null,
$this->buildHeaders()
);
$result = $this->extractProperty($result);
return (array) $result;
}
@ -48,6 +43,66 @@ class ImportSourceRestApi extends ImportSourceHook
return $columns;
}
/**
* Extract result from a property specified
*
* A simple key, like "objects", will take the final result from key objects
*
* If you have a deeper key like "objects" under the key "results", specify this as "results.objects".
*
* When a key of the JSON object contains a literal ".", this can be escaped as
*
* @param $result
*
* @return mixed
*/
protected function extractProperty($result)
{
$property = $this->getSetting('extract_property');
if (! $property) {
return $result;
}
$parts = preg_split('~(?<!\\\\)\.~', $property);
// iterate over parts of the attribute path
$data = $result;
foreach ($parts as $part) {
// un-escape any dots
$part = preg_replace('~\\\\.~', '.', $part);
if (property_exists($data, $part)) {
$data = $data->$part;
} else {
throw new \RuntimeException(sprintf(
'Result has no "%s" property. Available keys: %s',
$part,
implode(', ', array_keys((array) $data))
));
}
}
return $data;
}
protected function buildHeaders()
{
$headers = [];
$text = $this->getSetting('headers', '');
foreach (preg_split("~\r?\n~", $text) as $header) {
$header = trim($header);
$parts = preg_split('~\s*:\s*~', $header, 2);
if (count($parts) < 2) {
throw new InvalidPropertyException('Could not parse header: %s', $header);
}
$headers[$parts[0]] = $parts[1];
}
return $headers;
}
/**
* @param QuickForm $form
* @throws \Zend_Form_Exception
@ -59,6 +114,7 @@ class ImportSourceRestApi extends ImportSourceHook
static::addUrl($form);
static::addResultProperty($form);
static::addAuthentication($form);
static::addHeader($form);
static::addProxy($form);
}
@ -83,6 +139,23 @@ class ImportSourceRestApi extends ImportSourceHook
]);
}
/**
* @param QuickForm $form
* @throws \Zend_Form_Exception
*/
protected static function addHeader(QuickForm $form)
{
$form->addElement('textarea', 'headers', [
'label' => $form->translate('HTTP Header'),
'description' => join(' ', [
$form->translate('Additional headers for the HTTP request.'),
$form->translate('Specify headers in text format "Header: Value", each header on a new line.'),
]),
'class' => 'preformatted',
'rows' => 4,
]);
}
/**
* @param QuickForm $form
* @throws \Zend_Form_Exception
@ -131,11 +204,15 @@ class ImportSourceRestApi extends ImportSourceHook
protected static function addResultProperty(QuickForm $form)
{
$form->addElement('text', 'extract_property', array(
'label' => 'Extract property',
'description' => $form->translate(
'Often the expected result is provided in a property like "objects".'
. ' Please specify this if required'
),
'label' => 'Extract property',
'description' => join("\n", [
$form->translate('Often the expected result is provided in a property like "objects".'
. ' Please specify this if required.'),
$form->translate('Also deeper keys can be specific by a dot-notation:'),
'"result.objects", "key.deeper_key.very_deep"',
$form->translate('Literal dots in a key name can be written in the escape notation:'),
'"key\.with\.dots"',
])
));
}

View File

@ -106,4 +106,22 @@ abstract class BaseTestCase extends PHPUnit_Framework_TestCase
return self::$app;
}
/**
* Call a protected function for a class during testing
*
* @param $obj
* @param $name
* @param array $args
*
* @return mixed
* @throws \ReflectionException
*/
public static function callMethod($obj, $name, array $args)
{
$class = new \ReflectionClass($obj);
$method = $class->getMethod($name);
$method->setAccessible(true);
return $method->invokeArgs($obj, $args);
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace Tests\Icinga\Module\Director\Import;
use Icinga\Module\Director\Import\ImportSourceRestApi;
use Icinga\Module\Director\Test\BaseTestCase;
class ImportSourceRestApiTest extends BaseTestCase
{
public function testExtractProperty()
{
$examples = [
'' => json_decode('[{"name":"blau"}]'),
'objects' => json_decode('{"objects":[{"name":"blau"}]}'),
'results.objects.all' => json_decode('{"results":{"objects":{"all":[{"name":"blau"}]}}}'),
'results\.objects.all' => json_decode('{"results.objects":{"all":[{"name":"blau"}]}}'),
];
$source = new ImportSourceRestApi();
foreach ($examples as $property => $data) {
$source->setSettings(['extract_property' => $property]);
$result = static::callMethod($source, 'extractProperty', [$data]);
$this->assertCount(1, $result);
$this->assertArrayHasKey('name', (array) $result[0]);
}
}
}