mirror of
https://github.com/Icinga/icingaweb2-module-director.git
synced 2026-05-28 04:35:53 -04:00
parent
8c04de0a14
commit
ba3ce7cd17
3 changed files with 583 additions and 0 deletions
271
library/Director/Import/ImportSourceRestApi.php
Normal file
271
library/Director/Import/ImportSourceRestApi.php
Normal file
|
|
@ -0,0 +1,271 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Module\Director\Import;
|
||||
|
||||
use Icinga\Module\Director\Hook\ImportSourceHook;
|
||||
use Icinga\Module\Director\RestApi\RestApiClient;
|
||||
use Icinga\Module\Director\Web\Form\QuickForm;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class ImportSourceRestApi extends ImportSourceHook
|
||||
{
|
||||
public function getName()
|
||||
{
|
||||
return 'REST API';
|
||||
}
|
||||
|
||||
public function fetchData()
|
||||
{
|
||||
$url = $this->getSetting('url');
|
||||
$parts = \parse_url($url);
|
||||
if (isset($parts['host'])) {
|
||||
$host = $parts['host'];
|
||||
} else {
|
||||
throw new InvalidArgumentException("URL '$url' has no host");
|
||||
}
|
||||
|
||||
$api = new RestApiClient(
|
||||
$host,
|
||||
$this->getSetting('username'),
|
||||
$this->getSetting('password')
|
||||
);
|
||||
if (isset($parts['path'])) {
|
||||
$path = $parts['path'];
|
||||
} else {
|
||||
$path = '/';
|
||||
}
|
||||
if (isset($parts['query'])) {
|
||||
$url = "$path?" . $parts['query'];
|
||||
} else {
|
||||
$url = $path;
|
||||
}
|
||||
|
||||
$api->setScheme($this->getSetting('scheme'));
|
||||
if (isset($parts['port'])) {
|
||||
$api->setPort($parts['port']);
|
||||
}
|
||||
|
||||
$result = $api->get($url);
|
||||
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))
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return (array) $result;
|
||||
}
|
||||
|
||||
public function listColumns()
|
||||
{
|
||||
$rows = $this->fetchData();
|
||||
$columns = [];
|
||||
|
||||
foreach ($rows as $object) {
|
||||
foreach (array_keys((array) $object) as $column) {
|
||||
if (! isset($columns[$column])) {
|
||||
$columns[] = $column;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param QuickForm $form
|
||||
* @throws \Zend_Form_Exception
|
||||
*/
|
||||
public static function addSettingsFormFields(QuickForm $form)
|
||||
{
|
||||
static::addScheme($form);
|
||||
static::addSslOptions($form);
|
||||
static::addUrl($form);
|
||||
static::addResultProperty($form);
|
||||
static::addAuthentication($form);
|
||||
static::addProxy($form);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param QuickForm $form
|
||||
* @throws \Zend_Form_Exception
|
||||
*/
|
||||
protected static function addScheme(QuickForm $form)
|
||||
{
|
||||
$form->addElement('select', 'scheme', [
|
||||
'label' => $form->translate('Protocol'),
|
||||
'description' => $form->translate(
|
||||
'Whether to use encryption when talking to the REST API'
|
||||
),
|
||||
'multiOptions' => [
|
||||
'HTTPS' => $form->translate('HTTPS (strongly recommended)'),
|
||||
'HTTP' => $form->translate('HTTP (this is plaintext!)'),
|
||||
],
|
||||
'class' => 'autosubmit',
|
||||
'value' => 'HTTPS',
|
||||
'required' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param QuickForm $form
|
||||
* @throws \Zend_Form_Exception
|
||||
*/
|
||||
protected static function addSslOptions(QuickForm $form)
|
||||
{
|
||||
$ssl = ! ($form->getSentOrObjectSetting('scheme', 'HTTPS') === 'HTTP');
|
||||
|
||||
if ($ssl) {
|
||||
static::addBoolean($form, 'ssl_verify_peer', array(
|
||||
'label' => $form->translate('Verify Peer'),
|
||||
'description' => $form->translate(
|
||||
'Whether we should check that our peer\'s certificate has'
|
||||
. ' been signed by a trusted CA. This is strongly recommended.'
|
||||
)
|
||||
), 'y');
|
||||
static::addBoolean($form, 'ssl_verify_host', array(
|
||||
'label' => $form->translate('Verify Host'),
|
||||
'description' => $form->translate(
|
||||
'Whether we should check that the certificate matches the'
|
||||
. 'configured host'
|
||||
)
|
||||
), 'y');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param QuickForm $form
|
||||
* @throws \Zend_Form_Exception
|
||||
*/
|
||||
protected static function addUrl(QuickForm $form)
|
||||
{
|
||||
$form->addElement('text', 'url', array(
|
||||
'label' => 'REST API URL',
|
||||
'description' => $form->translate(
|
||||
'Something like https://api.example.com/rest/v2/objects'
|
||||
),
|
||||
'required' => true,
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param QuickForm $form
|
||||
* @throws \Zend_Form_Exception
|
||||
*/
|
||||
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'
|
||||
),
|
||||
'required' => false,
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param QuickForm $form
|
||||
* @throws \Zend_Form_Exception
|
||||
*/
|
||||
protected static function addAuthentication(QuickForm $form)
|
||||
{
|
||||
$form->addElement('text', 'username', array(
|
||||
'label' => $form->translate('Username'),
|
||||
'description' => $form->translate(
|
||||
'Will be used for SOAP authentication against your vCenter'
|
||||
),
|
||||
'required' => true,
|
||||
));
|
||||
|
||||
$form->addElement('password', 'password', array(
|
||||
'label' => $form->translate('Password'),
|
||||
'required' => true,
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param QuickForm $form
|
||||
* @throws \Zend_Form_Exception
|
||||
*/
|
||||
protected static function addProxy(QuickForm $form)
|
||||
{
|
||||
$form->addElement('select', 'proxy_type', [
|
||||
'label' => $form->translate('Proxy'),
|
||||
'description' => $form->translate(
|
||||
'In case your API is only reachable through a proxy, please'
|
||||
. ' choose it\'s protocol right here'
|
||||
),
|
||||
'multiOptions' => $form->optionalEnum([
|
||||
'HTTP' => $form->translate('HTTP proxy'),
|
||||
'SOCKS5' => $form->translate('SOCKS5 proxy'),
|
||||
]),
|
||||
'class' => 'autosubmit'
|
||||
]);
|
||||
|
||||
$proxyType = $form->getSentOrObjectSetting('proxy_type');
|
||||
|
||||
if ($proxyType) {
|
||||
$form->addElement('text', 'proxy', [
|
||||
'label' => $form->translate('Proxy Address'),
|
||||
'description' => $form->translate(
|
||||
'Hostname, IP or <host>:<port>'
|
||||
),
|
||||
'required' => true,
|
||||
]);
|
||||
if ($proxyType === 'HTTP') {
|
||||
$form->addElement('text', 'proxy_user', [
|
||||
'label' => $form->translate('Proxy Username'),
|
||||
'description' => $form->translate(
|
||||
'In case your proxy requires authentication, please'
|
||||
. ' configure this here'
|
||||
),
|
||||
]);
|
||||
|
||||
$passRequired = strlen($form->getSentOrObjectSetting('proxy_user')) > 0;
|
||||
|
||||
$form->addElement('password', 'proxy_pass', [
|
||||
'label' => $form->translate('Proxy Password'),
|
||||
'required' => $passRequired
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param QuickForm $form
|
||||
* @param string $key
|
||||
* @param array $options
|
||||
* @param string|null $default
|
||||
* @throws \Zend_Form_Exception
|
||||
*/
|
||||
protected static function addBoolean(QuickForm $form, $key, $options, $default = null)
|
||||
{
|
||||
if ($default === null) {
|
||||
$form->addElement('OptionalYesNo', $key, $options);
|
||||
} else {
|
||||
$form->addElement('YesNo', $key, $options);
|
||||
$form->getElement($key)->setValue($default);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param QuickForm $form
|
||||
* @param string $key
|
||||
* @param string $label
|
||||
* @param string $description
|
||||
* @throws \Zend_Form_Exception
|
||||
*/
|
||||
protected static function optionalBoolean(QuickForm $form, $key, $label, $description)
|
||||
{
|
||||
static::addBoolean($form, $key, array(
|
||||
'label' => $label,
|
||||
'description' => $description
|
||||
));
|
||||
}
|
||||
}
|
||||
311
library/Director/RestApi/RestApiClient.php
Normal file
311
library/Director/RestApi/RestApiClient.php
Normal file
|
|
@ -0,0 +1,311 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Module\Director\RestApi;
|
||||
|
||||
use Icinga\Module\Director\Core\Json;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
class RestApiClient
|
||||
{
|
||||
/** @var resource */
|
||||
private $curl;
|
||||
|
||||
/** @var string HTTP or HTTPS */
|
||||
private $scheme;
|
||||
|
||||
/** @var string */
|
||||
private $host;
|
||||
|
||||
/** @var int */
|
||||
private $port;
|
||||
|
||||
/** @var string */
|
||||
private $user;
|
||||
|
||||
/** @var string */
|
||||
private $pass;
|
||||
|
||||
/** @var bool */
|
||||
private $verifySslPeer = true;
|
||||
|
||||
/** @var bool */
|
||||
private $verifySslHost = true;
|
||||
|
||||
/** @var string */
|
||||
private $proxy;
|
||||
|
||||
/** @var string */
|
||||
private $proxyType;
|
||||
|
||||
/** @var string */
|
||||
private $proxyUser;
|
||||
|
||||
/** @var string */
|
||||
private $proxyPass;
|
||||
|
||||
/** @var array */
|
||||
private $proxyTypes = [
|
||||
'HTTP' => CURLPROXY_HTTP,
|
||||
'SOCKS5' => CURLPROXY_SOCKS5,
|
||||
];
|
||||
|
||||
/**
|
||||
* RestApiClient constructor.
|
||||
*
|
||||
* Please note that only the host is required, user and pass are optional
|
||||
*
|
||||
* @param string $host
|
||||
* @param string|null $user
|
||||
* @param string|null $pass
|
||||
*/
|
||||
public function __construct($host, $user = null, $pass = null)
|
||||
{
|
||||
$this->host = $host;
|
||||
$this->user = $user;
|
||||
$this->pass = $pass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a proxy
|
||||
*
|
||||
* @param $url
|
||||
* @param string $type Either HTTP or SOCKS5
|
||||
* @return $this
|
||||
*/
|
||||
public function setProxy($url, $type = 'HTTP')
|
||||
{
|
||||
$this->proxy = $url;
|
||||
if (\is_int($type)) {
|
||||
$this->proxyType = $type;
|
||||
} else {
|
||||
$this->proxyType = $this->proxyTypes[$type];
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
* @param string $pass
|
||||
* @return $this
|
||||
*/
|
||||
public function setProxyAuth($user, $pass)
|
||||
{
|
||||
$this->proxyUser = $user;
|
||||
$this->proxyPass = $pass;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getScheme()
|
||||
{
|
||||
if ($this->scheme === null) {
|
||||
return 'HTTPS';
|
||||
} else {
|
||||
return $this->scheme;
|
||||
}
|
||||
}
|
||||
|
||||
public function setScheme($scheme)
|
||||
{
|
||||
$scheme = \strtoupper($scheme);
|
||||
if (! \in_array($scheme, ['HTTP', 'HTTPS'])) {
|
||||
throw new InvalidArgumentException("Got invalid scheme: $scheme");
|
||||
}
|
||||
|
||||
$this->scheme = $scheme;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPort()
|
||||
{
|
||||
if ($this->port === null) {
|
||||
return $this->getScheme() === 'HTTPS' ? 443 : 80;
|
||||
} else {
|
||||
return $this->port;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|string|null $port
|
||||
* @return $this
|
||||
*/
|
||||
public function setPort($port)
|
||||
{
|
||||
if ($port === null) {
|
||||
$this->port = null;
|
||||
return $this;
|
||||
}
|
||||
$port = (int) ($port);
|
||||
if ($port < 1 || $port > 65535) {
|
||||
throw new InvalidArgumentException("Got invalid port: $port");
|
||||
}
|
||||
|
||||
$this->port = $port;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isDefaultPort()
|
||||
{
|
||||
return $this->port === null
|
||||
|| $this->getScheme() === 'HTTPS' && $this->getPort() === 443
|
||||
|| $this->getScheme() === 'HTTP' && $this->getPort() === 80;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $disable
|
||||
* @return $this
|
||||
*/
|
||||
public function disableSslPeerVerification($disable = true)
|
||||
{
|
||||
$this->verifySslPeer = ! $disable;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $disable
|
||||
* @return $this
|
||||
*/
|
||||
public function disableSslHostVerification($disable = true)
|
||||
{
|
||||
$this->verifySslHost = ! $disable;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @return string
|
||||
*/
|
||||
public function url($url)
|
||||
{
|
||||
return \sprintf(
|
||||
'%s://%s%s/%s',
|
||||
\strtolower($this->getScheme()),
|
||||
$this->host,
|
||||
$this->isDefaultPort() ? '' : ':' . $this->getPort(),
|
||||
ltrim($url, '/')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @param mixed $body
|
||||
* @param array $headers
|
||||
* @return mixed
|
||||
*/
|
||||
public function get($url, $body = null, $headers = [])
|
||||
{
|
||||
return $this->request('get', $url, $body, $headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $url
|
||||
* @param null $body
|
||||
* @param array $headers
|
||||
* @return mixed
|
||||
*/
|
||||
public function post($url, $body = null, $headers = [])
|
||||
{
|
||||
return $this->request('post', $url, Json::encode($body), $headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $method
|
||||
* @param $url
|
||||
* @param null $body
|
||||
* @param array $headers
|
||||
* @return mixed
|
||||
*/
|
||||
protected function request($method, $url, $body = null, $headers = [])
|
||||
{
|
||||
$sendHeaders = ['Host: ' . $this->host];
|
||||
foreach ($headers as $key => $val) {
|
||||
$sendHeaders[] = "$key: $val";
|
||||
}
|
||||
|
||||
if (! \in_array('Accept', $headers)) {
|
||||
$sendHeaders[] = 'Accept: application/json';
|
||||
}
|
||||
|
||||
$url = $this->url($url);
|
||||
$opts = [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_HTTPHEADER => $sendHeaders,
|
||||
CURLOPT_CUSTOMREQUEST => \strtoupper($method),
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_CONNECTTIMEOUT => 5,
|
||||
];
|
||||
|
||||
if ($this->getScheme() === 'HTTPS') {
|
||||
$opts[CURLOPT_SSL_VERIFYPEER] = $this->verifySslPeer;
|
||||
$opts[CURLOPT_SSL_VERIFYHOST] = $this->verifySslHost ? 2 : 0;
|
||||
}
|
||||
|
||||
if ($this->user !== null) {
|
||||
$opts[CURLOPT_USERPWD] = \sprintf('%s:%s', $this->user, $this->pass);
|
||||
}
|
||||
|
||||
if ($this->proxy) {
|
||||
$opts[CURLOPT_PROXY] = $this->proxy;
|
||||
$opts[CURLOPT_PROXYTYPE] = $this->proxyType;
|
||||
|
||||
if ($this->proxyUser) {
|
||||
$opts['CURLOPT_PROXYUSERPWD'] = \sprintf(
|
||||
'%s:%s',
|
||||
$this->proxyUser,
|
||||
$this->proxyPass
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($body !== null) {
|
||||
$opts[CURLOPT_POSTFIELDS] = $body;
|
||||
}
|
||||
|
||||
$curl = $this->curl();
|
||||
\curl_setopt_array($curl, $opts);
|
||||
|
||||
$res = \curl_exec($curl);
|
||||
if ($res === false) {
|
||||
throw new RuntimeException('CURL ERROR: ' . \curl_error($curl));
|
||||
}
|
||||
|
||||
$statusCode = \curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
||||
if ($statusCode === 401) {
|
||||
throw new RuntimeException(
|
||||
'Unable to authenticate, please check your REST API credentials'
|
||||
);
|
||||
}
|
||||
|
||||
if ($statusCode >= 400) {
|
||||
throw new RuntimeException(
|
||||
"Got $statusCode: " . \var_export($res, 1)
|
||||
);
|
||||
}
|
||||
|
||||
return Json::decode($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return resource
|
||||
*/
|
||||
protected function curl()
|
||||
{
|
||||
if ($this->curl === null) {
|
||||
$this->curl = \curl_init(\sprintf('https://%s:%d', $this->host, $this->port));
|
||||
if (! $this->curl) {
|
||||
throw new RuntimeException('CURL INIT ERROR: ' . \curl_error($this->curl));
|
||||
}
|
||||
}
|
||||
|
||||
return $this->curl;
|
||||
}
|
||||
}
|
||||
1
run.php
1
run.php
|
|
@ -17,6 +17,7 @@ $this->provideHook('monitoring/ServiceActions');
|
|||
$this->provideHook('director/ImportSource', $prefix . 'Import\\ImportSourceSql');
|
||||
$this->provideHook('director/ImportSource', $prefix . 'Import\\ImportSourceLdap');
|
||||
$this->provideHook('director/ImportSource', $prefix . 'Import\\ImportSourceCoreApi');
|
||||
$this->provideHook('director/ImportSource', $prefix . 'Import\\ImportSourceRestApi');
|
||||
|
||||
$this->provideHook('director/DataType', $prefix . 'DataType\\DataTypeArray');
|
||||
$this->provideHook('director/DataType', $prefix . 'DataType\\DataTypeBoolean');
|
||||
|
|
|
|||
Loading…
Reference in a new issue