From 96e7411e252829bb4e7371084afe46e18324bcdf Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 10 Feb 2017 14:11:56 +0100 Subject: [PATCH] TransportConfigForm: validate whether the Icinga 2 API can be connected to refs #2674 --- .../Config/Transport/ApiTransportForm.php | 3 +- .../forms/Config/TransportConfigForm.php | 114 ++++++++++++++++++ .../Command/Transport/ApiCommandTransport.php | 29 +++++ .../Monitoring/Exception/CurlException.php | 13 ++ .../Monitoring/Web/Rest/RestRequest.php | 18 ++- 5 files changed, 175 insertions(+), 2 deletions(-) create mode 100644 modules/monitoring/library/Monitoring/Exception/CurlException.php diff --git a/modules/monitoring/application/forms/Config/Transport/ApiTransportForm.php b/modules/monitoring/application/forms/Config/Transport/ApiTransportForm.php index bd0aa11ba..3d501e083 100644 --- a/modules/monitoring/application/forms/Config/Transport/ApiTransportForm.php +++ b/modules/monitoring/application/forms/Config/Transport/ApiTransportForm.php @@ -64,7 +64,8 @@ class ApiTransportForm extends Form 'description' => $this->translate( 'User to log in as on the remote Icinga instance. Please note that key-based SSH login must be' . ' possible for this user' - ) + ), + 'renderPassword' => true ) ) )); diff --git a/modules/monitoring/application/forms/Config/TransportConfigForm.php b/modules/monitoring/application/forms/Config/TransportConfigForm.php index eef114ab7..de7ad0c9d 100644 --- a/modules/monitoring/application/forms/Config/TransportConfigForm.php +++ b/modules/monitoring/application/forms/Config/TransportConfigForm.php @@ -3,6 +3,9 @@ namespace Icinga\Module\Monitoring\Forms\Config; +use Icinga\Data\ConfigObject; +use Icinga\Module\Monitoring\Command\Transport\CommandTransport; +use Icinga\Module\Monitoring\Exception\CommandTransportException; use InvalidArgumentException; use Icinga\Application\Platform; use Icinga\Exception\IcingaException; @@ -34,6 +37,11 @@ class TransportConfigForm extends ConfigForm */ protected $instanceNames; + /** + * @var bool + */ + protected $validatePartial = true; + /** * Initialize this form */ @@ -250,6 +258,59 @@ class TransportConfigForm extends ConfigForm $this->addSubForm($this->getTransportForm($transportType)->create($formData), 'transport_form'); } + /** + * Add a submit button to this form and one to manually validate the configuration + * + * Calls parent::addSubmitButton() to add the submit button. + * + * @return $this + */ + public function addSubmitButton() + { + parent::addSubmitButton(); + + if ($this->getSubForm('transport_form') instanceof ApiTransportForm) { + $this->getElement('btn_submit') + ->setDecorators(array('ViewHelper')); + + $this->addElement( + 'submit', + 'transport_validation', + array( + 'ignore' => true, + 'label' => $this->translate('Validate Configuration'), + 'data-progress-label' => $this->translate('Validation In Progress'), + 'decorators' => array('ViewHelper') + ) + ); + + $this->setAttrib('data-progress-element', 'transport-progress'); + $this->addElement( + 'note', + 'transport-progress', + array( + 'decorators' => array( + 'ViewHelper', + array('Spinner', array('id' => 'transport-progress')) + ) + ) + ); + + $this->addDisplayGroup( + array('btn_submit', 'transport_validation', 'transport-progress'), + 'submit_validation', + array( + 'decorators' => array( + 'FormElements', + array('HtmlTag', array('tag' => 'div', 'class' => 'control-group form-controls')) + ) + ) + ); + } + + return $this; + } + /** * Populate the configuration of the transport to load */ @@ -261,4 +322,57 @@ class TransportConfigForm extends ConfigForm $this->populate($data); } } + + /** + * {@inheritdoc} + */ + public function isValidPartial(array $formData) + { + $isValidPartial = parent::isValidPartial($formData); + + $transportValidation = $this->getElement('transport_validation'); + if ($transportValidation !== null && $transportValidation->isChecked() && $this->isValid($formData)) { + $this->info($this->translate('The configuration has been successfully validated.')); + } + + return $isValidPartial; + } + + /** + * {@inheritdoc} + */ + public function isValid($formData) + { + if (! parent::isValid($formData)) { + return false; + } + + if (! ($this->getElement('transport_validation') === null || ( + $this->isSubmitted() && isset($formData['force_creation']) && $formData['force_creation']) + )) { + try { + CommandTransport::createTransport(new ConfigObject($this->getValues()))->probe(); + } catch (CommandTransportException $e) { + $this->error(sprintf( + $this->translate('Failed to successfully validate the configuration: %s'), + $e->getMessage() + )); + + $this->addElement( + 'checkbox', + 'force_creation', + array( + 'order' => 0, + 'ignore' => true, + 'label' => $this->translate('Force Changes'), + 'description' => $this->translate('Check this box to enforce changes without connectivity validation') + ) + ); + + return false; + } + } + + return true; + } } diff --git a/modules/monitoring/library/Monitoring/Command/Transport/ApiCommandTransport.php b/modules/monitoring/library/Monitoring/Command/Transport/ApiCommandTransport.php index 1f88bebd9..bca3f80a1 100644 --- a/modules/monitoring/library/Monitoring/Command/Transport/ApiCommandTransport.php +++ b/modules/monitoring/library/Monitoring/Command/Transport/ApiCommandTransport.php @@ -9,6 +9,7 @@ use Icinga\Module\Monitoring\Command\IcingaApiCommand; use Icinga\Module\Monitoring\Command\IcingaCommand; use Icinga\Module\Monitoring\Command\Renderer\IcingaApiCommandRenderer; use Icinga\Module\Monitoring\Exception\CommandTransportException; +use Icinga\Module\Monitoring\Exception\CurlException; use Icinga\Module\Monitoring\Web\Rest\RestRequest; /** @@ -238,4 +239,32 @@ class ApiCommandTransport implements CommandTransportInterface { $this->sendCommand($this->renderer->render($command)); } + + /** + * Try to connect to the API + * + * @throws CommandTransportException In case of failure + */ + public function probe() + { + $request = RestRequest::get($this->getUriFor(null)) + ->authenticateWith($this->getUsername(), $this->getPassword()) + ->noStrictSsl(); + + try { + $response = $request->send(); + } catch (CurlException $e) { + throw new CommandTransportException('Couldn\'t connect to the Icinga 2 API: %s', $e->getMessage()); + } catch (JsonDecodeException $e) { + throw new CommandTransportException('Got invalid JSON response from the Icinga 2 API: %s', $e->getMessage()); + } + + if (isset($response['error'])) { + throw new CommandTransportException( + 'Can\'t connect to the Icinga 2 API: %u %s', + $response['error'], + $response['status'] + ); + } + } } diff --git a/modules/monitoring/library/Monitoring/Exception/CurlException.php b/modules/monitoring/library/Monitoring/Exception/CurlException.php new file mode 100644 index 000000000..01757af4c --- /dev/null +++ b/modules/monitoring/library/Monitoring/Exception/CurlException.php @@ -0,0 +1,13 @@ +uri = $uri; + $request->method = 'GET'; + return $request; + } + /** * Create a POST REST request * @@ -251,7 +267,7 @@ class RestRequest $result = curl_exec($ch); if ($result === false) { - throw new Exception(curl_error($ch)); + throw new CurlException('%s', curl_error($ch)); } curl_close($ch);