Add support for Apply for rules in services

Apply for rules are defined through `apply_for` property
Only Array custom variables will be eligible in apply_for dropdown.

API Example:
```bash
 ./director-curl POST director/service?name=my_service '{"apply_for": "for (checked_tcp_port in host.vars.checked_tcp_ports)" }'
 ```

 Will render as:
 ```
  apply Service "my_service" for (checked_tcp_port in host.vars.checked_tcp_ports) {
      ...
  }
  ```
Feature: https://dev.icinga.org/issues/11976

Depends-On: https://github.com/Icinga/icingaweb2-module-director/pull/20
refs #11976
This commit is contained in:
Corentin Ardeois 2016-09-06 11:18:29 -04:00 committed by Thomas Gelf
parent 0c5a568191
commit e7bd4343b5
10 changed files with 358 additions and 27 deletions

View File

@ -2,6 +2,7 @@
namespace Icinga\Module\Director\Forms;
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;
@ -81,6 +82,7 @@ class IcingaServiceForm extends DirectorObjectForm
->addImportsElement()
->addGroupsElement()
->addDisabledElement()
->addApplyForElement()
->groupMainProperties()
->addAssignmentElements()
->addCheckCommandElements()
@ -235,6 +237,67 @@ class IcingaServiceForm extends DirectorObjectForm
return $this;
}
protected function groupMainProperties()
{
$elements = array(
'imports',
'object_name',
'display_name',
'host_id',
'address',
'address6',
'groups',
'users',
'user_groups',
'apply_to',
'command_id', // Notification
'notification_interval',
'period_id',
'times_begin',
'times_end',
'email',
'pager',
'enable_notifications',
'create_live',
'disabled',
'apply_for'
);
$this->addDisplayGroup($elements, 'object_definition', array(
'decorators' => array(
'FormElements',
array('HtmlTag', array('tag' => 'dl')),
'Fieldset',
),
'order' => 20,
'legend' => $this->translate('Main properties')
));
return $this;
}
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, '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 (value in ' .
'host.vars.array_var)" where "value" will be accessible through "$$value$$". ' .
'NOTE: only custom variables of type "Array" are eligible.'
)
));
}
return $this;
}
protected function addGroupsElement()
{
$groups = $this->enumServicegroups();

View File

@ -0,0 +1,23 @@
<?php
namespace Icinga\Module\Director\Data;
class PropertiesFilter
{
public static $CUSTOM_PROPERTY = 'CUSTOM_PROPERTY';
public static $HOST_PROPERTY = 'HOST_PROPERTY';
protected $blacklist = array(
'id',
'object_name',
'object_type',
'disabled',
'has_agent',
'master_should_connect',
'accept_config',
);
public function match($type, $name, $object=null) {
return ($type != self::$HOST_PROPERTY || !in_array($name, $this->blacklist));
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace Icinga\Module\Director\Data\PropertiesFilter;
class ArrayCustomVariablesFilter extends CustomVariablesFilter {
public function match($type, $name, $object=null) {
return parent::match($type, $name, $object)
&& $object !== null
&& preg_match('/DataTypeArray[\w]*$/', $object->datatype);
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace Icinga\Module\Director\Data\PropertiesFilter;
use Icinga\Module\Director\Data\PropertiesFilter;
class CustomVariablesFilter extends PropertiesFilter
{
public function match($type, $name, $object=null) {
return parent::match($type, $name, $object) && $type === self::$CUSTOM_PROPERTY;
}
}

View File

@ -4,6 +4,7 @@ namespace Icinga\Module\Director\Objects;
use Icinga\Data\Db\DbConnection;
use Icinga\Exception\NotFoundError;
use Icinga\Module\Director\Data\PropertiesFilter;
use Icinga\Module\Director\Db;
use Icinga\Module\Director\IcingaConfig\IcingaConfig;
use Icinga\Module\Director\Web\Form\DirectorObjectForm;
@ -81,31 +82,27 @@ class IcingaHost extends IcingaObject
protected $supportsFields = true;
public static function enumProperties(DbConnection $connection = null, $prefix = '')
public static function enumProperties(DbConnection $connection = null, $prefix = '', $filter= null)
{
$hostProperties = array($prefix . 'name' => 'name');
$hostProperties = array();
if ($filter === null) {
$filter = new PropertiesFilter();
}
$realProperties = static::create()->listProperties();
sort($realProperties);
$blacklist = array(
'id',
'object_name',
'object_type',
'disabled',
'has_agent',
'master_should_connect',
'accept_config',
);
if ($filter->match(PropertiesFilter::$HOST_PROPERTY, 'name')) {
$hostProperties[$prefix . 'name'] = 'name';
}
foreach ($realProperties as $prop) {
if (in_array($prop, $blacklist)) {
if (!$filter->match(PropertiesFilter::$HOST_PROPERTY, $prop)) {
continue;
}
if (substr($prop, -3) === '_id') {
$prop = substr($prop, 0, -3);
}
$hostProperties[$prefix . $prop] = $prop;
}
@ -113,14 +110,16 @@ class IcingaHost extends IcingaObject
if ($connection !== null) {
foreach ($connection->fetchDistinctHostVars() as $var) {
if ($var->datatype) {
$hostVars[$prefix . 'vars.' . $var->varname] = sprintf(
'%s (%s)',
$var->varname,
$var->caption
);
} else {
$hostVars[$prefix . 'vars.' . $var->varname] = $var->varname;
if ($filter->match(PropertiesFilter::$CUSTOM_PROPERTY, $var->varname, $var)) {
if ($var->datatype) {
$hostVars[$prefix . 'vars.' . $var->varname] = sprintf(
'%s (%s)',
$var->varname,
$var->caption
);
} else {
$hostVars[$prefix . 'vars.' . $var->varname] = $var->varname;
}
}
}
}
@ -131,9 +130,11 @@ class IcingaHost extends IcingaObject
$props = mt('director', 'Host properties');
$vars = mt('director', 'Custom variables');
$properties = array(
$props => $hostProperties,
);
$properties = array();
if (!empty($hostProperties)) {
$properties[$props] = $hostProperties;
}
if (!empty($hostVars)) {
$properties[$vars] = $hostVars;

View File

@ -39,6 +39,7 @@ class IcingaService extends IcingaObject
'icon_image' => null,
'icon_image_alt' => null,
'use_agent' => null,
'apply_for' => null,
'use_var_overrides' => null,
);
@ -84,6 +85,13 @@ class IcingaService extends IcingaObject
protected $prioritizedProperties = array('host_id');
protected $propertiesNotForRendering = array(
'id',
'object_name',
'object_type',
'apply_for'
);
public function getCheckCommand()
{
$id = $this->getResolvedProperty('check_command_id');
@ -144,6 +152,23 @@ class IcingaService extends IcingaObject
return $this->renderRelationProperty('host', $this->host_id, 'host_name');
}
protected function renderObjectHeader()
{
if ($this->isApplyRule()
&& !$this->hasBeenAssignedToHostTemplate()
&& $this->get('apply_for') !== null) {
return sprintf(
"%s %s %s for (value in %s) {\n",
$this->getObjectTypeName(),
$this->getType(),
c::renderString($this->getObjectName()),
$this->get('apply_for')
);
}
return parent::renderObjectHeader();
}
protected function renderAssignments()
{
if (! $this->hasBeenAssignedToHostTemplate()) {

View File

@ -72,10 +72,10 @@ abstract class QuickBaseForm extends Zend_Form
return $this;
}
public function optionalEnum($enum)
public function optionalEnum($enum, $text='- please choose -')
{
return array(
null => $this->translate('- please choose -')
null => $this->translate($text)
) + $enum;
}

View File

@ -0,0 +1,6 @@
ALTER TABLE icinga_service
ADD COLUMN apply_for VARCHAR(255) DEFAULT NULL AFTER use_agent;
INSERT INTO director_schema_migration
(schema_version, migration_time)
VALUES (110, NOW());

View File

@ -0,0 +1,6 @@
ALTER TABLE icinga_service
ADD COLUMN apply_for character varying(255) DEFAULT NULL;
INSERT INTO director_schema_migration
(schema_version, migration_time)
VALUES (110, NOW());

View File

@ -2,7 +2,10 @@
namespace Tests\Icinga\Module\Director\Objects;
use Icinga\Module\Director\Data\PropertiesFilter\ArrayCustomVariablesFilter;
use Icinga\Module\Director\Data\PropertiesFilter\CustomVariablesFilter;
use Icinga\Module\Director\IcingaConfig\IcingaConfig;
use Icinga\Module\Director\Objects\DirectorDatafield;
use Icinga\Module\Director\Objects\IcingaHost;
use Icinga\Module\Director\Objects\IcingaZone;
use Icinga\Module\Director\Test\BaseTestCase;
@ -11,6 +14,7 @@ use Icinga\Exception\IcingaException;
class IcingaHostTest extends BaseTestCase
{
protected $testHostName = '___TEST___host';
protected $testDatafieldName = 'test5';
public function testPropertiesCanBeSet()
{
@ -473,6 +477,137 @@ class IcingaHostTest extends BaseTestCase
IcingaHost::loadWithApiKey('No___such___key', $db);
}
public function testEnumProperties() {
if ($this->skipForMissingDb()) {
return;
}
$db = $this->getDb();
$properties = IcingaHost::enumProperties($db);
$this->assertEquals(
array(
'Host properties' => $this->getDefaultHostProperties()
),
$properties
);
}
public function testEnumPropertiesWithCustomVars() {
if ($this->skipForMissingDb()) {
return;
}
$db = $this->getDb();
$host = $this->host();
$host->store($db);
$properties = IcingaHost::enumProperties($db);
$this->assertEquals(
array(
'Host properties' => $this->getDefaultHostProperties(),
'Custom variables' => array(
'vars.test1' => 'test1',
'vars.test2' => 'test2',
'vars.test3' => 'test3',
'vars.test4' => 'test4'
)
),
$properties
);
}
public function testEnumPropertiesWithPrefix() {
if ($this->skipForMissingDb()) {
return;
}
$db = $this->getDb();
$host = $this->host();
$host->store($db);
$properties = IcingaHost::enumProperties($db, 'host.');
$this->assertEquals(
array(
'Host properties' => $this->getDefaultHostProperties('host.'),
'Custom variables' => array(
'host.vars.test1' => 'test1',
'host.vars.test2' => 'test2',
'host.vars.test3' => 'test3',
'host.vars.test4' => 'test4'
)
),
$properties
);
}
public function testEnumPropertiesWithFilter() {
if ($this->skipForMissingDb()) {
return;
}
$db = $this->getDb();
DirectorDatafield::create(array(
'varname' => $this->testDatafieldName,
'caption' => 'Blah',
'description' => '',
'datatype' => 'Icinga\Module\Director\DataType\DataTypeArray',
'format' => 'json'
))->store($db);
$host = $this->host();
$host->{'vars.test5'} = array('a', '1');
$host->store($db);
$properties = IcingaHost::enumProperties($db, '', new CustomVariablesFilter());
$this->assertEquals(
array(
'Custom variables' => array(
'vars.test1' => 'test1',
'vars.test2' => 'test2',
'vars.test3' => 'test3',
'vars.test4' => 'test4',
'vars.test5' => 'test5 (Blah)'
)
),
$properties
);
}
public function testEnumPropertiesWithArrayFilter() {
if ($this->skipForMissingDb()) {
return;
}
$db = $this->getDb();
DirectorDatafield::create(array(
'varname' => $this->testDatafieldName,
'caption' => 'Blah',
'description' => '',
'datatype' => 'Icinga\Module\Director\DataType\DataTypeArray',
'format' => 'json'
))->store($db);
$host = $this->host();
$host->{'vars.test5'} = array('a', '1');
$host->store($db);
$properties = IcingaHost::enumProperties($db, '', new ArrayCustomVariablesFilter());
$this->assertEquals(
array(
'Custom variables' => array(
'vars.test5' => 'test5 (Blah)'
)
),
$properties
);
}
protected function getDummyRelatedProperties()
{
return array(
@ -506,6 +641,36 @@ class IcingaHostTest extends BaseTestCase
));
}
protected function getDefaultHostProperties($prefix = '') {
return array(
"${prefix}name" => "name",
"${prefix}action_url" => "action_url",
"${prefix}address" => "address",
"${prefix}address6" => "address6",
"${prefix}api_key" => "api_key",
"${prefix}check_command" => "check_command",
"${prefix}check_interval" => "check_interval",
"${prefix}check_period" => "check_period",
"${prefix}command_endpoint" => "command_endpoint",
"${prefix}display_name" => "display_name",
"${prefix}enable_active_checks" => "enable_active_checks",
"${prefix}enable_event_handler" => "enable_event_handler",
"${prefix}enable_flapping" => "enable_flapping",
"${prefix}enable_notifications" => "enable_notifications",
"${prefix}enable_passive_checks" => "enable_passive_checks",
"${prefix}enable_perfdata" => "enable_perfdata",
"${prefix}event_command" => "event_command",
"${prefix}flapping_threshold" => "flapping_threshold",
"${prefix}icon_image" => "icon_image",
"${prefix}icon_image_alt" => "icon_image_alt",
"${prefix}max_check_attempts" => "max_check_attempts",
"${prefix}notes" => "notes",
"${prefix}notes_url" => "notes_url",
"${prefix}retry_interval" => "retry_interval",
"${prefix}volatile" => "volatile",
"${prefix}zone" => "zone"
);
}
protected function loadRendered($name)
{
return file_get_contents(__DIR__ . '/rendered/' . $name . '.out');
@ -528,6 +693,24 @@ class IcingaHostTest extends BaseTestCase
IcingaZone::load($name, $db)->delete();
}
}
$this->deleteDatafields();
}
}
protected function deleteDatafields() {
$db = $this->getDb();
$dbAdapter = $db->getDbAdapter();
$kill = array($this->testDatafieldName);
foreach ($kill as $name) {
$query = $dbAdapter->select()
->from('director_datafield')
->where('varname = ?', $name);
foreach (DirectorDatafield::loadAll($db, $query, 'id') as $datafield) {
$datafield->delete();
}
}
}
}