From 50521bdecb78fe2cfbb33bfe8cba41b718fa590f Mon Sep 17 00:00:00 2001 From: Markus Frosch Date: Fri, 5 Jun 2020 16:35:47 +0200 Subject: [PATCH] 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" --- .../Director/Import/ImportSourceRestApi.php | 111 +++++++++++++++--- library/Director/Test/BaseTestCase.php | 18 +++ .../Import/ImportSourceRestApiTest.php | 29 +++++ 3 files changed, 141 insertions(+), 17 deletions(-) create mode 100644 test/php/library/Director/Import/ImportSourceRestApiTest.php diff --git a/library/Director/Import/ImportSourceRestApi.php b/library/Director/Import/ImportSourceRestApi.php index f12fe2f8..9b55dbcc 100644 --- a/library/Director/Import/ImportSourceRestApi.php +++ b/library/Director/Import/ImportSourceRestApi.php @@ -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('~(?$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"', + ]) )); } diff --git a/library/Director/Test/BaseTestCase.php b/library/Director/Test/BaseTestCase.php index 2c2645d7..611805be 100644 --- a/library/Director/Test/BaseTestCase.php +++ b/library/Director/Test/BaseTestCase.php @@ -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); + } } diff --git a/test/php/library/Director/Import/ImportSourceRestApiTest.php b/test/php/library/Director/Import/ImportSourceRestApiTest.php new file mode 100644 index 00000000..7bbce2d7 --- /dev/null +++ b/test/php/library/Director/Import/ImportSourceRestApiTest.php @@ -0,0 +1,29 @@ + 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]); + } + } +}