diff options
Diffstat (limited to 'application/forms/IcingaServiceForm.php')
-rw-r--r-- | application/forms/IcingaServiceForm.php | 806 |
1 files changed, 806 insertions, 0 deletions
diff --git a/application/forms/IcingaServiceForm.php b/application/forms/IcingaServiceForm.php new file mode 100644 index 0000000..f22f9e6 --- /dev/null +++ b/application/forms/IcingaServiceForm.php @@ -0,0 +1,806 @@ +<?php + +namespace Icinga\Module\Director\Forms; + +use gipfl\Web\Widget\Hint; +use Icinga\Data\Filter\Filter; +use Icinga\Exception\IcingaException; +use Icinga\Exception\ProgrammingError; +use Icinga\Module\Director\Data\PropertiesFilter\ArrayCustomVariablesFilter; +use Icinga\Module\Director\Exception\NestingError; +use Icinga\Module\Director\Web\Form\DirectorObjectForm; +use Icinga\Module\Director\Objects\IcingaHost; +use Icinga\Module\Director\Objects\IcingaService; +use Icinga\Module\Director\Objects\IcingaServiceSet; +use Icinga\Module\Director\Web\Table\ObjectsTableHost; +use ipl\Html\Html; +use gipfl\IcingaWeb2\Link; +use RuntimeException; + +class IcingaServiceForm extends DirectorObjectForm +{ + /** @var IcingaHost */ + private $host; + + /** @var IcingaServiceSet */ + private $set; + + private $apply; + + /** @var IcingaService */ + protected $object; + + /** @var IcingaService */ + private $applyGenerated; + + private $inheritedFrom; + + /** @var bool|null */ + private $blacklisted; + + public function setApplyGenerated(IcingaService $applyGenerated) + { + $this->applyGenerated = $applyGenerated; + + return $this; + } + + public function setInheritedFrom($hostname) + { + $this->inheritedFrom = $hostname; + + return $this; + } + + /** + * @throws IcingaException + * @throws ProgrammingError + * @throws \Zend_Form_Exception + */ + public function setup() + { + if (!$this->isNew() || $this->providesOverrides()) { + $this->tryToFetchHost(); + } + + if ($this->providesOverrides()) { + return; + } + + if ($this->host && $this->set) { + // Probably never reached, as providesOverrides includes this + $this->setupOnHostForSet(); + + return; + } + + if ($this->set !== null) { + $this->setupSetRelatedElements(); + } elseif ($this->host === null) { + $this->setupServiceElements(); + } else { + $this->setupHostRelatedElements(); + } + } + + protected function tryToFetchHost() + { + try { + if ($this->host === null) { + $this->host = $this->object->getResolvedRelated('host'); + } + } catch (NestingError $nestingError) { + // ignore for the form to load + } + } + + public function providesOverrides() + { + return $this->applyGenerated + || $this->inheritedFrom + || ($this->host && $this->set) + || ($this->object && $this->object->usesVarOverrides()); + } + + /** + * @throws IcingaException + * @throws ProgrammingError + * @throws \Zend_Form_Exception + */ + protected function addFields() + { + if ($this->providesOverrides() && $this->hasBeenBlacklisted()) { + $this->onAddedFields(); + + return; + } else { + parent::addFields(); + } + } + + /** + * @throws IcingaException + * @throws ProgrammingError + * @throws \Zend_Form_Exception + */ + protected function onAddedFields() + { + if (! $this->providesOverrides()) { + return; + } + $hasDeleteButton = false; + $isBranch = $this->branch && $this->branch->isBranch(); + + if ($this->hasBeenBlacklisted()) { + $this->addHtml( + Hint::warning($this->translate('This Service has been deactivated on this host')), + ['name' => 'HINT_blacklisted'] + ); + $group = null; + if (! $isBranch) { + $this->addDeleteButton($this->translate('Reactivate')); + $hasDeleteButton = true; + } + $this->setSubmitLabel(false); + } else { + $this->addOverrideHint(); + $group = $this->getDisplayGroup('custom_fields'); + if (! $group) { + foreach ($this->getDisplayGroups() as $groupName => $eventualGroup) { + if (preg_match('/^custom_fields:/', $groupName)) { + $group = $eventualGroup; + break; + } + } + } + if ($group) { + $elements = $group->getElements(); + $group->setElements([$this->getElement('inheritance_hint')]); + $group->addElements($elements); + $this->setSubmitLabel($this->translate('Override vars')); + } else { + $this->addElementsToGroup( + ['inheritance_hint'], + 'custom_fields', + 20, + $this->translate('Hints regarding this service') + ); + + $this->setSubmitLabel(false); + } + + if (! $isBranch) { + $this->addDeleteButton($this->translate('Deactivate')); + $hasDeleteButton = true; + } + } + + if (! $this->hasSubmitButton() && $hasDeleteButton) { + $this->addDisplayGroup([$this->deleteButtonName], 'buttons', [ + 'decorators' => [ + 'FormElements', + ['HtmlTag', ['tag' => 'dl']], + 'DtDdWrapper', + ], + 'order' => self::GROUP_ORDER_BUTTONS, + ]); + } + } + + /** + * @return IcingaHost|null + */ + public function getHost() + { + return $this->host; + } + + /** + * @param IcingaService $service + * @return IcingaService + * @throws \Icinga\Exception\NotFoundError + */ + protected function getFirstParent(IcingaService $service) + { + /** @var IcingaService[] $objects */ + $objects = $service->imports()->getObjects(); + if (empty($objects)) { + throw new RuntimeException('Something went wrong, got no parent'); + } + reset($objects); + + return current($objects); + } + + /** + * @return bool + * @throws \Icinga\Exception\NotFoundError + */ + protected function hasBeenBlacklisted() + { + if (! $this->providesOverrides() || $this->object === null) { + return false; + } + + if ($this->blacklisted === null) { + $host = $this->host; + // Safety check, branches + $hostId = $host->get('id'); + $service = $this->getServiceToBeBlacklisted(); + $serviceId = $service->get('id'); + if (! $hostId || ! $serviceId) { + return false; + } + $db = $this->db->getDbAdapter(); + if ($this->providesOverrides()) { + $this->blacklisted = 1 === (int)$db->fetchOne( + $db->select()->from('icinga_host_service_blacklist', 'COUNT(*)') + ->where('host_id = ?', $hostId) + ->where('service_id = ?', $serviceId) + ); + } else { + $this->blacklisted = false; + } + } + + return $this->blacklisted; + } + + /** + * @param $object + * @throws IcingaException + * @throws ProgrammingError + * @throws \Zend_Db_Adapter_Exception + */ + protected function deleteObject($object) + { + /** @var IcingaService $object */ + if ($this->providesOverrides()) { + if ($this->hasBeenBlacklisted()) { + $this->removeFromBlacklist(); + } else { + $this->blacklist(); + } + } else { + parent::deleteObject($object); + } + } + + /** + * @throws IcingaException + * @throws \Zend_Db_Adapter_Exception + */ + protected function blacklist() + { + $host = $this->host; + $service = $this->getServiceToBeBlacklisted(); + + $db = $this->db->getDbAdapter(); + $host->unsetOverriddenServiceVars($this->object->getObjectName())->store(); + + if ($db->insert('icinga_host_service_blacklist', [ + 'host_id' => $host->get('id'), + 'service_id' => $service->get('id') + ])) { + $msg = sprintf( + $this->translate('%s has been deactivated on %s'), + $service->getObjectName(), + $host->getObjectName() + ); + $this->redirectOnSuccess($msg); + } + } + + /** + * @return IcingaService + * @throws \Icinga\Exception\NotFoundError + */ + protected function getServiceToBeBlacklisted() + { + if ($this->set) { + return $this->object; + } else { + return $this->getFirstParent($this->object); + } + } + + /** + * @throws \Icinga\Exception\NotFoundError + */ + protected function removeFromBlacklist() + { + $host = $this->host; + $service = $this->getServiceToBeBlacklisted(); + + $db = $this->db->getDbAdapter(); + $where = implode(' AND ', [ + $db->quoteInto('host_id = ?', $host->get('id')), + $db->quoteInto('service_id = ?', $service->get('id')), + ]); + if ($db->delete('icinga_host_service_blacklist', $where)) { + $msg = sprintf( + $this->translate('%s is no longer deactivated on %s'), + $service->getObjectName(), + $host->getObjectName() + ); + $this->redirectOnSuccess($msg); + } + } + + /** + * @param IcingaService $service + * @return $this + */ + public function createApplyRuleFor(IcingaService $service) + { + $this->apply = $service; + $object = $this->object(); + $object->set('imports', $service->getObjectName()); + $object->set('object_type', 'apply'); + $object->set('object_name', $service->getObjectName()); + + return $this; + } + + /** + * @throws \Zend_Form_Exception + */ + protected function setupServiceElements() + { + if ($this->object) { + $objectType = $this->object->get('object_type'); + } elseif ($this->preferredObjectType) { + $objectType = $this->preferredObjectType; + } else { + $objectType = 'template'; + } + $this->addHidden('object_type', $objectType); + $forceCommandElements = $this->hasPermission('director/admin'); + + $this->addNameElement() + ->addHostObjectElement() + ->addImportsElement() + ->addChoices('service') + ->addGroupsElement() + ->addDisabledElement() + ->addApplyForElement() + ->groupMainProperties() + ->addAssignmentElements() + ->addCheckCommandElements($forceCommandElements) + ->addCheckExecutionElements() + ->addExtraInfoElements() + ->addAgentAndZoneElements() + ->setButtons(); + } + + /** + * @throws IcingaException + * @throws ProgrammingError + */ + protected function addOverrideHint() + { + if ($this->object && $this->object->usesVarOverrides()) { + $hint = $this->translate( + 'This service has been generated in an automated way, but still' + . ' allows you to override the following properties in a safe way.' + ); + } elseif ($apply = $this->applyGenerated) { + $hint = Html::sprintf( + $this->translate( + 'This service has been generated using the %s apply rule, assigned where %s' + ), + Link::create( + $apply->getObjectName(), + 'director/service', + ['id' => $apply->get('id')], + ['data-base-target' => '_next'] + ), + (string) Filter::fromQueryString($apply->assign_filter) + ); + } elseif ($this->host && $this->set) { + $hint = Html::sprintf( + $this->translate( + 'This service belongs to the %s Service Set. Still, you might want' + . ' to override the following properties for this host only.' + ), + Link::create( + $this->set->getObjectName(), + 'director/serviceset', + ['id' => $this->set->get('id')], + ['data-base-target' => '_next'] + ) + ); + } elseif ($this->inheritedFrom) { + $msg = $this->translate( + 'This service has been inherited from %s. Still, you might want' + . ' to change the following properties for this host only.' + ); + + $name = $this->inheritedFrom; + $link = Link::create( + $name, + 'director/service', + [ + 'host' => $name, + 'name' => $this->object->getObjectName(), + ], + ['data-base-target' => '_next'] + ); + + $hint = Html::sprintf($msg, $link); + } else { + throw new ProgrammingError('Got no override hint for your situation'); + } + + $this->setSubmitLabel($this->translate('Override vars')); + + $this->addHtmlHint($hint, ['name' => 'inheritance_hint']); + } + + protected function setupOnHostForSet() + { + $msg = $this->translate( + 'This service belongs to the service set "%s". Still, you might want' + . ' to change the following properties for this host only.' + ); + + $name = $this->set->getObjectName(); + $link = Link::create( + $name, + 'director/serviceset', + ['name' => $name], + ['data-base-target' => '_next'] + ); + + $this->addHtmlHint( + Html::sprintf($msg, $link), + ['name' => 'inheritance_hint'] + ); + + $this->addElementsToGroup( + ['inheritance_hint'], + 'custom_fields', + 50, + $this->translate('Custom properties') + ); + + $this->setSubmitLabel($this->translate('Override vars')); + } + + protected function addAssignmentElements() + { + $this->addAssignFilter([ + 'suggestionContext' => 'HostFilterColumns', + 'required' => true, + 'description' => $this->translate( + 'This allows you to configure an assignment filter. Please feel' + . ' free to combine as many nested operators as you want. The' + . ' "contains" operator is valid for arrays only. Please use' + . ' wildcards and the = (equals) operator when searching for' + . ' partial string matches, like in *.example.com' + ) + ]); + + return $this; + } + + /** + * @throws \Zend_Form_Exception + */ + protected function setupHostRelatedElements() + { + $this->addHidden('host', $this->host->getObjectName()); + $this->addHidden('object_type', 'object'); + $this->addImportsElement(); + $imports = $this->getSentOrObjectValue('imports'); + + if ($this->hasBeenSent()) { + $imports = $this->getElement('imports')->setValue($imports)->getValue(); + } + + if ($this->isNew() && empty($imports)) { + $this->groupMainProperties(); + return; + } + + $this->addNameElement() + ->addChoices('service') + ->addDisabledElement() + ->addGroupsElement() + ->groupMainProperties() + ->addCheckCommandElements() + ->addExtraInfoElements() + ->setButtons(); + + $this->setDefaultNameFromTemplate($imports); + } + + /** + * @param IcingaHost $host + * @return $this + */ + public function setHost(IcingaHost $host) + { + $this->host = $host; + return $this; + } + + /** + * @throws \Zend_Form_Exception + */ + protected function setupSetRelatedElements() + { + $this->addHidden('service_set', $this->set->getObjectName()); + $this->addHidden('object_type', 'apply'); + $this->addImportsElement(); + $this->setButtons(); + $imports = $this->getSentOrObjectValue('imports'); + + if ($this->hasBeenSent()) { + $imports = $this->getElement('imports')->setValue($imports)->getValue(); + } + + if ($this->isNew() && empty($imports)) { + $this->groupMainProperties(); + return; + } + + $this->addNameElement() + ->addDisabledElement() + ->addGroupsElement() + ->groupMainProperties(); + + if ($this->hasPermission('director/admin')) { + $this->addCheckCommandElements(true) + ->addCheckExecutionElements(true) + ->addExtraInfoElements(); + } + + $this->setDefaultNameFromTemplate($imports); + } + + public function setServiceSet(IcingaServiceSet $set) + { + $this->set = $set; + return $this; + } + + /** + * @return $this + * @throws \Zend_Form_Exception + */ + protected function addNameElement() + { + $this->addElement('text', 'object_name', array( + 'label' => $this->translate('Name'), + 'required' => !$this->object()->isApplyRule(), + 'description' => $this->translate( + 'Name for the Icinga service you are going to create' + ) + )); + + if ($this->object()->isApplyRule()) { + $this->eventuallyAddNameRestriction('director/service/apply/filter-by-name'); + } + + return $this; + } + + /** + * @return $this + * @throws \Zend_Form_Exception + */ + protected function addHostObjectElement() + { + if ($this->isObject()) { + $this->addElement('select', 'host', [ + 'label' => $this->translate('Host'), + 'required' => true, + 'multiOptions' => $this->optionalEnum($this->enumHostsAndTemplates()), + 'description' => $this->translate( + 'Choose the host this single service should be assigned to' + ) + ]); + } + + return $this; + } + + /** + * @return $this + * @throws \Zend_Form_Exception + */ + protected function addApplyForElement() + { + if ($this->object->isApplyRule()) { + $hostProperties = IcingaHost::enumProperties( + $this->object->getConnection(), + 'host.', + new ArrayCustomVariablesFilter() + ); + + $this->addElement('select', 'apply_for', array( + 'label' => $this->translate('Apply For'), + 'class' => 'assign-property autosubmit', + 'multiOptions' => $this->optionalEnum($hostProperties, $this->translate('None')), + 'description' => $this->translate( + 'Evaluates the apply for rule for ' . + 'all objects with the custom attribute specified. ' . + 'E.g selecting "host.vars.custom_attr" will generate "for (config in ' . + 'host.vars.array_var)" where "config" will be accessible through "$config$". ' . + 'NOTE: only custom variables of type "Array" are eligible.' + ) + )); + } + + return $this; + } + + /** + * @return $this + * @throws \Zend_Form_Exception + */ + protected function addGroupsElement() + { + $groups = $this->enumServicegroups(); + + if (! empty($groups)) { + $this->addElement('extensibleSet', 'groups', array( + 'label' => $this->translate('Groups'), + 'multiOptions' => $this->optionallyAddFromEnum($groups), + 'positional' => false, + 'description' => $this->translate( + 'Service groups that should be directly assigned to this service.' + . ' Servicegroups can be useful for various reasons. They are' + . ' helpful to provided service-type specific view in Icinga Web 2,' + . ' either for custom dashboards or as an instrument to enforce' + . ' restrictions. Service groups can be directly assigned to' + . ' single services or to service templates.' + ) + )); + } + + return $this; + } + + /** + * @return $this + * @throws \Zend_Form_Exception + */ + protected function addAgentAndZoneElements() + { + if (!$this->isTemplate()) { + return $this; + } + + $this->optionalBoolean( + 'use_agent', + $this->translate('Run on agent'), + $this->translate( + 'Whether the check commmand for this service should be executed' + . ' on the Icinga agent' + ) + ); + $this->addZoneElement(); + + $elements = array( + 'use_agent', + 'zone_id', + ); + $this->addDisplayGroup($elements, 'clustering', array( + 'decorators' => array( + 'FormElements', + array('HtmlTag', array('tag' => 'dl')), + 'Fieldset', + ), + 'order' => self::GROUP_ORDER_CLUSTERING, + 'legend' => $this->translate('Icinga Agent and zone settings') + )); + + return $this; + } + + protected function enumHostsAndTemplates() + { + if ($this->branch && $this->branch->isBranch()) { + return $this->enumHosts(); + } + + return [ + $this->translate('Templates') => $this->enumHostTemplates(), + $this->translate('Hosts') => $this->enumHosts(), + ]; + } + + protected function enumHostTemplates() + { + $names = array_values($this->db->enumHostTemplates()); + return array_combine($names, $names); + } + + protected function enumHosts() + { + $db = $this->db->getDbAdapter(); + $table = new ObjectsTableHost($this->db); + $table->setAuth($this->getAuth()); + if ($this->branch && $this->branch->isBranch()) { + $table->setBranchUuid($this->branch->getUuid()); + } + $result = []; + foreach ($db->fetchAll($table->getQuery()->reset(\Zend_Db_Select::LIMIT_COUNT)) as $row) { + $result[$row->object_name] = $row->object_name; + } + + return $result; + } + + protected function enumServicegroups() + { + $db = $this->db->getDbAdapter(); + $select = $db->select()->from( + 'icinga_servicegroup', + array( + 'name' => 'object_name', + 'display' => 'COALESCE(display_name, object_name)' + ) + )->where('object_type = ?', 'object')->order('display'); + + return $db->fetchPairs($select); + } + + protected function succeedForOverrides() + { + $vars = array(); + foreach ($this->object->vars() as $key => $var) { + $vars[$key] = $var->getValue(); + } + + $host = $this->host; + $serviceName = $this->object->getObjectName(); + + $this->host->overrideServiceVars($serviceName, (object) $vars); + + if ($host->hasBeenModified()) { + $msg = sprintf( + empty($vars) + ? $this->translate('All overrides have been removed from "%s"') + : $this->translate('The given properties have been stored for "%s"'), + $this->translate($host->getObjectName()) + ); + + $this->getDbObjectStore()->store($host); + } else { + if ($this->isApiRequest()) { + $this->setHttpResponseCode(304); + } + + $msg = $this->translate('No action taken, object has not been modified'); + } + + $this->redirectOnSuccess($msg); + } + + public function onSuccess() + { + if ($this->providesOverrides()) { + $this->succeedForOverrides(); + return; + } + + parent::onSuccess(); + } + + /** + * @param array $imports + */ + protected function setDefaultNameFromTemplate($imports) + { + if ($this->hasBeenSent()) { + $name = $this->getSentOrObjectValue('object_name'); + if ($name === null || !strlen($name)) { + $this->setElementValue('object_name', end($imports)); + $this->object->set('object_name', end($imports)); + } + } + } +} |