diff --git a/doc/installation.md b/doc/installation.md index 83939b143..f47d9b0b7 100644 --- a/doc/installation.md +++ b/doc/installation.md @@ -275,9 +275,11 @@ The first release candidate of Icinga Web 2 introduces the following non-backwar `icingaweb_group_membership` were altered to ensure referential integrity. Please use the upgrade script located in **etc/schema/** to update your database schema + * Users who are using PostgreSQL < v9.1 are required to upgrade their environment to v9.1+ as this is the new minimum required version for utilizing PostgreSQL as database backend + * The restrictions `monitoring/hosts/filter` and `monitoring/services/filter` provided by the monitoring module were merged together. The new restriction is called `monitoring/filter/objects` and supports only a @@ -287,12 +289,16 @@ The first release candidate of Icinga Web 2 introduces the following non-backwar ## Upgrading to Icinga Web 2 2.0.0 * Icinga Web 2 installations from package on RHEL/CentOS 7 now depend on `php-ZendFramework` which is available through -the [EPEL repository](http://fedoraproject.org/wiki/EPEL). Before, Zend was installed as Icinga Web 2 vendor library -through the package `icingaweb2-vendor-zend`. After upgrading, please make sure to remove the package -`icingaweb2-vendor-zend`. + the [EPEL repository](http://fedoraproject.org/wiki/EPEL). Before, Zend was installed as Icinga Web 2 vendor library + through the package `icingaweb2-vendor-zend`. After upgrading, please make sure to remove the package + `icingaweb2-vendor-zend`. * Icinga Web 2 version 2.0.0 requires permissions for accessing modules. Those permissions are automatically generated -for each installed module in the format `module/`. Administrators have to grant the module permissions to -users and/or user groups in the roles configuration for permitting access to specific modules. -In addition, restrictions provided by modules are now configurable for each installed module too. Before, -a module had to be enabled before having the possibility to configure restrictions. + for each installed module in the format `module/`. Administrators have to grant the module permissions to + users and/or user groups in the roles configuration for permitting access to specific modules. + In addition, restrictions provided by modules are now configurable for each installed module too. Before, + a module had to be enabled before having the possibility to configure restrictions. + +* The **instances.ini** configuration file provided by the monitoring module + has been renamed to **commandtransports.ini**. The content and location of + the file remains unchanged. diff --git a/modules/monitoring/application/controllers/ConfigController.php b/modules/monitoring/application/controllers/ConfigController.php index 55094bbba..8c5322f4d 100644 --- a/modules/monitoring/application/controllers/ConfigController.php +++ b/modules/monitoring/application/controllers/ConfigController.php @@ -8,11 +8,11 @@ use Icinga\Data\ResourceFactory; use Icinga\Exception\ConfigurationError; use Icinga\Exception\NotFoundError; use Icinga\Forms\ConfirmRemovalForm; -use Icinga\Module\Monitoring\Forms\Config\BackendConfigForm; -use Icinga\Module\Monitoring\Forms\Config\InstanceConfigForm; -use Icinga\Module\Monitoring\Forms\Config\SecurityConfigForm; -use Icinga\Web\Controller; use Icinga\Web\Notification; +use Icinga\Module\Monitoring\Controller; +use Icinga\Module\Monitoring\Forms\Config\BackendConfigForm; +use Icinga\Module\Monitoring\Forms\Config\SecurityConfigForm; +use Icinga\Module\Monitoring\Forms\Config\TransportConfigForm; /** * Configuration controller for editing monitoring resources @@ -20,12 +20,12 @@ use Icinga\Web\Notification; class ConfigController extends Controller { /** - * Display a list of available backends and instances + * Display a list of available backends and command transports */ public function indexAction() { $this->view->backendsConfig = $this->Config('backends'); - $this->view->instancesConfig = $this->Config('instances'); + $this->view->transportConfig = $this->Config('commandtransports'); $this->view->tabs = $this->Module()->getConfigTabs()->activate('backends'); } @@ -149,31 +149,34 @@ class ConfigController extends Controller } /** - * Remove a monitoring instance + * Remove a command transport */ - public function removeinstanceAction() + public function removetransportAction() { - $instanceName = $this->params->getRequired('instance'); + $transportName = $this->params->getRequired('transport'); - $instanceForm = new InstanceConfigForm(); - $instanceForm->setIniConfig($this->Config('instances')); + $transportForm = new TransportConfigForm(); + $transportForm->setIniConfig($this->Config('commandtransports')); $form = new ConfirmRemovalForm(); $form->setRedirectUrl('monitoring/config'); - $form->setTitle(sprintf($this->translate('Remove Monitoring Instance %s'), $instanceName)); - $form->addDescription($this->translate( - 'If you have still any environments or views referring to this instance, ' - . 'you won\'t be able to send commands anymore after deletion.' - )); - $form->setOnSuccess(function (ConfirmRemovalForm $form) use ($instanceName, $instanceForm) { + $form->setTitle(sprintf($this->translate('Remove Command Transport %s'), $transportName)); + $form->info( + $this->translate( + 'If you have still any environments or views referring to this transport, ' + . 'you won\'t be able to send commands anymore after deletion.' + ), + false + ); + $form->setOnSuccess(function (ConfirmRemovalForm $form) use ($transportName, $transportForm) { try { - $instanceForm->delete($instanceName); + $transportForm->delete($transportName); } catch (Exception $e) { $form->error($e->getMessage()); return false; } - if ($instanceForm->save()) { - Notification::success(sprintf(t('Monitoring instance "%s" successfully removed'), $instanceName)); + if ($transportForm->save()) { + Notification::success(sprintf(t('Command transport "%s" successfully removed'), $transportName)); return true; } @@ -186,19 +189,20 @@ class ConfigController extends Controller } /** - * Edit a monitoring instance + * Edit a command transport */ - public function editinstanceAction() + public function edittransportAction() { - $instanceName = $this->params->getRequired('instance'); + $transportName = $this->params->getRequired('transport'); - $form = new InstanceConfigForm(); + $form = new TransportConfigForm(); $form->setRedirectUrl('monitoring/config'); - $form->setTitle(sprintf($this->translate('Edit Monitoring Instance %s'), $instanceName)); - $form->setIniConfig($this->Config('instances')); - $form->setOnSuccess(function (InstanceConfigForm $form) use ($instanceName) { + $form->setTitle(sprintf($this->translate('Edit Command Transport %s'), $transportName)); + $form->setIniConfig($this->Config('commandtransports')); + $form->setInstanceNames($this->backend->select()->from('instance', array('instance_name'))->fetchColumn()); + $form->setOnSuccess(function (TransportConfigForm $form) use ($transportName) { try { - $form->edit($instanceName, array_map( + $form->edit($transportName, array_map( function ($v) { return $v !== '' ? $v : null; }, @@ -210,7 +214,7 @@ class ConfigController extends Controller } if ($form->save()) { - Notification::success(sprintf(t('Monitoring instance "%s" successfully updated'), $instanceName)); + Notification::success(sprintf(t('Command transport "%s" successfully updated'), $transportName)); return true; } @@ -218,10 +222,10 @@ class ConfigController extends Controller }); try { - $form->load($instanceName); + $form->load($transportName); $form->handleRequest(); } catch (NotFoundError $_) { - $this->httpNotFound(sprintf($this->translate('Monitoring instance "%s" not found'), $instanceName)); + $this->httpNotFound(sprintf($this->translate('Command transport "%s" not found'), $transportName)); } $this->view->form = $form; @@ -229,15 +233,16 @@ class ConfigController extends Controller } /** - * Create a new monitoring instance + * Create a new command transport */ - public function createinstanceAction() + public function createtransportAction() { - $form = new InstanceConfigForm(); + $form = new TransportConfigForm(); $form->setRedirectUrl('monitoring/config'); - $form->setTitle($this->translate('Create New Monitoring Instance')); - $form->setIniConfig($this->Config('instances')); - $form->setOnSuccess(function (InstanceConfigForm $form) { + $form->setTitle($this->translate('Create New Command Transport')); + $form->setIniConfig($this->Config('commandtransports')); + $form->setInstanceNames($this->backend->select()->from('instance', array('instance_name'))->fetchColumn()); + $form->setOnSuccess(function (TransportConfigForm $form) { try { $form->add(array_filter($form->getValues())); } catch (Exception $e) { @@ -246,7 +251,7 @@ class ConfigController extends Controller } if ($form->save()) { - Notification::success(t('Monitoring instance successfully created')); + Notification::success(t('Command transport successfully created')); return true; } @@ -269,6 +274,5 @@ class ConfigController extends Controller $this->view->form = $form; $this->view->tabs = $this->Module()->getConfigTabs()->activate('security'); - $this->render('form'); } } diff --git a/modules/monitoring/application/forms/Command/CommandForm.php b/modules/monitoring/application/forms/Command/CommandForm.php index ca30dba74..19aead6ec 100644 --- a/modules/monitoring/application/forms/Command/CommandForm.php +++ b/modules/monitoring/application/forms/Command/CommandForm.php @@ -3,10 +3,12 @@ namespace Icinga\Module\Monitoring\Forms\Command; -use Icinga\Module\Monitoring\Backend\MonitoringBackend; -use Icinga\Module\Monitoring\Command\Transport\CommandTransport; +use Icinga\Exception\ConfigurationError; use Icinga\Web\Form; use Icinga\Web\Request; +use Icinga\Module\Monitoring\Backend\MonitoringBackend; +use Icinga\Module\Monitoring\Command\Transport\CommandTransport; +use Icinga\Module\Monitoring\Command\Transport\CommandTransportInterface; /** * Base class for command forms @@ -46,18 +48,28 @@ abstract class CommandForm extends Form /** * Get the transport used to send commands * - * @param Request $request + * @param Request $request * - * @return \Icinga\Module\Monitoring\Command\Transport\CommandTransportInterface + * @return CommandTransportInterface + * + * @throws ConfigurationError */ public function getTransport(Request $request) { - $instance = $request->getParam('instance'); - if ($instance !== null) { - $transport = CommandTransport::create($instance); + if (($transportName = $request->getParam('transport')) !== null) { + $config = CommandTransport::getConfig(); + if ($config->hasSection($transportName)) { + $transport = CommandTransport::createTransport($config->getSection($transportName)); + } else { + throw new ConfigurationError(sprintf( + mt('monitoring', 'Command transport "%s" not found.'), + $transportName + )); + } } else { - $transport = CommandTransport::first(); + $transport = new CommandTransport(); } + return $transport; } } diff --git a/modules/monitoring/application/forms/Config/InstanceConfigForm.php b/modules/monitoring/application/forms/Config/InstanceConfigForm.php deleted file mode 100644 index 27e1e410d..000000000 --- a/modules/monitoring/application/forms/Config/InstanceConfigForm.php +++ /dev/null @@ -1,245 +0,0 @@ -setName('form_config_monitoring_instance'); - $this->setSubmitLabel($this->translate('Save Changes')); - } - - /** - * Return a form object for the given instance type - * - * @param string $type The instance type for which to return a form - * - * @return Form - * - * @throws InvalidArgumentException In case the given instance type is invalid - */ - public function getInstanceForm($type) - { - switch (strtolower($type)) { - case LocalCommandFile::TRANSPORT: - return new LocalInstanceForm(); - case RemoteCommandFile::TRANSPORT; - return new RemoteInstanceForm(); - default: - throw new InvalidArgumentException( - sprintf($this->translate('Invalid monitoring instance type "%s" given'), $type) - ); - } - } - - /** - * Populate the form with the given instance's config - * - * @param string $name - * - * @return $this - * - * @throws NotFoundError In case no instance with the given name is found - */ - public function load($name) - { - if (! $this->config->hasSection($name)) { - throw new NotFoundError('No monitoring instance called "%s" found', $name); - } - - $this->instanceToLoad = $name; - return $this; - } - - /** - * Add a new instance - * - * The instance to add is identified by the array-key `name'. - * - * @param array $data - * - * @return $this - * - * @throws InvalidArgumentException In case $data does not contain a instance name - * @throws IcingaException In case a instance with the same name already exists - */ - public function add(array $data) - { - if (! isset($data['name'])) { - throw new InvalidArgumentException('Key \'name\' missing'); - } - - $instanceName = $data['name']; - if ($this->config->hasSection($instanceName)) { - throw new IcingaException( - $this->translate('A monitoring instance with the name "%s" does already exist'), - $instanceName - ); - } - - unset($data['name']); - $this->config->setSection($instanceName, $data); - return $this; - } - - /** - * Edit an existing instance - * - * @param string $name - * @param array $data - * - * @return $this - * - * @throws NotFoundError In case no instance with the given name is found - */ - public function edit($name, array $data) - { - if (! $this->config->hasSection($name)) { - throw new NotFoundError('No monitoring instance called "%s" found', $name); - } - - $instanceConfig = $this->config->getSection($name); - if (isset($data['name'])) { - if ($data['name'] !== $name) { - $this->config->removeSection($name); - $name = $data['name']; - } - - unset($data['name']); - } - - $instanceConfig->merge($data); - foreach ($instanceConfig->toArray() as $k => $v) { - if ($v === null) { - unset($instanceConfig->$k); - } - } - - $this->config->setSection($name, $instanceConfig); - return $this; - } - - /** - * Remove a instance - * - * @param string $name - * - * @return $this - */ - public function delete($name) - { - $this->config->removeSection($name); - return $this; - } - - /** - * Create and add elements to this form - * - * @param array $formData - */ - public function createElements(array $formData) - { - $this->addElement( - 'text', - 'name', - array( - 'required' => true, - 'label' => $this->translate('Instance Name'), - 'description' => $this->translate( - 'The name of this monitoring instance that is used to differentiate it from others' - ), - 'validators' => array( - array( - 'Regex', - false, - array( - 'pattern' => '/^[^\\[\\]:]+$/', - 'messages' => array( - 'regexNotMatch' => $this->translate( - 'The name cannot contain \'[\', \']\' or \':\'.' - ) - ) - ) - ) - ) - ) - ); - - $instanceTypes = array( - LocalCommandFile::TRANSPORT => $this->translate('Local Command File'), - RemoteCommandFile::TRANSPORT => $this->translate('Remote Command File') - ); - - $instanceType = isset($formData['transport']) ? $formData['transport'] : null; - if ($instanceType === null) { - $instanceType = key($instanceTypes); - } - - $this->addElements(array( - array( - 'select', - 'transport', - array( - 'required' => true, - 'autosubmit' => true, - 'label' => $this->translate('Instance Type'), - 'description' => $this->translate('The type of transport to use for this monitoring instance'), - 'multiOptions' => $instanceTypes - ) - ) - )); - - $this->addSubForm($this->getInstanceForm($instanceType)->create($formData), 'instance_form'); - } - - /** - * Populate the configuration of the instance to load - */ - public function onRequest() - { - if ($this->instanceToLoad) { - $data = $this->config->getSection($this->instanceToLoad)->toArray(); - $data['name'] = $this->instanceToLoad; - $this->populate($data); - } - } - - /** - * Retrieve all form element values - * - * @param bool $suppressArrayNotation Ignored - * - * @return array - */ - public function getValues($suppressArrayNotation = false) - { - $values = parent::getValues(); - $values = array_merge($values, $values['instance_form']); - unset($values['instance_form']); - return $values; - } -} diff --git a/modules/monitoring/application/forms/Config/Instance/LocalInstanceForm.php b/modules/monitoring/application/forms/Config/Transport/LocalTransportForm.php similarity index 83% rename from modules/monitoring/application/forms/Config/Instance/LocalInstanceForm.php rename to modules/monitoring/application/forms/Config/Transport/LocalTransportForm.php index 15e1ed055..3c5f0655f 100644 --- a/modules/monitoring/application/forms/Config/Instance/LocalInstanceForm.php +++ b/modules/monitoring/application/forms/Config/Transport/LocalTransportForm.php @@ -1,11 +1,11 @@ setName('form_config_monitoring_instance_local'); + $this->setName('form_config_command_transport_local'); } /** diff --git a/modules/monitoring/application/forms/Config/Instance/RemoteInstanceForm.php b/modules/monitoring/application/forms/Config/Transport/RemoteTransportForm.php similarity index 95% rename from modules/monitoring/application/forms/Config/Instance/RemoteInstanceForm.php rename to modules/monitoring/application/forms/Config/Transport/RemoteTransportForm.php index e290866cc..e065919f4 100644 --- a/modules/monitoring/application/forms/Config/Instance/RemoteInstanceForm.php +++ b/modules/monitoring/application/forms/Config/Transport/RemoteTransportForm.php @@ -1,16 +1,16 @@ setName('form_config_monitoring_instance_remote'); + $this->setName('form_config_command_transport_remote'); } /** @@ -44,7 +44,7 @@ class RemoteInstanceForm extends Form } if (empty($resources)) { - throw new ConfigurationError($this->translate('Could not find any valid monitoring instance resources')); + throw new ConfigurationError($this->translate('Could not find any valid SSH resources')); } $this->resources = $resources; diff --git a/modules/monitoring/application/forms/Config/TransportConfigForm.php b/modules/monitoring/application/forms/Config/TransportConfigForm.php new file mode 100644 index 000000000..dc679c5da --- /dev/null +++ b/modules/monitoring/application/forms/Config/TransportConfigForm.php @@ -0,0 +1,290 @@ +setName('form_config_command_transports'); + $this->setSubmitLabel($this->translate('Save Changes')); + } + + /** + * Set the names of all available Icinga instances + * + * @param array $names + * + * @return $this + */ + public function setInstanceNames(array $names) + { + $this->instanceNames = $names; + return $this; + } + + /** + * Return the names of all available Icinga instances + * + * @return array + */ + public function getInstanceNames() + { + return $this->instanceNames ?: array(); + } + + /** + * Return a form object for the given transport type + * + * @param string $type The transport type for which to return a form + * + * @return Form + * + * @throws InvalidArgumentException In case the given transport type is invalid + */ + public function getTransportForm($type) + { + switch (strtolower($type)) { + case LocalCommandFile::TRANSPORT: + return new LocalTransportForm(); + case RemoteCommandFile::TRANSPORT; + return new RemoteTransportForm(); + default: + throw new InvalidArgumentException( + sprintf($this->translate('Invalid command transport type "%s" given'), $type) + ); + } + } + + /** + * Populate the form with the given transport's config + * + * @param string $name + * + * @return $this + * + * @throws NotFoundError In case no transport with the given name is found + */ + public function load($name) + { + if (! $this->config->hasSection($name)) { + throw new NotFoundError('No command transport called "%s" found', $name); + } + + $this->transportToLoad = $name; + return $this; + } + + /** + * Add a new command transport + * + * The transport to add is identified by the array-key `name'. + * + * @param array $data + * + * @return $this + * + * @throws InvalidArgumentException In case $data does not contain a transport name + * @throws IcingaException In case a transport with the same name already exists + */ + public function add(array $data) + { + if (! isset($data['name'])) { + throw new InvalidArgumentException('Key \'name\' missing'); + } + + $transportName = $data['name']; + if ($this->config->hasSection($transportName)) { + throw new IcingaException( + $this->translate('A command transport with the name "%s" does already exist'), + $transportName + ); + } + + unset($data['name']); + $this->config->setSection($transportName, $data); + return $this; + } + + /** + * Edit an existing command transport + * + * @param string $name + * @param array $data + * + * @return $this + * + * @throws NotFoundError In case no transport with the given name is found + */ + public function edit($name, array $data) + { + if (! $this->config->hasSection($name)) { + throw new NotFoundError('No command transport called "%s" found', $name); + } + + $transportConfig = $this->config->getSection($name); + if (isset($data['name'])) { + if ($data['name'] !== $name) { + $this->config->removeSection($name); + $name = $data['name']; + } + + unset($data['name']); + } + + $transportConfig->merge($data); + foreach ($transportConfig->toArray() as $k => $v) { + if ($v === null) { + unset($transportConfig->$k); + } + } + + $this->config->setSection($name, $transportConfig); + return $this; + } + + /** + * Remove a command transport + * + * @param string $name + * + * @return $this + */ + public function delete($name) + { + $this->config->removeSection($name); + return $this; + } + + /** + * Create and add elements to this form + * + * @param array $formData + */ + public function createElements(array $formData) + { + $instanceNames = $this->getInstanceNames(); + if (count($instanceNames) > 1) { + $options = array('none' => $this->translate('None', 'command transport instance association')); + $this->addElement( + 'select', + 'instance', + array( + 'label' => $this->translate('Instance Link'), + 'description' => $this->translate( + 'The name of the Icinga instance this transport should exclusively transfer commands to.' + ), + 'multiOptions' => array_merge($options, array_combine($instanceNames, $instanceNames)) + ) + ); + } + + $this->addElement( + 'text', + 'name', + array( + 'required' => true, + 'label' => $this->translate('Transport Name'), + 'description' => $this->translate( + 'The name of this command transport that is used to differentiate it from others' + ), + 'validators' => array( + array( + 'Regex', + false, + array( + 'pattern' => '/^[^\\[\\]:]+$/', + 'messages' => array( + 'regexNotMatch' => $this->translate( + 'The name cannot contain \'[\', \']\' or \':\'.' + ) + ) + ) + ) + ) + ) + ); + + $transportTypes = array( + LocalCommandFile::TRANSPORT => $this->translate('Local Command File'), + RemoteCommandFile::TRANSPORT => $this->translate('Remote Command File') + ); + + $transportType = isset($formData['transport']) ? $formData['transport'] : null; + if ($transportType === null) { + $transportType = key($transportTypes); + } + + $this->addElements(array( + array( + 'select', + 'transport', + array( + 'required' => true, + 'autosubmit' => true, + 'label' => $this->translate('Transport Type'), + 'multiOptions' => $transportTypes + ) + ) + )); + + $this->addSubForm($this->getTransportForm($transportType)->create($formData), 'transport_form'); + } + + /** + * Populate the configuration of the transport to load + */ + public function onRequest() + { + if ($this->transportToLoad) { + $data = $this->config->getSection($this->transportToLoad)->toArray(); + $data['name'] = $this->transportToLoad; + $this->populate($data); + } + } + + /** + * Retrieve all form element values + * + * @param bool $suppressArrayNotation Ignored + * + * @return array + */ + public function getValues($suppressArrayNotation = false) + { + $values = parent::getValues(); + $values = array_merge($values, $values['transport_form']); + unset($values['transport_form']); + return $values; + } +} diff --git a/modules/monitoring/application/forms/Setup/InstancePage.php b/modules/monitoring/application/forms/Setup/InstancePage.php deleted file mode 100644 index d16646e3e..000000000 --- a/modules/monitoring/application/forms/Setup/InstancePage.php +++ /dev/null @@ -1,32 +0,0 @@ -setName('setup_monitoring_instance'); - $this->setTitle($this->translate('Monitoring Instance', 'setup.page.title')); - $this->addDescription($this->translate( - 'Please define the settings specific to your monitoring instance below.' - )); - } - - public function createElements(array $formData) - { - $instanceConfigForm = new InstanceConfigForm(); - $this->addSubForm($instanceConfigForm, 'instance_form'); - $instanceConfigForm->create($formData); - $instanceConfigForm->getElement('name')->setValue('icinga'); - } - - public function getValues($suppressArrayNotation = false) - { - return $this->getSubForm('instance_form')->getValues($suppressArrayNotation); - } -} diff --git a/modules/monitoring/application/forms/Setup/TransportPage.php b/modules/monitoring/application/forms/Setup/TransportPage.php new file mode 100644 index 000000000..df8d7b3bf --- /dev/null +++ b/modules/monitoring/application/forms/Setup/TransportPage.php @@ -0,0 +1,33 @@ +setName('setup_command_transport'); + $this->setTitle($this->translate('Command Transport', 'setup.page.title')); + $this->addDescription($this->translate( + 'Please define below how you want to send commands to your monitoring instance.' + )); + } + + public function createElements(array $formData) + { + $transportConfigForm = new TransportConfigForm(); + $this->addSubForm($transportConfigForm, 'transport_form'); + $transportConfigForm->create($formData); + $transportConfigForm->removeElement('instance'); + $transportConfigForm->getElement('name')->setValue('icinga2'); + } + + public function getValues($suppressArrayNotation = false) + { + return $this->getSubForm('transport_form')->getValues($suppressArrayNotation); + } +} diff --git a/modules/monitoring/application/views/scripts/config/index.phtml b/modules/monitoring/application/views/scripts/config/index.phtml index c985805eb..d0e75c0ff 100644 --- a/modules/monitoring/application/views/scripts/config/index.phtml +++ b/modules/monitoring/application/views/scripts/config/index.phtml @@ -47,28 +47,28 @@ -

translate('Monitoring Instances') ?>

+

translate('Command Transports') ?>

- - icon('plus'); ?> translate('Create New Instance'); ?> + + icon('plus'); ?> translate('Create New Transport'); ?>

- + - instancesConfig as $instanceName => $config): ?> + transportConfig as $transportName => $config): ?> diff --git a/modules/monitoring/application/views/scripts/config/security.phtml b/modules/monitoring/application/views/scripts/config/security.phtml new file mode 100644 index 000000000..3801678b1 --- /dev/null +++ b/modules/monitoring/application/views/scripts/config/security.phtml @@ -0,0 +1,6 @@ +
+ +
+
+ +
\ No newline at end of file diff --git a/modules/monitoring/doc/commandtransports.md b/modules/monitoring/doc/commandtransports.md new file mode 100644 index 000000000..680f34ca0 --- /dev/null +++ b/modules/monitoring/doc/commandtransports.md @@ -0,0 +1,96 @@ +# The commandtransports.ini configuration file + +## Abstract + +The commandtransports.ini defines how Icinga Web 2 accesses the command pipe of +your Icinga instance in order to submit external commands. Depending on the +config path (default: /etc/icingaweb2) of your Icinga Web 2 installation you can +find it under ./modules/monitoring/commandtransports.ini. + +## Syntax + +You can define multiple command transports in the commandtransports.ini. Every +transport starts with a section header containing its name, followed by the +config directives for this transport in the standard INI-format. + +Icinga Web 2 will try one transport after another to send a command, depending +on the respective Icinga instance, until the command is successfully sent. The +order in which Icinga Web 2 processes the configured transports is defined by +the order of sections in the commandtransports.ini. + +## Using a local command pipe + +A local Icinga instance requires the following directives: + +```` +[icinga2] +transport = local +path = /var/run/icinga2/cmd/icinga2.cmd +```` + +When sending commands to the Icinga instance, Icinga Web 2 opens the file found +on the local filesystem underneath 'path' and writes the external command to it. + +## Using SSH for accessing a remote command pipe + +A command pipe on a remote host's filesystem can be accessed by configuring a +SSH based command transport and requires the following directives: + +```` +[icinga2] +transport = remote +path = /var/run/icinga2/cmd/icinga2.cmd +host = example.tld +;port = 22 ; Optional. The default is 22 +user = icinga +```` + +To make this example work, you'll need to permit your web-server's user +public-key based access to the defined remote host so that Icinga Web 2 can +connect to it and login as the defined user. + +You can also make use of a dedicated SSH resource to permit access for a +different user than the web-server's one. This way, you can provide a private +key file on the local filesystem that is used to access the remote host. + +To accomplish this, a new resource is required that is defined in your +transport's configuration instead of a user: + +```` +[icinga2] +transport = remote +path = /var/run/icinga2/cmd/icinga2.cmd +host = example.tld +;port = 22 ; Optional. The default is 22 +resource = example.tld-icinga2 +```` + +The resource's configuration needs to be put into the resources.ini file: + +```` +[example.tld-icinga2] +type = ssh +user = icinga +private_key = /etc/icingaweb2/ssh/icinga +```` + +## Configuring transports for different Icinga instances + +If there are multiple but different Icinga instances writing to your IDO you can +define which transport belongs to which Icinga instance by providing the +directive 'instance'. This directive should contain the name of the Icinga +instance you want to assign to the transport: + +```` +[icinga1] +... +instance = icinga1 + +[icinga2] +... +instance = icinga2 +```` + +Associating a transport to a specific Icinga instance causes this transport to +be used to send commands to the linked instance only. Transports without a +linked Icinga instance are utilized to send commands to all instances. \ No newline at end of file diff --git a/modules/monitoring/doc/configuration.md b/modules/monitoring/doc/configuration.md index 7636b1a52..5c3a23aa4 100644 --- a/modules/monitoring/doc/configuration.md +++ b/modules/monitoring/doc/configuration.md @@ -10,7 +10,7 @@ stored in `/etc/icingaweb2` by default (depending on your config setup). modules/monitoring | Directory | `monitoring` module specific configuration modules/monitoring | config.ini | Security settings (e.g. protected custom vars) for the `monitoring` module modules/monitoring | backends.ini | Backend type and resources (e.g. Icinga IDO DB) - modules/monitoring | [instances.ini](instances.md#instances) | Instances and their transport (e.g. local external command pipe) + modules/monitoring | [commandtransports.ini](commandtransports.md#commandtransports) | Command transports for specific Icinga instances diff --git a/modules/monitoring/doc/instances.md b/modules/monitoring/doc/instances.md deleted file mode 100644 index ea8b3f5b8..000000000 --- a/modules/monitoring/doc/instances.md +++ /dev/null @@ -1,55 +0,0 @@ -# The instance.ini configuration file - -## Abstract - -The instance.ini defines how icingaweb accesses the command pipe of your icinga process in order to submit external -commands. Depending on the config path (default: /etc/icingaweb2) of your icingaweb installation you can find it -under ./modules/monitoring/instances.ini. - -## Syntax - -You can define multiple instances in the instances.ini, icingaweb will use the first one as the default instance. - -Every instance starts with a section header containing the name of the instance, followed by the config directives for -this instance in the standard ini format used by icingaweb. - -## Using a local icinga pipe - -A local icinga instance can be easily setup and only requires the 'path' parameter: - - [icinga] - path=/usr/local/icinga/var/rw/icinga.cmd - -When sending commands to the icinga instance, icingaweb just opens the file found underneath 'path' and writes the external -command to it. - -## Using ssh for accessing an icinga pipe - -When providing at least a host directive to the instances.ini, SSH will be used for accessing the pipe. You must have -setup key authentication at the endpoint and allow your icingweb's user to access the machine without a password at this time: - - [icinga] - path=/usr/local/icinga/var/rw/icinga.cmd ; the path on the remote machine where the icinga.cmd can be found - host=my.remote.machine.com ; the hostname or address of the remote machine - port=22 ; the port to use (22 if none is given) - user=jdoe ; the user to authenticate with - -You can also make use of the ssh resource for accessing an icinga pipe with key-based authentication, which will give -you the possibility to define the location of the private key for a specific user, let's have a look: - - [icinga] - path=/usr/local/icinga/var/rw/icinga.cmd ; the path on the remote machine where the icinga.cmd can be found - host=my.remote.machine.com ; the hostname or address of the remote machine - port=22 ; the port to use (22 if none is given) - resource=ssh ; the ssh resource which contains the username and the location of the private key - -And the associated ssh resource: - - [ssh] - type = "ssh" - user = "ssh-user" - private_key = "/etc/icingaweb2/ssh/ssh-user" - - - - diff --git a/modules/monitoring/library/Monitoring/Command/Transport/CommandTransport.php b/modules/monitoring/library/Monitoring/Command/Transport/CommandTransport.php index f39b0b4c3..bb6709ea7 100644 --- a/modules/monitoring/library/Monitoring/Command/Transport/CommandTransport.php +++ b/modules/monitoring/library/Monitoring/Command/Transport/CommandTransport.php @@ -4,15 +4,19 @@ namespace Icinga\Module\Monitoring\Command\Transport; use Icinga\Application\Config; +use Icinga\Application\Logger; use Icinga\Data\ConfigObject; use Icinga\Exception\ConfigurationError; +use Icinga\Module\Monitoring\Command\IcingaCommand; +use Icinga\Module\Monitoring\Command\Object\ObjectCommand; +use Icinga\Module\Monitoring\Exception\CommandTransportException; /** - * Command transport factory + * Command transport * * This class is subject to change as we do not have environments yet (#4471). */ -abstract class CommandTransport +class CommandTransport implements CommandTransportInterface { /** * Transport configuration @@ -24,21 +28,25 @@ abstract class CommandTransport /** * Get transport configuration * - * @return Config - * @throws ConfigurationError + * @return Config + * + * @throws ConfigurationError */ public static function getConfig() { - if (! isset(self::$config)) { - self::$config = Config::module('monitoring', 'instances'); - if (self::$config->isEmpty()) { + if (static::$config === null) { + $config = Config::module('monitoring', 'commandtransports'); + if ($config->isEmpty()) { throw new ConfigurationError( - 'No instances have been configured in \'%s\'.', - self::$config->getConfigFile() + mt('monitoring', 'No command transports have been configured in "%s".'), + $config->getConfigFile() ); } + + static::$config = $config; } - return self::$config; + + return static::$config; } /** @@ -47,9 +55,10 @@ abstract class CommandTransport * @param ConfigObject $config * * @return LocalCommandFile|RemoteCommandFile + * * @throws ConfigurationError */ - public static function fromConfig(ConfigObject $config) + public static function createTransport(ConfigObject $config) { $config = clone $config; switch (strtolower($config->transport)) { @@ -62,14 +71,18 @@ abstract class CommandTransport break; default: throw new ConfigurationError( - 'Can\'t create command transport \'%s\'. Invalid transport defined in \'%s\'.' - . ' Use one of \'%s\' or \'%s\'.', + mt( + 'monitoring', + 'Cannot create command transport "%s". Invalid transport' + . ' defined in "%s". Use one of "%s" or "%s".' + ), $config->transport, - self::$config->getConfigFile(), + static::getConfig()->getConfigFile(), LocalCommandFile::TRANSPORT, RemoteCommandFile::TRANSPORT ); } + unset($config->transport); foreach ($config as $key => $value) { $method = 'set' . ucfirst($key); @@ -79,37 +92,80 @@ abstract class CommandTransport // when being about to send a command continue; } + $transport->$method($value); } + return $transport; } /** - * Create a transport by name + * Send the given command over an appropriate Icinga command transport * - * @param string $name + * This will try one configured transport after another until the command has been successfully sent. * - * @return LocalCommandFile|RemoteCommandFile - * @throws ConfigurationError + * @param IcingaCommand $command The command to send + * @param int|null $now Timestamp of the command or null for now + * + * @throws CommandTransportException If sending the Icinga command failed */ - public static function create($name) + public function send(IcingaCommand $command, $now = null) { - $config = self::getConfig()->getSection($name); - if ($config->isEmpty()) { - throw new ConfigurationError(); + $tries = 0; + foreach (static::getConfig() as $transportConfig) { + $transport = static::createTransport($transportConfig); + + if ($this->transferPossible($command, $transport)) { + try { + $transport->send($command, $now); + } catch (CommandTransportException $e) { + Logger::error($e); + $tries += 1; + continue; // Try the next transport + } + + return; // The command was successfully sent + } } - return self::fromConfig($config); + + if ($tries > 0) { + throw new CommandTransportException( + mt( + 'monitoring', + 'Failed to send external Icinga command. None of the configured transports' + . ' was able to transfer the command. Please see the log for more details.' + ) + ); + } + + throw new CommandTransportException( + mt( + 'monitoring', + 'Failed to send external Icinga command. No transport has been configured' + . ' for this instance. Please contact your Icinga Web administrator.' + ) + ); } /** - * Create a transport by the first section of the configuration + * Return whether it is possible to send the given command using the given transport * - * @return LocalCommandFile|RemoteCommandFile + * @param IcingaCommand $command + * @param CommandTransportInterface $transport + * + * @return bool */ - public static function first() + protected function transferPossible($command, $transport) { - $config = self::getConfig(); - $config->rewind(); - return self::fromConfig($config->current()); + if (! method_exists($transport, 'getInstance') || !$command instanceof ObjectCommand) { + return true; + } + + $transportInstance = $transport->getInstance(); + if (! $transportInstance || $transportInstance === 'none') { + return true; + } + + return strtolower($transportInstance) === strtolower($command->getObject()->instance_name); } } diff --git a/modules/monitoring/library/Monitoring/Command/Transport/LocalCommandFile.php b/modules/monitoring/library/Monitoring/Command/Transport/LocalCommandFile.php index 00756da9d..b2f21a05e 100644 --- a/modules/monitoring/library/Monitoring/Command/Transport/LocalCommandFile.php +++ b/modules/monitoring/library/Monitoring/Command/Transport/LocalCommandFile.php @@ -22,6 +22,13 @@ class LocalCommandFile implements CommandTransportInterface */ const TRANSPORT = 'local'; + /** + * The name of the Icinga instance this transport will transfer commands to + * + * @var string + */ + protected $instanceName; + /** * Path to the icinga command file * @@ -51,6 +58,29 @@ class LocalCommandFile implements CommandTransportInterface $this->renderer = new IcingaCommandFileCommandRenderer(); } + /** + * Set the name of the Icinga instance this transport will transfer commands to + * + * @param string $name + * + * @return $this + */ + public function setInstance($name) + { + $this->instanceName = $name; + return $this; + } + + /** + * Return the name of the Icinga instance this transport will transfer commands to + * + * @return string + */ + public function getInstance() + { + return $this->instanceName; + } + /** * Set the path to the local Icinga command file * diff --git a/modules/monitoring/library/Monitoring/Command/Transport/RemoteCommandFile.php b/modules/monitoring/library/Monitoring/Command/Transport/RemoteCommandFile.php index 383970b76..a0f597072 100644 --- a/modules/monitoring/library/Monitoring/Command/Transport/RemoteCommandFile.php +++ b/modules/monitoring/library/Monitoring/Command/Transport/RemoteCommandFile.php @@ -22,6 +22,13 @@ class RemoteCommandFile implements CommandTransportInterface */ const TRANSPORT = 'remote'; + /** + * The name of the Icinga instance this transport will transfer commands to + * + * @var string + */ + protected $instanceName; + /** * Remote host * @@ -74,6 +81,29 @@ class RemoteCommandFile implements CommandTransportInterface $this->renderer = new IcingaCommandFileCommandRenderer(); } + /** + * Set the name of the Icinga instance this transport will transfer commands to + * + * @param string $name + * + * @return $this + */ + public function setInstance($name) + { + $this->instanceName = $name; + return $this; + } + + /** + * Return the name of the Icinga instance this transport will transfer commands to + * + * @return string + */ + public function getInstance() + { + return $this->instanceName; + } + /** * Set the remote host * diff --git a/modules/monitoring/library/Monitoring/MonitoringWizard.php b/modules/monitoring/library/Monitoring/MonitoringWizard.php index 4b495c36e..c35705f62 100644 --- a/modules/monitoring/library/Monitoring/MonitoringWizard.php +++ b/modules/monitoring/library/Monitoring/MonitoringWizard.php @@ -12,8 +12,8 @@ use Icinga\Module\Setup\RequirementSet; use Icinga\Module\Setup\Forms\SummaryPage; use Icinga\Module\Monitoring\Forms\Setup\WelcomePage; use Icinga\Module\Monitoring\Forms\Setup\BackendPage; -use Icinga\Module\Monitoring\Forms\Setup\InstancePage; use Icinga\Module\Monitoring\Forms\Setup\SecurityPage; +use Icinga\Module\Monitoring\Forms\Setup\TransportPage; use Icinga\Module\Monitoring\Forms\Setup\IdoResourcePage; use Icinga\Module\Monitoring\Forms\Setup\LivestatusResourcePage; use Icinga\Module\Setup\Requirement\ClassRequirement; @@ -33,7 +33,7 @@ class MonitoringWizard extends Wizard implements SetupWizard $this->addPage(new BackendPage()); $this->addPage(new IdoResourcePage()); $this->addPage(new LivestatusResourcePage()); - $this->addPage(new InstancePage()); + $this->addPage(new TransportPage()); $this->addPage(new SecurityPage()); $this->addPage(new SummaryPage(array('name' => 'setup_monitoring_summary'))); } @@ -150,8 +150,8 @@ class MonitoringWizard extends Wizard implements SetupWizard ); $setup->addStep( - new InstanceStep(array( - 'instanceConfig' => $pageData['setup_monitoring_instance'] + new TransportStep(array( + 'transportConfig' => $pageData['setup_command_transport'] )) ); diff --git a/modules/monitoring/library/Monitoring/InstanceStep.php b/modules/monitoring/library/Monitoring/TransportStep.php similarity index 67% rename from modules/monitoring/library/Monitoring/InstanceStep.php rename to modules/monitoring/library/Monitoring/TransportStep.php index 7aa1ef760..936c9a6de 100644 --- a/modules/monitoring/library/Monitoring/InstanceStep.php +++ b/modules/monitoring/library/Monitoring/TransportStep.php @@ -8,7 +8,7 @@ use Icinga\Module\Setup\Step; use Icinga\Application\Config; use Icinga\Exception\IcingaException; -class InstanceStep extends Step +class TransportStep extends Step { protected $data; @@ -21,13 +21,13 @@ class InstanceStep extends Step public function apply() { - $instanceConfig = $this->data['instanceConfig']; - $instanceName = $instanceConfig['name']; - unset($instanceConfig['name']); + $transportConfig = $this->data['transportConfig']; + $transportName = $transportConfig['name']; + unset($transportConfig['name']); try { - Config::fromArray(array($instanceName => $instanceConfig)) - ->setConfigFile(Config::resolvePath('modules/monitoring/instances.ini')) + Config::fromArray(array($transportName => $transportConfig)) + ->setConfigFile(Config::resolvePath('modules/monitoring/commandtransports.ini')) ->saveIni(); } catch (Exception $e) { $this->error = $e; @@ -40,16 +40,16 @@ class InstanceStep extends Step public function getSummary() { - $pageTitle = '

' . mt('monitoring', 'Monitoring Instance', 'setup.page.title') . '

'; + $pageTitle = '

' . mt('monitoring', 'Command Transport', 'setup.page.title') . '

'; - if (isset($this->data['instanceConfig']['host'])) { + if (isset($this->data['transportConfig']['host'])) { $pipeHtml = '

' . sprintf( mt( 'monitoring', 'Icinga Web 2 will use the named pipe located on a remote machine at "%s" to send commands' . ' to your monitoring instance by using the connection details listed below:' ), - $this->data['instanceConfig']['path'] + $this->data['transportConfig']['path'] ) . '

'; $pipeHtml .= '' @@ -57,15 +57,15 @@ class InstanceStep extends Step . '' . '' . '' - . '' + . '' . '' . '' . '' - . '' + . '' . '' . '' . '' - . '' + . '' . '' . '' . '
translate('Instance'); ?>translate('Transport'); ?> translate('Remove'); ?>
qlink( - $instanceName, - '/monitoring/config/editinstance', - array('instance' => $instanceName), + $transportName, + '/monitoring/config/edittransport', + array('transport' => $transportName), array( 'icon' => 'edit', - 'title' => sprintf($this->translate('Edit monitoring instance %s'), $instanceName) + 'title' => sprintf($this->translate('Edit command transport %s'), $transportName) ) ); ?> ( qlink( '', - '/monitoring/config/removeinstance', - array('instance' => $instanceName), + '/monitoring/config/removetransport', + array('transport' => $transportName), array( 'icon' => 'trash', - 'title' => sprintf($this->translate('Remove monitoring instance %s'), $instanceName) + 'title' => sprintf($this->translate('Remove command transport %s'), $transportName) ) ); ?>
' . mt('monitoring', 'Remote Host') . '' . $this->data['instanceConfig']['host'] . '' . $this->data['transportConfig']['host'] . '
' . mt('monitoring', 'Remote SSH Port') . '' . $this->data['instanceConfig']['port'] . '' . $this->data['transportConfig']['port'] . '
' . mt('monitoring', 'Remote SSH User') . '' . $this->data['instanceConfig']['user'] . '' . $this->data['transportConfig']['user'] . '
'; @@ -76,7 +76,7 @@ class InstanceStep extends Step 'Icinga Web 2 will use the named pipe located at "%s"' . ' to send commands to your monitoring instance.' ), - $this->data['instanceConfig']['path'] + $this->data['transportConfig']['path'] ) . '

'; } @@ -87,17 +87,17 @@ class InstanceStep extends Step { if ($this->error === false) { return array(sprintf( - mt('monitoring', 'Monitoring instance configuration has been successfully created: %s'), - Config::resolvePath('modules/monitoring/instances.ini') + mt('monitoring', 'Command transport configuration has been successfully created: %s'), + Config::resolvePath('modules/monitoring/commandtransports.ini') )); } elseif ($this->error !== null) { return array( sprintf( mt( 'monitoring', - 'Monitoring instance configuration could not be written to: %s. An error occured:' + 'Command transport configuration could not be written to: %s. An error occured:' ), - Config::resolvePath('modules/monitoring/instances.ini') + Config::resolvePath('modules/monitoring/commandtransports.ini') ), sprintf(mt('setup', 'ERROR: %s'), IcingaException::describe($this->error)) ); diff --git a/packages/RPM.md b/packages/RPM.md index 6daf41a63..9c8e4a548 100644 --- a/packages/RPM.md +++ b/packages/RPM.md @@ -89,7 +89,7 @@ By default the Icinga 2 DB IDO is used by the monitoring module in The external command pipe is required for sending commands and configured for Icinga 2 in -`/etc/icingaweb2/modules/monitoring/instances.ini` +`/etc/icingaweb2/modules/monitoring/commandtransports.ini` #### Authentication configuration